@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.
- package/CHANGELOG.md +277 -1
- package/README.md +9 -0
- package/bin/cli.js +33 -1
- package/dist/cli.js +9914 -5001
- package/dist/cli.js.map +1 -1
- package/dist/constants.js +8 -6
- package/dist/constants.js.map +1 -1
- package/dist/init.gradle +73 -0
- package/dist/manifest-scripts/maven-extension/coana-maven-extension.jar +0 -0
- package/dist/manifest-scripts/socket-facts.init.gradle +581 -0
- package/dist/manifest-scripts/socket-facts.plugin.scala +544 -0
- package/dist/shadow-npm-bin2.js +6 -6
- package/dist/shadow-npm-bin2.js.map +1 -1
- package/dist/shadow-npm-inject.js +10 -10
- package/dist/shadow-npm-inject.js.map +1 -1
- package/dist/shadow-pnpm-bin2.js +13 -13
- package/dist/shadow-pnpm-bin2.js.map +1 -1
- package/dist/shadow-yarn-bin.js +3 -3
- package/dist/shadow-yarn-bin.js.map +1 -1
- package/dist/socket-completion.bash +0 -0
- package/dist/tsconfig.dts.tsbuildinfo +1 -1
- package/dist/types/commands/audit-log/output-audit-log.d.mts.map +1 -1
- package/dist/types/commands/ci/fetch-default-org-slug.d.mts.map +1 -1
- package/dist/types/commands/ci/handle-ci.d.mts.map +1 -1
- package/dist/types/commands/config/handle-config-get.d.mts.map +1 -1
- package/dist/types/commands/config/handle-config-set.d.mts.map +1 -1
- package/dist/types/commands/config/output-config-set.d.mts.map +1 -1
- package/dist/types/commands/fix/cmd-fix.d.mts.map +1 -1
- package/dist/types/commands/fix/coana-fix.d.mts.map +1 -1
- package/dist/types/commands/fix/handle-fix.d.mts +1 -1
- package/dist/types/commands/fix/handle-fix.d.mts.map +1 -1
- package/dist/types/commands/fix/types.d.mts +1 -0
- package/dist/types/commands/fix/types.d.mts.map +1 -1
- package/dist/types/commands/manifest/bazel/bazel-cquery.d.mts +70 -0
- package/dist/types/commands/manifest/bazel/bazel-cquery.d.mts.map +1 -0
- package/dist/types/commands/manifest/bazel/bazel-pypi-discovery.d.mts +44 -0
- package/dist/types/commands/manifest/bazel/bazel-pypi-discovery.d.mts.map +1 -0
- package/dist/types/commands/manifest/bazel/bazel-pypi-parser.d.mts +46 -0
- package/dist/types/commands/manifest/bazel/bazel-pypi-parser.d.mts.map +1 -0
- package/dist/types/commands/manifest/bazel/bazel-query-runner.d.mts +66 -8
- package/dist/types/commands/manifest/bazel/bazel-query-runner.d.mts.map +1 -1
- package/dist/types/commands/manifest/bazel/bazel-repo-discovery.d.mts +69 -29
- package/dist/types/commands/manifest/bazel/bazel-repo-discovery.d.mts.map +1 -1
- package/dist/types/commands/manifest/bazel/bazel-workspace-walk.d.mts +18 -0
- package/dist/types/commands/manifest/bazel/bazel-workspace-walk.d.mts.map +1 -0
- package/dist/types/commands/manifest/bazel/cmd-manifest-bazel.d.mts +26 -0
- package/dist/types/commands/manifest/bazel/cmd-manifest-bazel.d.mts.map +1 -1
- package/dist/types/commands/manifest/bazel/extract_bazel_to_maven.d.mts +110 -7
- package/dist/types/commands/manifest/bazel/extract_bazel_to_maven.d.mts.map +1 -1
- package/dist/types/commands/manifest/bazel/extract_bazel_to_pypi.d.mts +20 -0
- package/dist/types/commands/manifest/bazel/extract_bazel_to_pypi.d.mts.map +1 -0
- package/dist/types/commands/manifest/cmd-manifest-auto.d.mts.map +1 -1
- package/dist/types/commands/manifest/cmd-manifest-gradle.d.mts.map +1 -1
- package/dist/types/commands/manifest/cmd-manifest-kotlin.d.mts.map +1 -1
- package/dist/types/commands/manifest/cmd-manifest-maven.d.mts +9 -0
- package/dist/types/commands/manifest/cmd-manifest-maven.d.mts.map +1 -0
- package/dist/types/commands/manifest/cmd-manifest-scala.d.mts.map +1 -1
- package/dist/types/commands/manifest/cmd-manifest.d.mts.map +1 -1
- package/dist/types/commands/manifest/convert-gradle-to-facts.d.mts +15 -0
- package/dist/types/commands/manifest/convert-gradle-to-facts.d.mts.map +1 -0
- package/dist/types/commands/manifest/convert-maven-to-facts.d.mts +15 -0
- package/dist/types/commands/manifest/convert-maven-to-facts.d.mts.map +1 -0
- package/dist/types/commands/manifest/convert-sbt-to-facts.d.mts +19 -0
- package/dist/types/commands/manifest/convert-sbt-to-facts.d.mts.map +1 -0
- package/dist/types/commands/manifest/convert_gradle_to_maven.d.mts +2 -1
- package/dist/types/commands/manifest/convert_gradle_to_maven.d.mts.map +1 -1
- package/dist/types/commands/manifest/detect-manifest-actions.d.mts +1 -0
- package/dist/types/commands/manifest/detect-manifest-actions.d.mts.map +1 -1
- package/dist/types/commands/manifest/generate_auto_manifest.d.mts +12 -1
- package/dist/types/commands/manifest/generate_auto_manifest.d.mts.map +1 -1
- package/dist/types/commands/manifest/parse-build-tool-opts.d.mts +9 -0
- package/dist/types/commands/manifest/parse-build-tool-opts.d.mts.map +1 -0
- package/dist/types/commands/manifest/run-manifest-facts.d.mts +24 -0
- package/dist/types/commands/manifest/run-manifest-facts.d.mts.map +1 -0
- package/dist/types/commands/manifest/scripts/assemble.d.mts +16 -0
- package/dist/types/commands/manifest/scripts/assemble.d.mts.map +1 -0
- package/dist/types/commands/manifest/scripts/build-tool.d.mts +3 -0
- package/dist/types/commands/manifest/scripts/build-tool.d.mts.map +1 -0
- package/dist/types/commands/manifest/scripts/facts.d.mts +46 -0
- package/dist/types/commands/manifest/scripts/facts.d.mts.map +1 -0
- package/dist/types/commands/manifest/scripts/records.d.mts +66 -0
- package/dist/types/commands/manifest/scripts/records.d.mts.map +1 -0
- package/dist/types/commands/manifest/scripts/resolution-report-gradle.d.mts +10 -0
- package/dist/types/commands/manifest/scripts/resolution-report-gradle.d.mts.map +1 -0
- package/dist/types/commands/manifest/scripts/resolution-report-ivy.d.mts +7 -0
- package/dist/types/commands/manifest/scripts/resolution-report-ivy.d.mts.map +1 -0
- package/dist/types/commands/manifest/scripts/resolution-report-maven.d.mts +8 -0
- package/dist/types/commands/manifest/scripts/resolution-report-maven.d.mts.map +1 -0
- package/dist/types/commands/manifest/scripts/resolution-report-render.d.mts +43 -0
- package/dist/types/commands/manifest/scripts/resolution-report-render.d.mts.map +1 -0
- package/dist/types/commands/manifest/scripts/resolution-report.d.mts +18 -0
- package/dist/types/commands/manifest/scripts/resolution-report.d.mts.map +1 -0
- package/dist/types/commands/manifest/scripts/run.d.mts +43 -0
- package/dist/types/commands/manifest/scripts/run.d.mts.map +1 -0
- package/dist/types/commands/manifest/scripts/sidecar.d.mts +27 -0
- package/dist/types/commands/manifest/scripts/sidecar.d.mts.map +1 -0
- package/dist/types/commands/manifest/setup-manifest-config.d.mts.map +1 -1
- package/dist/types/commands/organization/fetch-organization-list.d.mts +2 -2
- package/dist/types/commands/organization/fetch-organization-list.d.mts.map +1 -1
- package/dist/types/commands/package/output-purls-shallow-score.d.mts.map +1 -1
- package/dist/types/commands/repository/fetch-create-repo.d.mts +2 -2
- package/dist/types/commands/repository/fetch-create-repo.d.mts.map +1 -1
- package/dist/types/commands/repository/fetch-delete-repo.d.mts +2 -2
- package/dist/types/commands/repository/fetch-delete-repo.d.mts.map +1 -1
- package/dist/types/commands/repository/fetch-list-all-repos.d.mts +2 -2
- package/dist/types/commands/repository/fetch-list-all-repos.d.mts.map +1 -1
- package/dist/types/commands/repository/fetch-list-repos.d.mts +2 -2
- package/dist/types/commands/repository/fetch-list-repos.d.mts.map +1 -1
- package/dist/types/commands/repository/fetch-update-repo.d.mts +2 -2
- package/dist/types/commands/repository/fetch-update-repo.d.mts.map +1 -1
- package/dist/types/commands/repository/fetch-view-repo.d.mts +2 -2
- package/dist/types/commands/repository/fetch-view-repo.d.mts.map +1 -1
- package/dist/types/commands/repository/handle-list-repos.d.mts.map +1 -1
- package/dist/types/commands/repository/output-create-repo.d.mts +2 -2
- package/dist/types/commands/repository/output-create-repo.d.mts.map +1 -1
- package/dist/types/commands/repository/output-delete-repo.d.mts +2 -2
- package/dist/types/commands/repository/output-delete-repo.d.mts.map +1 -1
- package/dist/types/commands/repository/output-list-repos.d.mts +2 -2
- package/dist/types/commands/repository/output-list-repos.d.mts.map +1 -1
- package/dist/types/commands/repository/output-update-repo.d.mts +2 -2
- package/dist/types/commands/repository/output-update-repo.d.mts.map +1 -1
- package/dist/types/commands/repository/output-view-repo.d.mts +2 -2
- package/dist/types/commands/repository/output-view-repo.d.mts.map +1 -1
- package/dist/types/commands/scan/cmd-scan-create.d.mts.map +1 -1
- package/dist/types/commands/scan/cmd-scan-reach.d.mts.map +1 -1
- package/dist/types/commands/scan/create-scan-from-github.d.mts.map +1 -1
- package/dist/types/commands/scan/exclude-paths.d.mts +2 -2
- package/dist/types/commands/scan/exclude-paths.d.mts.map +1 -1
- package/dist/types/commands/scan/fetch-create-org-full-scan.d.mts +2 -2
- package/dist/types/commands/scan/fetch-create-org-full-scan.d.mts.map +1 -1
- package/dist/types/commands/scan/fetch-delete-org-full-scan.d.mts +2 -2
- package/dist/types/commands/scan/fetch-delete-org-full-scan.d.mts.map +1 -1
- package/dist/types/commands/scan/fetch-list-scans.d.mts +2 -2
- package/dist/types/commands/scan/fetch-list-scans.d.mts.map +1 -1
- package/dist/types/commands/scan/fetch-scan-metadata.d.mts +2 -2
- package/dist/types/commands/scan/fetch-scan-metadata.d.mts.map +1 -1
- package/dist/types/commands/scan/fetch-scan.d.mts +4 -0
- package/dist/types/commands/scan/fetch-scan.d.mts.map +1 -1
- package/dist/types/commands/scan/fetch-supported-scan-file-names.d.mts +2 -1
- package/dist/types/commands/scan/fetch-supported-scan-file-names.d.mts.map +1 -1
- package/dist/types/commands/scan/finalize-tier1-scan.d.mts +9 -5
- package/dist/types/commands/scan/finalize-tier1-scan.d.mts.map +1 -1
- package/dist/types/commands/scan/handle-create-new-scan.d.mts.map +1 -1
- package/dist/types/commands/scan/handle-scan-reach.d.mts.map +1 -1
- package/dist/types/commands/scan/output-create-new-scan.d.mts +2 -2
- package/dist/types/commands/scan/output-create-new-scan.d.mts.map +1 -1
- package/dist/types/commands/scan/output-delete-scan.d.mts +2 -2
- package/dist/types/commands/scan/output-delete-scan.d.mts.map +1 -1
- package/dist/types/commands/scan/output-list-scans.d.mts +2 -2
- package/dist/types/commands/scan/output-list-scans.d.mts.map +1 -1
- package/dist/types/commands/scan/output-scan-metadata.d.mts +2 -2
- package/dist/types/commands/scan/output-scan-metadata.d.mts.map +1 -1
- package/dist/types/commands/scan/output-scan-reach.d.mts +2 -1
- package/dist/types/commands/scan/output-scan-reach.d.mts.map +1 -1
- package/dist/types/commands/scan/perform-reachability-analysis.d.mts +10 -3
- package/dist/types/commands/scan/perform-reachability-analysis.d.mts.map +1 -1
- package/dist/types/commands/scan/reachability-flags.d.mts.map +1 -1
- package/dist/types/commands/scan/reachability-units.d.mts +18 -0
- package/dist/types/commands/scan/reachability-units.d.mts.map +1 -0
- package/dist/types/commands/scan/stream-scan.d.mts +3 -1
- package/dist/types/commands/scan/stream-scan.d.mts.map +1 -1
- package/dist/types/constants.d.mts +2 -0
- package/dist/types/constants.d.mts.map +1 -1
- package/dist/types/utils/api.d.mts +53 -7
- package/dist/types/utils/api.d.mts.map +1 -1
- package/dist/types/utils/config.d.mts.map +1 -1
- package/dist/types/utils/dlx.d.mts +4 -3
- package/dist/types/utils/dlx.d.mts.map +1 -1
- package/dist/types/utils/ecosystem.d.mts +8 -1
- package/dist/types/utils/ecosystem.d.mts.map +1 -1
- package/dist/types/utils/fs.d.mts +1 -0
- package/dist/types/utils/fs.d.mts.map +1 -1
- package/dist/types/utils/glob.d.mts +5 -4
- package/dist/types/utils/glob.d.mts.map +1 -1
- package/dist/types/utils/meow-with-subcommands.d.mts.map +1 -1
- package/dist/types/utils/path-resolve.d.mts +1 -1
- package/dist/types/utils/path-resolve.d.mts.map +1 -1
- package/dist/types/utils/sdk.d.mts +6 -0
- package/dist/types/utils/sdk.d.mts.map +1 -1
- package/dist/types/utils/self-update.d.mts +3 -0
- package/dist/types/utils/self-update.d.mts.map +1 -0
- package/dist/types/utils/socket-json.d.mts +18 -0
- package/dist/types/utils/socket-json.d.mts.map +1 -1
- package/dist/types/utils/socket-package-alert.d.mts.map +1 -1
- package/dist/utils.js +614 -283
- package/dist/utils.js.map +1 -1
- package/dist/vendor.js +50578 -27781
- package/external/blessed/lib/tput.js +5 -5
- package/package.json +60 -132
- package/requirements.json +1 -1
- package/shadow-bin/npm +0 -0
- package/shadow-bin/npx +0 -0
- package/shadow-bin/pnpm +0 -0
- package/shadow-bin/yarn +0 -0
- package/dist/types/commands/manifest/bazel/bazel-build-parser.d.mts +0 -34
- package/dist/types/commands/manifest/bazel/bazel-build-parser.d.mts.map +0 -1
- package/external/blessed/usr/fonts/AUTHORS +0 -1
- package/external/blessed/usr/fonts/LICENSE +0 -94
- package/external/blessed/usr/fonts/README +0 -340
- package/external/blessed/usr/fonts/ter-u14b.json +0 -17826
- 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
|
+
}
|
package/dist/shadow-npm-bin2.js
CHANGED
|
@@ -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
|
|
7
|
-
var
|
|
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 =
|
|
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 =
|
|
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(
|
|
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=
|
|
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":"
|
|
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"}
|