@socketsecurity/cli 1.1.101 → 1.1.151

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 (201) hide show
  1. package/CHANGELOG.md +277 -1
  2. package/README.md +9 -0
  3. package/bin/cli.js +33 -1
  4. package/dist/cli.js +9914 -5001
  5. package/dist/cli.js.map +1 -1
  6. package/dist/constants.js +8 -6
  7. package/dist/constants.js.map +1 -1
  8. package/dist/init.gradle +73 -0
  9. package/dist/manifest-scripts/maven-extension/coana-maven-extension.jar +0 -0
  10. package/dist/manifest-scripts/socket-facts.init.gradle +581 -0
  11. package/dist/manifest-scripts/socket-facts.plugin.scala +544 -0
  12. package/dist/shadow-npm-bin2.js +6 -6
  13. package/dist/shadow-npm-bin2.js.map +1 -1
  14. package/dist/shadow-npm-inject.js +10 -10
  15. package/dist/shadow-npm-inject.js.map +1 -1
  16. package/dist/shadow-pnpm-bin2.js +13 -13
  17. package/dist/shadow-pnpm-bin2.js.map +1 -1
  18. package/dist/shadow-yarn-bin.js +3 -3
  19. package/dist/shadow-yarn-bin.js.map +1 -1
  20. package/dist/socket-completion.bash +0 -0
  21. package/dist/tsconfig.dts.tsbuildinfo +1 -1
  22. package/dist/types/commands/audit-log/output-audit-log.d.mts.map +1 -1
  23. package/dist/types/commands/ci/fetch-default-org-slug.d.mts.map +1 -1
  24. package/dist/types/commands/ci/handle-ci.d.mts.map +1 -1
  25. package/dist/types/commands/config/handle-config-get.d.mts.map +1 -1
  26. package/dist/types/commands/config/handle-config-set.d.mts.map +1 -1
  27. package/dist/types/commands/config/output-config-set.d.mts.map +1 -1
  28. package/dist/types/commands/fix/cmd-fix.d.mts.map +1 -1
  29. package/dist/types/commands/fix/coana-fix.d.mts.map +1 -1
  30. package/dist/types/commands/fix/handle-fix.d.mts +1 -1
  31. package/dist/types/commands/fix/handle-fix.d.mts.map +1 -1
  32. package/dist/types/commands/fix/types.d.mts +1 -0
  33. package/dist/types/commands/fix/types.d.mts.map +1 -1
  34. package/dist/types/commands/manifest/bazel/bazel-cquery.d.mts +70 -0
  35. package/dist/types/commands/manifest/bazel/bazel-cquery.d.mts.map +1 -0
  36. package/dist/types/commands/manifest/bazel/bazel-pypi-discovery.d.mts +44 -0
  37. package/dist/types/commands/manifest/bazel/bazel-pypi-discovery.d.mts.map +1 -0
  38. package/dist/types/commands/manifest/bazel/bazel-pypi-parser.d.mts +46 -0
  39. package/dist/types/commands/manifest/bazel/bazel-pypi-parser.d.mts.map +1 -0
  40. package/dist/types/commands/manifest/bazel/bazel-query-runner.d.mts +66 -8
  41. package/dist/types/commands/manifest/bazel/bazel-query-runner.d.mts.map +1 -1
  42. package/dist/types/commands/manifest/bazel/bazel-repo-discovery.d.mts +69 -29
  43. package/dist/types/commands/manifest/bazel/bazel-repo-discovery.d.mts.map +1 -1
  44. package/dist/types/commands/manifest/bazel/bazel-workspace-walk.d.mts +18 -0
  45. package/dist/types/commands/manifest/bazel/bazel-workspace-walk.d.mts.map +1 -0
  46. package/dist/types/commands/manifest/bazel/cmd-manifest-bazel.d.mts +26 -0
  47. package/dist/types/commands/manifest/bazel/cmd-manifest-bazel.d.mts.map +1 -1
  48. package/dist/types/commands/manifest/bazel/extract_bazel_to_maven.d.mts +110 -7
  49. package/dist/types/commands/manifest/bazel/extract_bazel_to_maven.d.mts.map +1 -1
  50. package/dist/types/commands/manifest/bazel/extract_bazel_to_pypi.d.mts +20 -0
  51. package/dist/types/commands/manifest/bazel/extract_bazel_to_pypi.d.mts.map +1 -0
  52. package/dist/types/commands/manifest/cmd-manifest-auto.d.mts.map +1 -1
  53. package/dist/types/commands/manifest/cmd-manifest-gradle.d.mts.map +1 -1
  54. package/dist/types/commands/manifest/cmd-manifest-kotlin.d.mts.map +1 -1
  55. package/dist/types/commands/manifest/cmd-manifest-maven.d.mts +9 -0
  56. package/dist/types/commands/manifest/cmd-manifest-maven.d.mts.map +1 -0
  57. package/dist/types/commands/manifest/cmd-manifest-scala.d.mts.map +1 -1
  58. package/dist/types/commands/manifest/cmd-manifest.d.mts.map +1 -1
  59. package/dist/types/commands/manifest/convert-gradle-to-facts.d.mts +15 -0
  60. package/dist/types/commands/manifest/convert-gradle-to-facts.d.mts.map +1 -0
  61. package/dist/types/commands/manifest/convert-maven-to-facts.d.mts +15 -0
  62. package/dist/types/commands/manifest/convert-maven-to-facts.d.mts.map +1 -0
  63. package/dist/types/commands/manifest/convert-sbt-to-facts.d.mts +19 -0
  64. package/dist/types/commands/manifest/convert-sbt-to-facts.d.mts.map +1 -0
  65. package/dist/types/commands/manifest/convert_gradle_to_maven.d.mts +2 -1
  66. package/dist/types/commands/manifest/convert_gradle_to_maven.d.mts.map +1 -1
  67. package/dist/types/commands/manifest/detect-manifest-actions.d.mts +1 -0
  68. package/dist/types/commands/manifest/detect-manifest-actions.d.mts.map +1 -1
  69. package/dist/types/commands/manifest/generate_auto_manifest.d.mts +12 -1
  70. package/dist/types/commands/manifest/generate_auto_manifest.d.mts.map +1 -1
  71. package/dist/types/commands/manifest/parse-build-tool-opts.d.mts +9 -0
  72. package/dist/types/commands/manifest/parse-build-tool-opts.d.mts.map +1 -0
  73. package/dist/types/commands/manifest/run-manifest-facts.d.mts +24 -0
  74. package/dist/types/commands/manifest/run-manifest-facts.d.mts.map +1 -0
  75. package/dist/types/commands/manifest/scripts/assemble.d.mts +16 -0
  76. package/dist/types/commands/manifest/scripts/assemble.d.mts.map +1 -0
  77. package/dist/types/commands/manifest/scripts/build-tool.d.mts +3 -0
  78. package/dist/types/commands/manifest/scripts/build-tool.d.mts.map +1 -0
  79. package/dist/types/commands/manifest/scripts/facts.d.mts +46 -0
  80. package/dist/types/commands/manifest/scripts/facts.d.mts.map +1 -0
  81. package/dist/types/commands/manifest/scripts/records.d.mts +66 -0
  82. package/dist/types/commands/manifest/scripts/records.d.mts.map +1 -0
  83. package/dist/types/commands/manifest/scripts/resolution-report-gradle.d.mts +10 -0
  84. package/dist/types/commands/manifest/scripts/resolution-report-gradle.d.mts.map +1 -0
  85. package/dist/types/commands/manifest/scripts/resolution-report-ivy.d.mts +7 -0
  86. package/dist/types/commands/manifest/scripts/resolution-report-ivy.d.mts.map +1 -0
  87. package/dist/types/commands/manifest/scripts/resolution-report-maven.d.mts +8 -0
  88. package/dist/types/commands/manifest/scripts/resolution-report-maven.d.mts.map +1 -0
  89. package/dist/types/commands/manifest/scripts/resolution-report-render.d.mts +43 -0
  90. package/dist/types/commands/manifest/scripts/resolution-report-render.d.mts.map +1 -0
  91. package/dist/types/commands/manifest/scripts/resolution-report.d.mts +18 -0
  92. package/dist/types/commands/manifest/scripts/resolution-report.d.mts.map +1 -0
  93. package/dist/types/commands/manifest/scripts/run.d.mts +43 -0
  94. package/dist/types/commands/manifest/scripts/run.d.mts.map +1 -0
  95. package/dist/types/commands/manifest/scripts/sidecar.d.mts +27 -0
  96. package/dist/types/commands/manifest/scripts/sidecar.d.mts.map +1 -0
  97. package/dist/types/commands/manifest/setup-manifest-config.d.mts.map +1 -1
  98. package/dist/types/commands/organization/fetch-organization-list.d.mts +2 -2
  99. package/dist/types/commands/organization/fetch-organization-list.d.mts.map +1 -1
  100. package/dist/types/commands/package/output-purls-shallow-score.d.mts.map +1 -1
  101. package/dist/types/commands/repository/fetch-create-repo.d.mts +2 -2
  102. package/dist/types/commands/repository/fetch-create-repo.d.mts.map +1 -1
  103. package/dist/types/commands/repository/fetch-delete-repo.d.mts +2 -2
  104. package/dist/types/commands/repository/fetch-delete-repo.d.mts.map +1 -1
  105. package/dist/types/commands/repository/fetch-list-all-repos.d.mts +2 -2
  106. package/dist/types/commands/repository/fetch-list-all-repos.d.mts.map +1 -1
  107. package/dist/types/commands/repository/fetch-list-repos.d.mts +2 -2
  108. package/dist/types/commands/repository/fetch-list-repos.d.mts.map +1 -1
  109. package/dist/types/commands/repository/fetch-update-repo.d.mts +2 -2
  110. package/dist/types/commands/repository/fetch-update-repo.d.mts.map +1 -1
  111. package/dist/types/commands/repository/fetch-view-repo.d.mts +2 -2
  112. package/dist/types/commands/repository/fetch-view-repo.d.mts.map +1 -1
  113. package/dist/types/commands/repository/handle-list-repos.d.mts.map +1 -1
  114. package/dist/types/commands/repository/output-create-repo.d.mts +2 -2
  115. package/dist/types/commands/repository/output-create-repo.d.mts.map +1 -1
  116. package/dist/types/commands/repository/output-delete-repo.d.mts +2 -2
  117. package/dist/types/commands/repository/output-delete-repo.d.mts.map +1 -1
  118. package/dist/types/commands/repository/output-list-repos.d.mts +2 -2
  119. package/dist/types/commands/repository/output-list-repos.d.mts.map +1 -1
  120. package/dist/types/commands/repository/output-update-repo.d.mts +2 -2
  121. package/dist/types/commands/repository/output-update-repo.d.mts.map +1 -1
  122. package/dist/types/commands/repository/output-view-repo.d.mts +2 -2
  123. package/dist/types/commands/repository/output-view-repo.d.mts.map +1 -1
  124. package/dist/types/commands/scan/cmd-scan-create.d.mts.map +1 -1
  125. package/dist/types/commands/scan/cmd-scan-reach.d.mts.map +1 -1
  126. package/dist/types/commands/scan/create-scan-from-github.d.mts.map +1 -1
  127. package/dist/types/commands/scan/exclude-paths.d.mts +2 -2
  128. package/dist/types/commands/scan/exclude-paths.d.mts.map +1 -1
  129. package/dist/types/commands/scan/fetch-create-org-full-scan.d.mts +2 -2
  130. package/dist/types/commands/scan/fetch-create-org-full-scan.d.mts.map +1 -1
  131. package/dist/types/commands/scan/fetch-delete-org-full-scan.d.mts +2 -2
  132. package/dist/types/commands/scan/fetch-delete-org-full-scan.d.mts.map +1 -1
  133. package/dist/types/commands/scan/fetch-list-scans.d.mts +2 -2
  134. package/dist/types/commands/scan/fetch-list-scans.d.mts.map +1 -1
  135. package/dist/types/commands/scan/fetch-scan-metadata.d.mts +2 -2
  136. package/dist/types/commands/scan/fetch-scan-metadata.d.mts.map +1 -1
  137. package/dist/types/commands/scan/fetch-scan.d.mts +4 -0
  138. package/dist/types/commands/scan/fetch-scan.d.mts.map +1 -1
  139. package/dist/types/commands/scan/fetch-supported-scan-file-names.d.mts +2 -1
  140. package/dist/types/commands/scan/fetch-supported-scan-file-names.d.mts.map +1 -1
  141. package/dist/types/commands/scan/finalize-tier1-scan.d.mts +9 -5
  142. package/dist/types/commands/scan/finalize-tier1-scan.d.mts.map +1 -1
  143. package/dist/types/commands/scan/handle-create-new-scan.d.mts.map +1 -1
  144. package/dist/types/commands/scan/handle-scan-reach.d.mts.map +1 -1
  145. package/dist/types/commands/scan/output-create-new-scan.d.mts +2 -2
  146. package/dist/types/commands/scan/output-create-new-scan.d.mts.map +1 -1
  147. package/dist/types/commands/scan/output-delete-scan.d.mts +2 -2
  148. package/dist/types/commands/scan/output-delete-scan.d.mts.map +1 -1
  149. package/dist/types/commands/scan/output-list-scans.d.mts +2 -2
  150. package/dist/types/commands/scan/output-list-scans.d.mts.map +1 -1
  151. package/dist/types/commands/scan/output-scan-metadata.d.mts +2 -2
  152. package/dist/types/commands/scan/output-scan-metadata.d.mts.map +1 -1
  153. package/dist/types/commands/scan/output-scan-reach.d.mts +2 -1
  154. package/dist/types/commands/scan/output-scan-reach.d.mts.map +1 -1
  155. package/dist/types/commands/scan/perform-reachability-analysis.d.mts +10 -3
  156. package/dist/types/commands/scan/perform-reachability-analysis.d.mts.map +1 -1
  157. package/dist/types/commands/scan/reachability-flags.d.mts.map +1 -1
  158. package/dist/types/commands/scan/reachability-units.d.mts +18 -0
  159. package/dist/types/commands/scan/reachability-units.d.mts.map +1 -0
  160. package/dist/types/commands/scan/stream-scan.d.mts +3 -1
  161. package/dist/types/commands/scan/stream-scan.d.mts.map +1 -1
  162. package/dist/types/constants.d.mts +2 -0
  163. package/dist/types/constants.d.mts.map +1 -1
  164. package/dist/types/utils/api.d.mts +53 -7
  165. package/dist/types/utils/api.d.mts.map +1 -1
  166. package/dist/types/utils/config.d.mts.map +1 -1
  167. package/dist/types/utils/dlx.d.mts +4 -3
  168. package/dist/types/utils/dlx.d.mts.map +1 -1
  169. package/dist/types/utils/ecosystem.d.mts +8 -1
  170. package/dist/types/utils/ecosystem.d.mts.map +1 -1
  171. package/dist/types/utils/fs.d.mts +1 -0
  172. package/dist/types/utils/fs.d.mts.map +1 -1
  173. package/dist/types/utils/glob.d.mts +5 -4
  174. package/dist/types/utils/glob.d.mts.map +1 -1
  175. package/dist/types/utils/meow-with-subcommands.d.mts.map +1 -1
  176. package/dist/types/utils/path-resolve.d.mts +1 -1
  177. package/dist/types/utils/path-resolve.d.mts.map +1 -1
  178. package/dist/types/utils/sdk.d.mts +6 -0
  179. package/dist/types/utils/sdk.d.mts.map +1 -1
  180. package/dist/types/utils/self-update.d.mts +3 -0
  181. package/dist/types/utils/self-update.d.mts.map +1 -0
  182. package/dist/types/utils/socket-json.d.mts +18 -0
  183. package/dist/types/utils/socket-json.d.mts.map +1 -1
  184. package/dist/types/utils/socket-package-alert.d.mts.map +1 -1
  185. package/dist/utils.js +614 -283
  186. package/dist/utils.js.map +1 -1
  187. package/dist/vendor.js +50578 -27781
  188. package/external/blessed/lib/tput.js +5 -5
  189. package/package.json +60 -132
  190. package/requirements.json +1 -1
  191. package/shadow-bin/npm +0 -0
  192. package/shadow-bin/npx +0 -0
  193. package/shadow-bin/pnpm +0 -0
  194. package/shadow-bin/yarn +0 -0
  195. package/dist/types/commands/manifest/bazel/bazel-build-parser.d.mts +0 -34
  196. package/dist/types/commands/manifest/bazel/bazel-build-parser.d.mts.map +0 -1
  197. package/external/blessed/usr/fonts/AUTHORS +0 -1
  198. package/external/blessed/usr/fonts/LICENSE +0 -94
  199. package/external/blessed/usr/fonts/README +0 -340
  200. package/external/blessed/usr/fonts/ter-u14b.json +0 -17826
  201. package/external/blessed/usr/fonts/ter-u14n.json +0 -17826
