socket 1.1.113 → 1.1.115

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "socket",
3
- "version": "1.1.113",
3
+ "version": "1.1.115",
4
4
  "description": "CLI for Socket.dev",
5
5
  "homepage": "https://github.com/SocketDev/socket-cli",
6
6
  "license": "MIT AND OFL-1.1",
@@ -96,7 +96,7 @@
96
96
  "@babel/preset-typescript": "7.27.1",
97
97
  "@babel/runtime": "7.28.4",
98
98
  "@biomejs/biome": "2.2.4",
99
- "@coana-tech/cli": "15.3.20",
99
+ "@coana-tech/cli": "15.3.21",
100
100
  "@cyclonedx/cdxgen": "12.1.2",
101
101
  "@dotenvx/dotenvx": "1.49.0",
102
102
  "@eslint/compat": "1.3.2",
@@ -1,429 +0,0 @@
1
- // Gradle init script that emits a single `.socket.facts.json` file at the
2
- // build root describing the resolved compile/runtime dependency graph of
3
- // every subproject combined.
4
- //
5
- // Schema matches the canonical SocketFacts shape consumed by depscan
6
- // (`workspaces/lib/src/socket-facts/socket-facts-schema.ts`):
7
- //
8
- // { components: SF_Artifact[] }
9
- //
10
- // Each Maven SF_Artifact is `{ type: 'maven', namespace, name, version?,
11
- // qualifiers? } & { id, direct?, dev?, tooling?, dependencies? }`.
12
- // `qualifiers` is strict on `{ classifier?, ext? }` — anything else is
13
- // dropped.
14
- //
15
- // Invoke via:
16
- // ./gradlew --init-script socket-facts.init.gradle socketFacts
17
- //
18
- // Structure:
19
- // - per-subproject `socketFactsCollect` tasks resolve that subproject's
20
- // configurations and contribute to shared accumulators on gradle.ext
21
- // - the root `socketFacts` task depends on every collector, then
22
- // serializes the accumulated graph to a single JSON file at the build
23
- // root
24
- //
25
- // Intra-project dependencies (i.e. `project(':lib')` style edges between
26
- // subprojects in the same build) are dropped from the output entirely.
27
- // Their reasoning: each subproject contributes its own external deps to
28
- // the shared facts; the inter-project edges would just be noise that
29
- // downstream consumers (coana mvn dependency:get) would try to resolve
30
- // against Maven Central and fail. The externals each intra-project dep
31
- // brings in are picked up via that subproject's own collector.
32
-
33
- import java.util.Collections
34
- import groovy.json.JsonOutput
35
-
36
- // Must stay in sync with `DOT_SOCKET_DOT_FACTS_JSON` in
37
- // src/constants.mts (TS side). Groovy can't import the TS constant, so
38
- // the two strings are intentionally duplicated; if you change one,
39
- // change the other.
40
- ext.SOCKET_FACTS_FILENAME = '.socket.facts.json'
41
-
42
- // Shared accumulators across all subprojects' contributions. Synchronized
43
- // collections so --parallel-enabled builds don't race. The accumulator
44
- // lives on `gradle.ext` so every subproject's collector and the root
45
- // aggregator share the same instance.
46
- gradle.ext.socketFactsState = [
47
- // id -> [coord, children, prod, nonTooling]
48
- nodes : Collections.synchronizedMap([:]),
49
- // first-level dep ids
50
- directIds : Collections.synchronizedSet([] as Set),
51
- // selectors we've already logged as unresolved (deduped across configs)
52
- reportedUnresolved : Collections.synchronizedSet([] as Set),
53
- // "group:name" of every project in this build — used to filter
54
- // intra-project deps. Populated once all projects are evaluated.
55
- projectKeys : Collections.synchronizedSet([] as Set),
56
- ]
57
-
58
- // Capture every project's (group:name) once all projects are configured so
59
- // per-subproject collectors can filter intra-project deps without an
60
- // ordering dependency on other subprojects.
61
- gradle.projectsEvaluated { g ->
62
- g.rootProject.allprojects.each { p ->
63
- g.socketFactsState.projectKeys.add("${p.group ?: ''}:${p.name}")
64
- }
65
- }
66
-
67
- allprojects { project ->
68
- def collectTask = project.tasks.create('socketFactsCollect') {
69
- description = "Resolves ${project.path}'s configurations into the build-wide Socket facts accumulator"
70
- // Dependency resolution depends on state Gradle's up-to-date tracking
71
- // can't represent reliably.
72
- outputs.upToDateWhen { false }
73
-
74
- doLast {
75
- def state = gradle.socketFactsState
76
- def nodes = state.nodes
77
- def directIds = state.directIds
78
- def reportedUnresolved = state.reportedUnresolved
79
- def projectKeys = state.projectKeys
80
-
81
- // `id` omits ext so Gradle's variant artifacts (e.g.
82
- // `java-classes-directory` and `jar` for the same project dep)
83
- // dedupe into a single component. Classifier stays in the id since
84
- // it identifies a distinct artifact (sources, javadoc, etc.).
85
- def coordId = { coord ->
86
- def parts = [coord.groupId, coord.artifactId]
87
- if (coord.classifier) parts << coord.classifier
88
- parts << coord.version
89
- parts.join(':')
90
- }
91
-
92
- def isIntraProject = { String group, String name ->
93
- projectKeys.contains("${group ?: ''}:${name}")
94
- }
95
-
96
- // Atomic upsert: bracket the read-modify-write under the nodes map's
97
- // monitor so concurrent contributions don't lose flag updates.
98
- def upsertNode = { Map coord, boolean isProd, boolean isNonTooling ->
99
- def id = coordId(coord)
100
- synchronized (nodes) {
101
- def node = nodes[id]
102
- if (node == null) {
103
- node = [coord: coord, children: [] as Set, prod: false, nonTooling: false]
104
- nodes[id] = node
105
- } else if (!node.coord.ext && coord.ext) {
106
- // Upgrade to the variant whose Gradle artifact has a real
107
- // packaging extension. Compile classpath visits often arrive
108
- // with no ext (a project dep exposes only its classes-directory
109
- // variant there); the runtime classpath visit then fills in
110
- // the canonical jar/aar.
111
- node.coord = coord
112
- }
113
- if (isProd) {
114
- node.prod = true
115
- }
116
- if (isNonTooling) {
117
- node.nonTooling = true
118
- }
119
- }
120
- id
121
- }
122
-
123
- // Walk a resolved dependency, emitting nodes for itself and its
124
- // transitive closure. `cache` is keyed by ResolvedDependency identity
125
- // and short-circuits revisits in diamond/cyclic graphs.
126
- //
127
- // We never touch `artifact.file` — that forces Gradle to *download*
128
- // the underlying file (catastrophic on large builds that declare
129
- // distribution archives as dependencies). `artifact.extension` and
130
- // `artifact.classifier` read from metadata that resolution already
131
- // needed.
132
- //
133
- // Intra-project deps (project(':lib') and friends) are dropped at
134
- // visit time: we return an empty produced-id set, don't emit a node,
135
- // and don't recurse into the dep's children. The transitives those
136
- // intra-project deps expose are picked up via the consumer
137
- // subproject's classpath directly (Gradle merges them) and via the
138
- // intra-project's own collector.
139
- def visit
140
- visit = { dep, boolean isProd, boolean isNonTooling, Map cache ->
141
- if (cache.containsKey(dep)) {
142
- return cache[dep]
143
- }
144
- if (isIntraProject(dep.moduleGroup, dep.moduleName)) {
145
- def empty = [] as Set
146
- cache[dep] = empty
147
- return empty
148
- }
149
- // Pre-populate the cache to break cycles before we recurse.
150
- def producedIds = [] as Set
151
- cache[dep] = producedIds
152
-
153
- def artifacts = dep.moduleArtifacts
154
- if (artifacts.isEmpty()) {
155
- producedIds << upsertNode([
156
- groupId : dep.moduleGroup ?: '',
157
- artifactId: dep.moduleName,
158
- version : dep.moduleVersion ?: '',
159
- classifier: '',
160
- ext : '',
161
- ], isProd, isNonTooling)
162
- } else {
163
- artifacts.each { a ->
164
- producedIds << upsertNode([
165
- groupId : dep.moduleGroup ?: '',
166
- artifactId: dep.moduleName,
167
- version : dep.moduleVersion ?: '',
168
- classifier: a.classifier ?: '',
169
- // Use the file extension Gradle reports. For Gradle-internal
170
- // directory variants (java-classes-directory etc.) the
171
- // extension is empty — we let that through and emit no ext
172
- // qualifier. Never fall back to artifact.type, which is
173
- // Gradle's variant attribute, not Maven packaging.
174
- ext : a.extension ?: '',
175
- ], isProd, isNonTooling)
176
- }
177
- }
178
-
179
- def childIds = [] as Set
180
- dep.children.each { child ->
181
- childIds.addAll(visit(child, isProd, isNonTooling, cache))
182
- }
183
- synchronized (nodes) {
184
- producedIds.each { pid ->
185
- nodes[pid].children.addAll(childIds)
186
- }
187
- }
188
- producedIds
189
- }
190
-
191
- // Configuration selection by name pattern. We match the conventional
192
- // suffixes used across Gradle plugins for resolvable classpath configs:
193
- // Java (`compileClasspath`, `runtimeClasspath`,
194
- // `testCompileClasspath`, `testRuntimeClasspath`), Kotlin Gradle Plugin
195
- // (`jvmMainCompileClasspath`, `linuxX64MainRuntimeClasspath`, ...) and
196
- // AGP per-variant (`debugCompileClasspath`, `releaseRuntimeClasspath`,
197
- // `debugUnitTestRuntimeClasspath`, ...).
198
- //
199
- // Beyond classpaths we also walk other resolvable configurations
200
- // (annotation processors, linter classpaths, etc.) so build-tooling
201
- // deps land in the output too — tagged `tooling: true` so downstream
202
- // reachability scanners can skip them.
203
- //
204
- // We exclude AGP's instrumented-test classpaths (`*AndroidTest*`)
205
- // because their variant resolution requires consumer attributes
206
- // (target SDK, device/host runtime) that an init-script-driven
207
- // resolution doesn't set, and they produce ambiguity errors at
208
- // resolution time. Unit-test classpaths (`*UnitTest*`) resolve fine.
209
- def isClasspath = { String name ->
210
- def lower = name.toLowerCase()
211
- lower.endsWith('compileclasspath') || lower.endsWith('runtimeclasspath')
212
- }
213
- def isAndroidInstrumentedTest = { String name ->
214
- name.toLowerCase().contains('androidtest')
215
- }
216
- def isTestConfig = { String name -> name.toLowerCase().contains('test') }
217
-
218
- // Optional user-supplied filter: comma-separated glob patterns matched
219
- // against full configuration names (case-sensitive — Gradle config
220
- // names are canonical camelCase, and matching the user's literal input
221
- // is more predictable than silently lower-casing). `*` matches any
222
- // sequence of characters, `?` matches a single character. When set,
223
- // only configurations whose name matches at least one pattern are
224
- // walked. e.g. `--configs=*CompileClasspath,*RuntimeClasspath` keeps
225
- // every variant of the standard classpath configs while filtering out
226
- // tooling configs like `annotationProcessor` and
227
- // `kotlinCompilerClasspath`.
228
- def globToRegex = { String glob ->
229
- def sb = new StringBuilder()
230
- glob.each { String ch ->
231
- switch (ch) {
232
- case '*': sb << '.*'; break
233
- case '?': sb << '.'; break
234
- case '.': case '\\': case '^': case '$': case '|':
235
- case '+': case '(': case ')':
236
- case '[': case ']': case '{': case '}':
237
- sb << '\\' << ch; break
238
- default: sb << ch
239
- }
240
- }
241
- java.util.regex.Pattern.compile(sb.toString())
242
- }
243
- def configsProp = project.findProperty('socket.configs')?.toString()
244
- def requestedPatterns = null
245
- if (configsProp != null && !configsProp.trim().isEmpty()) {
246
- requestedPatterns = configsProp.split(',')
247
- .collect { it.trim() }
248
- .findAll { !it.isEmpty() }
249
- .collect { globToRegex(it) }
250
- if (requestedPatterns.isEmpty()) {
251
- requestedPatterns = null
252
- }
253
- }
254
-
255
- def targetConfigs = project.configurations.findAll {
256
- if (!it.canBeResolved) return false
257
- if (isAndroidInstrumentedTest(it.name)) return false
258
- if (requestedPatterns != null) {
259
- return requestedPatterns.any { p -> p.matcher(it.name).matches() }
260
- }
261
- return true
262
- }
263
-
264
- targetConfigs.each { cfg ->
265
- def isProd = !isTestConfig(cfg.name)
266
- def isNonTooling = isClasspath(cfg.name)
267
- // Per-configuration try/catch: AGP-style configurations can fail
268
- // with "variant ambiguity" when resolved from an init-script
269
- // context that doesn't carry the consumer attributes AGP sets
270
- // internally. We log and continue so a single ambiguous config
271
- // doesn't sink the whole facts file.
272
- try {
273
- def lenient = cfg.resolvedConfiguration.lenientConfiguration
274
- def cache = [:]
275
- lenient.firstLevelModuleDependencies.each { dep ->
276
- directIds.addAll(visit(dep, isProd, isNonTooling, cache))
277
- }
278
- // Unresolved deps drive the abort/warn decision in the root
279
- // aggregator but are deliberately NOT emitted as nodes — the
280
- // selector-only coordinates (no classifier, no ext, possibly
281
- // empty version) would surface as half-formed entries downstream
282
- // and a consumer like coana's `mvn dependency:get` would try to
283
- // fetch a phantom artifact. Matches the sbt plugin, whose
284
- // `isEmittable` filter drops these the same way.
285
- lenient.unresolvedModuleDependencies.each { dep ->
286
- if (isIntraProject(dep.selector.group, dep.selector.name)) {
287
- return
288
- }
289
- def selectorKey = dep.selector.toString()
290
- if (reportedUnresolved.add(selectorKey)) {
291
- def reason = dep.problem?.message?.readLines()?.first() ?: 'unknown reason'
292
- println "[socket-facts] unresolved: ${selectorKey} in ${project.path}: ${reason}"
293
- }
294
- }
295
- } catch (Exception e) {
296
- println "[socket-facts] skipping ${project.path}:${cfg.name}: ${e.message?.readLines()?.first()}"
297
- }
298
- }
299
- }
300
- }
301
- }
302
-
303
- rootProject { rp ->
304
- // Capture project-derived values at configuration time so the task action
305
- // doesn't reach back into `project` at execution time. Gradle's
306
- // configuration cache forbids `Task.project` invocations from task
307
- // actions; the Socket CLI disables the cache for the facts run via the
308
- // `-Dorg.gradle.configuration-cache=false` flag, but hoisting these
309
- // reads is cheap defensive code that keeps the script working even if
310
- // a caller re-enables the cache by other means.
311
- def outDirOverride = rp.findProperty('socket.outputDirectory')?.toString()
312
- def outFileOverride = rp.findProperty('socket.outputFile')?.toString()
313
- def defaultOutDir = rp.projectDir
314
- def factsFileName = SOCKET_FACTS_FILENAME
315
- // Unresolved dependencies are fatal by default — the user's environment is
316
- // expected to resolve their declared deps. `-Psocket.ignoreUnresolved=true`
317
- // (set by the CLI's `--ignore-unresolved`) downgrades that to a warning so
318
- // the facts file still emits with whatever did resolve.
319
- def ignoreUnresolved = rp.findProperty('socket.ignoreUnresolved')?.toString()?.toLowerCase() == 'true'
320
-
321
- rp.tasks.create('socketFacts') {
322
- group = 'socket'
323
- description = 'Aggregates a single Socket facts JSON for the entire build'
324
- outputs.upToDateWhen { false }
325
-
326
- doLast {
327
- def state = gradle.socketFactsState
328
- def nodes = state.nodes
329
- def directIds = state.directIds
330
- def reportedUnresolved = state.reportedUnresolved
331
-
332
- // Fail the build (or warn) once we've seen every collector's
333
- // contributions. Subproject collectors already log each unresolved dep
334
- // inline; this is the summary + decision point.
335
- if (!reportedUnresolved.isEmpty()) {
336
- def sorted = (reportedUnresolved as List).sort()
337
- if (ignoreUnresolved) {
338
- println "[socket-facts] ignoring ${sorted.size()} unresolved dependency(ies):"
339
- sorted.each { println " - ${it}" }
340
- } else {
341
- println "[socket-facts] could not resolve ${sorted.size()} dependency(ies):"
342
- sorted.each { println " - ${it}" }
343
- throw new GradleException(
344
- "Socket facts aborted: ${sorted.size()} unresolved dependency(ies). " +
345
- "Pass --ignore-unresolved to skip them, or fix resolution (repositories, " +
346
- "credentials, offline cache) and retry."
347
- )
348
- }
349
- }
350
-
351
- // Snapshot the accumulators under the same monitor used by writers in
352
- // each subproject's socketFactsCollect doLast. Task dependencies
353
- // (`aggregator.dependsOn(collector)`) already guarantee a
354
- // happens-before edge between writes and this read, but we
355
- // synchronize on `nodes` here so the read path is symmetric with the
356
- // write path — no implicit reliance on Gradle's task-graph ordering
357
- // semantics for memory visibility of plain HashMap/HashSet fields.
358
- def components
359
- synchronized (nodes) {
360
- components = nodes.collect { id, node ->
361
- [id: id, coord: node.coord, prod: node.prod, nonTooling: node.nonTooling, children: (node.children as List).sort()]
362
- }
363
- }
364
-
365
- components = components.collect { snapshot ->
366
- def id = snapshot.id
367
- def coord = snapshot.coord
368
- def component = [
369
- type : 'maven',
370
- namespace: coord.groupId,
371
- name : coord.artifactId,
372
- ]
373
- if (coord.version) {
374
- component.version = coord.version
375
- }
376
- def qualifiers = [:]
377
- if (coord.classifier) {
378
- qualifiers.classifier = coord.classifier
379
- }
380
- if (coord.ext) {
381
- qualifiers.ext = coord.ext
382
- }
383
- if (!qualifiers.isEmpty()) {
384
- component.qualifiers = qualifiers
385
- }
386
- component.id = id
387
- if (directIds.contains(id)) {
388
- component.direct = true
389
- }
390
- if (!snapshot.prod) {
391
- component.dev = true
392
- }
393
- if (!snapshot.nonTooling) {
394
- component.tooling = true
395
- }
396
- if (!snapshot.children.isEmpty()) {
397
- component.dependencies = snapshot.children
398
- }
399
- component
400
- }
401
-
402
- if (components.isEmpty()) {
403
- println "[socket-facts] no resolvable dependencies in build, skipping"
404
- return
405
- }
406
-
407
- def outputDir = outDirOverride ? new File(outDirOverride) : defaultOutDir
408
- outputDir.mkdirs()
409
- def fileName = outFileOverride ?: factsFileName
410
- def outFile = new File(outputDir, fileName)
411
- outFile.text = JsonOutput.prettyPrint(JsonOutput.toJson([components: components]))
412
- println "Socket facts file written to: ${outFile.absolutePath}"
413
- }
414
- }
415
- }
416
-
417
- // Wire every subproject's collector as a dependency of the root aggregator
418
- // so the aggregator runs after all contributions have been made.
419
- gradle.projectsEvaluated { g ->
420
- def aggregator = g.rootProject.tasks.findByName('socketFacts')
421
- if (aggregator) {
422
- g.rootProject.allprojects.each { p ->
423
- def collector = p.tasks.findByName('socketFactsCollect')
424
- if (collector) {
425
- aggregator.dependsOn(collector)
426
- }
427
- }
428
- }
429
- }