@socketsecurity/cli-with-sentry 1.6.0 → 1.6.2

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 (30) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +5 -5
  3. package/dist/cli.js +184 -173
  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/generate-recursive-manifests.d.mts +1 -1
  13. package/dist/types/commands/manifest/generate-recursive-manifests.d.mts.map +1 -1
  14. package/dist/types/commands/manifest/run-manifest-facts.d.mts.map +1 -1
  15. package/dist/types/commands/manifest/scripts/assemble.d.mts.map +1 -1
  16. package/dist/types/commands/manifest/scripts/facts.d.mts +18 -16
  17. package/dist/types/commands/manifest/scripts/facts.d.mts.map +1 -1
  18. package/dist/types/commands/manifest/scripts/records.d.mts +16 -2
  19. package/dist/types/commands/manifest/scripts/records.d.mts.map +1 -1
  20. package/dist/types/commands/manifest/scripts/run.d.mts +2 -0
  21. package/dist/types/commands/manifest/scripts/run.d.mts.map +1 -1
  22. package/dist/types/commands/manifest/scripts/sidecar.d.mts +6 -8
  23. package/dist/types/commands/manifest/scripts/sidecar.d.mts.map +1 -1
  24. package/dist/types/commands/scan/cmd-scan-create.d.mts.map +1 -1
  25. package/dist/types/commands/scan/handle-create-new-scan.d.mts.map +1 -1
  26. package/dist/types/utils/coana.d.mts +3 -0
  27. package/dist/types/utils/coana.d.mts.map +1 -1
  28. package/dist/utils.js +10 -1
  29. package/dist/utils.js.map +1 -1
  30. 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
  }
@@ -2693,13 +2662,9 @@ function assembleFacts(parsed, opts = {}) {
2693
2662
  const {
2694
2663
  directByRoot,
2695
2664
  finalNodes
2696
- } = mergeByCoordinate(perRoot);
2665
+ } = mergeById(perRoot);
2697
2666
  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));
2667
+ const components = buildComponents(finalNodes, buildManifestFilesById(parsed, directByRoot, perRoot));
2703
2668
  const projects = opts.emitProjects === false ? [] : buildProjects(parsed, directByRoot, perRoot);