@@ -0,0 +1,544 @@
1
+ package socket
2
+
3
+ import sbt._
4
+ import sbt.Keys._
5
+
6
+ import scala.collection.mutable
7
+ import scala.reflect.ClassTag
8
+
9
+ /**
10
+ * Emits a flat line-protocol RECORDS file at the build root (NOT the final
11
+ * `.socket.facts.json`, and NOT to stdout — sbt prints resolution noise to stdout
12
+ * with no way to silence it). The TS assembler (utils/src/manifest-scripts/assemble.ts)
13
+ * reads the records and owns all SBOM construction — graph merge, content-addressed
14
+ * ids, contentHash. This plugin only RESOLVES and emits raw facts. See records.ts for
15
+ * the record grammar.
16
+ *
17
+ * Must compile on Scala 2.10/sbt 0.13 and Scala 2.12/sbt 1.x (compiled by the sbt
18
+ * meta-build), hence string-named TaskKeys and reflection for version-specific
19
+ * ExclusionRule / ResolveException / ConfigRef shapes.
20
+ */
21
+ object SocketFactsPlugin extends AutoPlugin {
22
+ override def trigger = allRequirements
23
+
24
+ object autoImport {
25
+ val socketFacts =
26
+ taskKey[Unit]("Emit Socket facts records for the whole build")
27
+ }
28
+ import autoImport._
29
+
30
+ override def projectSettings: Seq[Setting[_]] = Seq(
31
+ aggregate in socketFacts := false,
32
+ socketFacts := {
33
+ val st = state.value
34
+ val buildRoot = (baseDirectory in ThisBuild).value
35
+ val withFiles = boolProp("socket.withFiles")
36
+ val populateScope: Option[Set[String]] = readPopulateScope()
37
+
38
+ val extracted = Project.extract(st)
39
+ val allRefs = extracted.structure.allProjectRefs
40
+
41
+ // `-Dsocket.excludePaths` (scan-root-relative globs): a subproject whose dir is wholly excluded is
42
+ // never resolved and emits no project record. Source-file-level exclusion is left to the analysis.
43
+ val excludeMatchers = parseExcludeMatchers()
44
+ val rootCanonPath = buildRoot.getCanonicalFile.toPath
45
+ def relOf(f: File): String = {
46
+ val r = rootCanonPath.relativize(f.getCanonicalFile.toPath).toString.replace(java.io.File.separator, "/")
47
+ if (r.isEmpty) "." else r
48
+ }
49
+ def isExcludedRef(ref: ProjectRef): Boolean =
50
+ isExcludedPath(relOf(extracted.get(baseDirectory.in(ref))), excludeMatchers)
51
+
52
+ // Prefer `updateFull` (coursier `update` returns empty callers); fall back to `update` (sbt 0.13).
53
+ val hasUpdateFull =
54
+ extracted.structure.settings.map(_.key.key).exists(_.label == "updateFull")
55
+ val updateTaskName = if (hasUpdateFull) "updateFull" else "update"
56
+
57
+ // One tree per resolution root (project, config); the TS assembler merges them path-sensitively.
58
+ val perSub = mutable.LinkedHashMap.empty[String, RootTree]
59
+ val failures = mutable.LinkedHashSet.empty[Failure]
60
+ val scannedConfigs = mutable.LinkedHashSet.empty[String]
61
+ val matcher = buildConfigMatcher()
62
+
63
+ // Real artifact ext per build module, so an ext-less inter-project dep gets its true coordinate.
64
+ val moduleExts = buildModuleExts(allRefs, extracted)
65
+
66
+ allRefs.foreach { ref =>
67
+ if (!isExcludedRef(ref)) {
68
+ runUpdateResilient(updateTaskName, ref, extracted, st, failures).foreach { report =>
69
+ foldReport(report, ref, extracted, matcher, scannedConfigs, withFiles, populateScope, moduleExts).foreach {
70
+ case (rootKey, tree) => perSub(rootKey) = tree
71
+ }
72
+ }
73
+ }
74
+ }
75
+
76
+ val moduleDirs: Map[String, (Seq[String], Seq[String])] =
77
+ if (withFiles) buildModuleDirs(allRefs, extracted) else Map.empty
78
+
79
+ val sb = new StringBuilder
80
+ def rec(fields: String*): Unit = {
81
+ sb.append(fields.map(esc).mkString("\t")); sb.append('\n')
82
+ }
83
+
84
+ rec("meta", "sbt", extracted.getOpt(sbtVersion).getOrElse(""), sys.props.getOrElse("java.version", ""))
85
+
86
+ // One `project` record per build module (sources/targets only with --with-files). Excluded
87
+ // subprojects are omitted (they were also skipped during resolution above).
88
+ allRefs.foreach { ref =>
89
+ if (!isExcludedRef(ref)) {
90
+ val mid = rootIdOf(extracted, ref)
91
+ val ver = if (mid.revision == null) "" else mid.revision
92
+ rec("project", ref.project, mid.organization, mid.name, ver, relOf(extracted.get(baseDirectory.in(ref))))
93
+ if (withFiles) {
94
+ moduleDirs.get(mid.organization + ":" + mid.name + ":" + ver).foreach {
95
+ case (sources, targets) =>
96
+ sources.foreach(s => rec("projectSrc", ref.project, s))
97
+ targets.foreach(t => rec("projectTgt", ref.project, t))
98
+ }
99
+ }
100
+ }
101
+ }
102
+
103
+ // One resolution root per (subproject, configuration); the TS assembler content-addresses
104
+ // divergent subtrees.
105
+ var rootIdx = 0
106
+ perSub.foreach {
107
+ case (_, tree) =>
108
+ val rootId = rootIdx.toString
109
+ rootIdx += 1
110
+ rec("root", rootId, tree.projectKey, tree.config, if (tree.prod) "1" else "0")
111
+ tree.nodes.foreach {
112
+ case (coordId, node) =>
113
+ val c = node.coord
114
+ rec("node", rootId, coordId, c.org, c.name, c.version, c.ext, c.classifier, if (node.direct) "1" else "0")
115
+ node.children.foreach(ch => rec("edge", rootId, coordId, ch))
116
+ node.targets.foreach(p => rec("file", rootId, coordId, p))
117
+ }
118
+ }
119
+
120
+ // Scanned configs + raw failures (Coana owns the abort/warn policy and report rendering).
121
+ scannedConfigs.toList.sorted.foreach(c => rec("scanned", c))
122
+ failures.foreach(f => rec("failure", f.coord, f.detail, f.config))
123
+
124
+ val recordsFile = sys.props.get("socket.recordsFile").filter(_.nonEmpty) match {
125
+ case Some(p) => new File(p)
126
+ case None => new File(buildRoot, ".socket.facts.records.tsv")
127
+ }
128
+ Option(recordsFile.getParentFile).foreach(_.mkdirs())
129
+ IO.write(recordsFile, sb.toString)
130
+ println("Socket facts records written to: " + recordsFile.getAbsolutePath)
131
+ }
132
+ )
133
+
134
+ // ---- resolution ---------------------------------------------------------
135
+
136
+ private def rootIdOf(extracted: Extracted, ref: ProjectRef): ModuleID = {
137
+ val sv = extracted.get(scalaVersion.in(ref))
138
+ val sbv = extracted.get(scalaBinaryVersion.in(ref))
139
+ CrossVersion.apply(sv, sbv)(extracted.get(projectID.in(ref)))
140
+ }
141
+
142
+ // GAV -> a build module's real artifact extension (jar for a normal project). coursier reports an
143
+ // inter-project dependency with no resolved artifact, so it would otherwise be ext-less and not
144
+ // match how depscan ingests it (ext=jar); stamping the real ext keeps a full, matching coordinate.
145
+ private def buildModuleExts(allRefs: Seq[ProjectRef], extracted: Extracted): Map[String, String] = {
146
+ allRefs.map { ref =>
147
+ val ext = extracted.getOpt(artifact.in(ref)).map(_.extension).filter(e => e != null && e.nonEmpty).getOrElse("jar")
148
+ gavKey(rootIdOf(extracted, ref)) -> ext
149
+ }.toMap
150
+ }
151
+
152
+ // Absolute source roots + compiled-output dirs per build module, keyed by GAV. --with-files only;
153
+ // absolute because reachability locates an internal module's code on THIS machine (no registry jar).
154
+ // ALL of the project's configurations are scanned (not just Compile/Test) so custom ones
155
+ // (IntegrationTest, ...) reach the analysis; source-file-level exclusion is coana's job.
156
+ private def buildModuleDirs(
157
+ allRefs: Seq[ProjectRef],
158
+ extracted: Extracted
159
+ ): Map[String, (Seq[String], Seq[String])] = {
160
+ allRefs.map { ref =>
161
+ val mid = rootIdOf(extracted, ref)
162
+ val ver = if (mid.revision == null) "" else mid.revision
163
+ val configs: Seq[Configuration] =
164
+ extracted.getOpt(thisProject.in(ref)).map(_.configurations).getOrElse(Seq(Compile, Test))
165
+ val sources = configs
166
+ .flatMap(c => extracted.getOpt(sourceDirectories.in(ref).in(c)).getOrElse(Nil))
167
+ .map(_.getAbsolutePath).distinct.sorted
168
+ val targets = configs
169
+ .flatMap(c => extracted.getOpt(classDirectory.in(ref).in(c)))
170
+ .map(_.getAbsolutePath).distinct.sorted
171
+ (mid.organization + ":" + mid.name + ":" + ver) -> ((sources, targets))
172
+ }.toMap
173
+ }
174
+
175
+ // On a hard resolve failure, record the failed modules and retry once with them excluded. Never throws.
176
+ private def runUpdateResilient(
177
+ taskName: String,
178
+ ref: ProjectRef,
179
+ extracted: Extracted,
180
+ state: State,
181
+ failures: mutable.LinkedHashSet[Failure]
182
+ ): Option[UpdateReport] = {
183
+ val key = TaskKey[UpdateReport](taskName)
184
+
185
+ def runOn(st: State): Either[Seq[ModuleID], UpdateReport] =
186
+ Project.runTask(key.in(ref).scopedKey, st) match {
187
+ case Some((_, sbt.Value(rep))) => Right(rep)
188
+ case Some((_, sbt.Inc(inc))) => Left(extractFailedModules(inc))
189
+ case _ => Left(Nil)
190
+ }
191
+
192
+ runOn(state) match {
193
+ case Right(rep) => Some(rep)
194
+ case Left(failed) =>
195
+ failed.foreach { m =>
196
+ failures += Failure(coordOf(m), "unresolved dependency", taskName)
197
+ }
198
+ if (failed.isEmpty) None
199
+ else
200
+ runOn(extracted.append(exclusionSettings(extracted, failed), state)) match {
201
+ case Right(rep) => Some(rep)
202
+ case Left(_) => None
203
+ }
204
+ }
205
+ }
206
+
207
+ // Reflection: ResolveException's package and the `failed` accessor differ between sbt 0.13 and 1.x.
208
+ private def extractFailedModules(inc: sbt.Incomplete): Seq[ModuleID] = {
209
+ val out = mutable.ListBuffer.empty[ModuleID]
210
+ def walk(i: sbt.Incomplete): Unit = {
211
+ i.directCause.foreach { ex =>
212
+ val cn = ex.getClass.getName
213
+ if (cn == "sbt.ResolveException" || cn == "sbt.librarymanagement.ResolveException") {
214
+ try {
215
+ val m = ex.getClass.getMethod("failed")
216
+ out ++= m.invoke(ex).asInstanceOf[Seq[ModuleID]]
217
+ } catch { case _: Throwable => }
218
+ }
219
+ }
220
+ i.causes.foreach(walk)
221
+ }
222
+ walk(inc)
223
+ out.toList.distinct
224
+ }
225
+
226
+ // ExclusionRule constructor shape differs between sbt 0.13 (`sbt.ExclusionRule`) and 1.x (`sbt.SbtExclusionRule`).
227
+ private def exclusionSettings(extracted: Extracted, failed: Seq[ModuleID]): Seq[Setting[_]] = {
228
+ val cls: Class[_] =
229
+ try Class.forName("sbt.SbtExclusionRule")
230
+ catch { case _: ClassNotFoundException => classOf[sbt.ExclusionRule] }
231
+
232
+ def rule(m: ModuleID): Any =
233
+ if (cls.getName.contains("SbtExclusionRule")) {
234
+ cls.getConstructors.find(_.getParameterCount == 5) match {
235
+ case Some(ctor) =>
236
+ ctor.newInstance(m.organization, m.name, "*", Seq(), sbt.CrossVersion.Disabled)
237
+ case None =>
238
+ throw new IllegalStateException("No suitable SbtExclusionRule constructor")
239
+ }
240
+ } else ExclusionRule(organization = m.organization, name = m.name)
241
+
242
+ def castSeq[T](xs: Seq[Any])(implicit ct: ClassTag[T]): Seq[T] = xs.map(_.asInstanceOf[T])
243
+
244
+ extracted.structure.allProjectRefs.map { ref =>
245
+ excludeDependencies.in(ref) := {
246
+ val original = excludeDependencies.in(ref).value
247
+ castSeq(original ++ failed.map(rule))(ClassTag(cls))
248
+ }
249
+ }
250
+ }
251
+
252
+ // Fold one project's UpdateReport into one tree per configuration (rootKey -> tree), keyed by coordinate.
253
+ private def foldReport(
254
+ report: UpdateReport,
255
+ ref: ProjectRef,
256
+ extracted: Extracted,
257
+ matcher: String => Boolean,
258
+ scannedConfigs: mutable.LinkedHashSet[String],
259
+ withFiles: Boolean,
260
+ populateScope: Option[Set[String]],
261
+ moduleExts: Map[String, String]
262
+ ): mutable.LinkedHashMap[String, RootTree] = {
263
+ val perRoot = mutable.LinkedHashMap.empty[String, RootTree]
264
+ val rootGav = gavKey(rootIdOf(extracted, ref))
265
+
266
+ def emittable(m: ModuleReport): Boolean = !m.evicted
267
+
268
+ def inScope(m: ModuleID): Boolean = populateScope match {
269
+ case None => true
270
+ case Some(gavs) => gavs.contains(gavKey(m))
271
+ }
272
+
273
+ report.configurations.foreach { cr =>
274
+ val cfg = confName(cr)
275
+ if (matcher(cfg)) {
276
+ scannedConfigs += cfg
277
+ val prod = isProdConf(cfg) && !isTestConf(cfg)
278
+ val nodes = mutable.LinkedHashMap.empty[String, Node]
279
+ // module GAV -> component ids (caller edges are module-level).
280
+ val midToIds = mutable.HashMap.empty[String, mutable.LinkedHashSet[String]]
281
+
282
+ cr.modules.foreach { m =>
283
+ if (emittable(m)) {
284
+ val ids = midToIds.getOrElseUpdate(gavKey(m.module), mutable.LinkedHashSet.empty[String])
285
+ variantsOf(m, moduleExts).foreach { case (coord, fileOpt) =>
286
+ val node = nodes.getOrElseUpdate(coord.id, new Node(coord))
287
+ ids += coord.id
288
+ if (withFiles && inScope(m.module)) fileOpt.foreach(f => node.targets += f.getAbsolutePath)
289
+ }
290
+ }
291
+ }
292
+
293
+ // Caller edges within this config: a root caller marks the child direct, any other becomes its parent.
294
+ cr.modules.foreach { m =>
295
+ if (emittable(m)) {
296
+ midToIds.get(gavKey(m.module)).foreach { childIds =>
297
+ m.callers.foreach { c =>
298
+ val callerKey = gavKey(c.caller)
299
+ if (callerKey == rootGav) childIds.foreach(cid => nodes(cid).direct = true)
300
+ else
301
+ midToIds.get(callerKey).foreach { parentIds =>
302
+ // Drop self-edges (test → main resolving to the same coordinate), matching gradle.
303
+ parentIds.foreach(pid => childIds.foreach(cid => if (pid != cid) nodes(pid).children += cid))
304
+ }
305
+ }
306
+ }
307
+ }
308
+ }
309
+
310
+ if (nodes.nonEmpty) perRoot(ref.project + "::" + cfg) = new RootTree(ref.project, cfg, prod, nodes)
311
+ }
312
+ }
313
+ perRoot
314
+ }
315
+
316
+ private def variantsOf(m: ModuleReport, moduleExts: Map[String, String]): Seq[(Coord, Option[File])] = {
317
+ val mid = m.module
318
+ // `mid.revision` is normally set for a resolved module, but normalize null (Coord.id calls
319
+ // `.nonEmpty`, which NPEs on null) to match the other call sites (gavKey, module records).
320
+ val ver = if (mid.revision == null) "" else mid.revision
321
+ val arts = m.artifacts
322
+ if (arts == null || arts.isEmpty)
323
+ // No resolved artifact (e.g. an inter-project dep): stamp the module's real ext if it's a build
324
+ // module (matches depscan's ext=jar), else leave ext-less.
325
+ Seq((Coord(mid.organization, mid.name, ver, moduleExts.getOrElse(gavKey(mid), ""), ""), None))
326
+ else
327
+ arts.toList.map {
328
+ case (art, file) =>
329
+ (Coord(mid.organization, mid.name, ver, extOf(art), classifierOf(art)), Option(file))
330
+ }
331
+ }
332
+
333
+ // ---- config selection ---------------------------------------------------
334
+
335
+ // With no includes the default is ALL configurations (captures build/tooling deps).
336
+ private def buildConfigMatcher(): String => Boolean = {
337
+ def parse(prop: String): List[java.util.regex.Pattern] =
338
+ sys.props.get(prop) match {
339
+ case Some(s) if s.trim.nonEmpty =>
340
+ s.split(",").map(_.trim).filter(_.nonEmpty).toList.map(globToRegex)
341
+ case _ => Nil
342
+ }
343
+ val includes = parse("socket.includeConfigs")
344
+ val excludes = parse("socket.excludeConfigs")
345
+ (name: String) => {
346
+ val included =
347
+ if (includes.isEmpty) true
348
+ else includes.exists(_.matcher(name).matches())
349
+ included && !excludes.exists(_.matcher(name).matches())
350
+ }
351
+ }
352
+
353
+ // Case-SENSITIVE (matching is documented as case-sensitive). Supports `*`, `?`, and `[...]`
354
+ // character classes: enumerations (`[cC]`), ranges (`[a-z]`), and `[!..]`/`[^..]` negation. A
355
+ // malformed glob falls back to a literal match, never throws.
356
+ private def globToRegex(glob: String): java.util.regex.Pattern = {
357
+ val sb = new StringBuilder
358
+ var i = 0
359
+ val n = glob.length
360
+ while (i < n) {
361
+ val c = glob.charAt(i)
362
+ if (c == '*') { sb.append(".*"); i += 1 }
363
+ else if (c == '?') { sb.append('.'); i += 1 }
364
+ else if (c == '[') {
365
+ val j = glob.indexOf(']', i + 1)
366
+ // Treat as a class only with a non-empty body; else a literal `[`.
367
+ if (j <= i + 1) { sb.append("\\["); i += 1 }
368
+ else {
369
+ var body = glob.substring(i + 1, j)
370
+ val neg = body.startsWith("!")
371
+ if (neg) body = body.substring(1)
372
+ // Only literal chars and `-` ranges are meaningful; neutralize regex-class tricks.
373
+ body = body.replace("\\", "\\\\").replace("[", "\\[").replace("&", "\\&")
374
+ sb.append('[').append(if (neg) "^" else "").append(body).append(']')
375
+ i = j + 1
376
+ }
377
+ } else if ("\\.^$|+(){}]".indexOf(c.toInt) >= 0) {
378
+ sb.append('\\').append(c); i += 1
379
+ } else { sb.append(c); i += 1 }
380
+ }
381
+ try java.util.regex.Pattern.compile(sb.toString)
382
+ catch {
383
+ case _: java.util.regex.PatternSyntaxException =>
384
+ java.util.regex.Pattern.compile(java.util.regex.Pattern.quote(glob))
385
+ }
386
+ }
387
+
388
+ // ConfigurationReport.configuration is a String on sbt 0.13, a ConfigRef on 1.x: read `.name` reflectively.
389
+ private def confName(cr: ConfigurationReport): String = {
390
+ val c: Any = cr.configuration
391
+ try c.getClass.getMethod("name").invoke(c).toString
392
+ catch { case _: Throwable => c.toString }
393
+ }
394
+
395
+ // `-Dsocket.excludePaths` → glob PathMatchers, used only to skip whole excluded subprojects. Each
396
+ // entry variant yields the entry itself and `entry/**` so it matches the dir and its subtree (same expansion
397
+ // as the SCA ignore path). A trailing `/**` is stripped first, so a user-written `dir/**` still excludes the
398
+ // `dir` directory itself, not only its contents. Standard glob semantics (anchored to the scan root, matching
399
+ // the CLI flag): `x` is root-level, `**/x` matches at any depth. Mirrors the gradle/maven producers.
400
+ private def parseExcludeMatchers(): Seq[java.nio.file.PathMatcher] = {
401
+ sys.props.get("socket.excludePaths").map(_.trim).filter(_.nonEmpty) match {
402
+ case None => Nil
403
+ case Some(raw) =>
404
+ val fs = java.nio.file.FileSystems.getDefault
405
+ raw.split(",").toSeq.flatMap { r =>
406
+ var g = r.trim.replace("\\", "/")
407
+ while (g.startsWith("/")) g = g.substring(1)
408
+ while (g.endsWith("/")) g = g.substring(0, g.length - 1)
409
+ while (g.endsWith("/**")) {
410
+ g = g.substring(0, g.length - 3)
411
+ while (g.endsWith("/")) g = g.substring(0, g.length - 1)
412
+ }
413
+ if (g.isEmpty) Nil
414
+ else zeroDepthVariants(g).flatMap { v =>
415
+ Seq(fs.getPathMatcher("glob:" + v), fs.getPathMatcher("glob:" + v + "/**"))
416
+ }
417
+ }
418
+ }
419
+ }
420
+
421
+ // NIO glob requires a slash-adjacent `**` to consume at least one path segment, but the CLI's
422
+ // micromatch lets it match zero (`**/x` matches root-level `x`). Emit every variant with `**/`
423
+ // occurrences dropped so both semantics hold.
424
+ private def zeroDepthVariants(glob: String): Seq[String] = {
425
+ val out = mutable.LinkedHashSet[String]()
426
+ val work = mutable.Queue(glob)
427
+ while (work.nonEmpty) {
428
+ val cur = work.dequeue()
429
+ if (out.add(cur)) {
430
+ var idx = cur.indexOf("**/")
431
+ while (idx >= 0) {
432
+ if (idx == 0 || cur.charAt(idx - 1) == '/') {
433
+ val collapsed = cur.substring(0, idx) + cur.substring(idx + 3)
434
+ if (collapsed.nonEmpty) work.enqueue(collapsed)
435
+ }
436
+ idx = cur.indexOf("**/", idx + 1)
437
+ }
438
+ }
439
+ }
440
+ out.toSeq
441
+ }
442
+
443
+ private def isExcludedPath(rel: String, matchers: Seq[java.nio.file.PathMatcher]): Boolean = {
444
+ if (matchers.isEmpty) false
445
+ else {
446
+ var c = (if (rel == null) "" else rel).replace("\\", "/")
447
+ while (c.startsWith("./")) c = c.substring(2)
448
+ while (c.startsWith("/")) c = c.substring(1)
449
+ while (c.endsWith("/")) c = c.substring(0, c.length - 1)
450
+ if (c.isEmpty) false
451
+ else {
452
+ val p = java.nio.file.Paths.get(c)
453
+ matchers.exists(_.matches(p))
454
+ }
455
+ }
456
+ }
457
+
458
+ private def isTestConf(name: String): Boolean = name.toLowerCase.contains("test")
459
+
460
+ // Only feeds the informational `dev` flag, never gates analysis.
461
+ private def isProdConf(name: String): Boolean = {
462
+ val n = name.toLowerCase
463
+ n == "compile" || n == "runtime"
464
+ }
465
+
466
+ // ---- misc helpers --------------------------------------------------------
467
+
468
+ private def boolProp(name: String): Boolean =
469
+ java.lang.Boolean.parseBoolean(sys.props.getOrElse(name, "false"))
470
+
471
+ private def readPopulateScope(): Option[Set[String]] = {
472
+ sys.props.get("socket.populateFilesFor").map(_.trim).filter(_.nonEmpty) match {
473
+ case None => None
474
+ case Some(path) =>
475
+ val f = new java.io.File(path)
476
+ if (!f.exists) {
477
+ println("[socket-facts] WARN populateFilesFor file not found; recording files for all resolved artifacts")
478
+ None
479
+ } else {
480
+ val src = scala.io.Source.fromFile(f, "UTF-8")
481
+ try {
482
+ val set = src.getLines().map(_.trim).filter(_.nonEmpty).toSet
483
+ if (set.isEmpty) {
484
+ println("[socket-facts] WARN populateFilesFor file empty; recording files for all resolved artifacts")
485
+ None
486
+ } else {
487
+ println("[socket-facts] --with-files scoped to " + set.size + " SBOM artifact(s)")
488
+ Some(set)
489
+ }
490
+ } finally src.close()
491
+ }
492
+ }
493
+ }
494
+
495
+ private def gavKey(m: ModuleID): String = {
496
+ def s(v: String): String = if (v == null) "" else v
497
+ s(m.organization) + ":" + s(m.name) + ":" + s(m.revision)
498
+ }
499
+
500
+ private def coordOf(m: ModuleID): String = {
501
+ val rev = m.revision
502
+ m.organization + ":" + m.name + (if (rev != null && rev.nonEmpty) ":" + rev else "")
503
+ }
504
+
505
+ private def extOf(a: Artifact): String = {
506
+ val e = a.extension
507
+ if (e == null || e.isEmpty) "jar" else e
508
+ }
509
+
510
+ private def classifierOf(a: Artifact): String =
511
+ a.classifier.getOrElse("")
512
+
513
+ // Backslash-escape so a value can never break line/field framing (see records.ts unescape).
514
+ private def esc(v: String): String = {
515
+ if (v == null) ""
516
+ else v.replace("\\", "\\\\").replace("\t", "\\t").replace("\n", "\\n").replace("\r", "\\r")
517
+ }
518
+
519
+ private final case class Failure(coord: String, detail: String, config: String)
520
+
521
+ private final case class Coord(
522
+ org: String,
523
+ name: String,
524
+ version: String,
525
+ ext: String,
526
+ classifier: String
527
+ ) {
528
+ val id: String = Seq(org, name, ext, classifier, version).filter(_.nonEmpty).mkString(":")
529
+ }
530
+
531
+ private final class Node(val coord: Coord) {
532
+ val children = mutable.TreeSet.empty[String]
533
+ var direct = false
534
+ // External artifact's resolved jar(s); --with-files only.
535
+ val targets = mutable.TreeSet.empty[String]
536
+ }
537
+
538
+ private final class RootTree(
539
+ val projectKey: String,
540
+ val config: String,
541
+ val prod: Boolean,
542
+ val nodes: mutable.LinkedHashMap[String, Node]
543
+ )
544
+ }
@@ -3,8 +3,8 @@
3
3
  var constants = require('./constants.js');
