@socketsecurity/cli-with-sentry 1.1.96 → 1.1.98

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 (41) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/cli.js +1468 -78
  3. package/dist/cli.js.map +1 -1
  4. package/dist/constants.js +4 -4
  5. package/dist/constants.js.map +1 -1
  6. package/dist/tsconfig.dts.tsbuildinfo +1 -1
  7. package/dist/types/commands/manifest/bazel/bazel-bin-detect.d.mts +11 -0
  8. package/dist/types/commands/manifest/bazel/bazel-bin-detect.d.mts.map +1 -0
  9. package/dist/types/commands/manifest/bazel/bazel-build-parser.d.mts +34 -0
  10. package/dist/types/commands/manifest/bazel/bazel-build-parser.d.mts.map +1 -0
  11. package/dist/types/commands/manifest/bazel/bazel-java-shim.d.mts +10 -0
  12. package/dist/types/commands/manifest/bazel/bazel-java-shim.d.mts.map +1 -0
  13. package/dist/types/commands/manifest/bazel/bazel-output-base-check.d.mts +7 -0
  14. package/dist/types/commands/manifest/bazel/bazel-output-base-check.d.mts.map +1 -0
  15. package/dist/types/commands/manifest/bazel/bazel-python-shim.d.mts +9 -0
  16. package/dist/types/commands/manifest/bazel/bazel-python-shim.d.mts.map +1 -0
  17. package/dist/types/commands/manifest/bazel/bazel-query-runner.d.mts +41 -0
  18. package/dist/types/commands/manifest/bazel/bazel-query-runner.d.mts.map +1 -0
  19. package/dist/types/commands/manifest/bazel/bazel-repo-discovery.d.mts +34 -0
  20. package/dist/types/commands/manifest/bazel/bazel-repo-discovery.d.mts.map +1 -0
  21. package/dist/types/commands/manifest/bazel/bazel-workspace-detect.d.mts +13 -0
  22. package/dist/types/commands/manifest/bazel/bazel-workspace-detect.d.mts.map +1 -0
  23. package/dist/types/commands/manifest/bazel/cmd-manifest-bazel.d.mts +9 -0
  24. package/dist/types/commands/manifest/bazel/cmd-manifest-bazel.d.mts.map +1 -0
  25. package/dist/types/commands/manifest/bazel/extract_bazel_to_maven.d.mts +33 -0
  26. package/dist/types/commands/manifest/bazel/extract_bazel_to_maven.d.mts.map +1 -0
  27. package/dist/types/commands/manifest/cmd-manifest.d.mts.map +1 -1
  28. package/dist/types/commands/manifest/detect-manifest-actions.d.mts +1 -0
  29. package/dist/types/commands/manifest/detect-manifest-actions.d.mts.map +1 -1
  30. package/dist/types/commands/manifest/generate_auto_manifest.d.mts +4 -1
  31. package/dist/types/commands/manifest/generate_auto_manifest.d.mts.map +1 -1
  32. package/dist/types/commands/scan/handle-create-new-scan.d.mts.map +1 -1
  33. package/dist/types/utils/api.d.mts +5 -3
  34. package/dist/types/utils/api.d.mts.map +1 -1
  35. package/dist/types/utils/coana.d.mts +35 -0
  36. package/dist/types/utils/coana.d.mts.map +1 -1
  37. package/dist/types/utils/socket-json.d.mts +10 -0
  38. package/dist/types/utils/socket-json.d.mts.map +1 -1
  39. package/dist/utils.js +131 -28
  40. package/dist/utils.js.map +1 -1
  41. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -15,17 +15,18 @@ var words = require('../external/@socketsecurity/registry/lib/words');
15
15
  var fs$1 = require('node:fs');
16
16
  var arrays = require('../external/@socketsecurity/registry/lib/arrays');
17
17
  var prompts = require('../external/@socketsecurity/registry/lib/prompts');
18
+ var bin = require('../external/@socketsecurity/registry/lib/bin');
19
+ var childProcess = require('node:child_process');
20
+ var os = require('node:os');
18
21
  var spawn = require('../external/@socketsecurity/registry/lib/spawn');
19
22
  var fs$2 = require('../external/@socketsecurity/registry/lib/fs');
20
23
  var strings = require('../external/@socketsecurity/registry/lib/strings');
21
- var os = require('node:os');
22
24
  var path$1 = require('../external/@socketsecurity/registry/lib/path');
23
25
  var require$$11 = require('../external/@socketsecurity/registry/lib/objects');
24
26
  var registry = require('../external/@socketsecurity/registry');
25
27
  var packages = require('../external/@socketsecurity/registry/lib/packages');
26
28
  var require$$12 = require('../external/@socketsecurity/registry/lib/promises');
27
29
  var regexps = require('../external/@socketsecurity/registry/lib/regexps');
28
- var childProcess = require('node:child_process');
29
30
  var require$$1 = require('node:util');
30
31
  var promises = require('node:stream/promises');
31
32
 
@@ -331,9 +332,9 @@ const hidden$x = false;
331
332
  const cmdAnalytics = {
332
333
  description: description$F,
333
334
  hidden: hidden$x,
334
- run: run$S
335
+ run: run$T
335
336
  };