2704
2669
  const metadata = {
2705
2670
  format: 'socket-facts-sbom',
@@ -2720,35 +2685,50 @@ function assembleFacts(parsed, opts = {}) {
2720
2685
  return {
2721
2686
  facts,
2722
2687
  report: buildReport(parsed),
2723
- artifactPaths: buildArtifactPaths(finalNodes, [...parsed.projects.values()], projectsByGav, perRoot, fileExists)
2688
+ artifactPaths: buildArtifactPaths(finalNodes, [...parsed.projects.values()], perRoot, fileExists)
2724
2689
  };
2725
2690
  }
2726
- function gav(group, name, version) {
2727
- return `${group}:${name}:${version}`;
2691
+
2692
+ // A node resolving to a build project takes that project's id, so projects
2693
+ // sharing a coordinate stay apart and the project's variants collapse into it.
2694
+ function componentId(coordId, project) {
2695
+ return project || coordId;
2728
2696
  }
2729
2697
  function buildPerRoot(parsed) {
2730
2698
  const out = new Map();
2731
2699
  for (const [rootId, r] of parsed.roots) {
2732
- const childrenByParent = new Map();
2700
+ const idOf = coordId => componentId(coordId, r.nodes.get(coordId)?.project ?? '');
2701
+ const nodes = new Map();
2702
+ for (const [coordId, n] of r.nodes) {
2703
+ const id = idOf(coordId);
2704
+ let node = nodes.get(id);
2705
+ if (!node) {
2706
+ node = {
2707
+ coord: n.project ? {
2708
+ ...n.coord,
2709
+ classifier: '',
2710
+ ext: ''
2711
+ } : n.coord,
2712
+ children: [],
2713
+ direct: false,
2714
+ project: n.project,
2715
+ targets: []
2716
+ };
2717
+ nodes.set(id, node);
2718
+ }
2719
+ node.direct ||= n.direct;
2720
+ node.targets.push(...n.targets);
2721
+ }
2733
2722
  for (const [p, c] of r.edges) {
2734
2723
  if (!r.nodes.has(p) || !r.nodes.has(c)) {
2735
2724
  continue;
2736
2725
  }
2737
- let set = childrenByParent.get(p);
2738
- if (!set) {
2739
- set = new Set();
2740
- childrenByParent.set(p, set);
2726
+ const parentId = idOf(p);
2727
+ const childId = idOf(c);
2728
+ const parent = nodes.get(parentId);
2729
+ if (childId !== parentId && !parent.children.includes(childId)) {
2730
+ parent.children.push(childId);
2741
2731
  }
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
2732
  }
2753
2733
  out.set(rootId, {
2754
2734
  projectKey: r.projectKey,
@@ -2759,10 +2739,10 @@ function buildPerRoot(parsed) {
2759
2739
  return out;
2760
2740
  }
2761
2741
 
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) {
2742
+ // Components are merged by id across every resolution root; which ids belong
2743
+ // to which subproject is kept separately (classpathByProject) for
2744
+ // reachability, which needs each subproject's exact classpath.
2745
+ function mergeById(perRoot) {
2766
2746
  const finalNodes = new Map();
2767
2747
  const directByRoot = new Map();
2768
2748
  for (const [rootId, {
@@ -2777,6 +2757,7 @@ function mergeByCoordinate(perRoot) {
2777
2757
  children: new Set(),
2778
2758
  prod: false,
2779
2759
  direct: false,
2760
+ project: node.project,
2780
2761
  targets: new Set()
2781
2762
  };
2782
2763
  finalNodes.set(coordId, fn);
@@ -2808,18 +2789,23 @@ function mergeByCoordinate(perRoot) {
2808
2789
  directByRoot
2809
2790
  };
2810
2791
  }
2811
- function buildManifestFilesByCoord(parsed, directByRoot, perRoot) {
2792
+ function buildManifestFilesById(parsed, directByRoot, perRoot) {
2812
2793
  const buildFilesByCoord = new Map();
2813
2794
  for (const [rootId, ids] of directByRoot) {
2814
- const projectKey = perRoot.get(rootId)?.projectKey ?? '';
2815
- const buildFiles = parsed.projects.get(projectKey)?.buildFiles ?? [];
2795
+ const root = perRoot.get(rootId);
2796
+ const project = parsed.projects.get(root?.projectKey ?? '');
2816
2797
  for (const id of ids) {
2817
2798
  let set = buildFilesByCoord.get(id);
2818
2799
  if (!set) {
2819
2800
  set = new Set();
2820
2801
  buildFilesByCoord.set(id, set);
2821
2802
  }
2822
- for (const f of buildFiles) {
2803
+ const coord = root?.nodes.get(id)?.coord;
2804
+ // Without a known declaring script, the project's build file, even one
2805
+ // absent on disk: its presence marks the dependency direct here.
2806
+ const declared = coord && project?.declaredIn.get(`${coord.group}:${coord.name}`);
2807
+ const files = declared ?? [...(project?.buildFiles ?? []), ...(project?.missingBuildFiles ?? [])];
2808
+ for (const f of files) {
2823
2809
  set.add(f);
2824
2810
  }
2825
2811
  }
@@ -2831,7 +2817,7 @@ function buildManifestFilesByCoord(parsed, directByRoot, perRoot) {
2831
2817
  file
2832
2818
  }))]));
2833
2819
  }
2834
- function buildComponents(finalNodes, projectsByGav, manifestFilesByCoord) {
2820
+ function buildComponents(finalNodes, manifestFilesById) {
2835
2821
  return [...finalNodes.keys()].sort().map(id => {
2836
2822
  const fn = finalNodes.get(id);
2837
2823
  const c = fn.coord;
@@ -2862,13 +2848,13 @@ function buildComponents(finalNodes, projectsByGav, manifestFilesByCoord) {
2862
2848
  if (!fn.prod) {
2863
2849
  comp.dev = true;
2864
2850
  }
2865
- if (projectsByGav.has(gav(c.group, c.name, c.version ?? ''))) {
2851
+ if (fn.project) {
2866
2852
  comp.firstParty = true;
2867
2853
  }
2868
2854
  if (fn.children.size) {
2869
2855
  comp.dependencies = [...fn.children].sort();
2870
2856
  }
2871
- const manifestFiles = manifestFilesByCoord.get(id);
2857
+ const manifestFiles = manifestFilesById.get(id);
2872
2858
  if (manifestFiles) {
2873
2859
  comp.manifestFiles = manifestFiles;
2874
2860
  }
@@ -2890,6 +2876,7 @@ function buildProjects(parsed, directByRoot, perRoot) {
2890
2876
  }
2891
2877
  const projects = [...parsed.projects.values()].map(p => {
2892
2878
  const entry = {
2879
+ id: p.projectKey,
2893
2880
  type: PURL_TYPE_MAVEN,
2894
2881
  namespace: p.group,
2895
2882
  name: p.name,
@@ -2899,6 +2886,15 @@ function buildProjects(parsed, directByRoot, perRoot) {
2899
2886
  subprojectDir: p.dir,
2900
2887
  dependencies: [...(directByProject.get(p.projectKey) ?? [])].sort()
2901
2888
  };
2889
+ // A project without a build file of its own is defined by the scripts
2890
+ // declaring its dependencies, e.g. the root build script.
2891
+ const declared = [...new Set([...p.declaredIn.values()].flat())];
2892
+ const files = p.buildFiles.length ? p.buildFiles : declared.length ? declared : p.missingBuildFiles;
2893
+ if (files.length) {
2894
+ entry.manifestFiles = [...files].sort().map(file => ({
2895
+ file
2896
+ }));
2897
+ }
2902
2898
  return entry;
2903
2899
  });
2904
2900
  projects.sort((a, b) => {
@@ -2908,21 +2904,6 @@ function buildProjects(parsed, directByRoot, perRoot) {
2908
2904
  });
2909
2905
  return projects;
2910
2906
  }
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
2907
  function buildClasspathByProject(projects, perRoot) {
2927
2908
  const idsByProjectKey = new Map();
2928
2909
  for (const {
@@ -2940,11 +2921,7 @@ function buildClasspathByProject(projects, perRoot) {
2940
2921
  }
2941
2922
  const classpathByProject = new Map();
2942
2923
  for (const p of projects) {
2943
- const key = projectClasspathKey({
2944
- name: p.name,
2945
- namespace: p.group,
2946
- subprojectDir: p.dir
2947
- });
2924
+ const key = p.projectKey;
2948
2925
  let set = classpathByProject.get(key);
2949
2926
  if (!set) {
2950
2927
  set = new Set();
@@ -2959,61 +2936,26 @@ function buildClasspathByProject(projects, perRoot) {
2959
2936
  1: ids
2960
2937
  }) => [key, [...ids].sort()]));
2961
2938
  }
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
- }
2939
+ function buildArtifactPaths(finalNodes, projects, perRoot, fileExists) {
2940
+ const pathsById = new Map();
2941
+ for (const [id, fn] of finalNodes) {
2942
+ if (!fn.project) {
2943
+ pathsById.set(id, {
2944
+ sources: [],
2945
+ targets: [...fn.targets].filter(fileExists).sort()
2946
+ });
2996
2947
  }
2997
2948
  }
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.
2949
+ // A project's own component shares its id, so this also covers dependency
2950
+ // edges onto a sibling project.
3001
2951
  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);
2952
+ pathsById.set(p.projectKey, {
2953
+ sources: [...new Set(p.sources)].filter(fileExists).sort(),
2954
+ targets: [...new Set(p.targets)].filter(fileExists).sort()
2955
+ });
3011
2956
  }
3012
2957
  return {
3013
- targetsByCoord,
3014
- targetsByGav,
3015
- sourcesByCoord,
3016
- coords,
2958
+ pathsById,
3017
2959
  classpathByProject: buildClasspathByProject(projects, perRoot)
3018
2960
  };
3019
2961
  }
@@ -3185,18 +3127,23 @@ function serializeExcludePathPatterns(paths) {
3185
3127
  // <tag>\t<field>\t<field>...
3186
3128
  //
3187
3129
  // meta tool toolVersion javaVersion
3130
+ // buildRoot path (absolute; the facts file's directory)
3188
3131
  // project projectKey group name version dir
3189
3132
  // projectSrc projectKey path (--with-files only)
3190
3133
  // projectTgt projectKey path (--with-files only)
3191
- // projectBuild projectKey path (build-root-relative)
3134
+ // projectBuild projectKey path [missing] (build-root-relative; `missing`: configured, absent on disk)
3135
+ // declared projectKey group name path (script declaring the dependency; build-root-relative)
3192
3136
  // root rootId projectKey config prod(0|1)
3193
- // node rootId coordId group name version ext classifier direct(0|1)
3137
+ // node rootId coordId group name version ext classifier direct(0|1) project
3194
3138
  // edge rootId parentCoordId childCoordId
3195
3139
  // file rootId coordId path (--with-files only)
3196
3140
  // scanned config
3197
3141
  // failure coord detail config
3198
3142
  // unscannable config detail
3199
3143
  //
3144
+ // `projectKey` is the tool's unique project identity (Maven: GAV; Gradle:
3145
+ // project path; sbt: project id), emitted as the project id; a `node` whose
3146
+ // `project` names one resolves to that build project.
3200
3147
  // A `root` is one (subproject, configuration) resolution root; `coordId` is the
3201
3148
  // coordinate key (`group:name:ext:classifier:version`, empty segments dropped),
3202
3149
  // used opaquely as the per-root node key. Unknown tags are ignored.
@@ -3225,6 +3172,7 @@ function parseRecords(text) {
3225
3172
  tool: '',
3226
3173
  toolVersion: '',
3227
3174
  javaVersion: '',
3175
+ buildRoot: '',
3228
3176
  projects: new Map(),
3229
3177
  roots: new Map(),
3230
3178
  scannedConfigs: [],
@@ -3258,7 +3206,9 @@ function parseRecords(text) {
3258
3206
  dir: '',
3259
3207
  sources: [],
3260
3208
  targets: [],
3261
- buildFiles: []
3209
+ buildFiles: [],
3210
+ missingBuildFiles: [],
3211
+ declaredIn: new Map()
3262
3212
  };
3263
3213
  result.projects.set(key, p);
3264
3214
  }
@@ -3275,6 +3225,9 @@ function parseRecords(text) {
3275
3225
  result.toolVersion = f[2] ?? '';
3276
3226
  result.javaVersion = f[3] ?? '';
3277
3227
  break;
3228
+ case 'buildRoot':
3229
+ result.buildRoot = f[1] ?? '';
3230
+ break;
3278
3231
  case 'project':
3279
3232
  {
3280
3233
  const p = project(f[1] ?? '');
@@ -3296,7 +3249,17 @@ function parseRecords(text) {
3296
3249
  break;
3297
3250
  case 'projectBuild':
3298
3251
  if (f[2]) {
3299
- project(f[1] ?? '').buildFiles.push(f[2]);
3252
+ const p = project(f[1] ?? '');
3253
+ (f[3] === 'missing' ? p.missingBuildFiles : p.buildFiles).push(f[2]);
3254
+ }
3255
+ break;
3256
+ case 'declared':
3257
+ if (f[4]) {
3258
+ const {
3259
+ declaredIn
3260
+ } = project(f[1] ?? '');
3261
+ const ga = `${f[2] ?? ''}:${f[3] ?? ''}`;
3262
+ declaredIn.set(ga, [...(declaredIn.get(ga) ?? []), f[4]]);
3300
3263
  }
3301
3264
  break;
3302
3265
  case 'root':
@@ -3321,6 +3284,7 @@ function parseRecords(text) {
3321
3284
  classifier: f[7] ?? ''
3322
3285
  },
3323
3286
  direct: bool(f[8]),
3287
+ project: f[9] ?? '',
3324
3288
  targets: []
3325
3289
  });
3326
3290
  break;
@@ -3430,12 +3394,14 @@ async function writeSbtPlugin(globalBase, sourceFilename, destFilename) {
3430
3394
  }
3431
3395
  async function assembleFromRecords(out, recordsFile) {
3432
3396
  const text = fs$1.existsSync(recordsFile) ? await fs$1.promises.readFile(recordsFile, 'utf8') : '';
3397
+ const parsed = parseRecords(text);
3433
3398
  const {
3434
3399
  artifactPaths,
3435
3400
  facts,
3436
3401
  report
3437
- } = assembleFacts(parseRecords(text));
3402
+ } = assembleFacts(parsed);
3438
3403
  return {
3404
+ buildRoot: parsed.buildRoot || undefined,
3439
3405
  code: out.code,
3440
3406
  facts,
3441
3407
  report,
@@ -3606,7 +3572,6 @@ async function runManifestFacts({
3606
3572
  verbose,
3607
3573
  withFiles
3608
3574
  }) {
3609
- const factsPath = path.join(cwd, constants.default.DOT_SOCKET_DOT_FACTS_JSON);
3610
3575
  let resolvedJavaHome;
3611
3576
  if (javaHome) {
3612
3577
  const expanded = expandEnvVarRefs(javaHome);
@@ -3671,6 +3636,7 @@ async function runManifestFacts({
3671
3636
  }
3672
3637
  const {
3673
3638
  artifactPaths,
3639
+ buildRoot,
3674
3640
  code,
3675
3641
  facts,
3676
3642
  report,
@@ -3731,6 +3697,14 @@ async function runManifestFacts({
3731
3697
  logger.logger.warn(`No resolvable ${ecosystem} dependencies found; nothing to upload.`);
3732
3698
  return;
3733
3699
  }
3700
+ if (!buildRoot) {
3701
+ process.exitCode = 1;
3702
+ logger.logger.fail(`The ${ecosystem} build did not report its root directory, so its Socket facts file cannot be placed.`);
3703
+ return null;
3704
+ }
3705
+ // Every path in the facts is relative to the build root, which `-f`/`-p`
3706
+ // can move away from cwd.
3707
+ const factsPath = path.join(buildRoot, constants.default.DOT_SOCKET_DOT_FACTS_JSON);
3734
3708
  const socketCliVersion = constants.default.ENV.INLINED_SOCKET_CLI_VERSION;
3735
3709
  if (facts.metadata && socketCliVersion) {
3736
3710
  facts.metadata.socketCliVersion = socketCliVersion;
@@ -3876,8 +3850,11 @@ async function runEcosystemCandidates({
3876
3850
  continue;
3877
3851
  }
3878
3852
  covered.add(dir);
3853
+ // `-f`/`-p` can root the reactor away from `dir`.
3854
+ // eslint-disable-next-line no-await-in-loop
3855
+ const buildRoot = await utils.realpathOrResolved(path.dirname(result.factsPath));
3879
3856
  // 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))));
3857
+ const resolvedSubprojectDirs = await Promise.all(result.projects.map(project => utils.realpathOrResolved(path.resolve(buildRoot, project.subprojectDir))));
3881
3858
  for (const subprojectDir of resolvedSubprojectDirs) {
3882
3859
  // Only a genuinely nested member (a descendant of this reactor's own
3883
3860
  // directory) has no independent existence worth its own standalone
@@ -3890,7 +3867,7 @@ async function runEcosystemCandidates({
3890
3867
  // meaningful data point, not a redundant one. Never suppress its own
3891
3868
  // build-root invocation, regardless of which reactor(s) also
3892
3869
  // incorporate it or the order candidates happen to be discovered in.
3893
- if (subprojectDir.startsWith(`${dir}${path.sep}`)) {
3870
+ if (subprojectDir === buildRoot || subprojectDir.startsWith(`${buildRoot}${path.sep}`)) {
3894
3871
  covered.add(subprojectDir);
3895
3872
  }
3896
3873
  }
@@ -3905,7 +3882,7 @@ async function runEcosystemCandidates({
3905
3882
  return outcomes;
3906
3883
  }
3907
3884
 
3908
- // Generates one .socket.facts.json per independent gradle/sbt/maven build
3885
+ // Generates one Socket facts file per independent gradle/sbt/maven build
3909
3886
  // root under `cwd`. Coverage is tracked per ecosystem via the facts SBOM's
3910
3887
  // own projects[].subprojectDir, not by pruning the whole discovered subtree,
3911
3888
  // so an unrelated nested project a reactor doesn't declare still gets its
@@ -7231,7 +7208,7 @@ async function createNewScan({
7231
7208
  });
7232
7209
  if (reach.dynamicSbomInference) {
7233
7210
  // Already generated recursively above; resolving cwd's own build
7234
- // root a second time would race on the same .socket.facts.json.
7211
+ // root a second time would race on the same facts file.
7235
7212
  detected.gradle = false;
7236
7213
  detected.sbt = false;
7237
7214
  detected.maven = false;
@@ -7366,11 +7343,11 @@ async function createNewScan({
7366
7343
  reachabilityReport = reachResult.data?.reachabilityReport;
7367
7344
 
7368
7345
  // 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);
7346
+ // CycloneDX, SPDX, and Socket facts — matching Coana's
7347
+ // `--use-only-pregenerated-sboms` selection. Otherwise drop every facts
7348
+ // file; coana's fresh reachability report (appended below) is the
7349
+ // authoritative facts file for the scan.
7350
+ const pathsForScan = reach.reachUseOnlyPregeneratedSboms ? filterToPregeneratedSboms(packagePaths, supportedFiles) : packagePaths.filter(p => !utils.isSocketFactsFile(p));
7374
7351
 
7375
7352
  // Append coana's reachability report, but not twice: a pre-generated facts
7376
7353
  // input can resolve to the same path coana wrote its report to.
@@ -9006,9 +8983,6 @@ async function discoverGhsaIds(orgSlug, tarHash, options) {
9006
8983
  }).catch(() => {});
9007
8984
  }
9008
8985
  }
9009
- function isFactsFile(filepath) {
9010
- return path.basename(filepath).toLowerCase() === constants.DOT_SOCKET_DOT_FACTS_JSON;
9011
- }
9012
8986
  async function gitWorkingTreeChanges(cwd) {
9013
8987
  const {
9014
8988
  0: modifiedCResult,
@@ -9034,6 +9008,18 @@ function readWrittenFiles(outputFile) {
9034
9008
  const files = result?.modifiedFiles;
9035
9009
  return Array.isArray(files) && files.every(f => typeof f === 'string') ? new Set(files) : undefined;
9036
9010
  }
9011
+ // Coana exits with this code after writing an --output-file that lists computed fixes it left unapplied.
9012
+ const COANA_FIXES_NOT_APPLIED_EXIT_CODE = 5;
9013
+ function isFixesNotAppliedExit(result) {
9014
+ return !result.ok && result.data?.code === COANA_FIXES_NOT_APPLIED_EXIT_CODE;
9015
+ }
9016
+ function readNotAppliedFixes(fixesResultJson) {
9017
+ const notApplied = fixesResultJson?.notApplied;
9018
+ return Array.isArray(notApplied) ? notApplied : [];
9019
+ }
9020
+ function formatNotAppliedFixes(notApplied) {
9021
+ return notApplied.map(f => ` ${f.ghsa}: ${f.purl} -> ${f.fixedVersion}: ${f.reason}`).join('\n');
9022
+ }
9037
9023
  async function coanaFix(fixConfig) {
9038
9024
  if (!fixConfig.dynamicSbomInference) {
9039
9025
  return await coanaFixWithFacts(fixConfig, undefined);
@@ -9117,16 +9103,15 @@ async function coanaFixWithFacts(fixConfig, factsSlot) {
9117
9103
  cwd
9118
9104
  });
9119
9105
  const scanFilepaths = await findScanFilepaths();
9120
- // Fail if any .socket.facts.json files are present in the scan folder.
9121
- // These are analysis artifacts and must be removed before re-running fix.
9122
- const factsFiles = scanFilepaths.filter(isFactsFile);
9106
+ // Facts files are analysis artifacts and must be removed before re-running fix.
9107
+ const factsFiles = scanFilepaths.filter(utils.isSocketFactsFile);
9123
9108
  if (factsFiles.length) {
9124
9109
  if (!silence) {
9125
9110
  spinner?.stop();
9126
9111
  }
9127
9112
  return {
9128
9113
  ok: false,
9129
- message: `Found ${constants.DOT_SOCKET_DOT_FACTS_JSON} in manifest files`,
9114
+ message: 'Found Socket facts files in manifest files',
9130
9115
  cause: `Delete the following ${words.pluralize('file', factsFiles.length)} before running socket fix again:\n` + factsFiles.map(p => ` - ${p}`).join('\n')
9131
9116
  };
9132
9117
  }
@@ -9143,7 +9128,7 @@ async function coanaFixWithFacts(fixConfig, factsSlot) {
9143
9128
  });
9144
9129
  } catch (e) {
9145
9130
  // A failed build root aborts inference after others wrote their facts.
9146
- const partial = (await findScanFilepaths()).filter(isFactsFile);
9131
+ const partial = (await findScanFilepaths()).filter(utils.isSocketFactsFile);
9147
9132
  await Promise.all(partial.map(p => fs$1.promises.rm(p, {
9148
9133
  force: true
9149
9134
  })));
@@ -9250,7 +9235,7 @@ async function coanaFixWithFacts(fixConfig, factsSlot) {
9250
9235
  if (!silence) {
9251
9236
  spinner?.stop();
9252
9237
  }
9253
- if (!fixCResult.ok) {
9238
+ if (!fixCResult.ok && !isFixesNotAppliedExit(fixCResult)) {
9254
9239
  return fixCResult;
9255
9240
  }
9256
9241
 
@@ -9267,11 +9252,24 @@ async function coanaFixWithFacts(fixConfig, factsSlot) {
9267
9252
  const tmpContent = await fs$1.promises.readFile(tmpFile, 'utf8');
9268
9253
  await fs$1.promises.writeFile(outputFile, tmpContent, 'utf8');
9269
9254
  }
9255
+ const ghsaDetails = fixesResultJson ? [fixesResultJson] : [];
9256
+ const notApplied = readNotAppliedFixes(fixesResultJson);
9257
+ if (!fixCResult.ok || notApplied.length) {
9258
+ return {
9259
+ ok: false,
9260
+ message: fixesResultJson?.type === 'no-fixes-applied' ? 'No computed fixes were applied' : 'Some computed fixes were not applied',
9261
+ cause: formatNotAppliedFixes(notApplied),
9262
+ data: {
9263
+ fixedAll: false,
9264
+ ghsaDetails
9265
+ }
9266
+ };
9267
+ }
9270
9268
  return {
9271
9269
  ok: true,
9272
9270
  data: {
9273
9271
  fixedAll: true,
9274
- ghsaDetails: fixesResultJson ? [fixesResultJson] : []
9272
+ ghsaDetails
9275
9273
  }
9276
9274
  };
9277
9275
  } finally {
@@ -9378,7 +9376,13 @@ async function coanaFixWithFacts(fixConfig, factsSlot) {
9378
9376
  spinner: silence ? undefined : spinner,
9379
9377
  stdio: silence ? 'pipe' : 'inherit'
9380
9378
  });
9381
- if (!fixCResult.ok) {
9379
+ if (isFixesNotAppliedExit(fixCResult)) {
9380
+ if (!silence) {
9381
+ logger.logger.warn(`Not every fix for ${ghsaId} was applied:\n${formatNotAppliedFixes(readNotAppliedFixes(fs$2.readJsonSync(tmpFile, {
9382
+ throws: false
9383
+ })))}`);
9384
+ }
9385
+ } else if (!fixCResult.ok) {
9382
9386
  if (!silence) {
9383
9387
  logger.logger.error(`Update failed for ${ghsaId}: ${utils.getErrorCause(fixCResult)}`);
9384
9388
  }
@@ -19922,6 +19926,13 @@ const generalFlags$1 = {
19922
19926
  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.'
19923
19927
  }
19924
19928
  };
19929
+ function hasSocketFactsFileIn(dir) {
19930
+ try {
19931
+ return fs$1.readdirSync(dir).some(utils.isSocketFactsFile);
19932
+ } catch {
19933
+ return false;
19934
+ }
19935
+ }
19925
19936
  const cmdScanCreate = {
19926
19937
  description: description$c,
19927
19938
  hidden: hidden$a,
@@ -20143,12 +20154,12 @@ async function run$d(argv, importMeta, {
20143
20154
  }
20144
20155
  }
20145
20156
  const detected = await detectManifestActions(sockJson, cwd);
20146
- // Suppress the --auto-manifest suggestion when a `.socket.facts.json` is
20157
+ // Suppress the --auto-manifest suggestion when a Socket facts file is
20147
20158
  // already present at cwd. That file is the output of `socket manifest auto`
20148
20159
  // (and `--facts` mode of the per-ecosystem manifest commands), so suggesting
20149
20160
  // to regenerate it would be misleading; the manifest data is already there
20150
20161
  // and will be picked up by the scan.
20151
- const hasFactsFile = fs$1.existsSync(path.join(cwd, constants.default.DOT_SOCKET_DOT_FACTS_JSON));
20162
+ const hasFactsFile = hasSocketFactsFileIn(cwd);
20152
20163
  if (detected.count > 0 && !autoManifest && !dynamicSbomInference && !uvMembers && !hasFactsFile) {
20153
20164
  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.`);
20154
20165
  }
@@ -24467,5 +24478,5 @@ process.on('unhandledRejection', async (reason, promise) => {
24467
24478
  // eslint-disable-next-line n/no-process-exit
24468
24479
  process.exit(1);
24469
24480
  });
24470
- //# debugId=b7761d71-38de-4772-91bc-d1db319250b4
24481
+ //# debugId=40113ed0-ae58-43eb-a197-d1810801160e
24471
24482
  //# sourceMappingURL=cli.js.map