4
4
  var require$$0 = require('node:url');
5
5
  var agent = require('../external/@socketsecurity/registry/lib/agent');
6
- var require$$9 = require('../external/@socketsecurity/registry/lib/debug');
7
- var require$$11 = require('../external/@socketsecurity/registry/lib/objects');
6
+ var debug = require('../external/@socketsecurity/registry/lib/debug');
7
+ var objects = require('../external/@socketsecurity/registry/lib/objects');
8
8
  var spawn = require('../external/@socketsecurity/registry/lib/spawn');
9
9
  var utils = require('./utils.js');
10
10
 
@@ -34,7 +34,7 @@ async function shadowNpmBase(binName, args = process.argv.slice(2), options, ext
34
34
  __proto__: null,
35
35
  ...options
36
36
  };
37
- let cwd = require$$11.getOwn(spawnOpts, 'cwd') ?? process.cwd();
37
+ let cwd = objects.getOwn(spawnOpts, 'cwd') ?? process.cwd();
38
38
  if (cwd instanceof URL) {
39
39
  cwd = require$$0.fileURLToPath(cwd);
40
40
  }
@@ -51,7 +51,7 @@ async function shadowNpmBase(binName, args = process.argv.slice(2), options, ext
51
51
  // and package.json files.
52
52
  '--allow-fs-read=*', `--allow-fs-write=${cwd}/*`, `--allow-fs-write=${constants.default.npmGlobalPrefix}/*`, `--allow-fs-write=${constants.default.npmCachePath}/*`] : [];
