@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.
- package/CHANGELOG.md +16 -0
- package/dist/cli.js +1468 -78
- package/dist/cli.js.map +1 -1
- package/dist/constants.js +4 -4
- package/dist/constants.js.map +1 -1
- package/dist/tsconfig.dts.tsbuildinfo +1 -1
- package/dist/types/commands/manifest/bazel/bazel-bin-detect.d.mts +11 -0
- package/dist/types/commands/manifest/bazel/bazel-bin-detect.d.mts.map +1 -0
- package/dist/types/commands/manifest/bazel/bazel-build-parser.d.mts +34 -0
- package/dist/types/commands/manifest/bazel/bazel-build-parser.d.mts.map +1 -0
- package/dist/types/commands/manifest/bazel/bazel-java-shim.d.mts +10 -0
- package/dist/types/commands/manifest/bazel/bazel-java-shim.d.mts.map +1 -0
- package/dist/types/commands/manifest/bazel/bazel-output-base-check.d.mts +7 -0
- package/dist/types/commands/manifest/bazel/bazel-output-base-check.d.mts.map +1 -0
- package/dist/types/commands/manifest/bazel/bazel-python-shim.d.mts +9 -0
- package/dist/types/commands/manifest/bazel/bazel-python-shim.d.mts.map +1 -0
- package/dist/types/commands/manifest/bazel/bazel-query-runner.d.mts +41 -0
- package/dist/types/commands/manifest/bazel/bazel-query-runner.d.mts.map +1 -0
- package/dist/types/commands/manifest/bazel/bazel-repo-discovery.d.mts +34 -0
- package/dist/types/commands/manifest/bazel/bazel-repo-discovery.d.mts.map +1 -0
- package/dist/types/commands/manifest/bazel/bazel-workspace-detect.d.mts +13 -0
- package/dist/types/commands/manifest/bazel/bazel-workspace-detect.d.mts.map +1 -0
- package/dist/types/commands/manifest/bazel/cmd-manifest-bazel.d.mts +9 -0
- package/dist/types/commands/manifest/bazel/cmd-manifest-bazel.d.mts.map +1 -0
- package/dist/types/commands/manifest/bazel/extract_bazel_to_maven.d.mts +33 -0
- package/dist/types/commands/manifest/bazel/extract_bazel_to_maven.d.mts.map +1 -0
- package/dist/types/commands/manifest/cmd-manifest.d.mts.map +1 -1
- package/dist/types/commands/manifest/detect-manifest-actions.d.mts +1 -0
- package/dist/types/commands/manifest/detect-manifest-actions.d.mts.map +1 -1
- package/dist/types/commands/manifest/generate_auto_manifest.d.mts +4 -1
- package/dist/types/commands/manifest/generate_auto_manifest.d.mts.map +1 -1
- package/dist/types/commands/scan/handle-create-new-scan.d.mts.map +1 -1
- package/dist/types/utils/api.d.mts +5 -3
- package/dist/types/utils/api.d.mts.map +1 -1
- package/dist/types/utils/coana.d.mts +35 -0
- package/dist/types/utils/coana.d.mts.map +1 -1
- package/dist/types/utils/socket-json.d.mts +10 -0
- package/dist/types/utils/socket-json.d.mts.map +1 -1
- package/dist/utils.js +131 -28
- package/dist/utils.js.map +1 -1
- 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$
|
|
335
|
+
run: run$T
|
|
335
336
|
};
|
|
336
|
-
async function run$
|
|
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$
|
|
758
|
+
run: run$S
|
|
758
759
|
};
|
|
759
|
-
async function run$
|
|
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(
|
|
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
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
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$
|
|
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$
|
|
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$
|
|
2709
|
-
hidden: config$
|
|
2710
|
-
run: run$
|
|
3922
|
+
description: config$l.description,
|
|
3923
|
+
hidden: config$l.hidden,
|
|
3924
|
+
run: run$R
|
|
2711
3925
|
};
|
|
2712
|
-
async function run$
|
|
3926
|
+
async function run$R(argv, importMeta, {
|
|
2713
3927
|
parentName
|
|
2714
3928
|
}) {
|
|
2715
3929
|
const cli = utils.meowOrExit({
|
|
2716
3930
|
argv,
|
|
2717
|
-
config: config$
|
|
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$
|
|
4174
|
+
run: run$Q
|
|
2961
4175
|
};
|
|
2962
|
-
async function run$
|
|
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$
|
|
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$
|
|
3097
|
-
hidden: config$
|
|
3098
|
-
run: run$
|
|
4310
|
+
description: config$k.description,
|
|
4311
|
+
hidden: config$k.hidden,
|
|
4312
|
+
run: run$P
|
|
3099
4313
|
};
|
|
3100
|
-
async function run$
|
|
4314
|
+
async function run$P(argv, importMeta, {
|
|
3101
4315
|
parentName
|
|
3102
4316
|
}) {
|
|
3103
4317
|
const cli = utils.meowOrExit({
|
|
3104
4318
|
argv,
|
|
3105
|
-
config: config$
|
|
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$
|
|
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$
|
|
3233
|
-
hidden: config$
|
|
3234
|
-
run: run$
|
|
4446
|
+
description: config$j.description,
|
|
4447
|
+
hidden: config$j.hidden,
|
|
4448
|
+
run: run$O
|
|
3235
4449
|
};
|
|
3236
|
-
async function run$
|
|
4450
|
+
async function run$O(argv, importMeta, {
|
|
3237
4451
|
parentName
|
|
3238
4452
|
}) {
|
|
3239
4453
|
const cli = utils.meowOrExit({
|
|
3240
4454
|
argv,
|
|
3241
|
-
config: config$
|
|
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$
|
|
4540
|
+
run: run$N
|
|
3327
4541
|
};
|
|
3328
|
-
async function run$
|
|
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$
|
|
4667
|
+
run: run$M
|
|
3454
4668
|
};
|
|
3455
|
-
async function run$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
5172
|
-
hidden: config$
|
|
5173
|
-
run: run$
|
|
6385
|
+
description: config$i.description,
|
|
6386
|
+
hidden: config$i.hidden,
|
|
6387
|
+
run: run$K
|
|
5174
6388
|
};
|
|
5175
|
-
async function run$
|
|
6389
|
+
async function run$K(argv, importMeta, {
|
|
5176
6390
|
parentName
|
|
5177
6391
|
}) {
|
|
5178
6392
|
const cli = utils.meowOrExit({
|
|
5179
6393
|
argv,
|
|
5180
|
-
config: config$
|
|
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$
|
|
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$
|
|
5257
|
-
hidden: config$
|
|
5258
|
-
run: run$
|
|
6470
|
+
description: config$h.description,
|
|
6471
|
+
hidden: config$h.hidden,
|
|
6472
|
+
run: run$J
|
|
5259
6473
|
};
|
|
5260
|
-
async function run$
|
|
6474
|
+
async function run$J(argv, importMeta, {
|
|
5261
6475
|
parentName
|
|
5262
6476
|
}) {
|
|
5263
6477
|
const cli = utils.meowOrExit({
|
|
5264
6478
|
argv,
|
|
5265
|
-
config: config$
|
|
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$
|
|
6634
|
+
run: run$I
|
|
5421
6635
|
};
|
|
5422
|
-
async function run$
|
|
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$
|
|
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$
|
|
5519
|
-
hidden: config$
|
|
5520
|
-
run: run$
|
|
6732
|
+
description: config$g.description,
|
|
6733
|
+
hidden: config$g.hidden,
|
|
6734
|
+
run: run$H
|
|
5521
6735
|
};
|
|
5522
|
-
async function run$
|
|
6736
|
+
async function run$H(argv, importMeta, {
|
|
5523
6737
|
parentName
|
|
5524
6738
|
}) {
|
|
5525
6739
|
const cli = utils.meowOrExit({
|
|
5526
6740
|
argv,
|
|
5527
|
-
config: config$
|
|
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$
|
|
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$
|
|
5851
|
-
hidden: config$
|
|
5852
|
-
run: run$
|
|
7064
|
+
description: config$f.description,
|
|
7065
|
+
hidden: config$f.hidden,
|
|
7066
|
+
run: run$G
|
|
5853
7067
|
};
|
|
5854
|
-
async function run$
|
|
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$
|
|
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=
|
|
17258
|
+
//# debugId=70895ae2-8c82-49e4-a1fb-3cbc0ccb2c57
|
|
15869
17259
|
//# sourceMappingURL=cli.js.map
|