336
- async function run$S(argv, importMeta, {
337
+ async function run$T(argv, importMeta, {
337
338
  parentName
338
339
  }) {
339
340
  const config = {
@@ -754,9 +755,9 @@ const hidden$w = false;
754
755
  const cmdAuditLog = {
755
756
  description: description$E,
756
757
  hidden: hidden$w,
757
- run: run$R
758
+ run: run$S
758
759
  };
759
- async function run$R(argv, importMeta, {
760
+ async function run$S(argv, importMeta, {
760
761
  parentName
761
762
  }) {
762
763
  const config = {
@@ -1850,6 +1851,7 @@ async function detectManifestActions(
1850
1851
  // regardless of local socket.json status. Sometimes we want that.
1851
1852
  sockJson, cwd = process.cwd()) {
1852
1853
  const output = {
1854
+ bazel: false,
1853
1855
  cdxgen: false,
1854
1856
  // TODO
1855
1857
  count: 0,
@@ -1857,6 +1859,13 @@ sockJson, cwd = process.cwd()) {
1857
1859
  gradle: false,
1858
1860
  sbt: false
1859
1861
  };
1862
+ if (sockJson?.defaults?.manifest?.bazel?.disabled) {
1863
+ require$$9.debugLog('notice', `[DEBUG] - bazel auto-detection is disabled in ${constants.SOCKET_JSON}`);
1864
+ } else if (fs$1.existsSync(path.join(cwd, 'MODULE.bazel')) || fs$1.existsSync(path.join(cwd, 'WORKSPACE')) || fs$1.existsSync(path.join(cwd, 'WORKSPACE.bazel'))) {
1865
+ require$$9.debugLog('notice', '[DEBUG] - Detected a Bazel workspace');
1866
+ output.bazel = true;
1867
+ output.count += 1;
1868
+ }
1860
1869
  if (sockJson?.defaults?.manifest?.sbt?.disabled) {
1861
1870
  require$$9.debugLog('notice', `[DEBUG] - sbt auto-detection is disabled in ${constants.SOCKET_JSON}`);
1862
1871
  } else if (fs$1.existsSync(path.join(cwd, 'build.sbt'))) {
@@ -1884,7 +1893,1168 @@ sockJson, cwd = process.cwd()) {
1884
1893
  output.count += 1;
1885
1894
  }
1886
1895
  }
1887
- return output;
1896
+ return output;
1897
+ }
1898
+
1899
+ /**
1900
+ * Resolve the bazel binary to invoke for `socket manifest bazel`.
1901
+ *
1902
+ * Resolution order:
1903
+ * 1. If `explicit` is provided, return it iff it exists on disk; else throw.
1904
+ * 2. Look up `bazelisk` on PATH (preferred — respects `.bazelversion`).
1905
+ * 3. Fall back to `bazel` on PATH.
1906
+ * 4. If neither is found, throw InputError with install instructions.
1907
+ */
1908
+ async function resolveBazelBinary(explicit) {
1909
+ if (explicit) {
1910
+ if (!fs$1.existsSync(explicit)) {
1911
+ throw new utils.InputError(`--bazel path does not exist: ${explicit}. Install bazelisk or bazel, or pass an existing path via --bazel.`);
1912
+ }
1913
+ return explicit;
1914
+ }
1915
+ // Prefer bazelisk: respects .bazelversion in the workspace.
1916
+ const bazelisk = await bin.whichBin('bazelisk', {
1917
+ nothrow: true
1918
+ });
1919
+ if (bazelisk) {
1920
+ return bazelisk;
1921
+ }
1922
+ const bazel = await bin.whichBin('bazel', {
1923
+ nothrow: true
1924
+ });
1925
+ if (bazel) {
1926
+ return bazel;
1927
+ }
1928
+ throw new utils.InputError('Could not find bazelisk or bazel on PATH. ' + 'Install bazelisk (recommended; https://github.com/bazelbuild/bazelisk) ' + 'or bazel, or pass --bazel <path>.');
1929
+ }
1930
+
1931
+ /**
1932
+ * Parse `bazel query --output=build` text and `unsorted_deps.json` files
1933
+ * (rules_jvm_external) into a uniform `ExtractedArtifact` shape consumed by
1934
+ * the converter.
1935
+ *
1936
+ * Security gate: every regex uses bounded character classes to prevent
1937
+ * catastrophic backtracking on hostile bazel-query output. Rules without
1938
+ * `maven_coordinates=` are skipped. Caller is responsible for size-capping
1939
+ * the input string.
1940
+ */
1941
+
1942
+ // Per-rule block matcher: matches `<kind>(...)` where kind is jvm_import or
1943
+ // aar_import, bounded by `^)` (closing paren on its own line) — Bazel
1944
+ // `--output=build` output convention. Body length capped at 8 KiB; real
1945
+ // rules are ~500 bytes, so the cap is 16x normal. Prevents pathological
1946
+ // backtracking on hostile input.
1947
+ const RULE_RE = /^(jvm_import|aar_import)\(([\s\S]{0,8192}?)^\)/gm;
1948
+
1949
+ // Cache for per-attribute regexes — avoids recompiling the same pattern on
1950
+ // every rule block. Keyed by attr name; all attr names are safe alphanumeric
1951
+ // identifiers so no escaping is needed beyond the bounded character class.
1952
+ const ATTR_RE_CACHE = new Map();
1953
+
1954
+ // Cache for per-tag-key regexes used by extractTagValue.
1955
+ const TAG_RE_CACHE = new Map();
1956
+ function extractAttr(body, attr) {
1957
+ // Match `<attr> = "VALUE"` — quoted-string attrs only.
1958
+ // Quoted value capped at 4 KiB; canonical Maven URLs are ~150 bytes.
1959
+ let re = ATTR_RE_CACHE.get(attr);
1960
+ if (!re) {
1961
+ re = new RegExp(`\\b${attr}\\s*=\\s*"([^"\\n]{0,4096})"`);
1962
+ ATTR_RE_CACHE.set(attr, re);
1963
+ }
1964
+ const m = re.exec(body);
1965
+ return m?.[1];
1966
+ }
1967
+
1968
+ // Extracts a `key=value` pair from inside a Bazel `tags = [...]` attribute
1969
+ // (rules_jvm_external encodes maven_sha256, maven_coordinates etc. this way).
1970
+ // Pattern: `"maven_sha256=<hex>"` inside the tags list.
1971
+ // Returns undefined when the tag is absent or malformed.
1972
+ function extractTagValue(body, tagKey) {
1973
+ // Match the full tags = [...] block (bounded at 8 KiB).
1974
+ const tagsM = /\btags\s*=\s*\[([\s\S]{0,8192}?)\]/m.exec(body);
1975
+ if (!tagsM) {
1976
+ return undefined;
1977
+ }
1978
+ const tagsBlob = tagsM[1];
1979
+ // Within the blob, look for "<tagKey>=<value>" inside a quoted string.
1980
+ // Bounded at 512 bytes per tag entry (sha256 hex is 64 chars; URLs ~150).
1981
+ let tagRe = TAG_RE_CACHE.get(tagKey);
1982
+ if (!tagRe) {
1983
+ tagRe = new RegExp(`"${tagKey}=([^"\\n]{0,512})"`);
1984
+ TAG_RE_CACHE.set(tagKey, tagRe);
1985
+ }
1986
+ const m = tagRe.exec(tagsBlob);
1987
+ return m?.[1];
1988
+ }
1989
+ function extractDeps(body) {
1990
+ // Match `deps = ["a", "b", ...]`. Body length capped at 16 KiB; real
1991
+ // dep lists are <2 KiB.
1992
+ const m = /\bdeps\s*=\s*\[([\s\S]{0,16384}?)\]/m.exec(body);
1993
+ if (!m) {
1994
+ return [];
1995
+ }
1996
+ const out = [];
1997
+ // Per-label cap at 512 bytes; real Bazel labels are <100 bytes.
1998
+ for (const q of m[1].matchAll(/"([^"\n]{0,512})"/g)) {
1999
+ out.push(q[1]);
2000
+ }
2001
+ return out;
2002
+ }
2003
+
2004
+ /**
2005
+ * Parse `bazel query --output=build` stdout into `ExtractedArtifact[]`.
2006
+ * Skips rules without a `maven_coordinates` attribute (those aren't
2007
+ * rules_jvm_external lockfile rules).
2008
+ */
2009
+ function parseBazelBuildOutput(text) {
2010
+ const results = [];
2011
+ for (const m of text.matchAll(RULE_RE)) {
2012
+ const ruleKind = m[1];
2013
+ const body = m[2];
2014
+ const ruleName = extractAttr(body, 'name');
2015
+ // maven_coordinates can be:
2016
+ // (a) a top-level rule attribute: `maven_coordinates = "g:a:v"` (newer rje)
2017
+ // (b) inside tags = [...]: `"maven_coordinates=g:a:v"` (older rje, e.g. ray)
2018
+ const coords = extractAttr(body, 'maven_coordinates') ?? extractTagValue(body, 'maven_coordinates');
2019
+ if (!ruleName || !coords) {
2020
+ continue;
2021
+ }
2022
+ // maven_sha256 is encoded inside tags = [...] as "maven_sha256=<hex>" by
2023
+ // rules_jvm_external; try tags first, fall back to standalone attr for
2024
+ // older rule shapes that may declare it as a top-level attribute.
2025
+ const mavenSha256 = extractTagValue(body, 'maven_sha256') ?? extractAttr(body, 'maven_sha256');
2026
+ results.push({
2027
+ ruleKind,
2028
+ ruleName,
2029
+ mavenCoordinates: coords,
2030
+ mavenUrl: extractAttr(body, 'maven_url'),
2031
+ mavenSha256,
2032
+ deps: extractDeps(body)
2033
+ });
2034
+ }
2035
+ return results;
2036
+ }
2037
+ function ruleNameFromCoordinate(c) {
2038
+ return c.replace(/[^A-Za-z0-9]/g, '_');
2039
+ }
2040
+
2041
+ /**
2042
+ * Parse supported `external/<repo>/unsorted_deps.json` shapes emitted by
2043
+ * rules_jvm_external. Older files use an artifact array with full coordinates;
2044
+ * newer v2 lock-file-shaped files use artifact/dependency maps keyed by
2045
+ * `group:artifact`. Caller MUST size-cap the input because JSON.parse is
2046
+ * unbounded by default.
2047
+ */
2048
+ function parseUnsortedDepsJson(json) {
2049
+ let parsed;
2050
+ try {
2051
+ parsed = JSON.parse(json);
2052
+ } catch {
2053
+ return [];
2054
+ }
2055
+ const maybe = parsed;
2056
+ if (Array.isArray(maybe.artifacts)) {
2057
+ const out = [];
2058
+ for (const a of maybe.artifacts) {
2059
+ if (typeof a?.coordinates !== 'string') {
2060
+ continue;
2061
+ }
2062
+ const deps = [];
2063
+ if (Array.isArray(a.deps)) {
2064
+ for (const d of a.deps) {
2065
+ if (typeof d === 'string') {
2066
+ deps.push(d);
2067
+ }
2068
+ }
2069
+ }
2070
+ out.push({
2071
+ ruleKind: 'jvm_import',
2072
+ ruleName: ruleNameFromCoordinate(a.coordinates),
2073
+ mavenCoordinates: a.coordinates,
2074
+ mavenUrl: typeof a.url === 'string' ? a.url : undefined,
2075
+ mavenSha256: typeof a.sha256 === 'string' ? a.sha256 : undefined,
2076
+ deps
2077
+ });
2078
+ }
2079
+ return out;
2080
+ }
2081
+ if (!maybe.artifacts || typeof maybe.artifacts !== 'object') {
2082
+ return [];
2083
+ }
2084
+ const dependencies = maybe.dependencies ?? {};
2085
+ const out = [];
2086
+ for (const [groupArtifact, artifact] of Object.entries(maybe.artifacts)) {
2087
+ if (!artifact || typeof artifact.version !== 'string') {
2088
+ continue;
2089
+ }
2090
+ const shasums = artifact.shasums ?? {};
2091
+ const jarSha = shasums['jar'];
2092
+ if (typeof jarSha === 'string' || Object.keys(shasums).length === 0) {
2093
+ out.push(v2Artifact(groupArtifact, artifact.version, jarSha, dependencies));
2094
+ }
2095
+ for (const [classifier, sha256] of Object.entries(shasums)) {
2096
+ if (classifier === 'jar' || typeof sha256 !== 'string') {
2097
+ continue;
2098
+ }
2099
+ const classifierKey = `${groupArtifact}:jar:${classifier}`;
2100
+ out.push(v2Artifact(classifierKey, artifact.version, sha256, dependencies));
2101
+ }
2102
+ }
2103
+ return out;
2104
+ }
2105
+ function v2Artifact(artifactKey, version, sha256, dependencies) {
2106
+ return {
2107
+ ruleKind: 'jvm_import',
2108
+ ruleName: ruleNameFromCoordinate(artifactKey),
2109
+ mavenCoordinates: `${artifactKey}:${version}`,
2110
+ mavenSha256: sha256,
2111
+ deps: Array.isArray(dependencies[artifactKey]) ? dependencies[artifactKey].filter(d => typeof d === 'string') : []
2112
+ };
2113
+ }
2114
+
2115
+ let probed = false;
2116
+
2117
+ // Verifies `java` is functional in the current execution environment. Bazel
2118
+ // JVM manifest extraction (rules_jvm_external → Coursier) requires a real
2119
+ // JDK; the CLI does not attempt to discover Homebrew installs or mutate the
2120
+ // caller's PATH/JAVA_HOME. If `java -version` fails we throw with an
2121
+ // actionable message so the surfaced error names the prerequisite directly
2122
+ // instead of relying on Bazel's downstream diagnostic.
2123
+ function ensureJavaOnPath() {
2124
+ if (probed) {
2125
+ return;
2126
+ }
2127
+ try {
2128
+ childProcess.execSync('java -version', {
2129
+ stdio: 'ignore'
2130
+ });
2131
+ probed = true;
2132
+ } catch {
2133
+ throw new Error('Java is required for Bazel JVM manifest extraction ' + '(rules_jvm_external invokes Coursier, which needs a JDK). ' + 'Install a JDK (e.g. Temurin or OpenJDK) and ensure `java` is on PATH.');
2134
+ }
2135
+ }
2136
+
2137
+ // Validates that --bazel-output-base is a path we can use as Bazel's output_base.
2138
+ // Throws InputError if:
2139
+ // - the input contains `..` segments (path traversal guard)
2140
+ // - the existing path is not writable
2141
+ // - the path cannot be created (parent not writable)
2142
+ function validateOutputBase(outputBase, cwd) {
2143
+ // Path traversal guard: reject any literal `..` segment in user input.
2144
+ // After path.resolve these are normalised away, so we check the raw input.
2145
+ // Split on both separators. On Windows `path.sep === '\\'`, so
2146
+ // input like `foo/../etc` would not contain a `..` segment under the
2147
+ // platform-specific split, bypassing the guard — yet path.resolve below
2148
+ // would still normalise the `..` and a traversal target could materialise.
2149
+ const segments = outputBase.split(/[\\/]/);
2150
+ if (segments.includes('..')) {
2151
+ throw new utils.InputError(`--bazel-output-base must not contain '..' segments: ${outputBase}`);
2152
+ }
2153
+ const resolved = path.resolve(cwd, outputBase);
2154
+ if (fs$1.existsSync(resolved)) {
2155
+ try {
2156
+ fs$1.accessSync(resolved, fs$1.constants.W_OK);
2157
+ } catch {
2158
+ throw new utils.InputError(`--bazel-output-base is not writable: ${resolved}`);
2159
+ }
2160
+ return;
2161
+ }
2162
+ // Path does not exist yet — try to create it so bazel can populate it.
2163
+ try {
2164
+ fs$1.mkdirSync(resolved, {
2165
+ recursive: true
2166
+ });
2167
+ } catch (e) {
2168
+ throw new utils.InputError(`--bazel-output-base could not be created at ${resolved}: ${utils.getErrorCause(e)}`);
2169
+ }
2170
+ }
2171
+
2172
+ // Stable shim dir name — same process will get the same dir; concurrent
2173
+ // socket-cli invocations on the same machine share it. The symlink target
2174
+ // is whatever python3 resolves to NOW; if PATH changes between invocations
2175
+ // we replace the symlink.
2176
+ const SHIM_SUBDIR = 'socket-cli-bazel-python-shim';
2177
+
2178
+ // Cache the result for the lifetime of this process.
2179
+ let cached = null;
2180
+
2181
+ // Safe wrapper around whichBin that returns null instead of throwing when
2182
+ // nothrow semantics are broken in older registry versions (realpath 'null' bug).
2183
+ async function safeWhichBin(name) {
2184
+ try {
2185
+ return (await bin.whichBin(name, {
2186
+ nothrow: true
2187
+ })) ?? null;
2188
+ } catch {
2189
+ return null;
2190
+ }
2191
+ }
2192
+ async function provisionPythonShim() {
2193
+ if (cached) {
2194
+ return cached;
2195
+ }
2196
+ const pythonOnPath = await safeWhichBin('python');
2197
+ if (pythonOnPath) {
2198
+ cached = {
2199
+ augmentedEnv: undefined,
2200
+ shimDir: undefined
2201
+ };
2202
+ return cached;
2203
+ }
2204
+ const python3OnPath = await safeWhichBin('python3');
2205
+ if (!python3OnPath) {
2206
+ throw new utils.InputError('Neither `python` nor `python3` found on PATH. Older versions of ' + 'rules_jvm_external require a `python` interpreter for repository ' + 'rules. Install Python 3 and ensure it is on PATH, then retry.');
2207
+ }
2208
+ const shimDir = path.join(os.tmpdir(), SHIM_SUBDIR);
2209
+ fs$1.mkdirSync(shimDir, {
2210
+ recursive: true
2211
+ });
2212
+ const linkPath = path.join(shimDir, 'python');
2213
+ // Replace the symlink defensively in case python3's resolved path moved.
2214
+ if (fs$1.existsSync(linkPath)) {
2215
+ try {
2216
+ fs$1.unlinkSync(linkPath);
2217
+ } catch {
2218
+ // Tolerate races; the next symlinkSync may still succeed.
2219
+ }
2220
+ }
2221
+ // The shim dir is process-shared (os.tmpdir()/socket-cli-bazel-python-shim),
2222
+ // so a concurrent socket-cli invocation may re-create the link between our
2223
+ // unlinkSync and symlinkSync. Tolerate EEXIST when the link is back: the
2224
+ // other process won the race and left a usable shim in place.
2225
+ try {
2226
+ fs$1.symlinkSync(python3OnPath, linkPath);
2227
+ } catch (e) {
2228
+ if (e.code === 'EEXIST' && fs$1.existsSync(linkPath)) ; else {
2229
+ throw e;
2230
+ }
2231
+ }
2232
+ const augmentedEnv = {
2233
+ ...process.env,
2234
+ PATH: `${shimDir}${path.delimiter}${process.env['PATH'] ?? ''}`
2235
+ };
2236
+ cached = {
2237
+ augmentedEnv,
2238
+ shimDir
2239
+ };
2240
+ return cached;
2241
+ }
2242
+
2243
+ // Default per-invocation timeout for bazel queries. Bazel cold-cache starts
2244
+ // can take several minutes; 10 minutes is generous while still bounding CI hangs.
2245
+ const BAZEL_QUERY_TIMEOUT_MS = 600_000;
2246
+
2247
+ // Splits the user-supplied --bazel-flags string on whitespace.
2248
+ // Empty / undefined returns []. No shell parsing — quoted args with embedded
2249
+ // whitespace are not supported (documented limitation; same trust model as
2250
+ // gradleOpts).
2251
+ function splitBazelFlags(flags) {
2252
+ if (!flags) {
2253
+ return [];
2254
+ }
2255
+ return flags.split(/\s+/).filter(Boolean);
2256
+ }
2257
+ function buildBazelModShowVisibleReposArgv(opts) {
2258
+ const startup = [];
2259
+ if (opts.bazelRc) {
2260
+ startup.push(`--bazelrc=${opts.bazelRc}`);
2261
+ }
2262
+ if (opts.bazelOutputBase) {
2263
+ startup.push(`--output_base=${opts.bazelOutputBase}`);
2264
+ }
2265
+ const userFlags = splitBazelFlags(opts.bazelFlags);
2266
+ return [...startup, 'mod', 'show_repo', '--all_visible_repos', '--output=streamed_jsonproto', ...userFlags];
2267
+ }
2268
+ function buildBazelArgv(queryStr, opts) {
2269
+ // Startup flags MUST precede the `query` subcommand.
2270
+ // Bazel argv shape: <startup> query <queryFlags> <invocationFlags> <queryStr> --output=build <userFlags>
2271
+ const startup = [];
2272
+ if (opts.bazelRc) {
2273
+ startup.push(`--bazelrc=${opts.bazelRc}`);
2274
+ }
2275
+ if (opts.bazelOutputBase) {
2276
+ startup.push(`--output_base=${opts.bazelOutputBase}`);
2277
+ }
2278
+ // Keep query output stable and avoid updating Bazel lockfiles while extracting.
2279
+ const queryFlags = ['--lockfile_mode=off', '--noshow_progress'];
2280
+ const userFlags = splitBazelFlags(opts.bazelFlags);
2281
+ return [...startup, 'query', ...queryFlags, ...opts.invocationFlags, queryStr, '--output=build', ...userFlags];
2282
+ }
2283
+ function stringField(value) {
2284
+ return typeof value === 'string' ? value : '';
2285
+ }
2286
+ function numericExitCode(value) {
2287
+ return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
2288
+ }
2289
+ function normalizeSpawnError(error) {
2290
+ const e = error;
2291
+ return {
2292
+ code: numericExitCode(e?.code) ?? numericExitCode(e?.status) ?? -1,
2293
+ stderr: stringField(e?.stderr),
2294
+ stdout: stringField(e?.stdout)
2295
+ };
2296
+ }
2297
+
2298
+ /**
2299
+ * Run `bazel query` with the standardized argv shape and capture
2300
+ * stdout/stderr/code. Wraps the call in a spinner that resolves on success
2301
+ * and fails on non-zero exit. Rejected spawn calls are normalized into a
2302
+ * BazelQueryResult so retry/skip handling can inspect stderr.
2303
+ */
2304
+ async function runBazelQuery(queryStr, opts) {
2305
+ const argv = buildBazelArgv(queryStr, opts);
2306
+ if (opts.verbose) {
2307
+ logger.logger.log('[VERBOSE] Executing:', opts.bin, ', args:', argv);
2308
+ }
2309
+ const {
2310
+ spinner
2311
+ } = constants.default;
2312
+ let result;
2313
+ try {
2314
+ spinner.start(`Running bazel query (${queryStr.slice(0, 80)})...`);
2315
+ const output = await spawn.spawn(opts.bin, argv, {
2316
+ cwd: opts.cwd,
2317
+ timeout: BAZEL_QUERY_TIMEOUT_MS,
2318
+ ...(opts.env ? {
2319
+ env: opts.env
2320
+ } : {})
2321
+ });
2322
+ const {
2323
+ code,
2324
+ stderr,
2325
+ stdout
2326
+ } = output;
2327
+ result = {
2328
+ code,
2329
+ stdout,
2330
+ stderr
2331
+ };
2332
+ return result;
2333
+ } catch (e) {
2334
+ result = normalizeSpawnError(e);
2335
+ return result;
2336
+ } finally {
2337
+ const truncated = queryStr.slice(0, 80);
2338
+ if (result?.code === 0) {
2339
+ spinner.successAndStop(`bazel query completed (${truncated}).`);
2340
+ } else {
2341
+ spinner.failAndStop(`bazel query failed (${truncated}).`);
2342
+ }
2343
+ }
2344
+ }
2345
+
2346
+ /**
2347
+ * Bzlmod-native visible repository enumeration. This is only a candidate
2348
+ * source; callers must still validate each returned apparent repo name with a
2349
+ * semantic query for generated JVM Maven rules.
2350
+ */
2351
+ async function runBazelModShowVisibleRepos(opts) {
2352
+ const argv = buildBazelModShowVisibleReposArgv(opts);
2353
+ if (opts.verbose) {
2354
+ logger.logger.log('[VERBOSE] Executing:', opts.bin, ', args:', argv);
2355
+ }
2356
+ try {
2357
+ const output = await spawn.spawn(opts.bin, argv, {
2358
+ cwd: opts.cwd,
2359
+ timeout: BAZEL_QUERY_TIMEOUT_MS,
2360
+ ...(opts.env ? {
2361
+ env: opts.env
2362
+ } : {})
2363
+ });
2364
+ const {
2365
+ code,
2366
+ stderr,
2367
+ stdout
2368
+ } = output;
2369
+ return {
2370
+ code,
2371
+ stdout,
2372
+ stderr
2373
+ };
2374
+ } catch (e) {
2375
+ return normalizeSpawnError(e);
2376
+ }
2377
+ }
2378
+
2379
+ /**
2380
+ * Build a `RepoProbe` (compatible with bazel-repo-discovery) bound to opts.
2381
+ * Used by `discoverMavenRepos` to validate candidate Maven repo
2382
+ * names against the running workspace.
2383
+ */
2384
+ function buildProbeFor(opts) {
2385
+ return async repoName => {
2386
+ const queryStr = `kind("jvm_import rule|aar_import rule", @${repoName}//:*)`;
2387
+ const result = await runBazelQuery(queryStr, opts);
2388
+ return {
2389
+ stdout: result.stdout,
2390
+ code: result.code
2391
+ };
2392
+ };
2393
+ }
2394
+
2395
+ // Maximum size (bytes) we will read for any single Bazel workspace file.
2396
+ // Prevents DoS via maliciously large MODULE.bazel / WORKSPACE / .bzl files.
2397
+ const MAX_WORKSPACE_FILE_BYTES = 5 * 1024 * 1024;
2398
+
2399
+ // Maximum candidate count we will return (deduped) before truncating.
2400
+ // Real repos have <20; this is a hard ceiling against pathological inputs.
2401
+ const MAX_CANDIDATES = 256;
2402
+
2403
+ // Regex strategy: anchored, bounded character classes, no nested quantifiers.
2404
+ // Match `use_repo(maven, "X", "Y", ...)` with a bounded arg-list window to
2405
+ // avoid catastrophic backtracking on hostile input.
2406
+
2407
+ // Bzlmod use_repo(maven, "name1", "name2"...).
2408
+ // Bounded: matches up to ~4KB of arg list to avoid catastrophic backtracking.
2409
+ const USE_REPO_RE = /use_repo\s*\(\s*maven\s*,([^)]{0,4096})\)/g;
2410
+ const BAZEL_REPO_NAME_PATTERN = '[A-Za-z0-9._+-]{1,129}';
2411
+ const BAZEL_REPO_NAME_RE = new RegExp(`^${BAZEL_REPO_NAME_PATTERN}$`);
2412
+ // Quoted-name extractor inside the captured argument blob.
2413
+ const QUOTED_NAME_RE = new RegExp(`"(${BAZEL_REPO_NAME_PATTERN})"`, 'g');
2414
+
2415
+ // Legacy maven_install(name = "X", ...) on a single statement.
2416
+ // Match the name= keyword arg specifically; bounded.
2417
+ const MAVEN_INSTALL_NAME_RE = new RegExp(`maven_install\\s*\\([^)]{0,8192}?\\bname\\s*=\\s*"(${BAZEL_REPO_NAME_PATTERN})"`, 'g');
2418
+ const MAVEN_COORDINATES_MARKER_RE = /\bmaven_coordinates\s*=/;
2419
+
2420
+ // Reads file contents, refusing files that exceed MAX_WORKSPACE_FILE_BYTES.
2421
+ // Returns null when the file is missing, oversized, or unreadable.
2422
+ function safeReadFile(file) {
2423
+ if (!fs$1.existsSync(file)) {
2424
+ return null;
2425
+ }
2426
+ try {
2427
+ const stat = fs$1.statSync(file);
2428
+ if (stat.size > MAX_WORKSPACE_FILE_BYTES) {
2429
+ return null;
2430
+ }
2431
+ return fs$1.readFileSync(file, 'utf8');
2432
+ } catch {
2433
+ return null;
2434
+ }
2435
+ }
2436
+
2437
+ // Walks workspace root for legacy Starlark sources we can scan: WORKSPACE
2438
+ // (and WORKSPACE.bazel) plus top-level .bzl files. Non-recursive by design;
2439
+ // Phase 1 explicitly avoids static Starlark parsing at depth.
2440
+ function listLegacyStarlarkFiles(cwd) {
2441
+ const files = [];
2442
+ const candidates = ['WORKSPACE', 'WORKSPACE.bazel'];
2443
+ for (const c of candidates) {
2444
+ const p = path.join(cwd, c);
2445
+ if (fs$1.existsSync(p)) {
2446
+ files.push(p);
2447
+ }
2448
+ }
2449
+ // Top-level .bzl files only.
2450
+ try {
2451
+ for (const entry of fs$1.readdirSync(cwd)) {
2452
+ if (entry.endsWith('.bzl')) {
2453
+ files.push(path.join(cwd, entry));
2454
+ }
2455
+ }
2456
+ } catch {
2457
+ // Ignore unreadable cwd.
2458
+ }
2459
+ return files;
2460
+ }
2461
+
2462
+ // Returns deduplicated, sorted list of items, capped at MAX_CANDIDATES.
2463
+ function uniqueSorted(items) {
2464
+ const seen = new Set();
2465
+ const out = [];
2466
+ for (const item of items) {
2467
+ if (!seen.has(item)) {
2468
+ seen.add(item);
2469
+ out.push(item);
2470
+ if (out.length >= MAX_CANDIDATES) {
2471
+ break;
2472
+ }
2473
+ }
2474
+ }
2475
+ return out.sort();
2476
+ }
2477
+ function apparentNameFromJsonValue(value) {
2478
+ if (!value || typeof value !== 'object') {
2479
+ return undefined;
2480
+ }
2481
+ const obj = value;
2482
+ const direct = obj['apparentName'] ?? obj['apparent_name'];
2483
+ if (typeof direct === 'string') {
2484
+ return direct;
2485
+ }
2486
+ for (const nested of Object.values(obj)) {
2487
+ const found = apparentNameFromJsonValue(nested);
2488
+ if (found) {
2489
+ return found;
2490
+ }
2491
+ }
2492
+ return undefined;
2493
+ }
2494
+ function normalizeRepoName(name) {
2495
+ const repo = name.startsWith('@') ? name.slice(1) : name;
2496
+ return BAZEL_REPO_NAME_RE.test(repo) ? repo : undefined;
2497
+ }
2498
+
2499
+ // Parse `bazel mod show_repo --all_visible_repos --output=streamed_jsonproto`
2500
+ // output. Bazel's JSON proto field casing may vary by formatter; accept both
2501
+ // lowerCamel and snake_case, and tolerate wrapper objects around Repository.
2502
+ function parseVisibleRepoCandidates(output) {
2503
+ const candidates = [];
2504
+ for (const line of output.split(/\r?\n/)) {
2505
+ const trimmed = line.trim();
2506
+ if (!trimmed) {
2507
+ continue;
2508
+ }
2509
+ try {
2510
+ const parsed = JSON.parse(trimmed);
2511
+ const apparentName = apparentNameFromJsonValue(parsed);
2512
+ if (apparentName) {
2513
+ const repo = normalizeRepoName(apparentName);
2514
+ if (repo) {
2515
+ candidates.push(repo);
2516
+ }
2517
+ }
2518
+ } catch {
2519
+ // Ignore malformed lines; caller will fall back to static discovery when
2520
+ // no usable visible repo names are found.
2521
+ }
2522
+ }
2523
+ return uniqueSorted(candidates);
2524
+ }
2525
+
2526
+ // Step 1: parse candidate Maven repo names from Bzlmod and legacy entry points.
2527
+ function parseMavenRepoCandidates(cwd, verbose) {
2528
+ const candidates = [];
2529
+
2530
+ // Bzlmod path: parse MODULE.bazel for use_repo(maven, ...).
2531
+ const moduleBazel = path.join(cwd, 'MODULE.bazel');
2532
+ const moduleContent = safeReadFile(moduleBazel);
2533
+ if (moduleContent) {
2534
+ const bzlmodHits = [];
2535
+ for (const m of moduleContent.matchAll(USE_REPO_RE)) {
2536
+ const argBlob = m[1] ?? '';
2537
+ for (const n of argBlob.matchAll(QUOTED_NAME_RE)) {
2538
+ bzlmodHits.push(n[1]);
2539
+ }
2540
+ }
2541
+ candidates.push(...bzlmodHits);
2542
+ if (verbose) {
2543
+ logger.logger.log('[VERBOSE] discovery: scanned', moduleBazel, `(${bzlmodHits.length} use_repo match(es))`);
2544
+ }
2545
+ } else if (verbose) {
2546
+ logger.logger.log('[VERBOSE] discovery:', moduleBazel, 'not present (skipping bzlmod scan)');
2547
+ }
2548
+
2549
+ // Legacy path: scan WORKSPACE + top-level .bzl files for maven_install(name=...).
2550
+ const legacyFiles = listLegacyStarlarkFiles(cwd);
2551
+ if (verbose) {
2552
+ logger.logger.log('[VERBOSE] discovery: legacy files considered:', legacyFiles.length ? legacyFiles : '(none)');
2553
+ }
2554
+ for (const file of legacyFiles) {
2555
+ const content = safeReadFile(file);
2556
+ if (!content) {
2557
+ continue;
2558
+ }
2559
+ const fileHits = [];
2560
+ for (const m of content.matchAll(MAVEN_INSTALL_NAME_RE)) {
2561
+ fileHits.push(m[1]);
2562
+ }
2563
+ candidates.push(...fileHits);
2564
+ if (verbose) {
2565
+ logger.logger.log('[VERBOSE] discovery: scanned', file, `(${fileHits.length} maven_install name match(es))`);
2566
+ }
2567
+ }
2568
+ const deduped = uniqueSorted(candidates);
2569
+ if (verbose) {
2570
+ logger.logger.log('[VERBOSE] discovery: candidate set (pre-seed):', deduped);
2571
+ }
2572
+ return deduped;
2573
+ }
2574
+ // Step 2: validate a candidate by running the probe and confirming
2575
+ // `maven_coordinates=` appears in stdout (the marker emitted by jvm_import /
2576
+ // aar_import rules generated by rules_jvm_external). Returns the probe
2577
+ // stdout alongside the verdict so the caller can cache it and reuse it
2578
+ // instead of running an identical extraction query.
2579
+ async function validateMavenRepo(repoName, probe, verbose) {
2580
+ try {
2581
+ const result = await probe(repoName);
2582
+ if (result.code !== 0) {
2583
+ if (verbose) {
2584
+ logger.logger.log(`[VERBOSE] discovery: probe @${repoName}: REJECT (code=${result.code})`);
2585
+ }
2586
+ return {
2587
+ valid: false,
2588
+ stdout: result.stdout
2589
+ };
2590
+ }
2591
+ const valid = MAVEN_COORDINATES_MARKER_RE.test(result.stdout);
2592
+ if (verbose) {
2593
+ logger.logger.log(`[VERBOSE] discovery: probe @${repoName}:`, valid ? 'ACCEPT (maven_coordinates marker found)' : 'REJECT (no maven_coordinates marker in probe stdout)');
2594
+ }
2595
+ return {
2596
+ valid,
2597
+ stdout: result.stdout
2598
+ };
2599
+ } catch (e) {
2600
+ if (verbose) {
2601
+ logger.logger.log(`[VERBOSE] discovery: probe @${repoName}: REJECT (probe threw):`, utils.getErrorCause(e));
2602
+ }
2603
+ return {
2604
+ valid: false,
2605
+ stdout: ''
2606
+ };
2607
+ }
2608
+ }
2609
+
2610
+ // The default maven_install repo name when no explicit `name=` is given.
2611
+ // Included as a seed so repos that define maven_install in a subdirectory
2612
+ // .bzl file (not scanned by parseMavenRepoCandidates) are still discovered.
2613
+ const DEFAULT_MAVEN_REPO_SEED = 'maven';
2614
+
2615
+ // Composition: parse, then validate each candidate; return validated subset
2616
+ // as a Map keyed by repo name with the validated probe stdout as value.
2617
+ // Map iteration order matches insertion order, so callers that just want
2618
+ // the list of repo names can call `Array.from(repos.keys())`. Callers that
2619
+ // want to skip re-running the same `bazel query` during extraction can read
2620
+ // the cached stdout off the Map and parse it directly.
2621
+ //
2622
+ // Always seeds with the default `@maven` repo name so repos whose
2623
+ // maven_install is defined in a sub-directory .bzl file (not reachable by
2624
+ // the top-level static scan) can still be discovered via probe validation.
2625
+ async function discoverMavenRepos(cwd, probe, nativeCandidates, verbose) {
2626
+ const parsed = nativeCandidates && nativeCandidates.length ? nativeCandidates : parseMavenRepoCandidates(cwd, verbose);
2627
+ if (verbose) {
2628
+ logger.logger.log('[VERBOSE] discovery: candidate source:', nativeCandidates && nativeCandidates.length ? `bzlmod visible-repos (${nativeCandidates.length})` : `static parse (${parsed.length})`);
2629
+ }
2630
+ // Seed with the default repo name first (so it appears first in output if
2631
+ // validated). Dedup via Set before validation.
2632
+ const seen = new Set([DEFAULT_MAVEN_REPO_SEED]);
2633
+ const candidates = [DEFAULT_MAVEN_REPO_SEED];
2634
+ for (const c of parsed) {
2635
+ if (!seen.has(c)) {
2636
+ seen.add(c);
2637
+ candidates.push(c);
2638
+ }
2639
+ }
2640
+ if (verbose) {
2641
+ logger.logger.log('[VERBOSE] discovery: candidate set to probe (seed-first, deduped):', candidates);
2642
+ }
2643
+ const validated = new Map();
2644
+ for (const c of candidates) {
2645
+ // eslint-disable-next-line no-await-in-loop
2646
+ const result = await validateMavenRepo(c, probe, verbose);
2647
+ if (result.valid) {
2648
+ validated.set(c, result.stdout);
2649
+ }
2650
+ }
2651
+ if (verbose) {
2652
+ logger.logger.log('[VERBOSE] discovery: validated repos:', Array.from(validated.keys()));
2653
+ }
2654
+ return validated;
2655
+ }
2656
+
2657
+ // Detects whether the given Bazel workspace uses Bzlmod (MODULE.bazel),
2658
+ // legacy WORKSPACE (WORKSPACE or WORKSPACE.bazel), or both (migration).
2659
+ // Throws InputError when neither marker file is present.
2660
+ function detectWorkspaceMode(cwd) {
2661
+ const moduleBazel = fs$1.existsSync(path.join(cwd, 'MODULE.bazel'));
2662
+ const workspaceFile = fs$1.existsSync(path.join(cwd, 'WORKSPACE')) || fs$1.existsSync(path.join(cwd, 'WORKSPACE.bazel'));
2663
+ if (!moduleBazel && !workspaceFile) {
2664
+ throw new utils.InputError(`No Bazel workspace found at ${cwd} (looked for MODULE.bazel, WORKSPACE, WORKSPACE.bazel).`);
2665
+ }
2666
+ return {
2667
+ bzlmod: moduleBazel,
2668
+ workspace: workspaceFile
2669
+ };
2670
+ }
2671
+
2672
+ // Returns the bazel CLI flags needed to invoke the correct workspace mode.
2673
+ // Bzlmod-only or migration-window: rely on Bazel 7+ default (Bzlmod on).
2674
+ // Legacy-only: explicitly disable Bzlmod and enable WORKSPACE.
2675
+ function getBazelInvocationFlags(mode) {
2676
+ if (mode.bzlmod) {
2677
+ // Bzlmod-only or migration: Bzlmod wins; no flags needed (Bazel 7+ default).
2678
+ return [];
2679
+ }
2680
+ // Legacy-only: explicitly switch to WORKSPACE mode.
2681
+ return ['--noenable_bzlmod', '--enable_workspace'];
2682
+ }
2683
+
2684
+ // Splits "g:a:v" -> { groupArtifact: "g:a", version: "v" }.
2685
+ // Returns null on malformed input.
2686
+ function splitCoord(c) {
2687
+ const lastColon = c.lastIndexOf(':');
2688
+ if (lastColon < 1) {
2689
+ return null;
2690
+ }
2691
+ return {
2692
+ groupArtifact: c.slice(0, lastColon),
2693
+ version: c.slice(lastColon + 1)
2694
+ };
2695
+ }
2696
+ // Builds a lookup from rule label suffix (e.g. ":com_google_guava_guava") to canonical coord.
2697
+ function buildLabelToCoordMap(artifacts) {
2698
+ const fullLabels = new Map();
2699
+ const suffixToCoords = new Map();
2700
+ for (const a of artifacts) {
2701
+ // The rule name (e.g. "com_google_guava_guava") becomes the path under @<repo>//:<name>.
2702
+ // We record by ":<name>" suffix so we can look up regardless of repo name.
2703
+ const suffix = `:${a.ruleName}`;
2704
+ const coords = suffixToCoords.get(suffix) ?? new Set();
2705
+ coords.add(a.mavenCoordinates);
2706
+ suffixToCoords.set(suffix, coords);
2707
+ if (a.sourceRepo) {
2708
+ fullLabels.set(`@${a.sourceRepo}//${suffix}`, a.mavenCoordinates);
2709
+ }
2710
+ }
2711
+ return {
2712
+ fullLabels,
2713
+ suffixToCoords
2714
+ };
2715
+ }
2716
+
2717
+ // Converts a Bazel dep label to a Maven coordinate, using the label-to-coord map.
2718
+ // Returns null when the label is not recognised.
2719
+ function depLabelToCoord(label, labelToCoord) {
2720
+ // label may be "@maven//:com_google_guava_failureaccess".
2721
+ const colon = label.lastIndexOf(':');
2722
+ if (colon < 0) {
2723
+ return null;
2724
+ }
2725
+ const fullMatch = labelToCoord.fullLabels.get(label);
2726
+ if (fullMatch) {
2727
+ return fullMatch;
2728
+ }
2729
+ const key = label.slice(colon);
2730
+ const suffixMatches = labelToCoord.suffixToCoords.get(key);
2731
+ if (!suffixMatches) {
2732
+ return null;
2733
+ }
2734
+ if (suffixMatches.size > 1) {
2735
+ throw new Error(`Ambiguous Bazel dependency label ${label} maps rule suffix ${key} to multiple Maven coordinates: ${Array.from(suffixMatches).sort().join(', ')}. The generated maven_install.json cannot resolve this dependency label losslessly.`);
2736
+ }
2737
+ return Array.from(suffixMatches)[0] ?? null;
2738
+ }
2739
+ function normalizeToMavenInstallJson(artifacts) {
2740
+ const labelToCoord = buildLabelToCoordMap(artifacts);
2741
+ const out = {
2742
+ artifacts: {},
2743
+ dependencies: {}
2744
+ };
2745
+ const versionsByGroupArtifact = new Map();
2746
+ const dependencySets = new Map();
2747
+ for (const a of artifacts) {
2748
+ const split = splitCoord(a.mavenCoordinates);
2749
+ if (!split) {
2750
+ continue;
2751
+ }
2752
+ const existingVersion = versionsByGroupArtifact.get(split.groupArtifact);
2753
+ if (existingVersion && existingVersion !== split.version) {
2754
+ throw new Error(`Conflicting versions for ${split.groupArtifact}: ${existingVersion}, ${split.version}. The generated maven_install.json cannot represent multiple versions for the same group:artifact losslessly.`);
2755
+ }
2756
+ if (!existingVersion) {
2757
+ versionsByGroupArtifact.set(split.groupArtifact, split.version);
2758
+ out.artifacts[split.groupArtifact] = {
2759
+ shasums: a.mavenSha256 ? {
2760
+ jar: a.mavenSha256
2761
+ } : {},
2762
+ version: split.version
2763
+ };
2764
+ } else if (a.mavenSha256 && !out.artifacts[split.groupArtifact]?.shasums.jar) {
2765
+ out.artifacts[split.groupArtifact] = {
2766
+ shasums: {
2767
+ jar: a.mavenSha256
2768
+ },
2769
+ version: split.version
2770
+ };
2771
+ }
2772
+ // Dependency keys in maven_install.json use "g:a" (no version),
2773
+ // matching the canonical rules_jvm_external lockfile shape.
2774
+ // Only emit an entry when there are actual dependencies (lockfile omits
2775
+ // artifacts with an empty dep list).
2776
+ const depKey = split.groupArtifact;
2777
+ const depCoords = dependencySets.get(depKey) ?? new Set();
2778
+ for (const depLabel of a.deps) {
2779
+ // First try our rule-label lookup (the common case for --output=build text).
2780
+ const c = depLabelToCoord(depLabel, labelToCoord);
2781
+ if (c) {
2782
+ // c is "g:a:v"; strip the version to produce "g:a" per lockfile shape.
2783
+ const cs = splitCoord(c);
2784
+ depCoords.add(cs ? cs.groupArtifact : c);
2785
+ } else if (depLabel.includes(':') && !depLabel.startsWith('@') && !depLabel.startsWith(':')) {
2786
+ // unsorted_deps.json deps may be "g:a:v" in older files or
2787
+ // "g:a" in v2 lock-file-shaped maps. Strip only when a version is
2788
+ // present.
2789
+ const parts = depLabel.split(':');
2790
+ depCoords.add(parts.length >= 3 ? parts.slice(0, -1).join(':') : depLabel);
2791
+ }
2792
+ }
2793
+ if (depCoords.size) {
2794
+ dependencySets.set(depKey, depCoords);
2795
+ }
2796
+ }
2797
+ for (const [depKey, depCoords] of dependencySets) {
2798
+ out.dependencies[depKey] = Array.from(depCoords);
2799
+ }
2800
+ return out;
2801
+ }
2802
+
2803
+ // Resolves the bazel `external/` dir for the given workspace.
2804
+ //
2805
+ // Bazel's `bazel-out/` convenience symlink points at
2806
+ // `<output_base>/execroot/<workspace>/bazel-out/`; the `external/` dir we
2807
+ // want is at `<output_base>/external/`. `path.join` is purely lexical and
2808
+ // would collapse `bazel-out/..` to the cwd itself, which is the wrong place
2809
+ // Resolve the symlink at the filesystem level and walk up to
2810
+ // `<output_base>` instead.
2811
+ function bazelExternalDir(cwd, outputBase) {
2812
+ if (outputBase) {
2813
+ return path.join(outputBase, 'external');
2814
+ }
2815
+ const bazelOutLink = path.join(cwd, 'bazel-out');
2816
+ if (!fs$1.existsSync(bazelOutLink)) {
2817
+ return null;
2818
+ }
2819
+ try {
2820
+ // realpath follows symlinks: .../<output_base>/execroot/<workspace>/bazel-out
2821
+ const real = fs$1.realpathSync(bazelOutLink);
2822
+ // Walk up bazel-out -> <workspace> -> execroot -> <output_base>, then into external/.
2823
+ return path.join(real, '..', '..', '..', 'external');
2824
+ } catch {
2825
+ return null;
2826
+ }
2827
+ }
2828
+
2829
+ // Internal diagnostic: when truthy, skip the unsorted_deps.json fast path
2830
+ // and force the bazel-query regex fallback. Used by bazel-bench to
2831
+ // deterministically exercise parseBazelBuildOutput on every CI run. Truthy
2832
+ // values are '1', 'true', 'yes' (case-insensitive); anything else (unset,
2833
+ // '', '0', 'false') is treated as off. Not exposed as a user-facing CLI
2834
+ // flag, so it is read here rather than added to constants.mts.
2835
+ function isForceQueryFallbackEnabled() {
2836
+ const raw = process.env['SOCKET_BAZEL_FORCE_QUERY_FALLBACK'];
2837
+ if (!raw) {
2838
+ return false;
2839
+ }
2840
+ const normalized = raw.toLowerCase();
2841
+ return normalized === '1' || normalized === 'true' || normalized === 'yes';
2842
+ }
2843
+
2844
+ // Tries `external/<repo>/unsorted_deps.json` first; falls back to parsing the
2845
+ // probe stdout the caller already captured during discovery. Discovery runs
2846
+ // the same `kind("jvm_import rule|aar_import rule", @<repo>//:*)` query that
2847
+ // extraction needs, so reusing its stdout skips one bazel-query invocation
2848
+ // per repo on the unpinned path (where unsorted_deps.json isn't on disk).
2849
+ async function extractFromOneRepo(repoName, queryOpts, cachedProbeStdout) {
2850
+ const verbose = queryOpts.verbose;
2851
+ // unsorted_deps.json lives under the bazel external dir.
2852
+ // When --output_base is set, it's under that; otherwise under the workspace's
2853
+ // bazel-out symlink (resolved via realpath, NOT lexical path.join — the
2854
+ // lexical form would collapse `bazel-out/..` to cwd and miss the file).
2855
+ const externalDir = bazelExternalDir(queryOpts.cwd, queryOpts.bazelOutputBase);
2856
+ if (verbose) {
2857
+ logger.logger.log(`[VERBOSE] @${repoName}: external dir:`, externalDir ?? '(unresolved — bazel-out symlink absent)');
2858
+ }
2859
+ const forceFallback = isForceQueryFallbackEnabled();
2860
+ if (forceFallback && verbose) {
2861
+ logger.logger.log(`[VERBOSE] @${repoName}: SOCKET_BAZEL_FORCE_QUERY_FALLBACK set; skipping unsorted_deps.json fast path.`);
2862
+ }
2863
+ const candidates = forceFallback ? [] : externalDir ? [path.join(externalDir, repoName, 'unsorted_deps.json')] : [];
2864
+ for (const c of candidates) {
2865
+ if (fs$1.existsSync(c)) {
2866
+ // Bound the read to 1GB to prevent OOM on hostile content while allowing large real-world lockfiles.
2867
+ // eslint-disable-next-line no-await-in-loop
2868
+ const stat = await fs$1.promises.stat(c);
2869
+ if (stat.size > 1024 * 1024 * 1024) {
2870
+ logger.logger.warn(`Skipping oversized ${c} (${stat.size} bytes); falling back to cached probe stdout.`);
2871
+ break;
2872
+ }
2873
+ const json = fs$1.readFileSync(c, 'utf8');
2874
+ const parsed = parseUnsortedDepsJson(json);
2875
+ if (parsed.length) {
2876
+ if (verbose) {
2877
+ logger.logger.log(`[VERBOSE] @${repoName}: source=unsorted_deps.json (${c}, ${parsed.length} artifact(s))`);
2878
+ }
2879
+ return parsed.map(a => ({
2880
+ ...a,
2881
+ sourceRepo: repoName
2882
+ }));
2883
+ }
2884
+ } else if (verbose) {
2885
+ logger.logger.log(`[VERBOSE] @${repoName}: unsorted_deps.json miss at`, c);
2886
+ }
2887
+ }
2888
+ // Reuse the probe stdout that discovery already captured for this repo.
2889
+ // The probe ran exactly this query during validation and only validated
2890
+ // repos with code === 0 make it into the cache, so retry is unnecessary
2891
+ // — if the probe was flaky, the repo wouldn't be in the map.
2892
+ if (!cachedProbeStdout) {
2893
+ logger.logger.warn(`No cached probe stdout for @${repoName}; skipping. (This shouldn't happen — discovery should have populated it.)`);
2894
+ return [];
2895
+ }
2896
+ if (verbose) {
2897
+ logger.logger.log(`[VERBOSE] @${repoName}: source=cached probe stdout (${cachedProbeStdout.length} bytes)`);
2898
+ }
2899
+ return parseBazelBuildOutput(cachedProbeStdout).map(a => ({
2900
+ ...a,
2901
+ sourceRepo: repoName
2902
+ }));
2903
+ }
2904
+ async function extractBazelToMaven(opts) {
2905
+ const {
2906
+ cwd,
2907
+ out,
2908
+ verbose
2909
+ } = opts;
2910
+ logger.logger.group('bazel2maven:');
2911
+ logger.logger.info(`- src dir: \`${cwd}\``);
2912
+ logger.logger.info(`- out dir: \`${out}\``);
2913
+ if (!fs$1.existsSync(cwd)) {
2914
+ logger.logger.warn(`Warning: cwd does not exist: ${cwd}`);
2915
+ }
2916
+ logger.logger.groupEnd();
2917
+ try {
2918
+ // Validate caller-provided Bazel filesystem settings before invoking Bazel.
2919
+ if (opts.bazelOutputBase) {
2920
+ validateOutputBase(opts.bazelOutputBase, opts.cwd);
2921
+ }
2922
+ // Java must be available before rules_jvm_external/Coursier runs;
2923
+ // python shim follows so its augmented PATH inherits the JDK prefix.
2924
+ ensureJavaOnPath();
2925
+ const shim = await provisionPythonShim();
2926
+ const baseEnv = shim.augmentedEnv ?? opts.env;
2927
+
2928
+ // Step 1: workspace detection.
2929
+ const mode = detectWorkspaceMode(cwd);
2930
+ logger.logger.info(`Workspace mode: bzlmod=${mode.bzlmod} workspace=${mode.workspace}`);
2931
+ const invocationFlags = getBazelInvocationFlags(mode);
2932
+
2933
+ // Step 2: bazel binary resolution.
2934
+ const bin = await resolveBazelBinary(opts.bin);
2935
+ logger.logger.info(`Using bazel: ${bin}`);
2936
+ if (verbose) {
2937
+ logger.logger.log('[VERBOSE] resolved options:', {
2938
+ bin,
2939
+ bazelRc: opts.bazelRc ?? '(unset)',
2940
+ bazelOutputBase: opts.bazelOutputBase ?? '(unset)',
2941
+ bazelFlags: opts.bazelFlags ?? '(unset)',
2942
+ invocationFlags
2943
+ });
2944
+ }
2945
+
2946
+ // Step 3: build the shared query options object.
2947
+ const queryOpts = {
2948
+ bin,
2949
+ cwd,
2950
+ invocationFlags,
2951
+ ...(opts.bazelRc ? {
2952
+ bazelRc: opts.bazelRc
2953
+ } : {}),
2954
+ ...(opts.bazelFlags ? {
2955
+ bazelFlags: opts.bazelFlags
2956
+ } : {}),
2957
+ ...(opts.bazelOutputBase ? {
2958
+ bazelOutputBase: opts.bazelOutputBase
2959
+ } : {}),
2960
+ ...(baseEnv ? {
2961
+ env: baseEnv
2962
+ } : {}),
2963
+ verbose
2964
+ };
2965
+
2966
+ // Step 4: discover validated Maven repos via the two-step recipe.
2967
+ // Bzlmod has a native visible-repository surface; prefer that over static
2968
+ // MODULE.bazel parsing and keep bounded parsing as the legacy/fallback path.
2969
+ let nativeCandidates;
2970
+ if (mode.bzlmod) {
2971
+ const visibleRepos = await runBazelModShowVisibleRepos(queryOpts);
2972
+ if (visibleRepos.code === 0) {
2973
+ nativeCandidates = parseVisibleRepoCandidates(visibleRepos.stdout);
2974
+ if (verbose) {
2975
+ logger.logger.log('[VERBOSE] Bzlmod visible repo candidates:', nativeCandidates);
2976
+ }
2977
+ } else if (verbose) {
2978
+ logger.logger.log('[VERBOSE] bazel mod show_repo failed; falling back to static candidate parsing:', visibleRepos.stderr);
2979
+ }
2980
+ }
2981
+ // Returns Map<repoName, probeStdout> so extraction can reuse the probe
2982
+ // output and skip running an identical bazel-query a second time.
2983
+ const probe = buildProbeFor(queryOpts);
2984
+ const repos = await discoverMavenRepos(cwd, probe, nativeCandidates, verbose);
2985
+ const repoNames = Array.from(repos.keys());
2986
+ logger.logger.info(`Discovered ${repos.size} Maven repo(s): ${repoNames.join(', ') || '(none)'}`);
2987
+
2988
+ // Step 5: extract artifacts from each repo (preferring unsorted_deps.json).
2989
+ const allArtifacts = [];
2990
+ for (const [repo, probeStdout] of repos) {
2991
+ // eslint-disable-next-line no-await-in-loop
2992
+ const artifacts = await extractFromOneRepo(repo, queryOpts, probeStdout);
2993
+ allArtifacts.push(...artifacts);
2994
+ logger.logger.info(`@${repo}: ${artifacts.length} artifact(s)`);
2995
+ }
2996
+
2997
+ // Step 6: normalize to maven_install.json shape.
2998
+ const normalized = normalizeToMavenInstallJson(allArtifacts);
2999
+
3000
+ // Step 7: write outputs.
3001
+ // Standalone output writes directly to `out`; auto-manifest uses a sibling directory
3002
+ // to avoid colliding with a repo's checked-in rules_jvm_external lockfile and
3003
+ // to avoid repo-root gitignore patterns such as `/maven_install.json`.
3004
+ const layout = opts.outLayout ?? 'standalone';
3005
+ const manifestDir = layout === 'flat' ? path.join(out, '.socket-auto-manifest') : out;
3006
+ fs$1.mkdirSync(manifestDir, {
3007
+ recursive: true
3008
+ });
3009
+ const manifestPath = path.join(manifestDir, 'maven_install.json');
3010
+ await fs$1.promises.writeFile(manifestPath, JSON.stringify(normalized, null, 2), 'utf8');
3011
+ if (verbose) {
3012
+ logger.logger.log('[VERBOSE] outputs:', {
3013
+ artifactCount: allArtifacts.length,
3014
+ generatedManifest: path.relative(out, manifestPath),
3015
+ layout,
3016
+ manifest: manifestPath,
3017
+ mavenRepos: repoNames,
3018
+ tool: 'socket manifest bazel',
3019
+ workspace: {
3020
+ bzlmod: mode.bzlmod,
3021
+ legacyWorkspace: mode.workspace
3022
+ }
3023
+ });
3024
+ }
3025
+ if (!allArtifacts.length) {
3026
+ process.exitCode = 1;
3027
+ logger.logger.fail('No Maven artifacts extracted. See warnings above.');
3028
+ return {
3029
+ artifactCount: 0,
3030
+ manifestPath,
3031
+ ok: false
3032
+ };
3033
+ }
3034
+ logger.logger.success(`Wrote ${allArtifacts.length} artifact(s) to ${path.relative(cwd, manifestPath)}.`);
3035
+ return {
3036
+ artifactCount: allArtifacts.length,
3037
+ manifestPath,
3038
+ ok: true
3039
+ };
3040
+ } catch (e) {
3041
+ process.exitCode = 1;
3042
+ // Always surface the error message; users should not have to
3043
+ // re-run a multi-minute bazel build with --verbose just to see whether
3044
+ // the failure was a missing dependency, permission error, or network blip.
3045
+ logger.logger.fail(`Unexpected error in bazel2maven: ${utils.getErrorCause(e)}`);
3046
+ if (verbose) {
3047
+ logger.logger.group('[VERBOSE] error:');
3048
+ logger.logger.log(e);
3049
+ logger.logger.groupEnd();
3050
+ } else {
3051
+ logger.logger.info('Re-run with --verbose for the full stack.');
3052
+ }
3053
+ return {
3054
+ artifactCount: 0,
3055
+ ok: false
3056
+ };
3057
+ }
1888
3058
  }
1889
3059
 
1890
3060
  async function convertGradleToMaven({
@@ -2326,6 +3496,7 @@ async function generateAutoManifest({
2326
3496
  verbose
2327
3497
  }) {
2328
3498
  const sockJson = utils.readOrDefaultSocketJson(cwd);
3499
+ const generatedFiles = [];
2329
3500
  if (verbose) {
2330
3501
  logger.logger.info(`Using this ${constants.SOCKET_JSON} for defaults:`, sockJson);
2331
3502
  }
@@ -2362,6 +3533,32 @@ async function generateAutoManifest({
2362
3533
  verbose: Boolean(sockJson.defaults?.manifest?.conda?.verbose)
2363
3534
  });
2364
3535
  }
3536
+ if (!sockJson?.defaults?.manifest?.bazel?.disabled && detected.bazel) {
3537
+ const bazelConfig = sockJson?.defaults?.manifest?.bazel;
3538
+ logger.logger.log('Detected a Bazel workspace, extracting Maven dependencies via bazel query...');
3539
+ const bazelResult = await extractBazelToMaven({
3540
+ bazelFlags: bazelConfig?.bazelFlags,
3541
+ bazelOutputBase: bazelConfig?.bazelOutputBase,
3542
+ bazelRc: bazelConfig?.bazelRc,
3543
+ bin: bazelConfig?.bazel ?? bazelConfig?.bin,
3544
+ cwd,
3545
+ // Auto-manifest writes into a sibling directory instead of the repo root
3546
+ // so scan discovery can pick it up without colliding with a checked-in
3547
+ // rules_jvm_external lockfile or repo-root gitignore patterns.
3548
+ out: bazelConfig?.out ?? cwd,
3549
+ outLayout: 'flat',
3550
+ verbose: Boolean(bazelConfig?.verbose) || verbose
3551
+ });
3552
+ if (!bazelResult.ok) {
3553
+ throw new Error('Bazel auto-manifest generation failed');
3554
+ }
3555
+ if (bazelResult.manifestPath) {
3556
+ generatedFiles.push(bazelResult.manifestPath);
3557
+ }
3558
+ }
3559
+ return {
3560
+ generatedFiles
3561
+ };
2365
3562
  }
2366
3563
 
2367
3564
  // Keys for CDX and SPDX in the supported files response.
@@ -2414,6 +3611,7 @@ async function handleCreateNewScan({
2414
3611
  tmp,
2415
3612
  workspace
2416
3613
  }) {
3614
+ let scanTargets = targets;
2417
3615
  require$$9.debugFn('notice', `Creating new scan for ${orgSlug}/${workspace ? `${workspace}/` : ''}${repoName}`);
2418
3616
  require$$9.debugDir('inspect', {
2419
3617
  autoManifest,
@@ -2438,12 +3636,15 @@ async function handleCreateNewScan({
2438
3636
  require$$9.debugDir('inspect', {
2439
3637
  detected
2440
3638
  });
2441
- await generateAutoManifest({
3639
+ const autoManifestResult = await generateAutoManifest({
2442
3640
  detected,
2443
3641
  cwd,
2444
3642
  outputKind,
2445
3643
  verbose: false
2446
3644
  });
3645
+ if (autoManifestResult.generatedFiles.length) {
3646
+ scanTargets = Array.from(new Set([...targets, ...autoManifestResult.generatedFiles]));
3647
+ }
2447
3648
  logger.logger.info('Auto-generation finished. Proceeding with Scan creation.');
2448
3649
  }
2449
3650
  const {
@@ -2478,7 +3679,7 @@ async function handleCreateNewScan({
2478
3679
  reachabilityOptions: reach,
2479
3680
  target: targets[0]
2480
3681
  });
2481
- const packagePaths = await utils.getPackageFilesForScan(targets, supportedFiles, {
3682
+ const packagePaths = await utils.getPackageFilesForScan(scanTargets, supportedFiles, {
2482
3683
  additionalIgnores: additionalScaIgnores,
2483
3684
  config: socketConfig,
2484
3685
  cwd
@@ -2545,21 +3746,34 @@ async function handleCreateNewScan({
2545
3746
  scanPaths = [...pathsForScan, ...(reachabilityReport ? [reachabilityReport] : [])];
2546
3747
  tier1ReachabilityScanId = reachResult.data?.tier1ReachabilityScanId;
2547
3748
  }
2548
- const fullScanCResult = await fetchCreateOrgFullScan(scanPaths, orgSlug, {
2549
- commitHash,
2550
- commitMessage,
2551
- committers,
2552
- pullRequest,
2553
- repoName,
2554
- branchName,
2555
- scanType: reach.runReachabilityAnalysis ? constants.default.SCAN_TYPE_SOCKET_TIER1 : constants.default.SCAN_TYPE_SOCKET,
2556
- workspace
2557
- }, {
2558
- cwd,
2559
- defaultBranch,
2560
- pendingHead,
2561
- tmp
2562
- });
3749
+
3750
+ // Brotli-compress any .socket.facts.json paths in scanPaths just before
3751
+ // upload. depscan's api-v0 multipart boundary streams brotli decode based
3752
+ // on the .br filename suffix. Coana keeps writing plain .socket.facts.json
3753
+ // on disk, so the local read paths (extractTier1ReachabilityScanId,
3754
+ // extractReachabilityErrors) stay correct. The cleanup() in the finally
3755
+ // block removes the temp dirs whether the upload succeeded or threw.
3756
+ const compressed = await utils.compressSocketFactsForUpload(scanPaths);
3757
+ let fullScanCResult;
3758
+ try {
3759
+ fullScanCResult = await fetchCreateOrgFullScan(compressed.paths, orgSlug, {
3760
+ commitHash,
3761
+ commitMessage,
3762
+ committers,
3763
+ pullRequest,
3764
+ repoName,
3765
+ branchName,
3766
+ scanType: reach.runReachabilityAnalysis ? constants.default.SCAN_TYPE_SOCKET_TIER1 : constants.default.SCAN_TYPE_SOCKET,
3767
+ workspace
3768
+ }, {
3769
+ cwd,
3770
+ defaultBranch,
3771
+ pendingHead,
3772
+ tmp
3773
+ });
3774
+ } finally {
3775
+ await compressed.cleanup();
3776
+ }
2563
3777
  const scanId = fullScanCResult.ok ? fullScanCResult.data?.id : undefined;
2564
3778
  if (reach && scanId && tier1ReachabilityScanId) {
2565
3779
  await finalizeTier1Scan(tier1ReachabilityScanId, scanId);
@@ -2669,7 +3883,7 @@ async function handleCi(autoManifest) {
2669
3883
  });
2670
3884
  }
2671
3885
 
2672
- const config$k = {
3886
+ const config$l = {
2673
3887
  commandName: 'ci',
2674
3888
  description: 'Alias for `socket scan create --report` (creates report and exits with error if unhealthy)',
2675
3889
  hidden: false,
@@ -2687,7 +3901,7 @@ const config$k = {
2687
3901
  $ ${command} [options]
2688
3902
 
2689
3903
  Options
2690
- ${utils.getFlagListOutput(config$k.flags)}
3904
+ ${utils.getFlagListOutput(config$l.flags)}
2691
3905
 
2692
3906
  This command is intended to use in CI runs to allow automated systems to
2693
3907
  accept or reject a current build. It will use the default org of the
@@ -2705,16 +3919,16 @@ const config$k = {
2705
3919
  `
2706
3920
  };
2707
3921
  const cmdCI = {
2708
- description: config$k.description,
2709
- hidden: config$k.hidden,
2710
- run: run$Q
3922
+ description: config$l.description,
3923
+ hidden: config$l.hidden,
3924
+ run: run$R
2711
3925
  };
2712
- async function run$Q(argv, importMeta, {
3926
+ async function run$R(argv, importMeta, {
2713
3927
  parentName
2714
3928
  }) {
2715
3929
  const cli = utils.meowOrExit({
2716
3930
  argv,
2717
- config: config$k,
3931
+ config: config$l,
2718
3932
  parentName,
2719
3933
  importMeta
2720
3934
  });
@@ -2957,9 +4171,9 @@ const hidden$v = false;
2957
4171
  const cmdConfigAuto = {
2958
4172
  description: description$D,
2959
4173
  hidden: hidden$v,
2960
- run: run$P
4174
+ run: run$Q
2961
4175
  };
2962
- async function run$P(argv, importMeta, {
4176
+ async function run$Q(argv, importMeta, {
2963
4177
  parentName
2964
4178
  }) {
2965
4179
  const config = {
@@ -3063,7 +4277,7 @@ async function handleConfigGet({
3063
4277
  await outputConfigGet(key, result, outputKind);
3064
4278
  }
3065
4279
 
3066
- const config$j = {
4280
+ const config$k = {
3067
4281
  commandName: 'get',
3068
4282
  description: 'Get the value of a local CLI config item',
3069
4283
  hidden: false,
@@ -3093,16 +4307,16 @@ ${utils.getSupportedConfigEntries().map(({
3093
4307
  `
3094
4308
  };
3095
4309
  const cmdConfigGet = {
3096
- description: config$j.description,
3097
- hidden: config$j.hidden,
3098
- run: run$O
4310
+ description: config$k.description,
4311
+ hidden: config$k.hidden,
4312
+ run: run$P
3099
4313
  };
3100
- async function run$O(argv, importMeta, {
4314
+ async function run$P(argv, importMeta, {
3101
4315
  parentName
3102
4316
  }) {
3103
4317
  const cli = utils.meowOrExit({
3104
4318
  argv,
3105
- config: config$j,
4319
+ config: config$k,
3106
4320
  importMeta,
3107
4321
  parentName
3108
4322
  });
@@ -3204,7 +4418,7 @@ async function outputConfigList({
3204
4418
  }
3205
4419
  }
3206
4420
 
3207
- const config$i = {
4421
+ const config$j = {
3208
4422
  commandName: 'list',
3209
4423
  description: 'Show all local CLI config items and their values',
3210
4424
  hidden: false,
@@ -3229,16 +4443,16 @@ const config$i = {
3229
4443
  `
3230
4444
  };
3231
4445
  const cmdConfigList = {
3232
- description: config$i.description,
3233
- hidden: config$i.hidden,
3234
- run: run$N
4446
+ description: config$j.description,
4447
+ hidden: config$j.hidden,
4448
+ run: run$O
3235
4449
  };
3236
- async function run$N(argv, importMeta, {
4450
+ async function run$O(argv, importMeta, {
3237
4451
  parentName
3238
4452
  }) {
3239
4453
  const cli = utils.meowOrExit({
3240
4454
  argv,
3241
- config: config$i,
4455
+ config: config$j,
3242
4456
  importMeta,
3243
4457
  parentName
3244
4458
  });
@@ -3323,9 +4537,9 @@ const hidden$u = false;
3323
4537
  const cmdConfigSet = {
3324
4538
  description: description$C,
3325
4539
  hidden: hidden$u,
3326
- run: run$M
4540
+ run: run$N
3327
4541
  };
3328
- async function run$M(argv, importMeta, {
4542
+ async function run$N(argv, importMeta, {
3329
4543
  parentName
3330
4544
  }) {
3331
4545
  const config = {
@@ -3450,9 +4664,9 @@ const hidden$t = false;
3450
4664
  const cmdConfigUnset = {
3451
4665
  description: description$B,
3452
4666
  hidden: hidden$t,
3453
- run: run$L
4667
+ run: run$M
3454
4668
  };
3455
- async function run$L(argv, importMeta, {
4669
+ async function run$M(argv, importMeta, {
3456
4670
  parentName
3457
4671
  }) {
3458
4672
  const config = {
@@ -4650,7 +5864,7 @@ const hidden$s = false;
4650
5864
  const cmdFix = {
4651
5865
  description: description$z,
4652
5866
  hidden: hidden$s,
4653
- run: run$K
5867
+ run: run$L
4654
5868
  };
4655
5869
  const generalFlags$2 = {
4656
5870
  autopilot: {
@@ -4814,7 +6028,7 @@ const hiddenFlags = {
4814
6028
  hidden: true
4815
6029
  }
4816
6030
  };
4817
- async function run$K(argv, importMeta, {
6031
+ async function run$L(argv, importMeta, {
4818
6032
  parentName
4819
6033
  }) {
4820
6034
  const config = {
@@ -5131,7 +6345,7 @@ async function handleInstallCompletion(targetName) {
5131
6345
  await outputInstallCompletion(result);
5132
6346
  }
5133
6347
 
5134
- const config$h = {
6348
+ const config$i = {
5135
6349
  commandName: 'completion',
5136
6350
  description: 'Install bash completion for Socket CLI',
5137
6351
  hidden: false,
@@ -5168,16 +6382,16 @@ const config$h = {
5168
6382
  `
5169
6383
  };
5170
6384
  const cmdInstallCompletion = {
5171
- description: config$h.description,
5172
- hidden: config$h.hidden,
5173
- run: run$J
6385
+ description: config$i.description,
6386
+ hidden: config$i.hidden,
6387
+ run: run$K
5174
6388
  };
5175
- async function run$J(argv, importMeta, {
6389
+ async function run$K(argv, importMeta, {
5176
6390
  parentName
5177
6391
  }) {
5178
6392
  const cli = utils.meowOrExit({
5179
6393
  argv,
5180
- config: config$h,
6394
+ config: config$i,
5181
6395
  parentName,
5182
6396
  importMeta
5183
6397
  });
@@ -5234,7 +6448,7 @@ async function handleCmdJson(cwd) {
5234
6448
  await outputCmdJson(cwd);
5235
6449
  }
5236
6450
 
5237
- const config$g = {
6451
+ const config$h = {
5238
6452
  commandName: 'json',
5239
6453
  description: `Display the \`${constants.SOCKET_JSON}\` that would be applied for target folder`,
5240
6454
  hidden: true,
@@ -5253,16 +6467,16 @@ const config$g = {
5253
6467
  `
5254
6468
  };
5255
6469
  const cmdJson = {
5256
- description: config$g.description,
5257
- hidden: config$g.hidden,
5258
- run: run$I
6470
+ description: config$h.description,
6471
+ hidden: config$h.hidden,
6472
+ run: run$J
5259
6473
  };
5260
- async function run$I(argv, importMeta, {
6474
+ async function run$J(argv, importMeta, {
5261
6475
  parentName
5262
6476
  }) {
5263
6477
  const cli = utils.meowOrExit({
5264
6478
  argv,
5265
- config: config$g,
6479
+ config: config$h,
5266
6480
  parentName,
5267
6481
  importMeta
5268
6482
  });
@@ -5417,9 +6631,9 @@ const hidden$r = false;
5417
6631
  const cmdLogin = {
5418
6632
  description: description$x,
5419
6633
  hidden: hidden$r,
5420
- run: run$H
6634
+ run: run$I
5421
6635
  };
5422
- async function run$H(argv, importMeta, {
6636
+ async function run$I(argv, importMeta, {
5423
6637
  parentName
5424
6638
  }) {
5425
6639
  const config = {
@@ -5497,7 +6711,7 @@ function attemptLogout() {
5497
6711
  }
5498
6712
  }
5499
6713
 
5500
- const config$f = {
6714
+ const config$g = {
5501
6715
  commandName: 'logout',
5502
6716
  description: 'Socket API logout',
5503
6717
  hidden: false,
@@ -5515,16 +6729,16 @@ const config$f = {
5515
6729
  `
5516
6730
  };
5517
6731
  const cmdLogout = {
5518
- description: config$f.description,
5519
- hidden: config$f.hidden,
5520
- run: run$G
6732
+ description: config$g.description,
6733
+ hidden: config$g.hidden,
6734
+ run: run$H
5521
6735
  };
5522
- async function run$G(argv, importMeta, {
6736
+ async function run$H(argv, importMeta, {
5523
6737
  parentName
5524
6738
  }) {
5525
6739
  const cli = utils.meowOrExit({
5526
6740
  argv,
5527
- config: config$f,
6741
+ config: config$g,
5528
6742
  importMeta,
5529
6743
  parentName
5530
6744
  });
@@ -5837,7 +7051,7 @@ const yargsConfig = {
5837
7051
  'usages-slices-file' // hidden
5838
7052
  ]
5839
7053
  };
5840
- const config$e = {
7054
+ const config$f = {
5841
7055
  commandName: 'cdxgen',
5842
7056
  description: 'Run cdxgen for SBOM generation',
5843
7057
  hidden: false,
@@ -5847,11 +7061,11 @@ const config$e = {
5847
7061
  help: () => ''
5848
7062
  };
5849
7063
  const cmdManifestCdxgen = {
5850
- description: config$e.description,
5851
- hidden: config$e.hidden,
5852
- run: run$F
7064
+ description: config$f.description,
7065
+ hidden: config$f.hidden,
7066
+ run: run$G
5853
7067
  };
5854
- async function run$F(argv, importMeta, context) {
7068
+ async function run$G(argv, importMeta, context) {
5855
7069
  const {
5856
7070
  parentName
5857
7071
  } = {
@@ -5861,7 +7075,7 @@ async function run$F(argv, importMeta, context) {
5861
7075
  const cli = utils.meowOrExit({
5862
7076
  // Don't let meow take over --help.
5863
7077
  argv: argv.filter(a => !utils.isHelpFlag(a)),
5864
- config: config$e,
7078
+ config: config$f,
5865
7079
  importMeta,
5866
7080
  parentName
5867
7081
  });
@@ -5934,6 +7148,181 @@ async function run$F(argv, importMeta, context) {
5934
7148
  await spawnPromise;
5935
7149
  }
5936
7150
 
7151
+ const config$e = {
7152
+ commandName: 'bazel',
7153
+ description: '[beta] Bazel JVM SBOM support — generate manifest files (`maven_install.json`) for a Bazel/Maven project',
7154
+ hidden: false,
7155
+ flags: {
7156
+ ...flags.commonFlags,
7157
+ bazel: {
7158
+ type: 'string',
7159
+ description: 'Path to bazel/bazelisk binary; default: $(which bazelisk) || $(which bazel)'
7160
+ },
7161
+ bazelFlags: {
7162
+ type: 'string',
7163
+ description: 'Flags forwarded to every bazel invocation (single quoted string)'
7164
+ },
7165
+ bazelOutputBase: {
7166
+ type: 'string',
7167
+ description: 'Bazel --output_base for read-only-cache CI environments'
7168
+ },
7169
+ bazelRc: {
7170
+ type: 'string',
7171
+ description: 'Path to additional .bazelrc fragments forwarded to bazel'
7172
+ },
7173
+ out: {
7174
+ type: 'string',
7175
+ description: 'Output directory for generated manifests; default: ./.socket/bazel-manifests/'
7176
+ },
7177
+ verbose: {
7178
+ type: 'boolean',
7179
+ description: 'Stream bazel stdout/stderr'
7180
+ }
7181
+ },
7182
+ help: (command, config) => `
7183
+ Usage
7184
+ $ ${command} [options] [CWD=.]
7185
+
7186
+ Options
7187
+ ${utils.getFlagListOutput(config.flags)}
7188
+
7189
+ [beta] Generates Bazel JVM SBOM manifests (\`maven_install.json\`-shaped)
7190
+ by running \`bazel query\` against discovered Maven repos. Output is
7191
+ consumed by \`socket scan create\`'s server-side parser.
7192
+
7193
+ Note: this command generates Maven dependency manifests for Bazel JVM
7194
+ workspaces. It does not run reachability analysis.
7195
+
7196
+ To generate AND upload in one step, use \`socket scan create --auto-manifest\`
7197
+ instead — it detects Bazel workspaces, runs the same extraction, and uploads
7198
+ the result. This subcommand is for generation only.
7199
+
7200
+ Examples
7201
+ $ ${command} .
7202
+ $ ${command} --bazel=/usr/local/bin/bazelisk .
7203
+ `
7204
+ };
7205
+ const cmdManifestBazel = {
7206
+ description: config$e.description,
7207
+ hidden: config$e.hidden,
7208
+ run: run$F
7209
+ };
7210
+ async function run$F(argv, importMeta, {
7211
+ parentName
7212
+ }) {
7213
+ const cli = utils.meowOrExit({
7214
+ argv,
7215
+ config: config$e,
7216
+ importMeta,
7217
+ parentName
7218
+ });
7219
+ const {
7220
+ json = false,
7221
+ markdown = false
7222
+ } = cli.flags;
7223
+ const dryRun = !!cli.flags['dryRun'];
7224
+
7225
+ // TODO: Implement json/md further.
7226
+ const outputKind = utils.getOutputKind(json, markdown);
7227
+ let [cwd = '.'] = cli.input;
7228
+ // Note: path.resolve vs .join:
7229
+ // If given path is absolute then cwd should not affect it.
7230
+ cwd = path.resolve(process.cwd(), cwd);
7231
+ const sockJson = utils.readOrDefaultSocketJson(cwd);
7232
+ require$$9.debugFn('inspect', `override: ${constants.SOCKET_JSON} bazel`, sockJson?.defaults?.manifest?.bazel);
7233
+ let {
7234
+ bazel,
7235
+ bazelFlags,
7236
+ bazelOutputBase,
7237
+ bazelRc,
7238
+ out,
7239
+ verbose
7240
+ } = cli.flags;
7241
+
7242
+ // Set defaults for any flag/arg that is not given. Check socket.json first.
7243
+ if (!bazel) {
7244
+ const defaultBazel = sockJson.defaults?.manifest?.bazel?.bazel ?? sockJson.defaults?.manifest?.bazel?.bin;
7245
+ if (defaultBazel) {
7246
+ bazel = defaultBazel;
7247
+ logger.logger.info(`Using default --bazel from ${constants.SOCKET_JSON}:`, bazel);
7248
+ }
7249
+ // Otherwise leave undefined; resolveBazelBinary performs the PATH
7250
+ // lookup for bazelisk/bazel.
7251
+ }
7252
+ if (!bazelFlags) {
7253
+ if (sockJson.defaults?.manifest?.bazel?.bazelFlags) {
7254
+ bazelFlags = sockJson.defaults?.manifest?.bazel?.bazelFlags;
7255
+ logger.logger.info(`Using default --bazel-flags from ${constants.SOCKET_JSON}:`, bazelFlags);
7256
+ } else {
7257
+ bazelFlags = '';
7258
+ }
7259
+ }
7260
+ if (!bazelOutputBase) {
7261
+ if (sockJson.defaults?.manifest?.bazel?.bazelOutputBase) {
7262
+ bazelOutputBase = sockJson.defaults?.manifest?.bazel?.bazelOutputBase;
7263
+ logger.logger.info(`Using default --bazel-output-base from ${constants.SOCKET_JSON}:`, bazelOutputBase);
7264
+ }
7265
+ }
7266
+ if (!bazelRc) {
7267
+ if (sockJson.defaults?.manifest?.bazel?.bazelRc) {
7268
+ bazelRc = sockJson.defaults?.manifest?.bazel?.bazelRc;
7269
+ logger.logger.info(`Using default --bazel-rc from ${constants.SOCKET_JSON}:`, bazelRc);
7270
+ }
7271
+ }
7272
+ if (!out) {
7273
+ if (sockJson.defaults?.manifest?.bazel?.out) {
7274
+ out = sockJson.defaults?.manifest?.bazel?.out;
7275
+ logger.logger.info(`Using default --out from ${constants.SOCKET_JSON}:`, out);
7276
+ } else {
7277
+ out = path.join(cwd, '.socket', 'bazel-manifests');
7278
+ }
7279
+ }
7280
+ if (verbose === undefined) {
7281
+ if (sockJson.defaults?.manifest?.bazel?.verbose !== undefined) {
7282
+ verbose = sockJson.defaults?.manifest?.bazel?.verbose;
7283
+ logger.logger.info(`Using default --verbose from ${constants.SOCKET_JSON}:`, verbose);
7284
+ } else {
7285
+ verbose = false;
7286
+ }
7287
+ }
7288
+ if (verbose) {
7289
+ logger.logger.group('- ', parentName, config$e.commandName, ':');
7290
+ logger.logger.group('- flags:', cli.flags);
7291
+ logger.logger.groupEnd();
7292
+ logger.logger.log('- input:', cli.input);
7293
+ logger.logger.groupEnd();
7294
+ }
7295
+ const wasValidInput = utils.checkCommandInput(outputKind, {
7296
+ nook: true,
7297
+ test: cli.input.length <= 1,
7298
+ message: 'Can only accept one DIR (make sure to escape spaces!)',
7299
+ fail: 'received ' + cli.input.length
7300
+ });
7301
+ if (!wasValidInput) {
7302
+ return;
7303
+ }
7304
+ if (verbose) {
7305
+ logger.logger.group();
7306
+ logger.logger.info('- cwd:', cwd);
7307
+ logger.logger.info('- bazel bin:', bazel);
7308
+ logger.logger.info('- out:', out);
7309
+ logger.logger.groupEnd();
7310
+ }
7311
+ if (dryRun) {
7312
+ logger.logger.log(constants.default.DRY_RUN_BAILING_NOW);
7313
+ return;
7314
+ }
7315
+ await extractBazelToMaven({
7316
+ bazelFlags: bazelFlags,
7317
+ bazelOutputBase: bazelOutputBase,
7318
+ bazelRc: bazelRc,
7319
+ bin: bazel,
7320
+ cwd,
7321
+ out: out,
7322
+ verbose: Boolean(verbose)
7323
+ });
7324
+ }
7325
+
5937
7326
  const config$d = {
5938
7327
  commandName: 'auto',
5939
7328
  description: 'Auto-detect build and attempt to generate manifest file',
@@ -7167,6 +8556,7 @@ async function run$y(argv, importMeta, {
7167
8556
  importMeta,
7168
8557
  subcommands: {
7169
8558
  auto: cmdManifestAuto,
8559
+ bazel: cmdManifestBazel,
7170
8560
  cdxgen: cmdManifestCdxgen,
7171
8561
  conda: cmdManifestConda,
7172
8562
  gradle: cmdManifestGradle,
@@ -15865,5 +17255,5 @@ process.on('unhandledRejection', async (reason, promise) => {
15865
17255
  // eslint-disable-next-line n/no-process-exit
15866
17256
  process.exit(1);
15867
17257
  });
15868
- //# debugId=1acf2006-28da-49e4-9572-412f961998c4
17258
+ //# debugId=70895ae2-8c82-49e4-a1fb-3cbc0ccb2c57
15869
17259
  //# sourceMappingURL=cli.js.map