53
53
  const useAudit = rawBinArgs.includes('--audit');
54
- const useDebug = require$$9.isDebug('stdio');
54
+ const useDebug = debug.isDebug('stdio');
55
55
  const useNodeOptions = nodeOptionsArg || permArgs.length;
56
56
  const binArgs = rawBinArgs.filter(a => !agent.isNpmAuditFlag(a) && !agent.isNpmProgressFlag(a));
57
57
  const isSilent = !useDebug && !binArgs.some(agent.isNpmLoglevelFlag);
@@ -62,7 +62,7 @@ async function shadowNpmBase(binName, args = process.argv.slice(2), options, ext
62
62
  cwd,
63
63
  onlyDirectories: true
64
64
  })) ? [] : ['--no-audit'];
65
- const stdio = ensureIpcInStdio(require$$11.getOwn(spawnOpts, 'stdio'));
65
+ const stdio = ensureIpcInStdio(objects.getOwn(spawnOpts, 'stdio'));
66
66
  const realBinPath = isShadowNpm ? await utils.installNpmLinks(constants.default.shadowBinPath) : await utils.installNpxLinks(constants.default.shadowBinPath);
67
67
  const spawnPromise = spawn.spawn(constants.default.execPath, [...constants.default.nodeNoWarningsFlags, ...constants.default.nodeDebugFlags, ...constants.default.nodeHardenFlags, ...constants.default.nodeMemoryFlags, ...(constants.default.ENV.INLINED_SOCKET_CLI_SENTRY_BUILD ? ['--require', constants.default.instrumentWithSentryPath] : []), '--require', constants.default.shadowNpmInjectPath, realBinPath, ...noAuditArgs, ...(useNodeOptions ? [`--node-options='${nodeOptionsArg ? nodeOptionsArg.slice(15) : ''}${utils.cmdFlagsToString(permArgs)}'`] : []), '--no-fund',
68
68
  // Add '--no-progress' to fix input being swallowed by the npm spinner.
@@ -98,5 +98,5 @@ async function shadowNpmBin(args = process.argv.slice(2), options, extra) {
98
98
  exports.ensureIpcInStdio = ensureIpcInStdio;
99
99
  exports.shadowNpmBase = shadowNpmBase;
100
100
  exports.shadowNpmBin = shadowNpmBin;
101
- //# debugId=6d18cbc8-e145-4f23-9c89-eb5694f263dd
101
+ //# debugId=b8a12df1-9dde-45ca-ba67-92fe3a3c3e08
102
102
  //# sourceMappingURL=shadow-npm-bin2.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"shadow-npm-bin2.js","sources":["../src/shadow/stdio-ipc.mts","../src/shadow/npm-base.mts","../src/shadow/npm/bin.mts"],"sourcesContent":["import type { StdioOptions } from 'node:child_process'\n\n/**\n * Ensures stdio configuration includes IPC channel for process communication.\n * Converts various stdio formats to include 'ipc' as the fourth element.\n */\nexport function ensureIpcInStdio(\n stdio: StdioOptions | undefined,\n): StdioOptions {\n if (typeof stdio === 'string') {\n return [stdio, stdio, stdio, 'ipc']\n } else if (Array.isArray(stdio)) {\n if (!stdio.includes('ipc')) {\n return stdio.concat('ipc')\n }\n return stdio\n } else {\n return ['pipe', 'pipe', 'pipe', 'ipc']\n }\n}\n","import { fileURLToPath } from 'node:url'\n\nimport {\n isNpmAuditFlag,\n isNpmLoglevelFlag,\n isNpmNodeOptionsFlag,\n isNpmProgressFlag,\n} from '@socketsecurity/registry/lib/agent'\nimport { isDebug } from '@socketsecurity/registry/lib/debug'\nimport { getOwn } from '@socketsecurity/registry/lib/objects'\nimport { spawn } from '@socketsecurity/registry/lib/spawn'\n\nimport { ensureIpcInStdio } from './stdio-ipc.mts'\nimport constants, {\n FLAG_LOGLEVEL,\n NODE_MODULES,\n NPM,\n NPX,\n} from '../constants.mts'\nimport { cmdFlagsToString } from '../utils/cmd.mts'\nimport { findUp } from '../utils/fs.mts'\nimport { getPublicApiToken } from '../utils/sdk.mts'\nimport { installNpmLinks, installNpxLinks } from '../utils/shadow-links.mts'\n\nimport type { IpcObject } from '../constants.mts'\nimport type {\n SpawnExtra,\n SpawnOptions,\n SpawnResult,\n} from '@socketsecurity/registry/lib/spawn'\n\nexport type ShadowBinOptions = SpawnOptions & {\n ipc?: IpcObject | undefined\n}\n\nexport type ShadowBinResult = {\n spawnPromise: SpawnResult<string, SpawnExtra | undefined>\n}\n\nexport default async function shadowNpmBase(\n binName: typeof NPM | typeof NPX,\n args: string[] | readonly string[] = process.argv.slice(2),\n options?: ShadowBinOptions | undefined,\n extra?: SpawnExtra | undefined,\n): Promise<ShadowBinResult> {\n const {\n env: spawnEnv,\n ipc,\n ...spawnOpts\n } = { __proto__: null, ...options } as ShadowBinOptions\n\n let cwd = getOwn(spawnOpts, 'cwd') ?? process.cwd()\n if (cwd instanceof URL) {\n cwd = fileURLToPath(cwd)\n }\n\n const isShadowNpm = binName === NPM\n const terminatorPos = args.indexOf('--')\n const rawBinArgs = terminatorPos === -1 ? args : args.slice(0, terminatorPos)\n const nodeOptionsArg = rawBinArgs.findLast(isNpmNodeOptionsFlag)\n const progressArg = rawBinArgs.findLast(isNpmProgressFlag) !== '--no-progress'\n const otherArgs = terminatorPos === -1 ? [] : args.slice(terminatorPos)\n const permArgs =\n isShadowNpm && constants.SUPPORTS_NODE_PERMISSION_FLAG\n ? [\n '--permission',\n '--allow-child-process',\n // '--allow-addons',\n // '--allow-wasi',\n // Allow all reads because npm walks up directories looking for config\n // and package.json files.\n '--allow-fs-read=*',\n `--allow-fs-write=${cwd}/*`,\n `--allow-fs-write=${constants.npmGlobalPrefix}/*`,\n `--allow-fs-write=${constants.npmCachePath}/*`,\n ]\n : []\n\n const useAudit = rawBinArgs.includes('--audit')\n const useDebug = isDebug('stdio')\n const useNodeOptions = nodeOptionsArg || permArgs.length\n const binArgs = rawBinArgs.filter(\n a => !isNpmAuditFlag(a) && !isNpmProgressFlag(a),\n )\n const isSilent = !useDebug && !binArgs.some(isNpmLoglevelFlag)\n // The default value of loglevel is \"notice\". We default to \"error\" which is\n // two levels quieter.\n const logLevelArgs = isSilent ? [FLAG_LOGLEVEL, 'error'] : []\n const noAuditArgs =\n useAudit || !(await findUp(NODE_MODULES, { cwd, onlyDirectories: true }))\n ? []\n : ['--no-audit']\n\n const stdio = ensureIpcInStdio(getOwn(spawnOpts, 'stdio'))\n\n const realBinPath = isShadowNpm\n ? await installNpmLinks(constants.shadowBinPath)\n : await installNpxLinks(constants.shadowBinPath)\n\n const spawnPromise = spawn(\n constants.execPath,\n [\n ...constants.nodeNoWarningsFlags,\n ...constants.nodeDebugFlags,\n ...constants.nodeHardenFlags,\n ...constants.nodeMemoryFlags,\n ...(constants.ENV.INLINED_SOCKET_CLI_SENTRY_BUILD\n ? ['--require', constants.instrumentWithSentryPath]\n : []),\n '--require',\n constants.shadowNpmInjectPath,\n realBinPath,\n ...noAuditArgs,\n ...(useNodeOptions\n ? [\n `--node-options='${nodeOptionsArg ? nodeOptionsArg.slice(15) : ''}${cmdFlagsToString(permArgs)}'`,\n ]\n : []),\n '--no-fund',\n // Add '--no-progress' to fix input being swallowed by the npm spinner.\n '--no-progress',\n // Add '--loglevel=error' if a loglevel flag is not provided and the\n // SOCKET_CLI_DEBUG environment variable is not truthy.\n ...logLevelArgs,\n ...binArgs,\n ...otherArgs,\n ],\n {\n ...spawnOpts,\n env: {\n ...process.env,\n ...constants.processEnv,\n ...spawnEnv,\n },\n stdio,\n },\n extra,\n )\n\n spawnPromise.process.send({\n [constants.SOCKET_IPC_HANDSHAKE]: {\n [constants.SOCKET_CLI_SHADOW_API_TOKEN]: getPublicApiToken(),\n [constants.SOCKET_CLI_SHADOW_BIN]: binName,\n [constants.SOCKET_CLI_SHADOW_PROGRESS]: progressArg,\n ...ipc,\n },\n })\n\n return { spawnPromise }\n}\n","import { NPM } from '../../constants.mts'\nimport shadowNpmBase from '../npm-base.mts'\n\nimport type { ShadowBinOptions, ShadowBinResult } from '../npm-base.mts'\nimport type { SpawnExtra } from '@socketsecurity/registry/lib/spawn'\n\nexport type { ShadowBinOptions, ShadowBinResult }\n\nexport default async function shadowNpmBin(\n args: string[] | readonly string[] = process.argv.slice(2),\n options?: ShadowBinOptions | undefined,\n extra?: SpawnExtra | undefined,\n): Promise<ShadowBinResult> {\n return await shadowNpmBase(NPM, args, options, extra)\n}\n"],"names":["env","__proto__","cwd","onlyDirectories","stdio","spawnPromise"],"mappings":";;;;;;;;;;AAEA;AACA;AACA;AACA;AACO;AAGL;;;AAGE;AACE;AACF;AACA;AACF;;AAEA;AACF;;ACoBe;;AAOXA;;;AAGF;AAAMC;;;AAEN;;AAEEC;AACF;AAEA;AACA;AACA;AACA;;AAEA;;AAMQ;AACA;AACA;AACA;AACA;AAOR;AACA;AACA;AACA;;AAIA;AACA;;;;AAGkDC;AAAsB;;AAMxE;AAIA;AAoBI;;AAEA;AACA;;AAMA;AACAH;;;;;AAKAI;;AAKJC;;AAEI;AACA;AACA;;AAEF;AACF;;AAESA;;AACX;;AC7Ie;;AAMf;;;;","debugId":"6d18cbc8-e145-4f23-9c89-eb5694f263dd"}
1
+ {"version":3,"file":"shadow-npm-bin2.js","sources":["../src/shadow/stdio-ipc.mts","../src/shadow/npm-base.mts","../src/shadow/npm/bin.mts"],"sourcesContent":["import type { StdioOptions } from 'node:child_process'\n\n/**\n * Ensures stdio configuration includes IPC channel for process communication.\n * Converts various stdio formats to include 'ipc' as the fourth element.\n */\nexport function ensureIpcInStdio(\n stdio: StdioOptions | undefined,\n): StdioOptions {\n if (typeof stdio === 'string') {\n return [stdio, stdio, stdio, 'ipc']\n } else if (Array.isArray(stdio)) {\n if (!stdio.includes('ipc')) {\n return stdio.concat('ipc')\n }\n return stdio\n } else {\n return ['pipe', 'pipe', 'pipe', 'ipc']\n }\n}\n","import { fileURLToPath } from 'node:url'\n\nimport {\n isNpmAuditFlag,\n isNpmLoglevelFlag,\n isNpmNodeOptionsFlag,\n isNpmProgressFlag,\n} from '@socketsecurity/registry/lib/agent'\nimport { isDebug } from '@socketsecurity/registry/lib/debug'\nimport { getOwn } from '@socketsecurity/registry/lib/objects'\nimport { spawn } from '@socketsecurity/registry/lib/spawn'\n\nimport { ensureIpcInStdio } from './stdio-ipc.mts'\nimport constants, {\n FLAG_LOGLEVEL,\n NODE_MODULES,\n NPM,\n NPX,\n} from '../constants.mts'\nimport { cmdFlagsToString } from '../utils/cmd.mts'\nimport { findUp } from '../utils/fs.mts'\nimport { getPublicApiToken } from '../utils/sdk.mts'\nimport { installNpmLinks, installNpxLinks } from '../utils/shadow-links.mts'\n\nimport type { IpcObject } from '../constants.mts'\nimport type {\n SpawnExtra,\n SpawnOptions,\n SpawnResult,\n} from '@socketsecurity/registry/lib/spawn'\n\nexport type ShadowBinOptions = SpawnOptions & {\n ipc?: IpcObject | undefined\n}\n\nexport type ShadowBinResult = {\n spawnPromise: SpawnResult<string, SpawnExtra | undefined>\n}\n\nexport default async function shadowNpmBase(\n binName: typeof NPM | typeof NPX,\n args: string[] | readonly string[] = process.argv.slice(2),\n options?: ShadowBinOptions | undefined,\n extra?: SpawnExtra | undefined,\n): Promise<ShadowBinResult> {\n const {\n env: spawnEnv,\n ipc,\n ...spawnOpts\n } = { __proto__: null, ...options } as ShadowBinOptions\n\n let cwd = getOwn(spawnOpts, 'cwd') ?? process.cwd()\n if (cwd instanceof URL) {\n cwd = fileURLToPath(cwd)\n }\n\n const isShadowNpm = binName === NPM\n const terminatorPos = args.indexOf('--')\n const rawBinArgs = terminatorPos === -1 ? args : args.slice(0, terminatorPos)\n const nodeOptionsArg = rawBinArgs.findLast(isNpmNodeOptionsFlag)\n const progressArg = rawBinArgs.findLast(isNpmProgressFlag) !== '--no-progress'\n const otherArgs = terminatorPos === -1 ? [] : args.slice(terminatorPos)\n const permArgs =\n isShadowNpm && constants.SUPPORTS_NODE_PERMISSION_FLAG\n ? [\n '--permission',\n '--allow-child-process',\n // '--allow-addons',\n // '--allow-wasi',\n // Allow all reads because npm walks up directories looking for config\n // and package.json files.\n '--allow-fs-read=*',\n `--allow-fs-write=${cwd}/*`,\n `--allow-fs-write=${constants.npmGlobalPrefix}/*`,\n `--allow-fs-write=${constants.npmCachePath}/*`,\n ]\n : []\n\n const useAudit = rawBinArgs.includes('--audit')\n const useDebug = isDebug('stdio')\n const useNodeOptions = nodeOptionsArg || permArgs.length\n const binArgs = rawBinArgs.filter(\n a => !isNpmAuditFlag(a) && !isNpmProgressFlag(a),\n )\n const isSilent = !useDebug && !binArgs.some(isNpmLoglevelFlag)\n // The default value of loglevel is \"notice\". We default to \"error\" which is\n // two levels quieter.\n const logLevelArgs = isSilent ? [FLAG_LOGLEVEL, 'error'] : []\n const noAuditArgs =\n useAudit || !(await findUp(NODE_MODULES, { cwd, onlyDirectories: true }))\n ? []\n : ['--no-audit']\n\n const stdio = ensureIpcInStdio(getOwn(spawnOpts, 'stdio'))\n\n const realBinPath = isShadowNpm\n ? await installNpmLinks(constants.shadowBinPath)\n : await installNpxLinks(constants.shadowBinPath)\n\n const spawnPromise = spawn(\n constants.execPath,\n [\n ...constants.nodeNoWarningsFlags,\n ...constants.nodeDebugFlags,\n ...constants.nodeHardenFlags,\n ...constants.nodeMemoryFlags,\n ...(constants.ENV.INLINED_SOCKET_CLI_SENTRY_BUILD\n ? ['--require', constants.instrumentWithSentryPath]\n : []),\n '--require',\n constants.shadowNpmInjectPath,\n realBinPath,\n ...noAuditArgs,\n ...(useNodeOptions\n ? [\n `--node-options='${nodeOptionsArg ? nodeOptionsArg.slice(15) : ''}${cmdFlagsToString(permArgs)}'`,\n ]\n : []),\n '--no-fund',\n // Add '--no-progress' to fix input being swallowed by the npm spinner.\n '--no-progress',\n // Add '--loglevel=error' if a loglevel flag is not provided and the\n // SOCKET_CLI_DEBUG environment variable is not truthy.\n ...logLevelArgs,\n ...binArgs,\n ...otherArgs,\n ],\n {\n ...spawnOpts,\n env: {\n ...process.env,\n ...constants.processEnv,\n ...spawnEnv,\n },\n stdio,\n },\n extra,\n )\n\n spawnPromise.process.send({\n [constants.SOCKET_IPC_HANDSHAKE]: {\n [constants.SOCKET_CLI_SHADOW_API_TOKEN]: getPublicApiToken(),\n [constants.SOCKET_CLI_SHADOW_BIN]: binName,\n [constants.SOCKET_CLI_SHADOW_PROGRESS]: progressArg,\n ...ipc,\n },\n })\n\n return { spawnPromise }\n}\n","import { NPM } from '../../constants.mts'\nimport shadowNpmBase from '../npm-base.mts'\n\nimport type { ShadowBinOptions, ShadowBinResult } from '../npm-base.mts'\nimport type { SpawnExtra } from '@socketsecurity/registry/lib/spawn'\n\nexport type { ShadowBinOptions, ShadowBinResult }\n\nexport default async function shadowNpmBin(\n args: string[] | readonly string[] = process.argv.slice(2),\n options?: ShadowBinOptions | undefined,\n extra?: SpawnExtra | undefined,\n): Promise<ShadowBinResult> {\n return await shadowNpmBase(NPM, args, options, extra)\n}\n"],"names":["env","__proto__","cwd","onlyDirectories","stdio","spawnPromise"],"mappings":";;;;;;;;;;AAEA;AACA;AACA;AACA;AACO;AAGL;;;AAGE;AACE;AACF;AACA;AACF;;AAEA;AACF;;ACoBe;;AAOXA;;;AAGF;AAAMC;;;AAEN;;AAEEC;AACF;AAEA;AACA;AACA;AACA;;AAEA;;AAMQ;AACA;AACA;AACA;AACA;AAOR;AACA;AACA;AACA;;AAIA;AACA;;;;AAGkDC;AAAsB;;AAMxE;AAIA;AAoBI;;AAEA;AACA;;AAMA;AACAH;;;;;AAKAI;;AAKJC;;AAEI;AACA;AACA;;AAEF;AACF;;AAESA;;AACX;;AC7Ie;;AAMf;;;;","debugId":"b8a12df1-9dde-45ca-ba67-92fe3a3c3e08"}