@socketsecurity/cli-with-sentry 1.1.152 → 1.1.153
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 +5 -0
- package/dist/cli.js +2348 -629
- package/dist/cli.js.map +1 -1
- package/dist/constants.js +4 -4
- package/dist/constants.js.map +1 -1
- package/dist/manifest-scripts/maven-extension/coana-maven-extension.jar +0 -0
- package/dist/manifest-scripts/socket-facts.init.gradle +14 -45
- package/dist/manifest-scripts/socket-facts.plugin.scala +15 -46
- package/dist/manifest-scripts/socket-workspaces.init.gradle +99 -0
- package/dist/manifest-scripts/socket-workspaces.plugin.scala +112 -0
- package/dist/tsconfig.dts.tsbuildinfo +1 -1
- package/dist/types/commands/manifest/cmd-manifest-dynamic-sbom-inference.d.mts +9 -0
- package/dist/types/commands/manifest/cmd-manifest-dynamic-sbom-inference.d.mts.map +1 -0
- package/dist/types/commands/manifest/cmd-manifest-gradle.d.mts.map +1 -1
- package/dist/types/commands/manifest/cmd-manifest-kotlin.d.mts.map +1 -1
- package/dist/types/commands/manifest/cmd-manifest-maven.d.mts.map +1 -1
- package/dist/types/commands/manifest/cmd-manifest-scala.d.mts.map +1 -1
- package/dist/types/commands/manifest/cmd-manifest-setup.d.mts.map +1 -1
- package/dist/types/commands/manifest/cmd-manifest.d.mts.map +1 -1
- package/dist/types/commands/manifest/convert-gradle-to-facts.d.mts +2 -1
- package/dist/types/commands/manifest/convert-gradle-to-facts.d.mts.map +1 -1
- package/dist/types/commands/manifest/convert-maven-to-facts.d.mts +2 -1
- package/dist/types/commands/manifest/convert-maven-to-facts.d.mts.map +1 -1
- package/dist/types/commands/manifest/convert-sbt-to-facts.d.mts +2 -1
- package/dist/types/commands/manifest/convert-sbt-to-facts.d.mts.map +1 -1
- package/dist/types/commands/manifest/discover-manifest-roots.d.mts +18 -0
- package/dist/types/commands/manifest/discover-manifest-roots.d.mts.map +1 -0
- package/dist/types/commands/manifest/enumerate-workspaces.d.mts +19 -0
- package/dist/types/commands/manifest/enumerate-workspaces.d.mts.map +1 -0
- package/dist/types/commands/manifest/expand-env-var-refs.d.mts +10 -0
- package/dist/types/commands/manifest/expand-env-var-refs.d.mts.map +1 -0
- package/dist/types/commands/manifest/generate-recursive-manifests.d.mts +37 -0
- package/dist/types/commands/manifest/generate-recursive-manifests.d.mts.map +1 -0
- package/dist/types/commands/manifest/generate_auto_manifest.d.mts.map +1 -1
- package/dist/types/commands/manifest/handle-manifest-dynamic-sbom-inference.d.mts +8 -0
- package/dist/types/commands/manifest/handle-manifest-dynamic-sbom-inference.d.mts.map +1 -0
- package/dist/types/commands/manifest/handle-manifest-setup.d.mts +1 -1
- package/dist/types/commands/manifest/handle-manifest-setup.d.mts.map +1 -1
- package/dist/types/commands/manifest/manifest-flags.d.mts +6 -0
- package/dist/types/commands/manifest/manifest-flags.d.mts.map +1 -0
- package/dist/types/commands/manifest/output-manifest-dynamic-sbom-inference.d.mts +4 -0
- package/dist/types/commands/manifest/output-manifest-dynamic-sbom-inference.d.mts.map +1 -0
- package/dist/types/commands/manifest/run-manifest-facts.d.mts +13 -2
- package/dist/types/commands/manifest/run-manifest-facts.d.mts.map +1 -1
- package/dist/types/commands/manifest/scripts/exclude-paths-glob.d.mts +8 -0
- package/dist/types/commands/manifest/scripts/exclude-paths-glob.d.mts.map +1 -0
- package/dist/types/commands/manifest/scripts/run.d.mts +10 -1
- package/dist/types/commands/manifest/scripts/run.d.mts.map +1 -1
- package/dist/types/commands/manifest/setup-manifest-config.d.mts +19 -0
- package/dist/types/commands/manifest/setup-manifest-config.d.mts.map +1 -1
- package/dist/types/commands/manifest/setup-recursive-manifest-config.d.mts +120 -0
- package/dist/types/commands/manifest/setup-recursive-manifest-config.d.mts.map +1 -0
- package/dist/types/commands/scan/create-scan-from-github.d.mts +17 -0
- package/dist/types/commands/scan/create-scan-from-github.d.mts.map +1 -1
- package/dist/types/utils/github-errors.d.mts +51 -0
- package/dist/types/utils/github-errors.d.mts.map +1 -0
- package/dist/types/utils/socket-json.d.mts +38 -14
- package/dist/types/utils/socket-json.d.mts.map +1 -1
- package/dist/utils.js +307 -3
- package/dist/utils.js.map +1 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -333,9 +333,9 @@ const hidden$x = false;
|
|
|
333
333
|
const cmdAnalytics = {
|
|
334
334
|
description: description$F,
|
|
335
335
|
hidden: hidden$x,
|
|
336
|
-
run: run$
|
|
336
|
+
run: run$V
|
|
337
337
|
};
|
|
338
|
-
async function run$
|
|
338
|
+
async function run$V(argv, importMeta, {
|
|
339
339
|
parentName
|
|
340
340
|
}) {
|
|
341
341
|
const config = {
|
|
@@ -756,9 +756,9 @@ const hidden$w = false;
|
|
|
756
756
|
const cmdAuditLog = {
|
|
757
757
|
description: description$E,
|
|
758
758
|
hidden: hidden$w,
|
|
759
|
-
run: run$
|
|
759
|
+
run: run$U
|
|
760
760
|
};
|
|
761
|
-
async function run$
|
|
761
|
+
async function run$U(argv, importMeta, {
|
|
762
762
|
parentName
|
|
763
763
|
}) {
|
|
764
764
|
const config = {
|
|
@@ -4342,6 +4342,41 @@ async function extractBazelToMaven(opts) {
|
|
|
4342
4342
|
}
|
|
4343
4343
|
}
|
|
4344
4344
|
|
|
4345
|
+
const ENV_VAR_REF = /\$\$|\$\{(\w+)\}|\$(\w+)/g;
|
|
4346
|
+
|
|
4347
|
+
// Expands `$VAR`/`${VAR}` references (e.g. a team-shared `javaHome:
|
|
4348
|
+
// "$JAVA11_HOME"`) against the CLI process's own environment, so a socket.json
|
|
4349
|
+
// value works across machines instead of hardcoding one developer's path.
|
|
4350
|
+
// `$$` is a literal `$`, for a value that must contain a literal `$WORD`.
|
|
4351
|
+
function expandEnvVarRefs(value) {
|
|
4352
|
+
const missing = [];
|
|
4353
|
+
const expanded = value.replace(ENV_VAR_REF, (match, braced, bare) => {
|
|
4354
|
+
if (match === '$$') {
|
|
4355
|
+
return '$';
|
|
4356
|
+
}
|
|
4357
|
+
const name = braced ?? bare;
|
|
4358
|
+
const resolved = process.env[name];
|
|
4359
|
+
if (resolved === undefined) {
|
|
4360
|
+
if (!missing.includes(name)) {
|
|
4361
|
+
missing.push(name);
|
|
4362
|
+
}
|
|
4363
|
+
return '';
|
|
4364
|
+
}
|
|
4365
|
+
return resolved;
|
|
4366
|
+
});
|
|
4367
|
+
return missing.length ? {
|
|
4368
|
+
missing,
|
|
4369
|
+
value: expanded
|
|
4370
|
+
} : {
|
|
4371
|
+
value: expanded
|
|
4372
|
+
};
|
|
4373
|
+
}
|
|
4374
|
+
function formatMissingEnvVarRefs(missing) {
|
|
4375
|
+
const names = missing.map(name => `\`${name}\``).join(', ');
|
|
4376
|
+
const verb = missing.length > 1 ? 'are' : 'is';
|
|
4377
|
+
return `references ${names}, which ${verb} not set in this environment`;
|
|
4378
|
+
}
|
|
4379
|
+
|
|
4345
4380
|
// Gradle's variant-aware resolver: distinct exceptions give mutually-exclusive
|
|
4346
4381
|
// phrasing, so most-specific-first substring checks classify reliably.
|
|
4347
4382
|
function classifyGradleFailure(detail) {
|
|
@@ -5097,6 +5132,141 @@ function resolveBuildToolBin(tool, projectDir, bin) {
|
|
|
5097
5132
|
return DEFAULT_BUILD_TOOL_BIN[tool];
|
|
5098
5133
|
}
|
|
5099
5134
|
|
|
5135
|
+
// Single source of truth for --exclude-paths glob semantics against a JVM
|
|
5136
|
+
// build root's subproject dirs: anchored, relative, case-sensitive; `*`
|
|
5137
|
+
// within one path segment, `**` across zero or more segments, `?` within one
|
|
5138
|
+
// segment, and `[...]` character classes with `[!..]`/`[^..]` negation.
|
|
5139
|
+
// Entries are assumed pre-validated by assertValidExcludePaths (relative, no
|
|
5140
|
+
// negation, no `..`, balanced brackets, comma-free). A malformed entry falls
|
|
5141
|
+
// back to a literal match, never throws.
|
|
5142
|
+
//
|
|
5143
|
+
// Compiled HERE, once, and handed to every producer (the gradle/sbt/maven
|
|
5144
|
+
// facts and workspaces scripts) pre-compiled, so there is exactly one
|
|
5145
|
+
// implementation and one test suite instead of the same algorithm
|
|
5146
|
+
// re-implemented per language.
|
|
5147
|
+
//
|
|
5148
|
+
// Portability contract: gradle/sbt/maven all run on the JVM and share the
|
|
5149
|
+
// exact same regex engine (java.util.regex), so the same emitted subset
|
|
5150
|
+
// (`.*`, `[^/]*`, `[^/]`, `[...]`, `[^...]`, backslash-escaped
|
|
5151
|
+
// metacharacters) is authoritative for every producer - this compiler and JS
|
|
5152
|
+
// RegExp agree on the identical subset. Class bodies escape `&` because Java
|
|
5153
|
+
// classes support `&&` intersection (JS treats it literally). Each compiled
|
|
5154
|
+
// pattern already means "this path OR its whole subtree", matching the
|
|
5155
|
+
// documented --exclude-paths contract, so a producer never needs its own
|
|
5156
|
+
// "self + /**" expansion. Patterns transport comma-joined: an input glob can
|
|
5157
|
+
// never contain a comma, since --exclude-paths is comma-split before glob
|
|
5158
|
+
// parsing reaches this module.
|
|
5159
|
+
|
|
5160
|
+
function literalSource(glob) {
|
|
5161
|
+
const escaped = glob.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
5162
|
+
return `^(?:${escaped})(?:/.*)?$`;
|
|
5163
|
+
}
|
|
5164
|
+
|
|
5165
|
+
// Strips a trailing `/**` (and trailing slashes) so `dir` and `dir/**` both
|
|
5166
|
+
// compile to the same base pattern - the "self OR subtree" wrap below is
|
|
5167
|
+
// what actually encodes matching the subtree, not the user's own suffix.
|
|
5168
|
+
function stripTrailingGlobstar(glob) {
|
|
5169
|
+
let g = glob;
|
|
5170
|
+
while (g.endsWith('/')) {
|
|
5171
|
+
g = g.slice(0, -1);
|
|
5172
|
+
}
|
|
5173
|
+
while (g.endsWith('/**')) {
|
|
5174
|
+
g = g.slice(0, -3);
|
|
5175
|
+
while (g.endsWith('/')) {
|
|
5176
|
+
g = g.slice(0, -1);
|
|
5177
|
+
}
|
|
5178
|
+
}
|
|
5179
|
+
return g;
|
|
5180
|
+
}
|
|
5181
|
+
|
|
5182
|
+
// Translates one path segment's `*`/`?`/`[...]` - never crosses a `/`.
|
|
5183
|
+
function translateSegment(segment) {
|
|
5184
|
+
let sb = '';
|
|
5185
|
+
let i = 0;
|
|
5186
|
+
const n = segment.length;
|
|
5187
|
+
while (i < n) {
|
|
5188
|
+
const ch = segment.charAt(i);
|
|
5189
|
+
if (ch === '*') {
|
|
5190
|
+
sb += '[^/]*';
|
|
5191
|
+
i += 1;
|
|
5192
|
+
} else if (ch === '?') {
|
|
5193
|
+
sb += '[^/]';
|
|
5194
|
+
i += 1;
|
|
5195
|
+
} else if (ch === '[') {
|
|
5196
|
+
const j = segment.indexOf(']', i + 1);
|
|
5197
|
+
// Treat as a class only with a non-empty body; else a literal `[`.
|
|
5198
|
+
if (j <= i + 1) {
|
|
5199
|
+
sb += '\\[';
|
|
5200
|
+
i += 1;
|
|
5201
|
+
} else {
|
|
5202
|
+
let body = segment.slice(i + 1, j);
|
|
5203
|
+
const neg = body.startsWith('!') || body.startsWith('^');
|
|
5204
|
+
if (neg) {
|
|
5205
|
+
body = body.slice(1);
|
|
5206
|
+
}
|
|
5207
|
+
if (!body) {
|
|
5208
|
+
// `[!]`/`[^]` would emit `[^]`, which JS accepts but Java rejects;
|
|
5209
|
+
// signal the caller to fall back to a whole-glob literal match.
|
|
5210
|
+
throw new Error('empty-class');
|
|
5211
|
+
}
|
|
5212
|
+
// Only literal chars and `-` ranges are meaningful; neutralize
|
|
5213
|
+
// regex-class tricks (`&` guards Java's `&&` class intersection).
|
|
5214
|
+
body = body.replace(/\\/g, '\\\\').replace(/\[/g, '\\[').replace(/\]/g, '\\]').replace(/&/g, '\\&');
|
|
5215
|
+
sb += `[${neg ? '^' : ''}${body}]`;
|
|
5216
|
+
i = j + 1;
|
|
5217
|
+
}
|
|
5218
|
+
} else if ('.\\^$|+(){}]'.includes(ch)) {
|
|
5219
|
+
sb += `\\${ch}`;
|
|
5220
|
+
i += 1;
|
|
5221
|
+
} else {
|
|
5222
|
+
sb += ch;
|
|
5223
|
+
i += 1;
|
|
5224
|
+
}
|
|
5225
|
+
}
|
|
5226
|
+
return sb;
|
|
5227
|
+
}
|
|
5228
|
+
function excludePathGlobToRegexSource(glob) {
|
|
5229
|
+
const stripped = stripTrailingGlobstar(glob);
|
|
5230
|
+
if (!stripped) {
|
|
5231
|
+
// Defensive only: assertValidExcludePaths already rejects a pattern that
|
|
5232
|
+
// reduces to "match everything".
|
|
5233
|
+
return literalSource(glob);
|
|
5234
|
+
}
|
|
5235
|
+
try {
|
|
5236
|
+
const segments = stripped.split('/');
|
|
5237
|
+
let base = '';
|
|
5238
|
+
segments.forEach((segment, i) => {
|
|
5239
|
+
const isLast = i === segments.length - 1;
|
|
5240
|
+
if (segment === '**') {
|
|
5241
|
+
// Zero or more whole segments: mid-pattern, each consumed segment
|
|
5242
|
+
// carries its own trailing slash; as the last segment, anything
|
|
5243
|
+
// (including nothing) for the rest of the path.
|
|
5244
|
+
base += isLast ? '.*' : '(?:[^/]+/)*';
|
|
5245
|
+
} else {
|
|
5246
|
+
base += translateSegment(segment);
|
|
5247
|
+
if (!isLast) {
|
|
5248
|
+
base += '/';
|
|
5249
|
+
}
|
|
5250
|
+
}
|
|
5251
|
+
});
|
|
5252
|
+
const source = `^(?:${base})(?:/.*)?$`;
|
|
5253
|
+
// eslint-disable-next-line no-new
|
|
5254
|
+
new RegExp(source);
|
|
5255
|
+
return source;
|
|
5256
|
+
} catch {
|
|
5257
|
+
return literalSource(glob);
|
|
5258
|
+
}
|
|
5259
|
+
}
|
|
5260
|
+
function compileExcludePathPatterns(paths) {
|
|
5261
|
+
return (paths ?? []).map(excludePathGlobToRegexSource);
|
|
5262
|
+
}
|
|
5263
|
+
|
|
5264
|
+
// Transport form handed to the build-tool scripts: comma-joined pattern
|
|
5265
|
+
// sources. Empty string when there are no patterns.
|
|
5266
|
+
function serializeExcludePathPatterns(paths) {
|
|
5267
|
+
return compileExcludePathPatterns(paths).join(',');
|
|
5268
|
+
}
|
|
5269
|
+
|
|
5100
5270
|
// Line-protocol the build-tool scripts emit to a records file (NOT stdout — sbt
|
|
5101
5271
|
// prints unsilenceable resolution noise there). One record per line, fields
|
|
5102
5272
|
// backslash-escaped (\\, \t, \n, \r) so a value can't break framing:
|
|
@@ -5283,8 +5453,11 @@ function parseRecords(text) {
|
|
|
5283
5453
|
}
|
|
5284
5454
|
|
|
5285
5455
|
const FACTS_TASK = 'socketFacts';
|
|
5286
|
-
const
|
|
5287
|
-
|
|
5456
|
+
const WORKSPACES_TASK = 'socketWorkspaces';
|
|
5457
|
+
// Destination filename inside sbt's isolated plugins/ dir; unrelated to the source filename, which
|
|
5458
|
+
// is whatever socket-facts.plugin.scala / socket-workspaces.plugin.scala are called on disk.
|
|
5459
|
+
const SBT_FACTS_PLUGIN_FILENAME = 'SocketFactsPlugin.scala';
|
|
5460
|
+
const SBT_WORKSPACES_PLUGIN_FILENAME = 'SocketWorkspacesPlugin.scala';
|
|
5288
5461
|
// Bundled emitter assets, copied into dist by the rollup build.
|
|
5289
5462
|
function manifestScriptsPath(...parts) {
|
|
5290
5463
|
return path.join(constants.default.distPath, 'manifest-scripts', ...parts);
|
|
@@ -5329,13 +5502,13 @@ async function runNeverThrow(bin, args, opts) {
|
|
|
5329
5502
|
throw e;
|
|
5330
5503
|
}
|
|
5331
5504
|
}
|
|
5332
|
-
async function writeSbtPlugin(globalBase) {
|
|
5333
|
-
const src = await fs$1.promises.readFile(manifestScriptsPath(
|
|
5505
|
+
async function writeSbtPlugin(globalBase, sourceFilename, destFilename) {
|
|
5506
|
+
const src = await fs$1.promises.readFile(manifestScriptsPath(sourceFilename), 'utf8');
|
|
5334
5507
|
const pluginsDir = path.join(globalBase, 'plugins');
|
|
5335
5508
|
await fs$1.promises.mkdir(pluginsDir, {
|
|
5336
5509
|
recursive: true
|
|
5337
5510
|
});
|
|
5338
|
-
await fs$1.promises.writeFile(path.join(pluginsDir,
|
|
5511
|
+
await fs$1.promises.writeFile(path.join(pluginsDir, destFilename), src);
|
|
5339
5512
|
}
|
|
5340
5513
|
async function assembleFromRecords(out, recordsFile) {
|
|
5341
5514
|
const text = fs$1.existsSync(recordsFile) ? await fs$1.promises.readFile(recordsFile, 'utf8') : '';
|
|
@@ -5375,6 +5548,18 @@ async function runManifestScript(tool, opts) {
|
|
|
5375
5548
|
return await runMaven(opts);
|
|
5376
5549
|
}
|
|
5377
5550
|
}
|
|
5551
|
+
|
|
5552
|
+
// Emits only `project` records, never resolving dependencies, for discovery
|
|
5553
|
+
// before committing to a full `runManifestScript` resolution cost.
|
|
5554
|
+
async function enumerateWorkspaces$1(tool, opts) {
|
|
5555
|
+
const result = await (tool === 'gradle' ? enumerateGradleWorkspaces(opts) : tool === 'sbt' ? enumerateSbtWorkspaces(opts) : enumerateMavenWorkspaces(opts));
|
|
5556
|
+
return {
|
|
5557
|
+
code: result.code,
|
|
5558
|
+
projects: result.facts.projects ?? [],
|
|
5559
|
+
stderr: result.stderr,
|
|
5560
|
+
stdout: result.stdout
|
|
5561
|
+
};
|
|
5562
|
+
}
|
|
5378
5563
|
function commonProps(opts, prefix) {
|
|
5379
5564
|
const props = [];
|
|
5380
5565
|
if (opts.withFiles) {
|
|
@@ -5389,27 +5574,35 @@ function commonProps(opts, prefix) {
|
|
|
5389
5574
|
if (opts.excludeConfigs) {
|
|
5390
5575
|
props.push(`${prefix}socket.excludeConfigs=${opts.excludeConfigs}`);
|
|
5391
5576
|
}
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5577
|
+
// Globs compile to regex pattern sources HERE (exclude-paths-glob.mts is
|
|
5578
|
+
// the single glob implementation); the scripts just Pattern.compile what
|
|
5579
|
+
// they receive.
|
|
5580
|
+
const excludePatterns = serializeExcludePathPatterns(opts.excludePaths);
|
|
5581
|
+
if (excludePatterns) {
|
|
5582
|
+
props.push(`${prefix}socket.excludePaths=${excludePatterns}`);
|
|
5396
5583
|
}
|
|
5397
5584
|
return props;
|
|
5398
5585
|
}
|
|
5399
|
-
async function
|
|
5400
|
-
const initScript = manifestScriptsPath(
|
|
5401
|
-
return await utils.withTmpDir(
|
|
5586
|
+
async function invokeGradle(opts, initScriptFilename, task, tmpDirPrefix) {
|
|
5587
|
+
const initScript = manifestScriptsPath(initScriptFilename);
|
|
5588
|
+
return await utils.withTmpDir(tmpDirPrefix, async tmp => {
|
|
5402
5589
|
const recordsFile = path.join(tmp, 'records.tsv');
|
|
5403
5590
|
const bin = resolveBuildToolBin('gradle', opts.projectDir, opts.bin);
|
|
5404
5591
|
// Disable the configuration cache: the init script's legacy
|
|
5405
5592
|
// resolvedConfiguration API and shared accumulator aren't cache-safe.
|
|
5406
|
-
const args = ['--init-script', initScript, '-Dorg.gradle.configuration-cache=false', `-Psocket.recordsFile=${recordsFile}`, ...commonProps(opts, '-P'), ...(opts.toolOpts ?? []),
|
|
5593
|
+
const args = ['--init-script', initScript, '-Dorg.gradle.configuration-cache=false', `-Psocket.recordsFile=${recordsFile}`, ...commonProps(opts, '-P'), ...(opts.toolOpts ?? []), task, '--no-daemon', '--console=plain'];
|
|
5407
5594
|
const out = await runNeverThrow(bin, args, opts);
|
|
5408
5595
|
return await assembleFromRecords(out, recordsFile);
|
|
5409
5596
|
});
|
|
5410
5597
|
}
|
|
5411
|
-
async function
|
|
5412
|
-
await
|
|
5598
|
+
async function runGradle(opts) {
|
|
5599
|
+
return await invokeGradle(opts, 'socket-facts.init.gradle', FACTS_TASK, 'socket-gradle-facts-');
|
|
5600
|
+
}
|
|
5601
|
+
async function enumerateGradleWorkspaces(opts) {
|
|
5602
|
+
return await invokeGradle(opts, 'socket-workspaces.init.gradle', WORKSPACES_TASK, 'socket-gradle-workspaces-');
|
|
5603
|
+
}
|
|
5604
|
+
async function invokeSbtIn(globalBase, opts, pluginSourceFilename, pluginDestFilename, task) {
|
|
5605
|
+
await writeSbtPlugin(globalBase, pluginSourceFilename, pluginDestFilename);
|
|
5413
5606
|
const recordsFile = path.join(globalBase, 'records.tsv');
|
|
5414
5607
|
const bin = resolveBuildToolBin('sbt', opts.projectDir, opts.bin);
|
|
5415
5608
|
// Fresh per-run global base (not ~/.sbt): sbt executes everything under
|
|
@@ -5423,30 +5616,44 @@ async function runSbtIn(globalBase, opts) {
|
|
|
5423
5616
|
// caller-supplied --java-home.
|
|
5424
5617
|
const javaHome = opts.env?.['JAVA_HOME'] ?? process.env['JAVA_HOME'];
|
|
5425
5618
|
const javaHomeOpt = javaHome && !(opts.toolOpts ?? []).includes('--java-home') ? ['--java-home', javaHome] : [];
|
|
5426
|
-
const args = [...javaHomeOpt, ...props, ...(opts.toolOpts ?? []), '--batch',
|
|
5619
|
+
const args = [...javaHomeOpt, ...props, ...(opts.toolOpts ?? []), '--batch', task];
|
|
5427
5620
|
const out = await runNeverThrow(bin, args, opts);
|
|
5428
5621
|
return await assembleFromRecords(out, recordsFile);
|
|
5429
5622
|
}
|
|
5430
5623
|
async function runSbt(opts) {
|
|
5624
|
+
const run = globalBase => invokeSbtIn(globalBase, opts, 'socket-facts.plugin.scala', SBT_FACTS_PLUGIN_FILENAME, FACTS_TASK);
|
|
5431
5625
|
if (opts.tmpDir) {
|
|
5432
|
-
return await
|
|
5626
|
+
return await run(opts.tmpDir);
|
|
5433
5627
|
}
|
|
5434
|
-
return await utils.withTmpDir('socket-sbt-facts-',
|
|
5628
|
+
return await utils.withTmpDir('socket-sbt-facts-', run);
|
|
5435
5629
|
}
|
|
5436
|
-
async function
|
|
5630
|
+
async function enumerateSbtWorkspaces(opts) {
|
|
5631
|
+
const run = globalBase => invokeSbtIn(globalBase, opts, 'socket-workspaces.plugin.scala', SBT_WORKSPACES_PLUGIN_FILENAME, WORKSPACES_TASK);
|
|
5632
|
+
if (opts.tmpDir) {
|
|
5633
|
+
return await run(opts.tmpDir);
|
|
5634
|
+
}
|
|
5635
|
+
return await utils.withTmpDir('socket-sbt-workspaces-', run);
|
|
5636
|
+
}
|
|
5637
|
+
async function invokeMaven(opts, coanaTask, tmpDirPrefix) {
|
|
5437
5638
|
const jarPath = manifestScriptsPath('maven-extension', 'coana-maven-extension.jar');
|
|
5438
5639
|
assertMavenExtensionBuilt(jarPath);
|
|
5439
|
-
return await utils.withTmpDir(
|
|
5640
|
+
return await utils.withTmpDir(tmpDirPrefix, async tmp => {
|
|
5440
5641
|
const recordsFile = path.join(tmp, 'records.tsv');
|
|
5441
5642
|
const bin = resolveBuildToolBin('maven', opts.projectDir, opts.bin);
|
|
5442
|
-
// `validate` is the cheapest phase that triggers the
|
|
5443
|
-
//
|
|
5444
|
-
const props = [`-Dmaven.ext.class.path=${jarPath}`,
|
|
5643
|
+
// `validate` is the cheapest phase that triggers the extension; no compile
|
|
5644
|
+
// needed (analysis uses configured paths, not classes).
|
|
5645
|
+
const props = [`-Dmaven.ext.class.path=${jarPath}`, `-Dcoana.task=${coanaTask}`, `-Dsocket.recordsFile=${recordsFile}`, ...commonProps(opts, '-D')];
|
|
5445
5646
|
const args = [...props, ...(opts.toolOpts ?? []), '--batch-mode', 'validate'];
|
|
5446
5647
|
const out = await runNeverThrow(bin, args, opts);
|
|
5447
5648
|
return await assembleFromRecords(out, recordsFile);
|
|
5448
5649
|
});
|
|
5449
5650
|
}
|
|
5651
|
+
async function runMaven(opts) {
|
|
5652
|
+
return await invokeMaven(opts, 'socket-facts', 'socket-maven-facts-');
|
|
5653
|
+
}
|
|
5654
|
+
async function enumerateMavenWorkspaces(opts) {
|
|
5655
|
+
return await invokeMaven(opts, 'socket-workspaces', 'socket-maven-workspaces-');
|
|
5656
|
+
}
|
|
5450
5657
|
|
|
5451
5658
|
// Frozen contract with `coana run --compute-artifacts-sidecar`; change only in
|
|
5452
5659
|
// sync with the coana consumer. Per coordinate: targets/sources present →
|
|
@@ -5520,6 +5727,11 @@ function tailBuildOutput(stdout, stderr) {
|
|
|
5520
5727
|
return combined.split('\n').slice(-40).join('\n');
|
|
5521
5728
|
}
|
|
5522
5729
|
|
|
5730
|
+
// `null` = a real failure (crash, missing config, blocking unresolved
|
|
5731
|
+
// dependency); `undefined` = genuinely nothing to resolve, not a failure.
|
|
5732
|
+
// Distinguishing the two here means callers never have to infer it from
|
|
5733
|
+
// `process.exitCode`, which can already be non-zero for an unrelated reason.
|
|
5734
|
+
|
|
5523
5735
|
// Runs the bundled build-tool resolution script for a JVM project and writes
|
|
5524
5736
|
// `.socket.facts.json`. `withFiles` (reachability only) additionally folds
|
|
5525
5737
|
// resolved artifact paths into `sidecarAcc`. A blocking resolution failure sets
|
|
@@ -5535,17 +5747,33 @@ async function runManifestFacts({
|
|
|
5535
5747
|
excludePaths,
|
|
5536
5748
|
ignoreUnresolved,
|
|
5537
5749
|
includeConfigs,
|
|
5750
|
+
javaHome,
|
|
5538
5751
|
sidecarAcc,
|
|
5539
5752
|
tmpDir,
|
|
5540
5753
|
verbose,
|
|
5541
5754
|
withFiles
|
|
5542
5755
|
}) {
|
|
5543
5756
|
const factsPath = path.join(cwd, constants.default.DOT_SOCKET_DOT_FACTS_JSON);
|
|
5544
|
-
|
|
5757
|
+
let resolvedJavaHome;
|
|
5758
|
+
if (javaHome) {
|
|
5759
|
+
const expanded = expandEnvVarRefs(javaHome);
|
|
5760
|
+
if (expanded.missing) {
|
|
5761
|
+
process.exitCode = 1;
|
|
5762
|
+
logger.logger.fail(`javaHome (\`${javaHome}\`) ${formatMissingEnvVarRefs(expanded.missing)}.`);
|
|
5763
|
+
return null;
|
|
5764
|
+
}
|
|
5765
|
+
resolvedJavaHome = expanded.value;
|
|
5766
|
+
}
|
|
5767
|
+
logger.logger.info(`Generating Socket facts for the ${ecosystem} project at \`${cwd}\` ...`);
|
|
5545
5768
|
const scriptOpts = {
|
|
5546
5769
|
bin: bin || undefined,
|
|
5547
5770
|
excludeConfigs: excludeConfigs || undefined,
|
|
5548
5771
|
excludePaths: excludePaths?.length ? excludePaths : undefined,
|
|
5772
|
+
// `env` replaces the spawned process's whole environment, not just JAVA_HOME.
|
|
5773
|
+
env: resolvedJavaHome ? {
|
|
5774
|
+
...process.env,
|
|
5775
|
+
JAVA_HOME: resolvedJavaHome
|
|
5776
|
+
} : undefined,
|
|
5549
5777
|
includeConfigs: includeConfigs || undefined,
|
|
5550
5778
|
projectDir: cwd,
|
|
5551
5779
|
// Stream the build tool's output only when asked; otherwise capture it and
|
|
@@ -5580,8 +5808,8 @@ async function runManifestFacts({
|
|
|
5580
5808
|
spinner.failAndStop(`Failed to run ${ecosystem}.`);
|
|
5581
5809
|
}
|
|
5582
5810
|
process.exitCode = 1;
|
|
5583
|
-
logger.logger.fail(`Could not run the ${ecosystem} build tool` + (verbose ? `: ${e}` : ' (run with --verbose for details).'));
|
|
5584
|
-
return;
|
|
5811
|
+
logger.logger.fail(`Could not run the ${ecosystem} build tool` + (verbose ? `: ${utils.getErrorMessageOr(e, String(e))}` : ' (run with --verbose for details).'));
|
|
5812
|
+
return null;
|
|
5585
5813
|
}
|
|
5586
5814
|
const {
|
|
5587
5815
|
artifactPaths,
|
|
@@ -5602,16 +5830,16 @@ async function runManifestFacts({
|
|
|
5602
5830
|
process.exitCode = 1;
|
|
5603
5831
|
logger.logger.fail(rendered.summary);
|
|
5604
5832
|
if (verbose && rendered.details) {
|
|
5605
|
-
logger.logger.
|
|
5833
|
+
logger.logger.info(rendered.details);
|
|
5606
5834
|
}
|
|
5607
|
-
return;
|
|
5835
|
+
return null;
|
|
5608
5836
|
}
|
|
5609
5837
|
}
|
|
5610
5838
|
if (rendered.nonBlockingNotice) {
|
|
5611
5839
|
logger.logger.info(rendered.nonBlockingNotice);
|
|
5612
5840
|
}
|
|
5613
5841
|
if (verbose && rendered.details) {
|
|
5614
|
-
logger.logger.
|
|
5842
|
+
logger.logger.info(rendered.details);
|
|
5615
5843
|
}
|
|
5616
5844
|
|
|
5617
5845
|
// A non-zero build exit with no usable output (no graph, no first-party
|
|
@@ -5635,7 +5863,7 @@ async function runManifestFacts({
|
|
|
5635
5863
|
// successful run couldn't resolve.
|
|
5636
5864
|
process.exitCode = 1;
|
|
5637
5865
|
logger.logger.fail(`The ${ecosystem} build failed (exit code ${code}) before producing any Socket facts.`);
|
|
5638
|
-
return;
|
|
5866
|
+
return null;
|
|
5639
5867
|
}
|
|
5640
5868
|
|
|
5641
5869
|
// Nothing resolved at all — no dependencies and no first-party modules. A
|
|
@@ -5650,6 +5878,10 @@ async function runManifestFacts({
|
|
|
5650
5878
|
accumulateSidecar(sidecarAcc, facts, artifactPaths);
|
|
5651
5879
|
}
|
|
5652
5880
|
logger.logger.success('Generated Socket facts');
|
|
5881
|
+
return {
|
|
5882
|
+
factsPath,
|
|
5883
|
+
projects: facts.projects ?? []
|
|
5884
|
+
};
|
|
5653
5885
|
}
|
|
5654
5886
|
|
|
5655
5887
|
// Generates `.socket.facts.json` for a Gradle project via the bundled init script.
|
|
@@ -5661,6 +5893,7 @@ async function convertGradleToFacts({
|
|
|
5661
5893
|
gradleOpts,
|
|
5662
5894
|
ignoreUnresolved,
|
|
5663
5895
|
includeConfigs,
|
|
5896
|
+
javaHome,
|
|
5664
5897
|
sidecarAcc,
|
|
5665
5898
|
verbose,
|
|
5666
5899
|
withFiles
|
|
@@ -5674,6 +5907,7 @@ async function convertGradleToFacts({
|
|
|
5674
5907
|
excludePaths,
|
|
5675
5908
|
ignoreUnresolved,
|
|
5676
5909
|
includeConfigs,
|
|
5910
|
+
javaHome,
|
|
5677
5911
|
sidecarAcc,
|
|
5678
5912
|
verbose,
|
|
5679
5913
|
withFiles
|
|
@@ -5688,6 +5922,7 @@ async function convertMavenToFacts({
|
|
|
5688
5922
|
excludePaths,
|
|
5689
5923
|
ignoreUnresolved,
|
|
5690
5924
|
includeConfigs,
|
|
5925
|
+
javaHome,
|
|
5691
5926
|
mavenOpts,
|
|
5692
5927
|
sidecarAcc,
|
|
5693
5928
|
verbose,
|
|
@@ -5702,6 +5937,7 @@ async function convertMavenToFacts({
|
|
|
5702
5937
|
excludePaths,
|
|
5703
5938
|
ignoreUnresolved,
|
|
5704
5939
|
includeConfigs,
|
|
5940
|
+
javaHome,
|
|
5705
5941
|
sidecarAcc,
|
|
5706
5942
|
verbose,
|
|
5707
5943
|
withFiles
|
|
@@ -5718,6 +5954,7 @@ async function convertSbtToFacts({
|
|
|
5718
5954
|
excludePaths,
|
|
5719
5955
|
ignoreUnresolved,
|
|
5720
5956
|
includeConfigs,
|
|
5957
|
+
javaHome,
|
|
5721
5958
|
sbtOpts,
|
|
5722
5959
|
sidecarAcc,
|
|
5723
5960
|
tmpDir,
|
|
@@ -5733,6 +5970,7 @@ async function convertSbtToFacts({
|
|
|
5733
5970
|
excludePaths,
|
|
5734
5971
|
ignoreUnresolved,
|
|
5735
5972
|
includeConfigs,
|
|
5973
|
+
javaHome,
|
|
5736
5974
|
sidecarAcc,
|
|
5737
5975
|
tmpDir,
|
|
5738
5976
|
verbose,
|
|
@@ -6279,7 +6517,7 @@ async function generateAutoManifest({
|
|
|
6279
6517
|
// Note: `sbt` is more likely to be resolved against PATH env.
|
|
6280
6518
|
bin: sockJson.defaults?.manifest?.sbt?.bin ?? 'sbt',
|
|
6281
6519
|
cwd,
|
|
6282
|
-
sbtOpts: parseBuildToolOpts(sockJson.defaults?.manifest?.sbt?.sbtOpts),
|
|
6520
|
+
sbtOpts: parseBuildToolOpts(sockJson.defaults?.manifest?.sbt?.sbtOpts ?? undefined),
|
|
6283
6521
|
verbose: Boolean(sockJson.defaults?.manifest?.sbt?.verbose)
|
|
6284
6522
|
};
|
|
6285
6523
|
// Socket facts is the default; opt into pom generation with
|
|
@@ -6293,6 +6531,7 @@ async function generateAutoManifest({
|
|
|
6293
6531
|
excludePaths,
|
|
6294
6532
|
ignoreUnresolved: Boolean(sockJson.defaults?.manifest?.sbt?.ignoreUnresolved),
|
|
6295
6533
|
includeConfigs: sockJson.defaults?.manifest?.sbt?.includeConfigs ?? '',
|
|
6534
|
+
javaHome: sockJson.defaults?.manifest?.sbt?.javaHome ?? undefined,
|
|
6296
6535
|
sidecarAcc,
|
|
6297
6536
|
tmpDir,
|
|
6298
6537
|
withFiles: computeArtifactsSidecar
|
|
@@ -6314,7 +6553,7 @@ async function generateAutoManifest({
|
|
|
6314
6553
|
bin: sockJson.defaults?.manifest?.gradle?.bin ? path.resolve(cwd, sockJson.defaults.manifest.gradle.bin) : resolveBuildToolBin('gradle', cwd),
|
|
6315
6554
|
cwd,
|
|
6316
6555
|
verbose: Boolean(sockJson.defaults?.manifest?.gradle?.verbose),
|
|
6317
|
-
gradleOpts: parseBuildToolOpts(sockJson.defaults?.manifest?.gradle?.gradleOpts)
|
|
6556
|
+
gradleOpts: parseBuildToolOpts(sockJson.defaults?.manifest?.gradle?.gradleOpts ?? undefined)
|
|
6318
6557
|
};
|
|
6319
6558
|
// Socket facts is the default; opt into pom generation with
|
|
6320
6559
|
// `defaults.manifest.gradle.facts: false` in socket.json.
|
|
@@ -6327,6 +6566,7 @@ async function generateAutoManifest({
|
|
|
6327
6566
|
excludePaths,
|
|
6328
6567
|
ignoreUnresolved: Boolean(sockJson.defaults?.manifest?.gradle?.ignoreUnresolved),
|
|
6329
6568
|
includeConfigs: sockJson.defaults?.manifest?.gradle?.includeConfigs ?? '',
|
|
6569
|
+
javaHome: sockJson.defaults?.manifest?.gradle?.javaHome ?? undefined,
|
|
6330
6570
|
sidecarAcc,
|
|
6331
6571
|
withFiles: computeArtifactsSidecar
|
|
6332
6572
|
});
|
|
@@ -6349,7 +6589,8 @@ async function generateAutoManifest({
|
|
|
6349
6589
|
excludePaths,
|
|
6350
6590
|
ignoreUnresolved: Boolean(sockJson.defaults?.manifest?.maven?.ignoreUnresolved),
|
|
6351
6591
|
includeConfigs: sockJson.defaults?.manifest?.maven?.includeConfigs ?? '',
|
|
6352
|
-
|
|
6592
|
+
javaHome: sockJson.defaults?.manifest?.maven?.javaHome ?? undefined,
|
|
6593
|
+
mavenOpts: parseBuildToolOpts(sockJson.defaults?.manifest?.maven?.mavenOpts ?? undefined),
|
|
6353
6594
|
sidecarAcc,
|
|
6354
6595
|
verbose: Boolean(sockJson.defaults?.manifest?.maven?.verbose),
|
|
6355
6596
|
withFiles: computeArtifactsSidecar
|
|
@@ -6780,7 +7021,7 @@ async function handleCi(autoManifest) {
|
|
|
6780
7021
|
});
|
|
6781
7022
|
}
|
|
6782
7023
|
|
|
6783
|
-
const config$
|
|
7024
|
+
const config$n = {
|
|
6784
7025
|
commandName: 'ci',
|
|
6785
7026
|
description: 'Alias for `socket scan create --report` (creates report and exits with error if unhealthy)',
|
|
6786
7027
|
hidden: false,
|
|
@@ -6798,7 +7039,7 @@ const config$m = {
|
|
|
6798
7039
|
$ ${command} [options]
|
|
6799
7040
|
|
|
6800
7041
|
Options
|
|
6801
|
-
${utils.getFlagListOutput(config$
|
|
7042
|
+
${utils.getFlagListOutput(config$n.flags)}
|
|
6802
7043
|
|
|
6803
7044
|
This command is intended to use in CI runs to allow automated systems to
|
|
6804
7045
|
accept or reject a current build. It will use the default org of the
|
|
@@ -6816,16 +7057,16 @@ const config$m = {
|
|
|
6816
7057
|
`
|
|
6817
7058
|
};
|
|
6818
7059
|
const cmdCI = {
|
|
6819
|
-
description: config$
|
|
6820
|
-
hidden: config$
|
|
6821
|
-
run: run$
|
|
7060
|
+
description: config$n.description,
|
|
7061
|
+
hidden: config$n.hidden,
|
|
7062
|
+
run: run$T
|
|
6822
7063
|
};
|
|
6823
|
-
async function run$
|
|
7064
|
+
async function run$T(argv, importMeta, {
|
|
6824
7065
|
parentName
|
|
6825
7066
|
}) {
|
|
6826
7067
|
const cli = utils.meowOrExit({
|
|
6827
7068
|
argv,
|
|
6828
|
-
config: config$
|
|
7069
|
+
config: config$n,
|
|
6829
7070
|
parentName,
|
|
6830
7071
|
importMeta
|
|
6831
7072
|
});
|
|
@@ -7068,9 +7309,9 @@ const hidden$v = false;
|
|
|
7068
7309
|
const cmdConfigAuto = {
|
|
7069
7310
|
description: description$D,
|
|
7070
7311
|
hidden: hidden$v,
|
|
7071
|
-
run: run$
|
|
7312
|
+
run: run$S
|
|
7072
7313
|
};
|
|
7073
|
-
async function run$
|
|
7314
|
+
async function run$S(argv, importMeta, {
|
|
7074
7315
|
parentName
|
|
7075
7316
|
}) {
|
|
7076
7317
|
const config = {
|
|
@@ -7186,7 +7427,7 @@ async function handleConfigGet({
|
|
|
7186
7427
|
await outputConfigGet(key, result, outputKind);
|
|
7187
7428
|
}
|
|
7188
7429
|
|
|
7189
|
-
const config$
|
|
7430
|
+
const config$m = {
|
|
7190
7431
|
commandName: 'get',
|
|
7191
7432
|
description: 'Get the value of a local CLI config item',
|
|
7192
7433
|
hidden: false,
|
|
@@ -7216,16 +7457,16 @@ ${utils.getSupportedConfigEntries().map(({
|
|
|
7216
7457
|
`
|
|
7217
7458
|
};
|
|
7218
7459
|
const cmdConfigGet = {
|
|
7219
|
-
description: config$
|
|
7220
|
-
hidden: config$
|
|
7221
|
-
run: run$
|
|
7460
|
+
description: config$m.description,
|
|
7461
|
+
hidden: config$m.hidden,
|
|
7462
|
+
run: run$R
|
|
7222
7463
|
};
|
|
7223
|
-
async function run$
|
|
7464
|
+
async function run$R(argv, importMeta, {
|
|
7224
7465
|
parentName
|
|
7225
7466
|
}) {
|
|
7226
7467
|
const cli = utils.meowOrExit({
|
|
7227
7468
|
argv,
|
|
7228
|
-
config: config$
|
|
7469
|
+
config: config$m,
|
|
7229
7470
|
importMeta,
|
|
7230
7471
|
parentName
|
|
7231
7472
|
});
|
|
@@ -7327,7 +7568,7 @@ async function outputConfigList({
|
|
|
7327
7568
|
}
|
|
7328
7569
|
}
|
|
7329
7570
|
|
|
7330
|
-
const config$
|
|
7571
|
+
const config$l = {
|
|
7331
7572
|
commandName: 'list',
|
|
7332
7573
|
description: 'Show all local CLI config items and their values',
|
|
7333
7574
|
hidden: false,
|
|
@@ -7352,16 +7593,16 @@ const config$k = {
|
|
|
7352
7593
|
`
|
|
7353
7594
|
};
|
|
7354
7595
|
const cmdConfigList = {
|
|
7355
|
-
description: config$
|
|
7356
|
-
hidden: config$
|
|
7357
|
-
run: run$
|
|
7596
|
+
description: config$l.description,
|
|
7597
|
+
hidden: config$l.hidden,
|
|
7598
|
+
run: run$Q
|
|
7358
7599
|
};
|
|
7359
|
-
async function run$
|
|
7600
|
+
async function run$Q(argv, importMeta, {
|
|
7360
7601
|
parentName
|
|
7361
7602
|
}) {
|
|
7362
7603
|
const cli = utils.meowOrExit({
|
|
7363
7604
|
argv,
|
|
7364
|
-
config: config$
|
|
7605
|
+
config: config$l,
|
|
7365
7606
|
importMeta,
|
|
7366
7607
|
parentName
|
|
7367
7608
|
});
|
|
@@ -7451,9 +7692,9 @@ const hidden$u = false;
|
|
|
7451
7692
|
const cmdConfigSet = {
|
|
7452
7693
|
description: description$C,
|
|
7453
7694
|
hidden: hidden$u,
|
|
7454
|
-
run: run$
|
|
7695
|
+
run: run$P
|
|
7455
7696
|
};
|
|
7456
|
-
async function run$
|
|
7697
|
+
async function run$P(argv, importMeta, {
|
|
7457
7698
|
parentName
|
|
7458
7699
|
}) {
|
|
7459
7700
|
const config = {
|
|
@@ -7578,9 +7819,9 @@ const hidden$t = false;
|
|
|
7578
7819
|
const cmdConfigUnset = {
|
|
7579
7820
|
description: description$B,
|
|
7580
7821
|
hidden: hidden$t,
|
|
7581
|
-
run: run$
|
|
7822
|
+
run: run$O
|
|
7582
7823
|
};
|
|
7583
|
-
async function run$
|
|
7824
|
+
async function run$O(argv, importMeta, {
|
|
7584
7825
|
parentName
|
|
7585
7826
|
}) {
|
|
7586
7827
|
const config = {
|
|
@@ -8880,7 +9121,7 @@ const hidden$s = false;
|
|
|
8880
9121
|
const cmdFix = {
|
|
8881
9122
|
description: description$z,
|
|
8882
9123
|
hidden: hidden$s,
|
|
8883
|
-
run: run$
|
|
9124
|
+
run: run$N
|
|
8884
9125
|
};
|
|
8885
9126
|
const generalFlags$2 = {
|
|
8886
9127
|
autopilot: {
|
|
@@ -9055,7 +9296,7 @@ const hiddenFlags = {
|
|
|
9055
9296
|
hidden: true
|
|
9056
9297
|
}
|
|
9057
9298
|
};
|
|
9058
|
-
async function run$
|
|
9299
|
+
async function run$N(argv, importMeta, {
|
|
9059
9300
|
parentName
|
|
9060
9301
|
}) {
|
|
9061
9302
|
const config = {
|
|
@@ -9384,7 +9625,7 @@ async function handleInstallCompletion(targetName) {
|
|
|
9384
9625
|
await outputInstallCompletion(result);
|
|
9385
9626
|
}
|
|
9386
9627
|
|
|
9387
|
-
const config$
|
|
9628
|
+
const config$k = {
|
|
9388
9629
|
commandName: 'completion',
|
|
9389
9630
|
description: 'Install bash completion for Socket CLI',
|
|
9390
9631
|
hidden: false,
|
|
@@ -9421,16 +9662,16 @@ const config$j = {
|
|
|
9421
9662
|
`
|
|
9422
9663
|
};
|
|
9423
9664
|
const cmdInstallCompletion = {
|
|
9424
|
-
description: config$
|
|
9425
|
-
hidden: config$
|
|
9426
|
-
run: run$
|
|
9665
|
+
description: config$k.description,
|
|
9666
|
+
hidden: config$k.hidden,
|
|
9667
|
+
run: run$M
|
|
9427
9668
|
};
|
|
9428
|
-
async function run$
|
|
9669
|
+
async function run$M(argv, importMeta, {
|
|
9429
9670
|
parentName
|
|
9430
9671
|
}) {
|
|
9431
9672
|
const cli = utils.meowOrExit({
|
|
9432
9673
|
argv,
|
|
9433
|
-
config: config$
|
|
9674
|
+
config: config$k,
|
|
9434
9675
|
parentName,
|
|
9435
9676
|
importMeta
|
|
9436
9677
|
});
|
|
@@ -9487,7 +9728,7 @@ async function handleCmdJson(cwd) {
|
|
|
9487
9728
|
await outputCmdJson(cwd);
|
|
9488
9729
|
}
|
|
9489
9730
|
|
|
9490
|
-
const config$
|
|
9731
|
+
const config$j = {
|
|
9491
9732
|
commandName: 'json',
|
|
9492
9733
|
description: `Display the \`${constants.SOCKET_JSON}\` that would be applied for target folder`,
|
|
9493
9734
|
hidden: true,
|
|
@@ -9506,16 +9747,16 @@ const config$i = {
|
|
|
9506
9747
|
`
|
|
9507
9748
|
};
|
|
9508
9749
|
const cmdJson = {
|
|
9509
|
-
description: config$
|
|
9510
|
-
hidden: config$
|
|
9511
|
-
run: run$
|
|
9750
|
+
description: config$j.description,
|
|
9751
|
+
hidden: config$j.hidden,
|
|
9752
|
+
run: run$L
|
|
9512
9753
|
};
|
|
9513
|
-
async function run$
|
|
9754
|
+
async function run$L(argv, importMeta, {
|
|
9514
9755
|
parentName
|
|
9515
9756
|
}) {
|
|
9516
9757
|
const cli = utils.meowOrExit({
|
|
9517
9758
|
argv,
|
|
9518
|
-
config: config$
|
|
9759
|
+
config: config$j,
|
|
9519
9760
|
parentName,
|
|
9520
9761
|
importMeta
|
|
9521
9762
|
});
|
|
@@ -9670,9 +9911,9 @@ const hidden$r = false;
|
|
|
9670
9911
|
const cmdLogin = {
|
|
9671
9912
|
description: description$x,
|
|
9672
9913
|
hidden: hidden$r,
|
|
9673
|
-
run: run$
|
|
9914
|
+
run: run$K
|
|
9674
9915
|
};
|
|
9675
|
-
async function run$
|
|
9916
|
+
async function run$K(argv, importMeta, {
|
|
9676
9917
|
parentName
|
|
9677
9918
|
}) {
|
|
9678
9919
|
const config = {
|
|
@@ -9750,7 +9991,7 @@ function attemptLogout() {
|
|
|
9750
9991
|
}
|
|
9751
9992
|
}
|
|
9752
9993
|
|
|
9753
|
-
const config$
|
|
9994
|
+
const config$i = {
|
|
9754
9995
|
commandName: 'logout',
|
|
9755
9996
|
description: 'Socket API logout',
|
|
9756
9997
|
hidden: false,
|
|
@@ -9768,16 +10009,16 @@ const config$h = {
|
|
|
9768
10009
|
`
|
|
9769
10010
|
};
|
|
9770
10011
|
const cmdLogout = {
|
|
9771
|
-
description: config$
|
|
9772
|
-
hidden: config$
|
|
9773
|
-
run: run$
|
|
10012
|
+
description: config$i.description,
|
|
10013
|
+
hidden: config$i.hidden,
|
|
10014
|
+
run: run$J
|
|
9774
10015
|
};
|
|
9775
|
-
async function run$
|
|
10016
|
+
async function run$J(argv, importMeta, {
|
|
9776
10017
|
parentName
|
|
9777
10018
|
}) {
|
|
9778
10019
|
const cli = utils.meowOrExit({
|
|
9779
10020
|
argv,
|
|
9780
|
-
config: config$
|
|
10021
|
+
config: config$i,
|
|
9781
10022
|
importMeta,
|
|
9782
10023
|
parentName
|
|
9783
10024
|
});
|
|
@@ -10090,7 +10331,7 @@ const yargsConfig = {
|
|
|
10090
10331
|
'usages-slices-file' // hidden
|
|
10091
10332
|
]
|
|
10092
10333
|
};
|
|
10093
|
-
const config$
|
|
10334
|
+
const config$h = {
|
|
10094
10335
|
commandName: 'cdxgen',
|
|
10095
10336
|
description: 'Run cdxgen for SBOM generation',
|
|
10096
10337
|
hidden: false,
|
|
@@ -10100,11 +10341,11 @@ const config$g = {
|
|
|
10100
10341
|
help: () => ''
|
|
10101
10342
|
};
|
|
10102
10343
|
const cmdManifestCdxgen = {
|
|
10103
|
-
description: config$
|
|
10104
|
-
hidden: config$
|
|
10105
|
-
run: run$
|
|
10344
|
+
description: config$h.description,
|
|
10345
|
+
hidden: config$h.hidden,
|
|
10346
|
+
run: run$I
|
|
10106
10347
|
};
|
|
10107
|
-
async function run$
|
|
10348
|
+
async function run$I(argv, importMeta, context) {
|
|
10108
10349
|
const {
|
|
10109
10350
|
parentName
|
|
10110
10351
|
} = {
|
|
@@ -10114,7 +10355,7 @@ async function run$H(argv, importMeta, context) {
|
|
|
10114
10355
|
const cli = utils.meowOrExit({
|
|
10115
10356
|
// Don't let meow take over --help.
|
|
10116
10357
|
argv: argv.filter(a => !utils.isHelpFlag(a)),
|
|
10117
|
-
config: config$
|
|
10358
|
+
config: config$h,
|
|
10118
10359
|
importMeta,
|
|
10119
10360
|
parentName
|
|
10120
10361
|
});
|
|
@@ -11153,7 +11394,7 @@ async function buildSpokeTagLookup(reached, queryOpts, verbose) {
|
|
|
11153
11394
|
return lookup;
|
|
11154
11395
|
}
|
|
11155
11396
|
|
|
11156
|
-
const config$
|
|
11397
|
+
const config$g = {
|
|
11157
11398
|
commandName: 'bazel',
|
|
11158
11399
|
description: '[beta] Bazel SBOM support — generate manifest files for a Bazel project (Maven, PyPI)',
|
|
11159
11400
|
hidden: false,
|
|
@@ -11237,9 +11478,9 @@ const config$f = {
|
|
|
11237
11478
|
// shorter default lives in extract_bazel_to_maven.mts.
|
|
11238
11479
|
const EXPLICIT_PER_REPO_TIMEOUT_MS = 120_000;
|
|
11239
11480
|
const cmdManifestBazel = {
|
|
11240
|
-
description: config$
|
|
11241
|
-
hidden: config$
|
|
11242
|
-
run: run$
|
|
11481
|
+
description: config$g.description,
|
|
11482
|
+
hidden: config$g.hidden,
|
|
11483
|
+
run: run$H
|
|
11243
11484
|
};
|
|
11244
11485
|
// Pure outcome-matrix evaluator. Exported so dispatcher behavior can be
|
|
11245
11486
|
// unit-tested without spawning the CLI binary. Throws InputError on
|
|
@@ -11323,12 +11564,12 @@ function pypiOutcome(result) {
|
|
|
11323
11564
|
status: 'hardFailure'
|
|
11324
11565
|
};
|
|
11325
11566
|
}
|
|
11326
|
-
async function run$
|
|
11567
|
+
async function run$H(argv, importMeta, {
|
|
11327
11568
|
parentName
|
|
11328
11569
|
}) {
|
|
11329
11570
|
const cli = utils.meowOrExit({
|
|
11330
11571
|
argv,
|
|
11331
|
-
config: config$
|
|
11572
|
+
config: config$g,
|
|
11332
11573
|
importMeta,
|
|
11333
11574
|
parentName
|
|
11334
11575
|
});
|
|
@@ -11416,7 +11657,7 @@ async function run$G(argv, importMeta, {
|
|
|
11416
11657
|
}
|
|
11417
11658
|
}
|
|
11418
11659
|
if (verbose) {
|
|
11419
|
-
logger.logger.group('- ', parentName, config$
|
|
11660
|
+
logger.logger.group('- ', parentName, config$g.commandName, ':');
|
|
11420
11661
|
logger.logger.group('- flags:', cli.flags);
|
|
11421
11662
|
logger.logger.groupEnd();
|
|
11422
11663
|
logger.logger.log('- input:', cli.input);
|
|
@@ -11493,136 +11734,29 @@ async function run$G(argv, importMeta, {
|
|
|
11493
11734
|
evaluateEcosystemOutcomes(outcomes, wasExplicitEcosystemSelection);
|
|
11494
11735
|
}
|
|
11495
11736
|
|
|
11496
|
-
|
|
11497
|
-
|
|
11498
|
-
|
|
11499
|
-
|
|
11500
|
-
|
|
11501
|
-
description: 'Internal: enables dynamic SBOM inference for full application reachability analysis. Passes --maven-use-only-root-socket-facts to Coana and implies --auto-manifest.'
|
|
11502
|
-
},
|
|
11503
|
-
reachVersion: {
|
|
11504
|
-
type: 'string',
|
|
11505
|
-
description: `Override the version of @coana-tech/cli used for reachability analysis. Default: ${constants.default.ENV.INLINED_SOCKET_CLI_COANA_TECH_CLI_VERSION}.`
|
|
11506
|
-
},
|
|
11507
|
-
reachAnalysisMemoryLimit: {
|
|
11508
|
-
type: 'string',
|
|
11509
|
-
default: '8192',
|
|
11510
|
-
description: 'The maximum memory for the reachability analysis as a whole number optionally followed by MB or GB (e.g. 512MB, 8GB). The default is 8GB.'
|
|
11511
|
-
},
|
|
11512
|
-
reachAnalysisTimeout: {
|
|
11737
|
+
// Manifest-scoped variant of `../scan/reachability-flags.mts`'s
|
|
11738
|
+
// `excludePathsFlag`: description avoids "scan"/reachability wording since
|
|
11739
|
+
// these commands only ever generate a manifest/facts file.
|
|
11740
|
+
const excludePathsFlag$1 = {
|
|
11741
|
+
excludePaths: {
|
|
11513
11742
|
type: 'string',
|
|
11514
|
-
|
|
11515
|
-
description: '
|
|
11516
|
-
}
|
|
11517
|
-
|
|
11518
|
-
|
|
11519
|
-
|
|
11520
|
-
|
|
11521
|
-
|
|
11522
|
-
|
|
11523
|
-
|
|
11524
|
-
|
|
11525
|
-
|
|
11526
|
-
|
|
11527
|
-
|
|
11528
|
-
|
|
11529
|
-
|
|
11530
|
-
|
|
11531
|
-
},
|
|
11532
|
-
reachContinueOnMissingLockFiles: {
|
|
11533
|
-
type: 'boolean',
|
|
11534
|
-
default: false,
|
|
11535
|
-
description: 'Continue reachability analysis when a Gradle or SBT project is missing its lock file (or version catalog / pre-generated SBOM). By default, the CLI halts.'
|
|
11536
|
-
},
|
|
11537
|
-
reachContinueOnNoSourceFiles: {
|
|
11538
|
-
type: 'boolean',
|
|
11539
|
-
default: false,
|
|
11540
|
-
description: 'Continue reachability analysis when a workspace contains no source files for its ecosystem. By default, the CLI halts.'
|
|
11541
|
-
},
|
|
11542
|
-
reachDisableExternalToolChecks: {
|
|
11543
|
-
type: 'boolean',
|
|
11544
|
-
default: false,
|
|
11545
|
-
description: 'Disable external tool checks during reachability analysis.'
|
|
11546
|
-
},
|
|
11547
|
-
reachDebug: {
|
|
11548
|
-
type: 'boolean',
|
|
11549
|
-
default: false,
|
|
11550
|
-
description: 'Enable debug mode for reachability analysis. Provides verbose logging from the reachability CLI.'
|
|
11551
|
-
},
|
|
11552
|
-
reachDetailedAnalysisLogFile: {
|
|
11553
|
-
type: 'boolean',
|
|
11554
|
-
default: false,
|
|
11555
|
-
description: 'A log file with detailed analysis logs is written to root of each analyzed workspace.'
|
|
11556
|
-
},
|
|
11557
|
-
reachDisableAnalytics: {
|
|
11558
|
-
type: 'boolean',
|
|
11559
|
-
default: false,
|
|
11560
|
-
description: 'Disable reachability analytics sharing with Socket. Also disables caching-based optimizations.'
|
|
11561
|
-
},
|
|
11562
|
-
reachDisableAnalysisSplitting: {
|
|
11563
|
-
type: 'boolean',
|
|
11564
|
-
default: false,
|
|
11565
|
-
hidden: true,
|
|
11566
|
-
description: 'Deprecated: Analysis splitting is now disabled by default. This flag is a no-op.'
|
|
11567
|
-
},
|
|
11568
|
-
reachEnableAnalysisSplitting: {
|
|
11569
|
-
type: 'boolean',
|
|
11570
|
-
default: false,
|
|
11571
|
-
description: 'Allow the reachability analysis to partition CVEs into buckets that are processed in separate analysis runs. May improve accuracy, but not recommended by default.'
|
|
11572
|
-
},
|
|
11573
|
-
reachEcosystems: {
|
|
11574
|
-
type: 'string',
|
|
11575
|
-
isMultiple: true,
|
|
11576
|
-
description: `List of ecosystems to conduct reachability analysis on, as either a comma separated value or as multiple flags. Supported: ${utils.getReachabilityEcosystemChoices().join(', ')}. Defaults to all supported ecosystems.`
|
|
11577
|
-
},
|
|
11578
|
-
reachExcludePaths: {
|
|
11579
|
-
type: 'string',
|
|
11580
|
-
isMultiple: true,
|
|
11581
|
-
hidden: true,
|
|
11582
|
-
description: 'Deprecated: use --exclude-paths instead. List of paths to exclude from reachability analysis, as either a comma separated value or as multiple flags.'
|
|
11583
|
-
},
|
|
11584
|
-
reachLazyMode: {
|
|
11585
|
-
type: 'boolean',
|
|
11586
|
-
default: false,
|
|
11587
|
-
description: 'Enable lazy mode for reachability analysis.',
|
|
11588
|
-
hidden: true
|
|
11589
|
-
},
|
|
11590
|
-
reachRetainFactsFile: {
|
|
11591
|
-
type: 'boolean',
|
|
11592
|
-
default: false,
|
|
11593
|
-
description: 'Keep the `.socket.facts.json` reachability report that the analysis writes to the scan directory instead of deleting it after a successful scan. IMPORTANT: you must delete this file before running a fresh full application reachability scan. A stale `.socket.facts.json` left in place is picked up as a pre-generated input and silently overrides fresh analysis, so the new scan results will not be reliable.'
|
|
11594
|
-
},
|
|
11595
|
-
reachSkipCache: {
|
|
11596
|
-
type: 'boolean',
|
|
11597
|
-
default: false,
|
|
11598
|
-
description: 'Skip caching-based optimizations. By default, the reachability analysis will use cached configurations from previous runs to speed up the analysis.'
|
|
11599
|
-
},
|
|
11600
|
-
reachUseOnlyPregeneratedSboms: {
|
|
11601
|
-
type: 'boolean',
|
|
11602
|
-
default: false,
|
|
11603
|
-
description: 'When using this option, the scan is created based only on pre-generated CDX and SPDX files in your project.'
|
|
11604
|
-
}
|
|
11605
|
-
};
|
|
11606
|
-
const excludePathsFlag = {
|
|
11607
|
-
excludePaths: {
|
|
11608
|
-
type: 'string',
|
|
11609
|
-
isMultiple: true,
|
|
11610
|
-
description: 'List of glob patterns to exclude from the scan, including SCA/SBOM manifest discovery and (when --reach is enabled) full application reachability analysis. Patterns are anchored micromatch globs matched relative to the Socket scan root, which is the command working directory (`--cwd` if set), not the reachability target: `tests` matches only `<cwd>/tests`; use `**/tests` to match at any depth. Negation patterns (`!path`) are not supported. Accepts a comma-separated value or multiple flags.'
|
|
11611
|
-
}
|
|
11612
|
-
};
|
|
11613
|
-
|
|
11614
|
-
const config$e = {
|
|
11615
|
-
commandName: 'auto',
|
|
11616
|
-
description: 'Auto-detect build and attempt to generate manifest file',
|
|
11617
|
-
hidden: false,
|
|
11618
|
-
flags: {
|
|
11619
|
-
...flags.commonFlags,
|
|
11620
|
-
...excludePathsFlag,
|
|
11621
|
-
verbose: {
|
|
11622
|
-
type: 'boolean',
|
|
11623
|
-
default: false,
|
|
11624
|
-
description: 'Enable debug output (only for auto itself; sub-steps need to have it pre-configured), may help when running into errors'
|
|
11625
|
-
}
|
|
11743
|
+
isMultiple: true,
|
|
11744
|
+
description: 'List of glob patterns to exclude from manifest/facts generation. Patterns are anchored micromatch globs matched relative to CWD (`--cwd` if set): `tests` matches only `<cwd>/tests`; use `**/tests` to match at any depth. Negation patterns (`!path`) are not supported. Accepts a comma-separated value or multiple flags.'
|
|
11745
|
+
}
|
|
11746
|
+
};
|
|
11747
|
+
|
|
11748
|
+
const config$f = {
|
|
11749
|
+
commandName: 'auto',
|
|
11750
|
+
description: 'Auto-detect build and attempt to generate manifest file',
|
|
11751
|
+
hidden: false,
|
|
11752
|
+
flags: {
|
|
11753
|
+
...flags.commonFlags,
|
|
11754
|
+
...excludePathsFlag$1,
|
|
11755
|
+
verbose: {
|
|
11756
|
+
type: 'boolean',
|
|
11757
|
+
default: false,
|
|
11758
|
+
description: 'Enable debug output (only for auto itself; sub-steps need to have it pre-configured), may help when running into errors'
|
|
11759
|
+
}
|
|
11626
11760
|
},
|
|
11627
11761
|
help: (command, config) => `
|
|
11628
11762
|
Usage
|
|
@@ -11645,16 +11779,16 @@ const config$e = {
|
|
|
11645
11779
|
`
|
|
11646
11780
|
};
|
|
11647
11781
|
const cmdManifestAuto = {
|
|
11648
|
-
description: config$
|
|
11649
|
-
hidden: config$
|
|
11650
|
-
run: run$
|
|
11782
|
+
description: config$f.description,
|
|
11783
|
+
hidden: config$f.hidden,
|
|
11784
|
+
run: run$G
|
|
11651
11785
|
};
|
|
11652
|
-
async function run$
|
|
11786
|
+
async function run$G(argv, importMeta, {
|
|
11653
11787
|
parentName
|
|
11654
11788
|
}) {
|
|
11655
11789
|
const cli = utils.meowOrExit({
|
|
11656
11790
|
argv,
|
|
11657
|
-
config: config$
|
|
11791
|
+
config: config$f,
|
|
11658
11792
|
importMeta,
|
|
11659
11793
|
parentName
|
|
11660
11794
|
});
|
|
@@ -11672,7 +11806,7 @@ async function run$F(argv, importMeta, {
|
|
|
11672
11806
|
cwd = path.resolve(process.cwd(), cwd);
|
|
11673
11807
|
const outputKind = utils.getOutputKind(json, markdown);
|
|
11674
11808
|
if (verbose) {
|
|
11675
|
-
logger.logger.group('- ', parentName, config$
|
|
11809
|
+
logger.logger.group('- ', parentName, config$f.commandName, ':');
|
|
11676
11810
|
logger.logger.group('- flags:', cli.flags);
|
|
11677
11811
|
logger.logger.groupEnd();
|
|
11678
11812
|
logger.logger.log('- input:', cli.input);
|
|
@@ -11710,7 +11844,7 @@ async function run$F(argv, importMeta, {
|
|
|
11710
11844
|
logger.logger.success(`Finished. Should have attempted to generate manifest files for ${detected.count} targets.`);
|
|
11711
11845
|
}
|
|
11712
11846
|
|
|
11713
|
-
const config$
|
|
11847
|
+
const config$e = {
|
|
11714
11848
|
commandName: 'conda',
|
|
11715
11849
|
description: `[beta] Convert a Conda ${constants.ENVIRONMENT_YML} file to a python ${constants.REQUIREMENTS_TXT}`,
|
|
11716
11850
|
hidden: false,
|
|
@@ -11763,16 +11897,16 @@ const config$d = {
|
|
|
11763
11897
|
`
|
|
11764
11898
|
};
|
|
11765
11899
|
const cmdManifestConda = {
|
|
11766
|
-
description: config$
|
|
11767
|
-
hidden: config$
|
|
11768
|
-
run: run$
|
|
11900
|
+
description: config$e.description,
|
|
11901
|
+
hidden: config$e.hidden,
|
|
11902
|
+
run: run$F
|
|
11769
11903
|
};
|
|
11770
|
-
async function run$
|
|
11904
|
+
async function run$F(argv, importMeta, {
|
|
11771
11905
|
parentName
|
|
11772
11906
|
}) {
|
|
11773
11907
|
const cli = utils.meowOrExit({
|
|
11774
11908
|
argv,
|
|
11775
|
-
config: config$
|
|
11909
|
+
config: config$e,
|
|
11776
11910
|
importMeta,
|
|
11777
11911
|
parentName
|
|
11778
11912
|
});
|
|
@@ -11830,7 +11964,7 @@ async function run$E(argv, importMeta, {
|
|
|
11830
11964
|
verbose = false;
|
|
11831
11965
|
}
|
|
11832
11966
|
if (verbose) {
|
|
11833
|
-
logger.logger.group('- ', parentName, config$
|
|
11967
|
+
logger.logger.group('- ', parentName, config$e.commandName, ':');
|
|
11834
11968
|
logger.logger.group('- flags:', cli.flags);
|
|
11835
11969
|
logger.logger.groupEnd();
|
|
11836
11970
|
logger.logger.log('- target:', cwd);
|
|
@@ -11866,260 +12000,481 @@ async function run$E(argv, importMeta, {
|
|
|
11866
12000
|
});
|
|
11867
12001
|
}
|
|
11868
12002
|
|
|
11869
|
-
const
|
|
11870
|
-
commandName: 'gradle',
|
|
11871
|
-
description: '[beta] Generate a Socket facts file (or `pom.xml` with --pom) for a Gradle/Java/Kotlin/etc project',
|
|
11872
|
-
hidden: false,
|
|
11873
|
-
flags: {
|
|
11874
|
-
...flags.commonFlags,
|
|
11875
|
-
bin: {
|
|
11876
|
-
type: 'string',
|
|
11877
|
-
description: 'Location of the gradle binary to use, default: ./gradlew if present, else gradle on PATH'
|
|
11878
|
-
},
|
|
11879
|
-
facts: {
|
|
11880
|
-
type: 'boolean',
|
|
11881
|
-
description: 'Emit a Socket facts JSON file (`.socket.facts.json`) describing the resolved dependency graph. This is the default; pass `--pom` to generate `pom.xml` files instead'
|
|
11882
|
-
},
|
|
11883
|
-
pom: {
|
|
11884
|
-
type: 'boolean',
|
|
11885
|
-
description: 'Generate `pom.xml` manifest file(s) instead of the default Socket facts file (`.socket.facts.json`)'
|
|
11886
|
-
},
|
|
11887
|
-
includeConfigs: {
|
|
11888
|
-
type: 'string',
|
|
11889
|
-
description: 'When generating facts: comma-separated glob patterns matched against Gradle configuration names (case-sensitive; `*`, `?`, and `[...]` wildcards). Only configurations matching at least one pattern are resolved. e.g. `*CompileClasspath,*RuntimeClasspath`. Default: every resolvable configuration'
|
|
11890
|
-
},
|
|
11891
|
-
excludeConfigs: {
|
|
11892
|
-
type: 'string',
|
|
11893
|
-
description: 'When generating facts: comma-separated glob patterns; Gradle configurations matching any pattern are skipped (applied after --include-configs)'
|
|
11894
|
-
},
|
|
11895
|
-
...excludePathsFlag,
|
|
11896
|
-
ignoreUnresolved: {
|
|
11897
|
-
type: 'boolean',
|
|
11898
|
-
description: 'When generating facts: warn on unresolved dependencies instead of failing the run (unresolved deps are not emitted to the facts file)'
|
|
11899
|
-
},
|
|
11900
|
-
gradleOpts: {
|
|
11901
|
-
type: 'string',
|
|
11902
|
-
description: 'Additional options to pass on to ./gradlew, see `./gradlew --help`'
|
|
11903
|
-
},
|
|
11904
|
-
verbose: {
|
|
11905
|
-
type: 'boolean',
|
|
11906
|
-
description: 'Print debug messages'
|
|
11907
|
-
}
|
|
11908
|
-
},
|
|
11909
|
-
help: (command, config) => `
|
|
11910
|
-
Usage
|
|
11911
|
-
$ ${command} [options] [CWD=.]
|
|
11912
|
-
|
|
11913
|
-
Options
|
|
11914
|
-
${utils.getFlagListOutput(config.flags)}
|
|
11915
|
-
|
|
11916
|
-
By default, emits a single \`.socket.facts.json\` describing the resolved
|
|
11917
|
-
dependency graph of the whole build, using gradle (preferably your local
|
|
11918
|
-
\`gradlew\`). An unresolved dependency is a fatal error. You can pass
|
|
11919
|
-
--include-configs / --exclude-configs (comma-separated glob patterns) to
|
|
11920
|
-
control which configurations are resolved (e.g.
|
|
11921
|
-
--include-configs=\`*CompileClasspath,*RuntimeClasspath\`), and
|
|
11922
|
-
--ignore-unresolved to warn on unresolved dependencies instead of failing.
|
|
11923
|
-
|
|
11924
|
-
Pass --pom to instead generate \`pom.xml\` manifest files via gradle (one per
|
|
11925
|
-
task). The \`pom.xml\` is a manifest file similar to \`package.json\` for npm
|
|
11926
|
-
(or ${constants.REQUIREMENTS_TXT} for PyPi), but specifically for Maven, which is
|
|
11927
|
-
Java's dependency repository. Caveats of the \`pom.xml\` conversion:
|
|
11928
|
-
|
|
11929
|
-
- each task generates its own xml file (one per task by default)
|
|
11930
|
-
|
|
11931
|
-
- certain features may not translate well into the xml; reach out if
|
|
11932
|
-
something you need is missing
|
|
11933
|
-
|
|
11934
|
-
- it works with your \`gradlew\` from your repo and local settings and config
|
|
11935
|
-
|
|
11936
|
-
Support is beta. Please report issues or give us feedback on what's missing.
|
|
11937
|
-
|
|
11938
|
-
Examples
|
|
12003
|
+
const BUILD_TOOLS = ['gradle', 'maven', 'sbt'];
|
|
11939
12004
|
|
|
11940
|
-
|
|
11941
|
-
|
|
11942
|
-
|
|
11943
|
-
|
|
12005
|
+
// One glob-suffix set per ecosystem's build-descriptor marker(s); `settings.gradle(.kts)`
|
|
12006
|
+
// covers Kotlin-DSL roots that have no root `build.gradle`.
|
|
12007
|
+
const MARKERS_BY_TOOL = {
|
|
12008
|
+
__proto__: null,
|
|
12009
|
+
gradle: ['build.gradle', 'build.gradle.kts', 'settings.gradle', 'settings.gradle.kts'],
|
|
12010
|
+
maven: ['pom.xml'],
|
|
12011
|
+
sbt: ['build.sbt']
|
|
11944
12012
|
};
|
|
11945
|
-
const
|
|
11946
|
-
|
|
11947
|
-
hidden: config$c.hidden,
|
|
11948
|
-
run: run$D
|
|
12013
|
+
const TOOL_BY_MARKER = {
|
|
12014
|
+
__proto__: null
|
|
11949
12015
|
};
|
|
11950
|
-
|
|
11951
|
-
|
|
11952
|
-
|
|
11953
|
-
|
|
11954
|
-
|
|
11955
|
-
config: config$c,
|
|
11956
|
-
importMeta,
|
|
11957
|
-
parentName
|
|
11958
|
-
});
|
|
11959
|
-
const {
|
|
11960
|
-
json = false,
|
|
11961
|
-
markdown = false
|
|
11962
|
-
} = cli.flags;
|
|
11963
|
-
const dryRun = !!cli.flags['dryRun'];
|
|
11964
|
-
|
|
11965
|
-
// TODO: Implement json/md further.
|
|
11966
|
-
const outputKind = utils.getOutputKind(json, markdown);
|
|
11967
|
-
let [cwd = '.'] = cli.input;
|
|
11968
|
-
// Note: path.resolve vs .join:
|
|
11969
|
-
// If given path is absolute then cwd should not affect it.
|
|
11970
|
-
cwd = path.resolve(process.cwd(), cwd);
|
|
11971
|
-
const sockJson = utils.readOrDefaultSocketJson(cwd);
|
|
11972
|
-
debug.debugFn('inspect', `override: ${constants.SOCKET_JSON} gradle`, sockJson?.defaults?.manifest?.gradle);
|
|
11973
|
-
let {
|
|
11974
|
-
bin,
|
|
11975
|
-
excludeConfigs,
|
|
11976
|
-
facts,
|
|
11977
|
-
gradleOpts,
|
|
11978
|
-
ignoreUnresolved,
|
|
11979
|
-
includeConfigs,
|
|
11980
|
-
verbose
|
|
11981
|
-
} = cli.flags;
|
|
12016
|
+
for (const tool of BUILD_TOOLS) {
|
|
12017
|
+
for (const marker of MARKERS_BY_TOOL[tool]) {
|
|
12018
|
+
TOOL_BY_MARKER[marker] = tool;
|
|
12019
|
+
}
|
|
12020
|
+
}
|
|
11982
12021
|
|
|
11983
|
-
|
|
11984
|
-
|
|
11985
|
-
|
|
11986
|
-
|
|
11987
|
-
|
|
11988
|
-
|
|
11989
|
-
|
|
11990
|
-
|
|
12022
|
+
// A disabled ecosystem is skipped entirely by the glob scan below, so a
|
|
12023
|
+
// nested socket.json re-enabling it would never even be searched for. Callers
|
|
12024
|
+
// that need the cascade (not just the root file) to decide skip vs. include
|
|
12025
|
+
// must strip `disabled` here first, and let their own per-directory cascade
|
|
12026
|
+
// check apply it instead.
|
|
12027
|
+
function withoutDisabledFlags(sockJson) {
|
|
12028
|
+
const manifest = sockJson.defaults?.manifest;
|
|
12029
|
+
if (!manifest) {
|
|
12030
|
+
return sockJson;
|
|
12031
|
+
}
|
|
12032
|
+
const stripped = {
|
|
12033
|
+
...manifest
|
|
12034
|
+
};
|
|
12035
|
+
for (const tool of BUILD_TOOLS) {
|
|
12036
|
+
const section = manifest[tool];
|
|
12037
|
+
if (section?.disabled) {
|
|
12038
|
+
stripped[tool] = {
|
|
12039
|
+
...section,
|
|
12040
|
+
disabled: false
|
|
12041
|
+
};
|
|
11991
12042
|
}
|
|
11992
12043
|
}
|
|
11993
|
-
|
|
11994
|
-
|
|
11995
|
-
|
|
11996
|
-
|
|
11997
|
-
|
|
11998
|
-
gradleOpts = '';
|
|
12044
|
+
return {
|
|
12045
|
+
...sockJson,
|
|
12046
|
+
defaults: {
|
|
12047
|
+
...sockJson.defaults,
|
|
12048
|
+
manifest: stripped
|
|
11999
12049
|
}
|
|
12050
|
+
};
|
|
12051
|
+
}
|
|
12052
|
+
async function realpathOrResolved(dir) {
|
|
12053
|
+
try {
|
|
12054
|
+
return await fs$1.promises.realpath(dir);
|
|
12055
|
+
} catch {
|
|
12056
|
+
return path.resolve(dir);
|
|
12000
12057
|
}
|
|
12001
|
-
|
|
12002
|
-
|
|
12003
|
-
|
|
12004
|
-
|
|
12005
|
-
|
|
12006
|
-
|
|
12058
|
+
}
|
|
12059
|
+
function sortByDepthThenPath(dirs, cwd) {
|
|
12060
|
+
return [...dirs].sort((a, b) => {
|
|
12061
|
+
const relA = path.relative(cwd, a);
|
|
12062
|
+
const relB = path.relative(cwd, b);
|
|
12063
|
+
const depthA = relA === '' ? 0 : relA.split(path.sep).length;
|
|
12064
|
+
const depthB = relB === '' ? 0 : relB.split(path.sep).length;
|
|
12065
|
+
if (depthA !== depthB) {
|
|
12066
|
+
return depthA - depthB;
|
|
12007
12067
|
}
|
|
12068
|
+
return relA < relB ? -1 : relA > relB ? 1 : 0;
|
|
12069
|
+
});
|
|
12070
|
+
}
|
|
12071
|
+
|
|
12072
|
+
// Depth-sorted (root-most first) so the caller, using each build root's
|
|
12073
|
+
// `subprojectDir` facts, visits a reactor root before its own members and can
|
|
12074
|
+
// skip subprojects a parent root already covers.
|
|
12075
|
+
async function findBuildToolCandidates({
|
|
12076
|
+
cwd,
|
|
12077
|
+
excludePaths,
|
|
12078
|
+
sockJson
|
|
12079
|
+
}) {
|
|
12080
|
+
const enabledTools = BUILD_TOOLS.filter(tool => !sockJson.defaults?.manifest?.[tool]?.disabled);
|
|
12081
|
+
const result = new Map();
|
|
12082
|
+
if (!enabledTools.length) {
|
|
12083
|
+
return result;
|
|
12008
12084
|
}
|
|
12009
|
-
|
|
12010
|
-
|
|
12011
|
-
|
|
12012
|
-
|
|
12013
|
-
|
|
12014
|
-
|
|
12015
|
-
|
|
12085
|
+
const patterns = enabledTools.flatMap(tool => MARKERS_BY_TOOL[tool].map(marker => `**/${marker}`));
|
|
12086
|
+
const additionalIgnores = (excludePaths ?? []).flatMap(excludePathToScanIgnores);
|
|
12087
|
+
const hits = await utils.globWithGitIgnore(patterns, {
|
|
12088
|
+
absolute: true,
|
|
12089
|
+
additionalIgnores,
|
|
12090
|
+
cwd
|
|
12091
|
+
});
|
|
12092
|
+
const dirSetsByTool = new Map(enabledTools.map(tool => [tool, new Set()]));
|
|
12093
|
+
const resolvedHits = await Promise.all(hits.map(async hit => ({
|
|
12094
|
+
dir: await realpathOrResolved(path.dirname(hit)),
|
|
12095
|
+
tool: TOOL_BY_MARKER[path.basename(hit)]
|
|
12096
|
+
})));
|
|
12097
|
+
for (const {
|
|
12098
|
+
dir,
|
|
12099
|
+
tool
|
|
12100
|
+
} of resolvedHits) {
|
|
12101
|
+
if (tool) {
|
|
12102
|
+
dirSetsByTool.get(tool)?.add(dir);
|
|
12016
12103
|
}
|
|
12017
12104
|
}
|
|
12018
|
-
|
|
12019
|
-
|
|
12020
|
-
|
|
12021
|
-
if (cli.flags['facts'] !== undefined) {
|
|
12022
|
-
logger.logger.warn('The `--facts` and `--pom` options are mutually exclusive; generating Socket facts.');
|
|
12023
|
-
} else {
|
|
12024
|
-
facts = false;
|
|
12025
|
-
}
|
|
12105
|
+
const realCwd = await realpathOrResolved(cwd);
|
|
12106
|
+
for (const tool of enabledTools) {
|
|
12107
|
+
result.set(tool, sortByDepthThenPath([...(dirSetsByTool.get(tool) ?? [])], realCwd));
|
|
12026
12108
|
}
|
|
12027
|
-
|
|
12028
|
-
|
|
12029
|
-
|
|
12030
|
-
|
|
12031
|
-
|
|
12032
|
-
|
|
12033
|
-
|
|
12109
|
+
return result;
|
|
12110
|
+
}
|
|
12111
|
+
|
|
12112
|
+
// A cascaded (not just root-level) disabled additionally skips this one build
|
|
12113
|
+
// root here, on top of its existing root-only ecosystem-wide meaning for
|
|
12114
|
+
// auto/gradle/etc. facts:false has no pom-mode equivalent here (facts-only),
|
|
12115
|
+
// so it skips too.
|
|
12116
|
+
function getSkipReason(disabled, facts) {
|
|
12117
|
+
if (disabled) {
|
|
12118
|
+
return 'defaults.manifest.<ecosystem>.disabled is true';
|
|
12034
12119
|
}
|
|
12035
|
-
if (
|
|
12036
|
-
|
|
12037
|
-
excludeConfigs = sockJson.defaults?.manifest?.gradle?.excludeConfigs;
|
|
12038
|
-
logger.logger.info(`Using default --exclude-configs from ${constants.SOCKET_JSON}:`, excludeConfigs);
|
|
12039
|
-
} else {
|
|
12040
|
-
excludeConfigs = '';
|
|
12041
|
-
}
|
|
12120
|
+
if (facts === false) {
|
|
12121
|
+
return 'defaults.manifest.<ecosystem>.facts is false (pom mode)';
|
|
12042
12122
|
}
|
|
12043
|
-
|
|
12044
|
-
|
|
12045
|
-
|
|
12046
|
-
|
|
12047
|
-
|
|
12048
|
-
|
|
12123
|
+
return undefined;
|
|
12124
|
+
}
|
|
12125
|
+
// Nearest already-confirmed-disabled ancestor of `dir`, if any - lets the
|
|
12126
|
+
// caller shorten `readSocketJsonCascade`'s walk to start there instead of
|
|
12127
|
+
// all the way back at `cwd`, without skipping any nested override.
|
|
12128
|
+
function nearestDisabledRoot(dir, disabledRoots) {
|
|
12129
|
+
let nearest;
|
|
12130
|
+
for (const root of disabledRoots) {
|
|
12131
|
+
if (dir.startsWith(`${root.dir}${path.sep}`) && (!nearest || root.dir.length > nearest.dir.length)) {
|
|
12132
|
+
nearest = root;
|
|
12049
12133
|
}
|
|
12050
12134
|
}
|
|
12135
|
+
return nearest;
|
|
12136
|
+
}
|
|
12051
12137
|
|
|
12052
|
-
|
|
12053
|
-
|
|
12054
|
-
|
|
12055
|
-
|
|
12056
|
-
if (
|
|
12057
|
-
|
|
12138
|
+
// A wrapper-preferred `bin` default is resolved per-root (`dir`, not `cwd`)
|
|
12139
|
+
// since a wrapper script only exists at the actual build root. Exported for
|
|
12140
|
+
// reuse by the recursive setup wizard's reactor-coverage pruning.
|
|
12141
|
+
function resolveEcosystemConfig(ecosystem, dir, sockJson) {
|
|
12142
|
+
if (ecosystem === 'sbt') {
|
|
12143
|
+
const config = sockJson.defaults?.manifest?.sbt;
|
|
12144
|
+
const bin = config?.bin ?? undefined;
|
|
12145
|
+
return {
|
|
12146
|
+
bin: bin ?? 'sbt',
|
|
12147
|
+
buildOpts: parseBuildToolOpts(config?.sbtOpts ?? undefined),
|
|
12148
|
+
excludeConfigs: config?.excludeConfigs ?? '',
|
|
12149
|
+
ignoreUnresolved: Boolean(config?.ignoreUnresolved),
|
|
12150
|
+
includeConfigs: config?.includeConfigs ?? '',
|
|
12151
|
+
javaHome: config?.javaHome ?? undefined,
|
|
12152
|
+
skipReason: getSkipReason(config?.disabled, config?.facts)
|
|
12153
|
+
};
|
|
12154
|
+
}
|
|
12155
|
+
if (ecosystem === 'gradle') {
|
|
12156
|
+
const config = sockJson.defaults?.manifest?.gradle;
|
|
12157
|
+
const bin = config?.bin ?? undefined;
|
|
12158
|
+
return {
|
|
12159
|
+
bin: bin ? path.resolve(dir, bin) : resolveBuildToolBin('gradle', dir),
|
|
12160
|
+
buildOpts: parseBuildToolOpts(config?.gradleOpts ?? undefined),
|
|
12161
|
+
excludeConfigs: config?.excludeConfigs ?? '',
|
|
12162
|
+
ignoreUnresolved: Boolean(config?.ignoreUnresolved),
|
|
12163
|
+
includeConfigs: config?.includeConfigs ?? '',
|
|
12164
|
+
javaHome: config?.javaHome ?? undefined,
|
|
12165
|
+
skipReason: getSkipReason(config?.disabled, config?.facts)
|
|
12166
|
+
};
|
|
12167
|
+
}
|
|
12168
|
+
const config = sockJson.defaults?.manifest?.maven;
|
|
12169
|
+
const bin = config?.bin ?? undefined;
|
|
12170
|
+
return {
|
|
12171
|
+
bin: bin ?? resolveBuildToolBin('maven', dir),
|
|
12172
|
+
buildOpts: parseBuildToolOpts(config?.mavenOpts ?? undefined),
|
|
12173
|
+
excludeConfigs: config?.excludeConfigs ?? '',
|
|
12174
|
+
ignoreUnresolved: Boolean(config?.ignoreUnresolved),
|
|
12175
|
+
includeConfigs: config?.includeConfigs ?? '',
|
|
12176
|
+
javaHome: config?.javaHome ?? undefined,
|
|
12177
|
+
skipReason: getSkipReason(config?.disabled)
|
|
12178
|
+
};
|
|
12179
|
+
}
|
|
12180
|
+
|
|
12181
|
+
// Generates one .socket.facts.json per independent gradle/sbt/maven build
|
|
12182
|
+
// root under `cwd`. Coverage is tracked per ecosystem via the facts SBOM's
|
|
12183
|
+
// own projects[].subprojectDir, not by pruning the whole discovered subtree,
|
|
12184
|
+
// so an unrelated nested project a reactor doesn't declare still gets its
|
|
12185
|
+
// own invocation. Fail-closed per ecosystem, not globally: a root whose
|
|
12186
|
+
// workspace layout can't be determined aborts only that ecosystem's own
|
|
12187
|
+
// remaining walk (marking its untried candidates 'aborted'), since coverage
|
|
12188
|
+
// is tracked per ecosystem and an unrelated one has nothing to lose from it.
|
|
12189
|
+
async function generateRecursiveManifests({
|
|
12190
|
+
cwd,
|
|
12191
|
+
excludePaths,
|
|
12192
|
+
verbose
|
|
12193
|
+
}) {
|
|
12194
|
+
const rootSockJson = utils.readOrDefaultSocketJson(cwd);
|
|
12195
|
+
// Candidate dirs come back realpath-resolved (findBuildToolCandidates); cwd
|
|
12196
|
+
// must match or every boundary/relative-path comparison below breaks as
|
|
12197
|
+
// soon as cwd contains a symlink (macOS /tmp -> /private/tmp, etc.).
|
|
12198
|
+
const realCwd = await realpathOrResolved(cwd);
|
|
12199
|
+
// A root-disabled ecosystem must still be scanned for - a nested socket.json
|
|
12200
|
+
// may re-enable it - so the per-directory cascade below, not this scan, is
|
|
12201
|
+
// what actually decides skip vs. include.
|
|
12202
|
+
const candidatesByTool = await findBuildToolCandidates({
|
|
12203
|
+
cwd,
|
|
12204
|
+
excludePaths,
|
|
12205
|
+
sockJson: withoutDisabledFlags(rootSockJson)
|
|
12206
|
+
});
|
|
12207
|
+
const outcomes = [];
|
|
12208
|
+
for (const [ecosystem, dirs] of candidatesByTool) {
|
|
12209
|
+
const covered = new Set();
|
|
12210
|
+
const disabledRoots = [];
|
|
12211
|
+
for (let dirIndex = 0; dirIndex < dirs.length; dirIndex += 1) {
|
|
12212
|
+
const dir = dirs[dirIndex];
|
|
12213
|
+
if (covered.has(dir)) {
|
|
12214
|
+
outcomes.push({
|
|
12215
|
+
dir,
|
|
12216
|
+
ecosystem,
|
|
12217
|
+
status: 'skippedCovered'
|
|
12218
|
+
});
|
|
12219
|
+
continue;
|
|
12220
|
+
}
|
|
12221
|
+
const nearestRoot = nearestDisabledRoot(dir, disabledRoots);
|
|
12222
|
+
const sockJson = nearestRoot ? utils.readSocketJsonCascade(dir, nearestRoot.dir, nearestRoot.sockJson) : utils.readSocketJsonCascade(dir, realCwd, rootSockJson);
|
|
12223
|
+
const {
|
|
12224
|
+
bin,
|
|
12225
|
+
buildOpts,
|
|
12226
|
+
excludeConfigs,
|
|
12227
|
+
ignoreUnresolved,
|
|
12228
|
+
includeConfigs,
|
|
12229
|
+
javaHome,
|
|
12230
|
+
skipReason
|
|
12231
|
+
} = resolveEcosystemConfig(ecosystem, dir, sockJson);
|
|
12232
|
+
if (skipReason) {
|
|
12233
|
+
// Only warn for a genuinely new disabled root, not one already
|
|
12234
|
+
// covered by an ancestor's warning above - otherwise a big disabled
|
|
12235
|
+
// reactor with hundreds of nested poms would spam one warning line
|
|
12236
|
+
// per pom for what's really a single root cause. The aggregate
|
|
12237
|
+
// count still shows up in the final summary either way.
|
|
12238
|
+
if (!nearestRoot) {
|
|
12239
|
+
logger.logger.warn(`Skipping ${dir} (${ecosystem}): ${skipReason}.`);
|
|
12240
|
+
}
|
|
12241
|
+
outcomes.push({
|
|
12242
|
+
dir,
|
|
12243
|
+
ecosystem,
|
|
12244
|
+
status: 'skippedDisabled'
|
|
12245
|
+
});
|
|
12246
|
+
disabledRoots.push({
|
|
12247
|
+
dir,
|
|
12248
|
+
sockJson
|
|
12249
|
+
});
|
|
12250
|
+
continue;
|
|
12251
|
+
}
|
|
12252
|
+
const excludePathsForRoot = projectIgnorePathsToReachExcludePaths(excludePaths, {
|
|
12253
|
+
cwd: realCwd,
|
|
12254
|
+
target: dir
|
|
12255
|
+
});
|
|
12256
|
+
|
|
12257
|
+
// eslint-disable-next-line no-await-in-loop
|
|
12258
|
+
const result = await runManifestFacts({
|
|
12259
|
+
bin,
|
|
12260
|
+
buildOpts,
|
|
12261
|
+
cwd: dir,
|
|
12262
|
+
ecosystem,
|
|
12263
|
+
excludeConfigs,
|
|
12264
|
+
excludePaths: excludePathsForRoot,
|
|
12265
|
+
ignoreUnresolved,
|
|
12266
|
+
includeConfigs,
|
|
12267
|
+
javaHome,
|
|
12268
|
+
verbose
|
|
12269
|
+
});
|
|
12270
|
+
if (result === null) {
|
|
12271
|
+
outcomes.push({
|
|
12272
|
+
dir,
|
|
12273
|
+
ecosystem,
|
|
12274
|
+
status: 'failed'
|
|
12275
|
+
});
|
|
12276
|
+
logger.logger.warn(`Aborting ${ecosystem} discovery: ${dir}'s workspace layout could not be determined, so its remaining build roots cannot be safely classified as covered or independent.`);
|
|
12277
|
+
for (const abortedDir of dirs.slice(dirIndex + 1)) {
|
|
12278
|
+
outcomes.push({
|
|
12279
|
+
dir: abortedDir,
|
|
12280
|
+
ecosystem,
|
|
12281
|
+
status: 'aborted'
|
|
12282
|
+
});
|
|
12283
|
+
}
|
|
12284
|
+
break;
|
|
12285
|
+
}
|
|
12286
|
+
if (!result) {
|
|
12287
|
+
outcomes.push({
|
|
12288
|
+
dir,
|
|
12289
|
+
ecosystem,
|
|
12290
|
+
status: 'empty'
|
|
12291
|
+
});
|
|
12292
|
+
continue;
|
|
12293
|
+
}
|
|
12294
|
+
covered.add(dir);
|
|
12295
|
+
// eslint-disable-next-line no-await-in-loop
|
|
12296
|
+
const resolvedSubprojectDirs = await Promise.all(result.projects.map(project => realpathOrResolved(path.resolve(dir, project.subprojectDir))));
|
|
12297
|
+
for (const subprojectDir of resolvedSubprojectDirs) {
|
|
12298
|
+
covered.add(subprojectDir);
|
|
12299
|
+
}
|
|
12300
|
+
outcomes.push({
|
|
12301
|
+
dir,
|
|
12302
|
+
ecosystem,
|
|
12303
|
+
factsPath: result.factsPath,
|
|
12304
|
+
status: 'generated'
|
|
12305
|
+
});
|
|
12306
|
+
}
|
|
12058
12307
|
}
|
|
12059
12308
|
if (verbose) {
|
|
12060
|
-
logger.logger.
|
|
12309
|
+
logger.logger.info(`Discovered ${outcomes.length} build-tool candidate(s).`);
|
|
12310
|
+
}
|
|
12311
|
+
return outcomes;
|
|
12312
|
+
}
|
|
12313
|
+
|
|
12314
|
+
function renderTable(outcomes) {
|
|
12315
|
+
// A covered reactor member is implied by its parent's line above it.
|
|
12316
|
+
return outcomes.filter(o => o.status !== 'skippedCovered').map(o => `- ${o.dir} (${o.ecosystem}): ${o.status}${o.factsPath ? ` -> ${o.factsPath}` : ''}`).join('\n');
|
|
12317
|
+
}
|
|
12318
|
+
function summarize(outcomes) {
|
|
12319
|
+
const generated = outcomes.filter(o => o.status === 'generated').length;
|
|
12320
|
+
return `Generated ${generated} Socket facts file(s).`;
|
|
12321
|
+
}
|
|
12322
|
+
async function outputManifestDynamicSbomInference(result, outputKind) {
|
|
12323
|
+
if (!result.ok) {
|
|
12324
|
+
process.exitCode = result.code ?? 1;
|
|
12325
|
+
}
|
|
12326
|
+
if (outputKind === 'json') {
|
|
12327
|
+
logger.logger.log(utils.serializeResultJson(result));
|
|
12328
|
+
return;
|
|
12329
|
+
}
|
|
12330
|
+
if (!result.ok) {
|
|
12331
|
+
logger.logger.fail(utils.failMsgWithBadge(result.message, result.cause));
|
|
12332
|
+
const data = result.data;
|
|
12333
|
+
if (Array.isArray(data)) {
|
|
12334
|
+
const table = renderTable(data);
|
|
12335
|
+
if (table) {
|
|
12336
|
+
logger.logger.log(table);
|
|
12337
|
+
}
|
|
12338
|
+
logger.logger.log(summarize(data));
|
|
12339
|
+
}
|
|
12340
|
+
return;
|
|
12341
|
+
}
|
|
12342
|
+
const table = renderTable(result.data);
|
|
12343
|
+
if (outputKind === 'markdown') {
|
|
12344
|
+
logger.logger.log(['# Dynamic SBOM inference', '', ...(table ? [table, ''] : []), summarize(result.data)].join('\n'));
|
|
12345
|
+
return;
|
|
12346
|
+
}
|
|
12347
|
+
if (table) {
|
|
12348
|
+
logger.logger.log(table);
|
|
12349
|
+
}
|
|
12350
|
+
logger.logger.log(summarize(result.data));
|
|
12351
|
+
}
|
|
12352
|
+
|
|
12353
|
+
async function handleManifestDynamicSbomInference({
|
|
12354
|
+
cwd,
|
|
12355
|
+
excludePaths,
|
|
12356
|
+
outputKind,
|
|
12357
|
+
verbose
|
|
12358
|
+
}) {
|
|
12359
|
+
const outcomes = await generateRecursiveManifests({
|
|
12360
|
+
cwd,
|
|
12361
|
+
excludePaths,
|
|
12362
|
+
verbose
|
|
12363
|
+
});
|
|
12364
|
+
const result = outcomes.some(o => o.status === 'failed') ? {
|
|
12365
|
+
ok: false,
|
|
12366
|
+
code: 1,
|
|
12367
|
+
message: 'One or more build roots failed to generate Socket facts.',
|
|
12368
|
+
data: outcomes
|
|
12369
|
+
} : {
|
|
12370
|
+
ok: true,
|
|
12371
|
+
data: outcomes
|
|
12372
|
+
};
|
|
12373
|
+
await outputManifestDynamicSbomInference(result, outputKind);
|
|
12374
|
+
}
|
|
12375
|
+
|
|
12376
|
+
const config$d = {
|
|
12377
|
+
commandName: 'dynamic-sbom-inference',
|
|
12378
|
+
description: 'Recursively discover gradle/sbt/maven build roots and generate a Socket facts SBOM for each',
|
|
12379
|
+
// Hidden: `--dynamic-sbom-inference` already names an unrelated, root-only
|
|
12380
|
+
// scan create/reach flag (see reachability-flags.mts). Keep this hidden
|
|
12381
|
+
// until the naming collision between the two is resolved.
|
|
12382
|
+
hidden: true,
|
|
12383
|
+
flags: {
|
|
12384
|
+
...flags.commonFlags,
|
|
12385
|
+
...flags.outputFlags,
|
|
12386
|
+
...excludePathsFlag$1,
|
|
12387
|
+
verbose: {
|
|
12388
|
+
type: 'boolean',
|
|
12389
|
+
default: false,
|
|
12390
|
+
description: 'Print debug messages'
|
|
12391
|
+
}
|
|
12392
|
+
},
|
|
12393
|
+
help: (command, config) => `
|
|
12394
|
+
Usage
|
|
12395
|
+
$ ${command} [options] [CWD=.]
|
|
12396
|
+
|
|
12397
|
+
Recursively walks CWD, discovers independent gradle, sbt, and maven build
|
|
12398
|
+
roots, and generates a Socket facts SBOM (.socket.facts.json) for each,
|
|
12399
|
+
skipping subproject/reactor-module directories a parent build root already
|
|
12400
|
+
covers. Unlike \`socket manifest auto\`, this looks beyond CWD itself.
|
|
12401
|
+
|
|
12402
|
+
Options
|
|
12403
|
+
${utils.getFlagListOutput(config.flags)}
|
|
12404
|
+
|
|
12405
|
+
Examples
|
|
12406
|
+
|
|
12407
|
+
$ ${command}
|
|
12408
|
+
$ ${command} ./monorepo
|
|
12409
|
+
`
|
|
12410
|
+
};
|
|
12411
|
+
const cmdManifestDynamicSbomInference = {
|
|
12412
|
+
description: config$d.description,
|
|
12413
|
+
hidden: config$d.hidden,
|
|
12414
|
+
run: run$E
|
|
12415
|
+
};
|
|
12416
|
+
async function run$E(argv, importMeta, {
|
|
12417
|
+
parentName
|
|
12418
|
+
}) {
|
|
12419
|
+
const cli = utils.meowOrExit({
|
|
12420
|
+
argv,
|
|
12421
|
+
config: config$d,
|
|
12422
|
+
importMeta,
|
|
12423
|
+
parentName
|
|
12424
|
+
});
|
|
12425
|
+
const {
|
|
12426
|
+
dryRun,
|
|
12427
|
+
json,
|
|
12428
|
+
markdown,
|
|
12429
|
+
verbose: verboseFlag
|
|
12430
|
+
} = cli.flags;
|
|
12431
|
+
const verbose = !!verboseFlag;
|
|
12432
|
+
let [cwd = '.'] = cli.input;
|
|
12433
|
+
// Note: path.resolve vs .join:
|
|
12434
|
+
// If given path is absolute then cwd should not affect it.
|
|
12435
|
+
cwd = path.resolve(process.cwd(), cwd);
|
|
12436
|
+
|
|
12437
|
+
// This debug block prints to stdout; --json's payload does too, so skip it
|
|
12438
|
+
// here (unlike the other manifest commands, this one supports --json).
|
|
12439
|
+
if (verbose && !json) {
|
|
12440
|
+
logger.logger.group('- ', parentName, config$d.commandName, ':');
|
|
12061
12441
|
logger.logger.group('- flags:', cli.flags);
|
|
12062
12442
|
logger.logger.groupEnd();
|
|
12063
|
-
logger.logger.log('-
|
|
12443
|
+
logger.logger.log('- target:', cwd);
|
|
12064
12444
|
logger.logger.groupEnd();
|
|
12065
12445
|
}
|
|
12066
|
-
|
|
12067
|
-
// TODO: We're not sure it's feasible to parse source file from stdin. We could
|
|
12068
|
-
// try, store contents in a file in some folder, target that folder... what
|
|
12069
|
-
// would the file name be?
|
|
12070
|
-
|
|
12446
|
+
const outputKind = utils.getOutputKind(json, markdown);
|
|
12071
12447
|
const wasValidInput = utils.checkCommandInput(outputKind, {
|
|
12072
12448
|
nook: true,
|
|
12073
12449
|
test: cli.input.length <= 1,
|
|
12074
12450
|
message: 'Can only accept one DIR (make sure to escape spaces!)',
|
|
12075
|
-
fail:
|
|
12451
|
+
fail: `received ${cli.input.length}`
|
|
12452
|
+
}, {
|
|
12453
|
+
nook: true,
|
|
12454
|
+
test: !json || !markdown,
|
|
12455
|
+
message: `The \`${constants.FLAG_JSON}\` and \`${constants.FLAG_MARKDOWN}\` flags can not be used at the same time`,
|
|
12456
|
+
fail: 'bad'
|
|
12076
12457
|
});
|
|
12077
12458
|
if (!wasValidInput) {
|
|
12078
12459
|
return;
|
|
12079
12460
|
}
|
|
12080
|
-
if (verbose) {
|
|
12081
|
-
logger.logger.group();
|
|
12082
|
-
logger.logger.info('- cwd:', cwd);
|
|
12083
|
-
logger.logger.info('- gradle bin:', bin);
|
|
12084
|
-
logger.logger.groupEnd();
|
|
12085
|
-
}
|
|
12086
|
-
if (dryRun) {
|
|
12087
|
-
logger.logger.log(constants.default.DRY_RUN_BAILING_NOW);
|
|
12088
|
-
return;
|
|
12089
|
-
}
|
|
12090
|
-
const parsedGradleOpts = parseBuildToolOpts(String(gradleOpts || ''));
|
|
12091
12461
|
const excludePaths = utils.cmdFlagValueToArray(cli.flags['excludePaths']);
|
|
12092
12462
|
assertValidExcludePaths(excludePaths);
|
|
12093
|
-
if (
|
|
12094
|
-
|
|
12095
|
-
bin: String(bin),
|
|
12096
|
-
cwd,
|
|
12097
|
-
excludeConfigs: String(excludeConfigs || ''),
|
|
12098
|
-
excludePaths,
|
|
12099
|
-
gradleOpts: parsedGradleOpts,
|
|
12100
|
-
ignoreUnresolved: Boolean(ignoreUnresolved),
|
|
12101
|
-
includeConfigs: String(includeConfigs || ''),
|
|
12102
|
-
verbose: Boolean(verbose)
|
|
12103
|
-
});
|
|
12463
|
+
if (dryRun) {
|
|
12464
|
+
logger.logger.log(constants.default.DRY_RUN_BAILING_NOW);
|
|
12104
12465
|
return;
|
|
12105
12466
|
}
|
|
12106
|
-
await
|
|
12107
|
-
bin: String(bin),
|
|
12467
|
+
await handleManifestDynamicSbomInference({
|
|
12108
12468
|
cwd,
|
|
12109
12469
|
excludePaths,
|
|
12110
|
-
|
|
12111
|
-
verbose
|
|
12470
|
+
outputKind,
|
|
12471
|
+
verbose
|
|
12112
12472
|
});
|
|
12113
12473
|
}
|
|
12114
12474
|
|
|
12115
|
-
|
|
12116
|
-
|
|
12117
|
-
|
|
12118
|
-
// `socket manifest kotlin` to print help screens with `gradle` as the
|
|
12119
|
-
// command. Room for improvement.
|
|
12120
|
-
const config$b = {
|
|
12121
|
-
commandName: 'kotlin',
|
|
12122
|
-
description: '[beta] Generate a Socket facts file (or `pom.xml` with --pom) for a Kotlin project',
|
|
12475
|
+
const config$c = {
|
|
12476
|
+
commandName: 'gradle',
|
|
12477
|
+
description: '[beta] Generate a Socket facts file (or `pom.xml` with --pom) for a Gradle/Java/Kotlin/etc project',
|
|
12123
12478
|
hidden: false,
|
|
12124
12479
|
flags: {
|
|
12125
12480
|
...flags.commonFlags,
|
|
@@ -12143,7 +12498,7 @@ const config$b = {
|
|
|
12143
12498
|
type: 'string',
|
|
12144
12499
|
description: 'When generating facts: comma-separated glob patterns; Gradle configurations matching any pattern are skipped (applied after --include-configs)'
|
|
12145
12500
|
},
|
|
12146
|
-
...excludePathsFlag,
|
|
12501
|
+
...excludePathsFlag$1,
|
|
12147
12502
|
ignoreUnresolved: {
|
|
12148
12503
|
type: 'boolean',
|
|
12149
12504
|
description: 'When generating facts: warn on unresolved dependencies instead of failing the run (unresolved deps are not emitted to the facts file)'
|
|
@@ -12193,17 +12548,17 @@ const config$b = {
|
|
|
12193
12548
|
$ ${command} --bin=../gradlew .
|
|
12194
12549
|
`
|
|
12195
12550
|
};
|
|
12196
|
-
const
|
|
12197
|
-
description: config$
|
|
12198
|
-
hidden: config$
|
|
12199
|
-
run: run$
|
|
12551
|
+
const cmdManifestGradle = {
|
|
12552
|
+
description: config$c.description,
|
|
12553
|
+
hidden: config$c.hidden,
|
|
12554
|
+
run: run$D
|
|
12200
12555
|
};
|
|
12201
|
-
async function run$
|
|
12556
|
+
async function run$D(argv, importMeta, {
|
|
12202
12557
|
parentName
|
|
12203
12558
|
}) {
|
|
12204
12559
|
const cli = utils.meowOrExit({
|
|
12205
12560
|
argv,
|
|
12206
|
-
config: config$
|
|
12561
|
+
config: config$c,
|
|
12207
12562
|
importMeta,
|
|
12208
12563
|
parentName
|
|
12209
12564
|
});
|
|
@@ -12276,16 +12631,16 @@ async function run$C(argv, importMeta, {
|
|
|
12276
12631
|
}
|
|
12277
12632
|
}
|
|
12278
12633
|
if (includeConfigs === undefined) {
|
|
12279
|
-
if (sockJson.defaults?.manifest?.gradle?.includeConfigs
|
|
12280
|
-
includeConfigs = sockJson.defaults
|
|
12634
|
+
if (sockJson.defaults?.manifest?.gradle?.includeConfigs) {
|
|
12635
|
+
includeConfigs = sockJson.defaults.manifest.gradle.includeConfigs;
|
|
12281
12636
|
logger.logger.info(`Using default --include-configs from ${constants.SOCKET_JSON}:`, includeConfigs);
|
|
12282
12637
|
} else {
|
|
12283
12638
|
includeConfigs = '';
|
|
12284
12639
|
}
|
|
12285
12640
|
}
|
|
12286
12641
|
if (excludeConfigs === undefined) {
|
|
12287
|
-
if (sockJson.defaults?.manifest?.gradle?.excludeConfigs
|
|
12288
|
-
excludeConfigs = sockJson.defaults
|
|
12642
|
+
if (sockJson.defaults?.manifest?.gradle?.excludeConfigs) {
|
|
12643
|
+
excludeConfigs = sockJson.defaults.manifest.gradle.excludeConfigs;
|
|
12289
12644
|
logger.logger.info(`Using default --exclude-configs from ${constants.SOCKET_JSON}:`, excludeConfigs);
|
|
12290
12645
|
} else {
|
|
12291
12646
|
excludeConfigs = '';
|
|
@@ -12301,12 +12656,14 @@ async function run$C(argv, importMeta, {
|
|
|
12301
12656
|
}
|
|
12302
12657
|
|
|
12303
12658
|
// `--include-configs`, `--exclude-configs`, and `--ignore-unresolved` only
|
|
12304
|
-
// affect facts generation; the pom path has no equivalent knobs.
|
|
12659
|
+
// affect facts generation; the pom path has no equivalent knobs. Warn rather
|
|
12660
|
+
// than silently ignore an explicitly-passed flag. (socket.json defaults don't
|
|
12661
|
+
// trip this — only a flag actually present on the command line does.)
|
|
12305
12662
|
if (!facts && (cli.flags['includeConfigs'] !== undefined || cli.flags['excludeConfigs'] !== undefined || cli.flags['ignoreUnresolved'] !== undefined)) {
|
|
12306
12663
|
logger.logger.warn('The `--include-configs`, `--exclude-configs`, and `--ignore-unresolved` options only apply when generating Socket facts (not with `--pom`); ignoring them.');
|
|
12307
12664
|
}
|
|
12308
12665
|
if (verbose) {
|
|
12309
|
-
logger.logger.group('- ', parentName, config$
|
|
12666
|
+
logger.logger.group('- ', parentName, config$c.commandName, ':');
|
|
12310
12667
|
logger.logger.group('- flags:', cli.flags);
|
|
12311
12668
|
logger.logger.groupEnd();
|
|
12312
12669
|
logger.logger.log('- input:', cli.input);
|
|
@@ -12326,10 +12683,12 @@ async function run$C(argv, importMeta, {
|
|
|
12326
12683
|
if (!wasValidInput) {
|
|
12327
12684
|
return;
|
|
12328
12685
|
}
|
|
12686
|
+
const javaHome = sockJson.defaults?.manifest?.gradle?.javaHome ?? undefined;
|
|
12329
12687
|
if (verbose) {
|
|
12330
12688
|
logger.logger.group();
|
|
12331
12689
|
logger.logger.info('- cwd:', cwd);
|
|
12332
12690
|
logger.logger.info('- gradle bin:', bin);
|
|
12691
|
+
logger.logger.info('- java home:', javaHome || '(inherited)');
|
|
12333
12692
|
logger.logger.groupEnd();
|
|
12334
12693
|
}
|
|
12335
12694
|
if (dryRun) {
|
|
@@ -12348,6 +12707,7 @@ async function run$C(argv, importMeta, {
|
|
|
12348
12707
|
gradleOpts: parsedGradleOpts,
|
|
12349
12708
|
ignoreUnresolved: Boolean(ignoreUnresolved),
|
|
12350
12709
|
includeConfigs: String(includeConfigs || ''),
|
|
12710
|
+
javaHome,
|
|
12351
12711
|
verbose: Boolean(verbose)
|
|
12352
12712
|
});
|
|
12353
12713
|
return;
|
|
@@ -12361,16 +12721,268 @@ async function run$C(argv, importMeta, {
|
|
|
12361
12721
|
});
|
|
12362
12722
|
}
|
|
12363
12723
|
|
|
12364
|
-
|
|
12365
|
-
|
|
12366
|
-
|
|
12367
|
-
|
|
12368
|
-
|
|
12369
|
-
|
|
12370
|
-
|
|
12371
|
-
|
|
12372
|
-
|
|
12373
|
-
|
|
12724
|
+
// TODO: We may want to dedupe some pieces for all gradle languages. I think it
|
|
12725
|
+
// makes sense to have separate commands for them and I think it makes
|
|
12726
|
+
// sense for the help panels to note the requested language, rather than
|
|
12727
|
+
// `socket manifest kotlin` to print help screens with `gradle` as the
|
|
12728
|
+
// command. Room for improvement.
|
|
12729
|
+
const config$b = {
|
|
12730
|
+
commandName: 'kotlin',
|
|
12731
|
+
description: '[beta] Generate a Socket facts file (or `pom.xml` with --pom) for a Kotlin project',
|
|
12732
|
+
hidden: false,
|
|
12733
|
+
flags: {
|
|
12734
|
+
...flags.commonFlags,
|
|
12735
|
+
bin: {
|
|
12736
|
+
type: 'string',
|
|
12737
|
+
description: 'Location of the gradle binary to use, default: ./gradlew if present, else gradle on PATH'
|
|
12738
|
+
},
|
|
12739
|
+
facts: {
|
|
12740
|
+
type: 'boolean',
|
|
12741
|
+
description: 'Emit a Socket facts JSON file (`.socket.facts.json`) describing the resolved dependency graph. This is the default; pass `--pom` to generate `pom.xml` files instead'
|
|
12742
|
+
},
|
|
12743
|
+
pom: {
|
|
12744
|
+
type: 'boolean',
|
|
12745
|
+
description: 'Generate `pom.xml` manifest file(s) instead of the default Socket facts file (`.socket.facts.json`)'
|
|
12746
|
+
},
|
|
12747
|
+
includeConfigs: {
|
|
12748
|
+
type: 'string',
|
|
12749
|
+
description: 'When generating facts: comma-separated glob patterns matched against Gradle configuration names (case-sensitive; `*`, `?`, and `[...]` wildcards). Only configurations matching at least one pattern are resolved. e.g. `*CompileClasspath,*RuntimeClasspath`. Default: every resolvable configuration'
|
|
12750
|
+
},
|
|
12751
|
+
excludeConfigs: {
|
|
12752
|
+
type: 'string',
|
|
12753
|
+
description: 'When generating facts: comma-separated glob patterns; Gradle configurations matching any pattern are skipped (applied after --include-configs)'
|
|
12754
|
+
},
|
|
12755
|
+
...excludePathsFlag$1,
|
|
12756
|
+
ignoreUnresolved: {
|
|
12757
|
+
type: 'boolean',
|
|
12758
|
+
description: 'When generating facts: warn on unresolved dependencies instead of failing the run (unresolved deps are not emitted to the facts file)'
|
|
12759
|
+
},
|
|
12760
|
+
gradleOpts: {
|
|
12761
|
+
type: 'string',
|
|
12762
|
+
description: 'Additional options to pass on to ./gradlew, see `./gradlew --help`'
|
|
12763
|
+
},
|
|
12764
|
+
verbose: {
|
|
12765
|
+
type: 'boolean',
|
|
12766
|
+
description: 'Print debug messages'
|
|
12767
|
+
}
|
|
12768
|
+
},
|
|
12769
|
+
help: (command, config) => `
|
|
12770
|
+
Usage
|
|
12771
|
+
$ ${command} [options] [CWD=.]
|
|
12772
|
+
|
|
12773
|
+
Options
|
|
12774
|
+
${utils.getFlagListOutput(config.flags)}
|
|
12775
|
+
|
|
12776
|
+
By default, emits a single \`.socket.facts.json\` describing the resolved
|
|
12777
|
+
dependency graph of the whole build, using gradle (preferably your local
|
|
12778
|
+
\`gradlew\`). An unresolved dependency is a fatal error. You can pass
|
|
12779
|
+
--include-configs / --exclude-configs (comma-separated glob patterns) to
|
|
12780
|
+
control which configurations are resolved (e.g.
|
|
12781
|
+
--include-configs=\`*CompileClasspath,*RuntimeClasspath\`), and
|
|
12782
|
+
--ignore-unresolved to warn on unresolved dependencies instead of failing.
|
|
12783
|
+
|
|
12784
|
+
Pass --pom to instead generate \`pom.xml\` manifest files via gradle (one per
|
|
12785
|
+
task). The \`pom.xml\` is a manifest file similar to \`package.json\` for npm
|
|
12786
|
+
(or ${constants.REQUIREMENTS_TXT} for PyPi), but specifically for Maven, which is
|
|
12787
|
+
Java's dependency repository. Caveats of the \`pom.xml\` conversion:
|
|
12788
|
+
|
|
12789
|
+
- each task generates its own xml file (one per task by default)
|
|
12790
|
+
|
|
12791
|
+
- certain features may not translate well into the xml; reach out if
|
|
12792
|
+
something you need is missing
|
|
12793
|
+
|
|
12794
|
+
- it works with your \`gradlew\` from your repo and local settings and config
|
|
12795
|
+
|
|
12796
|
+
Support is beta. Please report issues or give us feedback on what's missing.
|
|
12797
|
+
|
|
12798
|
+
Examples
|
|
12799
|
+
|
|
12800
|
+
$ ${command} .
|
|
12801
|
+
$ ${command} --pom .
|
|
12802
|
+
$ ${command} --bin=../gradlew .
|
|
12803
|
+
`
|
|
12804
|
+
};
|
|
12805
|
+
const cmdManifestKotlin = {
|
|
12806
|
+
description: config$b.description,
|
|
12807
|
+
hidden: config$b.hidden,
|
|
12808
|
+
run: run$C
|
|
12809
|
+
};
|
|
12810
|
+
async function run$C(argv, importMeta, {
|
|
12811
|
+
parentName
|
|
12812
|
+
}) {
|
|
12813
|
+
const cli = utils.meowOrExit({
|
|
12814
|
+
argv,
|
|
12815
|
+
config: config$b,
|
|
12816
|
+
importMeta,
|
|
12817
|
+
parentName
|
|
12818
|
+
});
|
|
12819
|
+
const {
|
|
12820
|
+
json = false,
|
|
12821
|
+
markdown = false
|
|
12822
|
+
} = cli.flags;
|
|
12823
|
+
const dryRun = !!cli.flags['dryRun'];
|
|
12824
|
+
|
|
12825
|
+
// TODO: Implement json/md further.
|
|
12826
|
+
const outputKind = utils.getOutputKind(json, markdown);
|
|
12827
|
+
let [cwd = '.'] = cli.input;
|
|
12828
|
+
// Note: path.resolve vs .join:
|
|
12829
|
+
// If given path is absolute then cwd should not affect it.
|
|
12830
|
+
cwd = path.resolve(process.cwd(), cwd);
|
|
12831
|
+
const sockJson = utils.readOrDefaultSocketJson(cwd);
|
|
12832
|
+
debug.debugFn('inspect', `override: ${constants.SOCKET_JSON} gradle`, sockJson?.defaults?.manifest?.gradle);
|
|
12833
|
+
let {
|
|
12834
|
+
bin,
|
|
12835
|
+
excludeConfigs,
|
|
12836
|
+
facts,
|
|
12837
|
+
gradleOpts,
|
|
12838
|
+
ignoreUnresolved,
|
|
12839
|
+
includeConfigs,
|
|
12840
|
+
verbose
|
|
12841
|
+
} = cli.flags;
|
|
12842
|
+
|
|
12843
|
+
// Set defaults for any flag/arg that is not given. Check socket.json first.
|
|
12844
|
+
if (!bin) {
|
|
12845
|
+
if (sockJson.defaults?.manifest?.gradle?.bin) {
|
|
12846
|
+
bin = sockJson.defaults?.manifest?.gradle?.bin;
|
|
12847
|
+
logger.logger.info(`Using default --bin from ${constants.SOCKET_JSON}:`, bin);
|
|
12848
|
+
} else {
|
|
12849
|
+
// Prefer the project's ./gradlew wrapper, else `gradle` on PATH.
|
|
12850
|
+
bin = resolveBuildToolBin('gradle', cwd);
|
|
12851
|
+
}
|
|
12852
|
+
}
|
|
12853
|
+
if (!gradleOpts) {
|
|
12854
|
+
if (sockJson.defaults?.manifest?.gradle?.gradleOpts) {
|
|
12855
|
+
gradleOpts = sockJson.defaults?.manifest?.gradle?.gradleOpts;
|
|
12856
|
+
logger.logger.info(`Using default --gradle-opts from ${constants.SOCKET_JSON}:`, gradleOpts);
|
|
12857
|
+
} else {
|
|
12858
|
+
gradleOpts = '';
|
|
12859
|
+
}
|
|
12860
|
+
}
|
|
12861
|
+
if (verbose === undefined) {
|
|
12862
|
+
if (sockJson.defaults?.manifest?.gradle?.verbose !== undefined) {
|
|
12863
|
+
verbose = sockJson.defaults?.manifest?.gradle?.verbose;
|
|
12864
|
+
logger.logger.info(`Using default --verbose from ${constants.SOCKET_JSON}:`, verbose);
|
|
12865
|
+
} else {
|
|
12866
|
+
verbose = false;
|
|
12867
|
+
}
|
|
12868
|
+
}
|
|
12869
|
+
if (facts === undefined) {
|
|
12870
|
+
if (sockJson.defaults?.manifest?.gradle?.facts !== undefined) {
|
|
12871
|
+
facts = sockJson.defaults?.manifest?.gradle?.facts;
|
|
12872
|
+
logger.logger.info(`Using default --facts from ${constants.SOCKET_JSON}:`, facts);
|
|
12873
|
+
} else {
|
|
12874
|
+
// Socket facts generation is the default; pass --pom to generate poms.
|
|
12875
|
+
facts = true;
|
|
12876
|
+
}
|
|
12877
|
+
}
|
|
12878
|
+
// --pom opts into legacy pom.xml generation. It overrides the facts default
|
|
12879
|
+
// (and the socket.json default) but conflicts with an explicit --facts.
|
|
12880
|
+
if (cli.flags['pom']) {
|
|
12881
|
+
if (cli.flags['facts'] !== undefined) {
|
|
12882
|
+
logger.logger.warn('The `--facts` and `--pom` options are mutually exclusive; generating Socket facts.');
|
|
12883
|
+
} else {
|
|
12884
|
+
facts = false;
|
|
12885
|
+
}
|
|
12886
|
+
}
|
|
12887
|
+
if (includeConfigs === undefined) {
|
|
12888
|
+
if (sockJson.defaults?.manifest?.gradle?.includeConfigs) {
|
|
12889
|
+
includeConfigs = sockJson.defaults.manifest.gradle.includeConfigs;
|
|
12890
|
+
logger.logger.info(`Using default --include-configs from ${constants.SOCKET_JSON}:`, includeConfigs);
|
|
12891
|
+
} else {
|
|
12892
|
+
includeConfigs = '';
|
|
12893
|
+
}
|
|
12894
|
+
}
|
|
12895
|
+
if (excludeConfigs === undefined) {
|
|
12896
|
+
if (sockJson.defaults?.manifest?.gradle?.excludeConfigs) {
|
|
12897
|
+
excludeConfigs = sockJson.defaults.manifest.gradle.excludeConfigs;
|
|
12898
|
+
logger.logger.info(`Using default --exclude-configs from ${constants.SOCKET_JSON}:`, excludeConfigs);
|
|
12899
|
+
} else {
|
|
12900
|
+
excludeConfigs = '';
|
|
12901
|
+
}
|
|
12902
|
+
}
|
|
12903
|
+
if (ignoreUnresolved === undefined) {
|
|
12904
|
+
if (sockJson.defaults?.manifest?.gradle?.ignoreUnresolved !== undefined) {
|
|
12905
|
+
ignoreUnresolved = sockJson.defaults?.manifest?.gradle?.ignoreUnresolved;
|
|
12906
|
+
logger.logger.info(`Using default --ignore-unresolved from ${constants.SOCKET_JSON}:`, ignoreUnresolved);
|
|
12907
|
+
} else {
|
|
12908
|
+
ignoreUnresolved = false;
|
|
12909
|
+
}
|
|
12910
|
+
}
|
|
12911
|
+
|
|
12912
|
+
// `--include-configs`, `--exclude-configs`, and `--ignore-unresolved` only
|
|
12913
|
+
// affect facts generation; the pom path has no equivalent knobs.
|
|
12914
|
+
if (!facts && (cli.flags['includeConfigs'] !== undefined || cli.flags['excludeConfigs'] !== undefined || cli.flags['ignoreUnresolved'] !== undefined)) {
|
|
12915
|
+
logger.logger.warn('The `--include-configs`, `--exclude-configs`, and `--ignore-unresolved` options only apply when generating Socket facts (not with `--pom`); ignoring them.');
|
|
12916
|
+
}
|
|
12917
|
+
if (verbose) {
|
|
12918
|
+
logger.logger.group('- ', parentName, config$b.commandName, ':');
|
|
12919
|
+
logger.logger.group('- flags:', cli.flags);
|
|
12920
|
+
logger.logger.groupEnd();
|
|
12921
|
+
logger.logger.log('- input:', cli.input);
|
|
12922
|
+
logger.logger.groupEnd();
|
|
12923
|
+
}
|
|
12924
|
+
|
|
12925
|
+
// TODO: We're not sure it's feasible to parse source file from stdin. We could
|
|
12926
|
+
// try, store contents in a file in some folder, target that folder... what
|
|
12927
|
+
// would the file name be?
|
|
12928
|
+
|
|
12929
|
+
const wasValidInput = utils.checkCommandInput(outputKind, {
|
|
12930
|
+
nook: true,
|
|
12931
|
+
test: cli.input.length <= 1,
|
|
12932
|
+
message: 'Can only accept one DIR (make sure to escape spaces!)',
|
|
12933
|
+
fail: 'received ' + cli.input.length
|
|
12934
|
+
});
|
|
12935
|
+
if (!wasValidInput) {
|
|
12936
|
+
return;
|
|
12937
|
+
}
|
|
12938
|
+
const javaHome = sockJson.defaults?.manifest?.gradle?.javaHome ?? undefined;
|
|
12939
|
+
if (verbose) {
|
|
12940
|
+
logger.logger.group();
|
|
12941
|
+
logger.logger.info('- cwd:', cwd);
|
|
12942
|
+
logger.logger.info('- gradle bin:', bin);
|
|
12943
|
+
logger.logger.info('- java home:', javaHome || '(inherited)');
|
|
12944
|
+
logger.logger.groupEnd();
|
|
12945
|
+
}
|
|
12946
|
+
if (dryRun) {
|
|
12947
|
+
logger.logger.log(constants.default.DRY_RUN_BAILING_NOW);
|
|
12948
|
+
return;
|
|
12949
|
+
}
|
|
12950
|
+
const parsedGradleOpts = parseBuildToolOpts(String(gradleOpts || ''));
|
|
12951
|
+
const excludePaths = utils.cmdFlagValueToArray(cli.flags['excludePaths']);
|
|
12952
|
+
assertValidExcludePaths(excludePaths);
|
|
12953
|
+
if (facts) {
|
|
12954
|
+
await convertGradleToFacts({
|
|
12955
|
+
bin: String(bin),
|
|
12956
|
+
cwd,
|
|
12957
|
+
excludeConfigs: String(excludeConfigs || ''),
|
|
12958
|
+
excludePaths,
|
|
12959
|
+
gradleOpts: parsedGradleOpts,
|
|
12960
|
+
ignoreUnresolved: Boolean(ignoreUnresolved),
|
|
12961
|
+
includeConfigs: String(includeConfigs || ''),
|
|
12962
|
+
javaHome,
|
|
12963
|
+
verbose: Boolean(verbose)
|
|
12964
|
+
});
|
|
12965
|
+
return;
|
|
12966
|
+
}
|
|
12967
|
+
await convertGradleToMaven({
|
|
12968
|
+
bin: String(bin),
|
|
12969
|
+
cwd,
|
|
12970
|
+
excludePaths,
|
|
12971
|
+
gradleOpts: parsedGradleOpts,
|
|
12972
|
+
verbose: Boolean(verbose)
|
|
12973
|
+
});
|
|
12974
|
+
}
|
|
12975
|
+
|
|
12976
|
+
const config$a = {
|
|
12977
|
+
commandName: 'maven',
|
|
12978
|
+
description: '[beta] Generate a Socket facts file from a Maven `pom.xml` project',
|
|
12979
|
+
hidden: false,
|
|
12980
|
+
flags: {
|
|
12981
|
+
...flags.commonFlags,
|
|
12982
|
+
bin: {
|
|
12983
|
+
type: 'string',
|
|
12984
|
+
description: 'Location of the maven binary to use, default: ./mvnw if present, else mvn on PATH'
|
|
12985
|
+
},
|
|
12374
12986
|
includeConfigs: {
|
|
12375
12987
|
type: 'string',
|
|
12376
12988
|
description: 'Comma-separated glob patterns matched against Maven dependency scopes (case-sensitive; `*`, `?`, and `[...]` wildcards). Only scopes matching at least one pattern are resolved. e.g. `compile,runtime`. Default: every scope'
|
|
@@ -12379,7 +12991,7 @@ const config$a = {
|
|
|
12379
12991
|
type: 'string',
|
|
12380
12992
|
description: 'Comma-separated glob patterns; Maven scopes matching any pattern are skipped (applied after --include-configs)'
|
|
12381
12993
|
},
|
|
12382
|
-
...excludePathsFlag,
|
|
12994
|
+
...excludePathsFlag$1,
|
|
12383
12995
|
ignoreUnresolved: {
|
|
12384
12996
|
type: 'boolean',
|
|
12385
12997
|
description: 'Warn on unresolved dependencies instead of failing the run (unresolved deps are not emitted to the facts file)'
|
|
@@ -12477,16 +13089,16 @@ async function run$B(argv, importMeta, {
|
|
|
12477
13089
|
}
|
|
12478
13090
|
}
|
|
12479
13091
|
if (includeConfigs === undefined) {
|
|
12480
|
-
if (sockJson.defaults?.manifest?.maven?.includeConfigs
|
|
12481
|
-
includeConfigs = sockJson.defaults
|
|
13092
|
+
if (sockJson.defaults?.manifest?.maven?.includeConfigs) {
|
|
13093
|
+
includeConfigs = sockJson.defaults.manifest.maven.includeConfigs;
|
|
12482
13094
|
logger.logger.info(`Using default --include-configs from ${constants.SOCKET_JSON}:`, includeConfigs);
|
|
12483
13095
|
} else {
|
|
12484
13096
|
includeConfigs = '';
|
|
12485
13097
|
}
|
|
12486
13098
|
}
|
|
12487
13099
|
if (excludeConfigs === undefined) {
|
|
12488
|
-
if (sockJson.defaults?.manifest?.maven?.excludeConfigs
|
|
12489
|
-
excludeConfigs = sockJson.defaults
|
|
13100
|
+
if (sockJson.defaults?.manifest?.maven?.excludeConfigs) {
|
|
13101
|
+
excludeConfigs = sockJson.defaults.manifest.maven.excludeConfigs;
|
|
12490
13102
|
logger.logger.info(`Using default --exclude-configs from ${constants.SOCKET_JSON}:`, excludeConfigs);
|
|
12491
13103
|
} else {
|
|
12492
13104
|
excludeConfigs = '';
|
|
@@ -12524,10 +13136,12 @@ async function run$B(argv, importMeta, {
|
|
|
12524
13136
|
if (!wasValidInput) {
|
|
12525
13137
|
return;
|
|
12526
13138
|
}
|
|
13139
|
+
const javaHome = sockJson.defaults?.manifest?.maven?.javaHome ?? undefined;
|
|
12527
13140
|
if (verbose) {
|
|
12528
13141
|
logger.logger.group();
|
|
12529
13142
|
logger.logger.info('- cwd:', cwd);
|
|
12530
13143
|
logger.logger.info('- maven bin:', bin);
|
|
13144
|
+
logger.logger.info('- java home:', javaHome || '(inherited)');
|
|
12531
13145
|
logger.logger.groupEnd();
|
|
12532
13146
|
}
|
|
12533
13147
|
if (dryRun) {
|
|
@@ -12544,6 +13158,7 @@ async function run$B(argv, importMeta, {
|
|
|
12544
13158
|
excludePaths,
|
|
12545
13159
|
ignoreUnresolved: Boolean(ignoreUnresolved),
|
|
12546
13160
|
includeConfigs: String(includeConfigs || ''),
|
|
13161
|
+
javaHome,
|
|
12547
13162
|
mavenOpts: parsedMavenOpts,
|
|
12548
13163
|
verbose: Boolean(verbose)
|
|
12549
13164
|
});
|
|
@@ -12575,7 +13190,7 @@ const config$9 = {
|
|
|
12575
13190
|
type: 'string',
|
|
12576
13191
|
description: 'When generating facts: comma-separated glob patterns; sbt configurations matching any pattern are skipped (applied after --include-configs)'
|
|
12577
13192
|
},
|
|
12578
|
-
...excludePathsFlag,
|
|
13193
|
+
...excludePathsFlag$1,
|
|
12579
13194
|
ignoreUnresolved: {
|
|
12580
13195
|
type: 'boolean',
|
|
12581
13196
|
description: 'When generating facts: warn on unresolved dependencies instead of failing the run (unresolved deps are not emitted to the facts file)'
|
|
@@ -12713,16 +13328,16 @@ async function run$A(argv, importMeta, {
|
|
|
12713
13328
|
}
|
|
12714
13329
|
}
|
|
12715
13330
|
if (includeConfigs === undefined) {
|
|
12716
|
-
if (sockJson.defaults?.manifest?.sbt?.includeConfigs
|
|
12717
|
-
includeConfigs = sockJson.defaults
|
|
13331
|
+
if (sockJson.defaults?.manifest?.sbt?.includeConfigs) {
|
|
13332
|
+
includeConfigs = sockJson.defaults.manifest.sbt.includeConfigs;
|
|
12718
13333
|
logger.logger.info(`Using default --include-configs from ${constants.SOCKET_JSON}:`, includeConfigs);
|
|
12719
13334
|
} else {
|
|
12720
13335
|
includeConfigs = '';
|
|
12721
13336
|
}
|
|
12722
13337
|
}
|
|
12723
13338
|
if (excludeConfigs === undefined) {
|
|
12724
|
-
if (sockJson.defaults?.manifest?.sbt?.excludeConfigs
|
|
12725
|
-
excludeConfigs = sockJson.defaults
|
|
13339
|
+
if (sockJson.defaults?.manifest?.sbt?.excludeConfigs) {
|
|
13340
|
+
excludeConfigs = sockJson.defaults.manifest.sbt.excludeConfigs;
|
|
12726
13341
|
logger.logger.info(`Using default --exclude-configs from ${constants.SOCKET_JSON}:`, excludeConfigs);
|
|
12727
13342
|
} else {
|
|
12728
13343
|
excludeConfigs = '';
|
|
@@ -12803,11 +13418,13 @@ async function run$A(argv, importMeta, {
|
|
|
12803
13418
|
if (!wasValidInput) {
|
|
12804
13419
|
return;
|
|
12805
13420
|
}
|
|
13421
|
+
const javaHome = sockJson.defaults?.manifest?.sbt?.javaHome ?? undefined;
|
|
12806
13422
|
if (verbose) {
|
|
12807
13423
|
logger.logger.group();
|
|
12808
13424
|
logger.logger.log('- target:', cwd);
|
|
12809
13425
|
logger.logger.log('- sbt bin:', bin);
|
|
12810
13426
|
logger.logger.log('- out:', out);
|
|
13427
|
+
logger.logger.log('- java home:', javaHome || '(inherited)');
|
|
12811
13428
|
logger.logger.groupEnd();
|
|
12812
13429
|
}
|
|
12813
13430
|
if (dryRun) {
|
|
@@ -12825,6 +13442,7 @@ async function run$A(argv, importMeta, {
|
|
|
12825
13442
|
excludePaths,
|
|
12826
13443
|
ignoreUnresolved: Boolean(ignoreUnresolved),
|
|
12827
13444
|
includeConfigs: String(includeConfigs || ''),
|
|
13445
|
+
javaHome,
|
|
12828
13446
|
sbtOpts: parsedSbtOpts,
|
|
12829
13447
|
tmpDir,
|
|
12830
13448
|
verbose: Boolean(verbose)
|
|
@@ -12980,7 +13598,7 @@ async function setupManifestConfig(cwd, defaultOnReadError = false) {
|
|
|
12980
13598
|
}
|
|
12981
13599
|
default:
|
|
12982
13600
|
{
|
|
12983
|
-
result = canceledByUser$
|
|
13601
|
+
result = canceledByUser$2();
|
|
12984
13602
|
}
|
|
12985
13603
|
}
|
|
12986
13604
|
if (!result.ok || result.data.canceled) {
|
|
@@ -13003,12 +13621,12 @@ async function setupManifestConfig(cwd, defaultOnReadError = false) {
|
|
|
13003
13621
|
})) {
|
|
13004
13622
|
return await utils.writeSocketJson(cwd, sockJson);
|
|
13005
13623
|
}
|
|
13006
|
-
return canceledByUser$
|
|
13624
|
+
return canceledByUser$2();
|
|
13007
13625
|
}
|
|
13008
13626
|
async function setupConda(config) {
|
|
13009
13627
|
const on = await askForEnabled(!config.disabled);
|
|
13010
13628
|
if (on === undefined) {
|
|
13011
|
-
return canceledByUser$
|
|
13629
|
+
return canceledByUser$2();
|
|
13012
13630
|
} else if (on) {
|
|
13013
13631
|
delete config.disabled;
|
|
13014
13632
|
} else {
|
|
@@ -13016,7 +13634,7 @@ async function setupConda(config) {
|
|
|
13016
13634
|
}
|
|
13017
13635
|
const infile = await askForInputFile(config.infile || 'environment.yml');
|
|
13018
13636
|
if (infile === undefined) {
|
|
13019
|
-
return canceledByUser$
|
|
13637
|
+
return canceledByUser$2();
|
|
13020
13638
|
} else if (infile === '-') {
|
|
13021
13639
|
config.stdin = true;
|
|
13022
13640
|
} else {
|
|
@@ -13029,7 +13647,7 @@ async function setupConda(config) {
|
|
|
13029
13647
|
}
|
|
13030
13648
|
const stdout = await askForStdout(config.stdout);
|
|
13031
13649
|
if (stdout === undefined) {
|
|
13032
|
-
return canceledByUser$
|
|
13650
|
+
return canceledByUser$2();
|
|
13033
13651
|
} else if (stdout === 'yes') {
|
|
13034
13652
|
config.stdout = true;
|
|
13035
13653
|
} else if (stdout === 'no') {
|
|
@@ -13040,7 +13658,7 @@ async function setupConda(config) {
|
|
|
13040
13658
|
if (!config.stdout) {
|
|
13041
13659
|
const out = await askForOutputFile(config.outfile || constants.REQUIREMENTS_TXT);
|
|
13042
13660
|
if (out === undefined) {
|
|
13043
|
-
return canceledByUser$
|
|
13661
|
+
return canceledByUser$2();
|
|
13044
13662
|
} else if (out === '-') {
|
|
13045
13663
|
config.stdout = true;
|
|
13046
13664
|
} else {
|
|
@@ -13054,23 +13672,43 @@ async function setupConda(config) {
|
|
|
13054
13672
|
}
|
|
13055
13673
|
const verbose = await askForVerboseFlag(config.verbose);
|
|
13056
13674
|
if (verbose === undefined) {
|
|
13057
|
-
return canceledByUser$
|
|
13675
|
+
return canceledByUser$2();
|
|
13058
13676
|
} else if (verbose === 'yes' || verbose === 'no') {
|
|
13059
13677
|
config.verbose = verbose === 'yes';
|
|
13060
13678
|
} else {
|
|
13061
13679
|
delete config.verbose;
|
|
13062
13680
|
}
|
|
13063
|
-
return notCanceled$
|
|
13681
|
+
return notCanceled$2();
|
|
13064
13682
|
}
|
|
13065
|
-
async function setupGradle(config
|
|
13066
|
-
|
|
13683
|
+
async function setupGradle(config,
|
|
13684
|
+
// Set by the recursive dynamic-sbom-inference wizard, which has no
|
|
13685
|
+
// pom-mode equivalent - skips the facts/pom question entirely.
|
|
13686
|
+
{
|
|
13687
|
+
factsOnly = false
|
|
13688
|
+
} = {}) {
|
|
13689
|
+
const priorBin = config.bin;
|
|
13690
|
+
const bin = await askForBin(config.bin || '', './gradlew');
|
|
13067
13691
|
if (bin === undefined) {
|
|
13068
|
-
return canceledByUser$
|
|
13692
|
+
return canceledByUser$2();
|
|
13069
13693
|
} else if (bin) {
|
|
13070
13694
|
config.bin = bin;
|
|
13695
|
+
} else if (priorBin !== undefined) {
|
|
13696
|
+
config.bin = null;
|
|
13071
13697
|
} else {
|
|
13072
13698
|
delete config.bin;
|
|
13073
13699
|
}
|
|
13700
|
+
const priorJavaHome = config.javaHome;
|
|
13701
|
+
const javaHome = await askForJavaHome(config.javaHome || '');
|
|
13702
|
+
if (javaHome === undefined) {
|
|
13703
|
+
return canceledByUser$2();
|
|
13704
|
+
} else if (javaHome) {
|
|
13705
|
+
config.javaHome = javaHome;
|
|
13706
|
+
} else if (priorJavaHome !== undefined) {
|
|
13707
|
+
config.javaHome = null;
|
|
13708
|
+
} else {
|
|
13709
|
+
delete config.javaHome;
|
|
13710
|
+
}
|
|
13711
|
+
const priorGradleOpts = config.gradleOpts;
|
|
13074
13712
|
const opts = await prompts.input({
|
|
13075
13713
|
message: '(--gradle-opts) Enter gradle options to pass through',
|
|
13076
13714
|
default: config.gradleOpts || '',
|
|
@@ -13078,24 +13716,28 @@ async function setupGradle(config) {
|
|
|
13078
13716
|
// validate: async string => bool
|
|
13079
13717
|
});
|
|
13080
13718
|
if (opts === undefined) {
|
|
13081
|
-
return canceledByUser$
|
|
13719
|
+
return canceledByUser$2();
|
|
13082
13720
|
} else if (opts) {
|
|
13083
13721
|
config.gradleOpts = opts;
|
|
13722
|
+
} else if (priorGradleOpts !== undefined) {
|
|
13723
|
+
config.gradleOpts = null;
|
|
13084
13724
|
} else {
|
|
13085
13725
|
delete config.gradleOpts;
|
|
13086
13726
|
}
|
|
13087
|
-
|
|
13088
|
-
|
|
13089
|
-
|
|
13090
|
-
|
|
13091
|
-
|
|
13092
|
-
|
|
13093
|
-
|
|
13727
|
+
if (!factsOnly) {
|
|
13728
|
+
const facts = await askForFactsFlag(config.facts);
|
|
13729
|
+
if (facts === undefined) {
|
|
13730
|
+
return canceledByUser$2();
|
|
13731
|
+
} else if (facts === 'yes' || facts === 'no') {
|
|
13732
|
+
config.facts = facts === 'yes';
|
|
13733
|
+
} else {
|
|
13734
|
+
delete config.facts;
|
|
13735
|
+
}
|
|
13094
13736
|
}
|
|
13095
13737
|
|
|
13096
13738
|
// The config filters and --ignore-unresolved only apply to facts generation
|
|
13097
13739
|
// (the default); skip them when pom generation (--pom) is selected.
|
|
13098
|
-
if (config.facts !== false) {
|
|
13740
|
+
if (factsOnly || config.facts !== false) {
|
|
13099
13741
|
const factsOptions = await setupFactsOptions(config);
|
|
13100
13742
|
if (!factsOptions.ok || factsOptions.data.canceled) {
|
|
13101
13743
|
return factsOptions;
|
|
@@ -13103,32 +13745,49 @@ async function setupGradle(config) {
|
|
|
13103
13745
|
}
|
|
13104
13746
|
const verbose = await askForVerboseFlag(config.verbose);
|
|
13105
13747
|
if (verbose === undefined) {
|
|
13106
|
-
return canceledByUser$
|
|
13748
|
+
return canceledByUser$2();
|
|
13107
13749
|
} else if (verbose === 'yes' || verbose === 'no') {
|
|
13108
13750
|
config.verbose = verbose === 'yes';
|
|
13109
13751
|
} else {
|
|
13110
13752
|
delete config.verbose;
|
|
13111
13753
|
}
|
|
13112
|
-
return notCanceled$
|
|
13754
|
+
return notCanceled$2();
|
|
13113
13755
|
}
|
|
13114
13756
|
async function setupMaven(config) {
|
|
13115
|
-
const
|
|
13757
|
+
const priorBin = config.bin;
|
|
13758
|
+
const bin = await askForBin(config.bin || '', 'mvn');
|
|
13116
13759
|
if (bin === undefined) {
|
|
13117
|
-
return canceledByUser$
|
|
13760
|
+
return canceledByUser$2();
|
|
13118
13761
|
} else if (bin) {
|
|
13119
13762
|
config.bin = bin;
|
|
13763
|
+
} else if (priorBin !== undefined) {
|
|
13764
|
+
config.bin = null;
|
|
13120
13765
|
} else {
|
|
13121
13766
|
delete config.bin;
|
|
13122
13767
|
}
|
|
13768
|
+
const priorJavaHome = config.javaHome;
|
|
13769
|
+
const javaHome = await askForJavaHome(config.javaHome || '');
|
|
13770
|
+
if (javaHome === undefined) {
|
|
13771
|
+
return canceledByUser$2();
|
|
13772
|
+
} else if (javaHome) {
|
|
13773
|
+
config.javaHome = javaHome;
|
|
13774
|
+
} else if (priorJavaHome !== undefined) {
|
|
13775
|
+
config.javaHome = null;
|
|
13776
|
+
} else {
|
|
13777
|
+
delete config.javaHome;
|
|
13778
|
+
}
|
|
13779
|
+
const priorMavenOpts = config.mavenOpts;
|
|
13123
13780
|
const opts = await prompts.input({
|
|
13124
13781
|
message: '(--maven-opts) Enter maven options to pass through',
|
|
13125
13782
|
default: config.mavenOpts || '',
|
|
13126
13783
|
required: false
|
|
13127
13784
|
});
|
|
13128
13785
|
if (opts === undefined) {
|
|
13129
|
-
return canceledByUser$
|
|
13786
|
+
return canceledByUser$2();
|
|
13130
13787
|
} else if (opts) {
|
|
13131
13788
|
config.mavenOpts = opts;
|
|
13789
|
+
} else if (priorMavenOpts !== undefined) {
|
|
13790
|
+
config.mavenOpts = null;
|
|
13132
13791
|
} else {
|
|
13133
13792
|
delete config.mavenOpts;
|
|
13134
13793
|
}
|
|
@@ -13141,23 +13800,42 @@ async function setupMaven(config) {
|
|
|
13141
13800
|
}
|
|
13142
13801
|
const verbose = await askForVerboseFlag(config.verbose);
|
|
13143
13802
|
if (verbose === undefined) {
|
|
13144
|
-
return canceledByUser$
|
|
13803
|
+
return canceledByUser$2();
|
|
13145
13804
|
} else if (verbose === 'yes' || verbose === 'no') {
|
|
13146
13805
|
config.verbose = verbose === 'yes';
|
|
13147
13806
|
} else {
|
|
13148
13807
|
delete config.verbose;
|
|
13149
13808
|
}
|
|
13150
|
-
return notCanceled$
|
|
13809
|
+
return notCanceled$2();
|
|
13151
13810
|
}
|
|
13152
|
-
async function setupSbt(config
|
|
13153
|
-
|
|
13811
|
+
async function setupSbt(config,
|
|
13812
|
+
// See setupGradle's matching parameter for why.
|
|
13813
|
+
{
|
|
13814
|
+
factsOnly = false
|
|
13815
|
+
} = {}) {
|
|
13816
|
+
const priorBin = config.bin;
|
|
13817
|
+
const bin = await askForBin(config.bin || '', 'sbt');
|
|
13154
13818
|
if (bin === undefined) {
|
|
13155
|
-
return canceledByUser$
|
|
13819
|
+
return canceledByUser$2();
|
|
13156
13820
|
} else if (bin) {
|
|
13157
13821
|
config.bin = bin;
|
|
13822
|
+
} else if (priorBin !== undefined) {
|
|
13823
|
+
config.bin = null;
|
|
13158
13824
|
} else {
|
|
13159
13825
|
delete config.bin;
|
|
13160
13826
|
}
|
|
13827
|
+
const priorJavaHome = config.javaHome;
|
|
13828
|
+
const javaHome = await askForJavaHome(config.javaHome || '');
|
|
13829
|
+
if (javaHome === undefined) {
|
|
13830
|
+
return canceledByUser$2();
|
|
13831
|
+
} else if (javaHome) {
|
|
13832
|
+
config.javaHome = javaHome;
|
|
13833
|
+
} else if (priorJavaHome !== undefined) {
|
|
13834
|
+
config.javaHome = null;
|
|
13835
|
+
} else {
|
|
13836
|
+
delete config.javaHome;
|
|
13837
|
+
}
|
|
13838
|
+
const priorSbtOpts = config.sbtOpts;
|
|
13161
13839
|
const opts = await prompts.input({
|
|
13162
13840
|
message: '(--sbt-opts) Enter sbt options to pass through',
|
|
13163
13841
|
default: config.sbtOpts || '',
|
|
@@ -13165,28 +13843,32 @@ async function setupSbt(config) {
|
|
|
13165
13843
|
// validate: async string => bool
|
|
13166
13844
|
});
|
|
13167
13845
|
if (opts === undefined) {
|
|
13168
|
-
return canceledByUser$
|
|
13846
|
+
return canceledByUser$2();
|
|
13169
13847
|
} else if (opts) {
|
|
13170
13848
|
config.sbtOpts = opts;
|
|
13849
|
+
} else if (priorSbtOpts !== undefined) {
|
|
13850
|
+
config.sbtOpts = null;
|
|
13171
13851
|
} else {
|
|
13172
13852
|
delete config.sbtOpts;
|
|
13173
13853
|
}
|
|
13174
|
-
|
|
13175
|
-
|
|
13176
|
-
|
|
13177
|
-
|
|
13178
|
-
|
|
13179
|
-
|
|
13180
|
-
|
|
13854
|
+
if (!factsOnly) {
|
|
13855
|
+
const facts = await askForFactsFlag(config.facts);
|
|
13856
|
+
if (facts === undefined) {
|
|
13857
|
+
return canceledByUser$2();
|
|
13858
|
+
} else if (facts === 'yes' || facts === 'no') {
|
|
13859
|
+
config.facts = facts === 'yes';
|
|
13860
|
+
} else {
|
|
13861
|
+
delete config.facts;
|
|
13862
|
+
}
|
|
13181
13863
|
}
|
|
13182
13864
|
|
|
13183
13865
|
// Socket facts is the default. The pom output questions (stdout/outfile)
|
|
13184
13866
|
// only apply when pom generation (--pom) is explicitly selected; otherwise
|
|
13185
13867
|
// ask the facts-only options.
|
|
13186
|
-
if (config.facts === false) {
|
|
13868
|
+
if (!factsOnly && config.facts === false) {
|
|
13187
13869
|
const stdout = await askForStdout(config.stdout);
|
|
13188
13870
|
if (stdout === undefined) {
|
|
13189
|
-
return canceledByUser$
|
|
13871
|
+
return canceledByUser$2();
|
|
13190
13872
|
} else if (stdout === 'yes') {
|
|
13191
13873
|
config.stdout = true;
|
|
13192
13874
|
} else if (stdout === 'no') {
|
|
@@ -13197,7 +13879,7 @@ async function setupSbt(config) {
|
|
|
13197
13879
|
if (config.stdout !== true) {
|
|
13198
13880
|
const out = await askForOutputFile(config.outfile || 'sbt.pom.xml');
|
|
13199
13881
|
if (out === undefined) {
|
|
13200
|
-
return canceledByUser$
|
|
13882
|
+
return canceledByUser$2();
|
|
13201
13883
|
} else if (out === '-') {
|
|
13202
13884
|
config.stdout = true;
|
|
13203
13885
|
} else {
|
|
@@ -13217,13 +13899,13 @@ async function setupSbt(config) {
|
|
|
13217
13899
|
}
|
|
13218
13900
|
const verbose = await askForVerboseFlag(config.verbose);
|
|
13219
13901
|
if (verbose === undefined) {
|
|
13220
|
-
return canceledByUser$
|
|
13902
|
+
return canceledByUser$2();
|
|
13221
13903
|
} else if (verbose === 'yes' || verbose === 'no') {
|
|
13222
13904
|
config.verbose = verbose === 'yes';
|
|
13223
13905
|
} else {
|
|
13224
13906
|
delete config.verbose;
|
|
13225
13907
|
}
|
|
13226
|
-
return notCanceled$
|
|
13908
|
+
return notCanceled$2();
|
|
13227
13909
|
}
|
|
13228
13910
|
async function askForStdout(defaultValue) {
|
|
13229
13911
|
return await prompts.select({
|
|
@@ -13279,14 +13961,25 @@ async function askForOutputFile(defaultName = '') {
|
|
|
13279
13961
|
// validate: async string => bool
|
|
13280
13962
|
});
|
|
13281
13963
|
}
|
|
13282
|
-
|
|
13964
|
+
|
|
13965
|
+
// `defaultName` must be a prior explicit/cascaded value, never the tool's
|
|
13966
|
+
// hardcoded fallback (e.g. `mvn`) - pre-filling that would freeze it into
|
|
13967
|
+
// socket.json on a bare Enter. `fallbackHint` is informational text only.
|
|
13968
|
+
async function askForBin(defaultName = '', fallbackHint = '') {
|
|
13283
13969
|
return await prompts.input({
|
|
13284
|
-
message: '(--bin) What should be the command to execute? Usually your build binary.' + (defaultName ? ' (Backspace to leave default)' : ''),
|
|
13970
|
+
message: '(--bin) What should be the command to execute? Usually your build binary.' + (defaultName ? ' (Backspace to leave default)' : fallbackHint ? ` (blank = ${fallbackHint})` : ''),
|
|
13285
13971
|
default: defaultName,
|
|
13286
13972
|
required: false
|
|
13287
13973
|
// validate: async string => bool
|
|
13288
13974
|
});
|
|
13289
13975
|
}
|
|
13976
|
+
async function askForJavaHome(defaultName = '') {
|
|
13977
|
+
return await prompts.input({
|
|
13978
|
+
message: 'What JDK should this build tool use? Leave blank to use the JDK already on PATH/JAVA_HOME. Supports $VAR/${VAR}.' + (defaultName ? ' (Backspace to leave default)' : ''),
|
|
13979
|
+
default: defaultName,
|
|
13980
|
+
required: false
|
|
13981
|
+
});
|
|
13982
|
+
}
|
|
13290
13983
|
async function askForVerboseFlag(current) {
|
|
13291
13984
|
return await prompts.select({
|
|
13292
13985
|
message: '(--verbose) Should this run in verbose mode by default?',
|
|
@@ -13348,62 +14041,897 @@ async function askForIgnoreUnresolvedFlag(current) {
|
|
|
13348
14041
|
// Prompts for the facts-only options shared by gradle and sbt: the config
|
|
13349
14042
|
// include/exclude filters and --ignore-unresolved. Mutates `config` in place.
|
|
13350
14043
|
async function setupFactsOptions(config) {
|
|
14044
|
+
const priorIncludeConfigs = config.includeConfigs;
|
|
13351
14045
|
const includeConfigs = await prompts.input({
|
|
13352
14046
|
message: '(--include-configs) Comma-separated config-name globs to resolve (blank = all configurations)',
|
|
13353
14047
|
default: config.includeConfigs || '',
|
|
13354
14048
|
required: false
|
|
13355
14049
|
});
|
|
13356
14050
|
if (includeConfigs === undefined) {
|
|
13357
|
-
return canceledByUser$
|
|
14051
|
+
return canceledByUser$2();
|
|
13358
14052
|
} else if (includeConfigs) {
|
|
13359
14053
|
config.includeConfigs = includeConfigs;
|
|
14054
|
+
} else if (priorIncludeConfigs !== undefined) {
|
|
14055
|
+
// Was previously set; clear it explicitly instead of just deleting the
|
|
14056
|
+
// key, so it doesn't silently start inheriting an ancestor's value again.
|
|
14057
|
+
config.includeConfigs = null;
|
|
13360
14058
|
} else {
|
|
13361
14059
|
delete config.includeConfigs;
|
|
13362
14060
|
}
|
|
14061
|
+
const priorExcludeConfigs = config.excludeConfigs;
|
|
13363
14062
|
const excludeConfigs = await prompts.input({
|
|
13364
14063
|
message: '(--exclude-configs) Comma-separated config-name globs to skip (blank = none)',
|
|
13365
14064
|
default: config.excludeConfigs || '',
|
|
13366
14065
|
required: false
|
|
13367
14066
|
});
|
|
13368
|
-
if (excludeConfigs === undefined) {
|
|
13369
|
-
return canceledByUser$
|
|
13370
|
-
} else if (excludeConfigs) {
|
|
13371
|
-
config.excludeConfigs = excludeConfigs;
|
|
13372
|
-
} else {
|
|
13373
|
-
|
|
14067
|
+
if (excludeConfigs === undefined) {
|
|
14068
|
+
return canceledByUser$2();
|
|
14069
|
+
} else if (excludeConfigs) {
|
|
14070
|
+
config.excludeConfigs = excludeConfigs;
|
|
14071
|
+
} else if (priorExcludeConfigs !== undefined) {
|
|
14072
|
+
config.excludeConfigs = null;
|
|
14073
|
+
} else {
|
|
14074
|
+
delete config.excludeConfigs;
|
|
14075
|
+
}
|
|
14076
|
+
const ignoreUnresolved = await askForIgnoreUnresolvedFlag(config.ignoreUnresolved);
|
|
14077
|
+
if (ignoreUnresolved === undefined) {
|
|
14078
|
+
return canceledByUser$2();
|
|
14079
|
+
} else if (ignoreUnresolved === 'yes' || ignoreUnresolved === 'no') {
|
|
14080
|
+
config.ignoreUnresolved = ignoreUnresolved === 'yes';
|
|
14081
|
+
} else {
|
|
14082
|
+
delete config.ignoreUnresolved;
|
|
14083
|
+
}
|
|
14084
|
+
return notCanceled$2();
|
|
14085
|
+
}
|
|
14086
|
+
function canceledByUser$2() {
|
|
14087
|
+
logger.logger.log('');
|
|
14088
|
+
logger.logger.info('User canceled');
|
|
14089
|
+
logger.logger.log('');
|
|
14090
|
+
return {
|
|
14091
|
+
ok: true,
|
|
14092
|
+
data: {
|
|
14093
|
+
canceled: true
|
|
14094
|
+
}
|
|
14095
|
+
};
|
|
14096
|
+
}
|
|
14097
|
+
function notCanceled$2() {
|
|
14098
|
+
return {
|
|
14099
|
+
ok: true,
|
|
14100
|
+
data: {
|
|
14101
|
+
canceled: false
|
|
14102
|
+
}
|
|
14103
|
+
};
|
|
14104
|
+
}
|
|
14105
|
+
|
|
14106
|
+
// Cheap subproject discovery (no dependency resolution) for
|
|
14107
|
+
// `socket manifest setup --dynamic-sbom-inference`; dynamic-sbom-inference
|
|
14108
|
+
// itself instead gets this list for free as a side effect of its own full
|
|
14109
|
+
// facts run.
|
|
14110
|
+
async function enumerateWorkspaces({
|
|
14111
|
+
bin,
|
|
14112
|
+
buildOpts,
|
|
14113
|
+
cwd,
|
|
14114
|
+
ecosystem,
|
|
14115
|
+
excludePaths,
|
|
14116
|
+
javaHome,
|
|
14117
|
+
verbose
|
|
14118
|
+
}) {
|
|
14119
|
+
let resolvedJavaHome;
|
|
14120
|
+
if (javaHome) {
|
|
14121
|
+
const expanded = expandEnvVarRefs(javaHome);
|
|
14122
|
+
if (expanded.missing) {
|
|
14123
|
+
process.exitCode = 1;
|
|
14124
|
+
logger.logger.fail(`javaHome (\`${javaHome}\`) ${formatMissingEnvVarRefs(expanded.missing)}.`);
|
|
14125
|
+
return;
|
|
14126
|
+
}
|
|
14127
|
+
resolvedJavaHome = expanded.value;
|
|
14128
|
+
}
|
|
14129
|
+
const scriptOpts = {
|
|
14130
|
+
bin: bin || undefined,
|
|
14131
|
+
excludePaths: excludePaths?.length ? excludePaths : undefined,
|
|
14132
|
+
// `env` replaces the spawned process's whole environment, not just JAVA_HOME.
|
|
14133
|
+
env: resolvedJavaHome ? {
|
|
14134
|
+
...process.env,
|
|
14135
|
+
JAVA_HOME: resolvedJavaHome
|
|
14136
|
+
} : undefined,
|
|
14137
|
+
projectDir: cwd,
|
|
14138
|
+
stdio: 'pipe',
|
|
14139
|
+
toolOpts: buildOpts
|
|
14140
|
+
};
|
|
14141
|
+
let result;
|
|
14142
|
+
try {
|
|
14143
|
+
result = await enumerateWorkspaces$1(ecosystem, scriptOpts);
|
|
14144
|
+
} catch (e) {
|
|
14145
|
+
process.exitCode = 1;
|
|
14146
|
+
logger.logger.fail(`Could not run the ${ecosystem} build tool` + (' (run with --verbose for details).'));
|
|
14147
|
+
return;
|
|
14148
|
+
}
|
|
14149
|
+
if (!result.projects.length) {
|
|
14150
|
+
process.exitCode = 1;
|
|
14151
|
+
logger.logger.fail(`The ${ecosystem} build produced no workspace records (exit code ${result.code}); every build reports at least its own root project, so the enumeration task did not run.`);
|
|
14152
|
+
return;
|
|
14153
|
+
}
|
|
14154
|
+
return {
|
|
14155
|
+
projects: result.projects
|
|
14156
|
+
};
|
|
14157
|
+
}
|
|
14158
|
+
|
|
14159
|
+
// A single discovered build root - a directory with its own gradle/sbt/maven
|
|
14160
|
+
// marker file.
|
|
14161
|
+
|
|
14162
|
+
const ROOT_ECOSYSTEMS = ['maven', 'gradle', 'sbt'];
|
|
14163
|
+
const ECOSYSTEM_LABELS = {
|
|
14164
|
+
__proto__: null,
|
|
14165
|
+
gradle: 'Gradle',
|
|
14166
|
+
maven: 'Maven',
|
|
14167
|
+
sbt: 'sbt'
|
|
14168
|
+
};
|
|
14169
|
+
|
|
14170
|
+
// The shallowest directory matching `--exclude-paths`, not necessarily a
|
|
14171
|
+
// project dir itself - one write here covers every project beneath it.
|
|
14172
|
+
|
|
14173
|
+
function canceledByUser$1() {
|
|
14174
|
+
logger.logger.log('');
|
|
14175
|
+
logger.logger.info('User canceled');
|
|
14176
|
+
logger.logger.log('');
|
|
14177
|
+
return {
|
|
14178
|
+
ok: true,
|
|
14179
|
+
data: {
|
|
14180
|
+
canceled: true
|
|
14181
|
+
}
|
|
14182
|
+
};
|
|
14183
|
+
}
|
|
14184
|
+
function notCanceled$1() {
|
|
14185
|
+
return {
|
|
14186
|
+
ok: true,
|
|
14187
|
+
data: {
|
|
14188
|
+
canceled: false
|
|
14189
|
+
}
|
|
14190
|
+
};
|
|
14191
|
+
}
|
|
14192
|
+
function getEcosystemSection(sockJson, ecosystem) {
|
|
14193
|
+
return sockJson.defaults?.manifest?.[ecosystem] ?? {};
|
|
14194
|
+
}
|
|
14195
|
+
|
|
14196
|
+
// Depth-then-path sort: for exclusion roots it's just stable log ordering;
|
|
14197
|
+
// for candidates it's load-bearing, since a parent must be prompted before
|
|
14198
|
+
// its children so a child's shown default reflects a parent's just-written
|
|
14199
|
+
// cascade instead of its pre-run value.
|
|
14200
|
+
function sortCandidatesForDisplay(candidates, cwd) {
|
|
14201
|
+
return [...candidates].sort((a, b) => {
|
|
14202
|
+
const relA = path.relative(cwd, a.dir);
|
|
14203
|
+
const relB = path.relative(cwd, b.dir);
|
|
14204
|
+
const depthA = relA.split(path.sep).length;
|
|
14205
|
+
const depthB = relB.split(path.sep).length;
|
|
14206
|
+
if (depthA !== depthB) {
|
|
14207
|
+
return depthA - depthB;
|
|
14208
|
+
}
|
|
14209
|
+
return relA < relB ? -1 : relA > relB ? 1 : 0;
|
|
14210
|
+
});
|
|
14211
|
+
}
|
|
14212
|
+
function toPosixRelative(cwd, dir) {
|
|
14213
|
+
return path.relative(cwd, dir).split(path.sep).join('/');
|
|
14214
|
+
}
|
|
14215
|
+
|
|
14216
|
+
// Shallowest prefix of `relDir` that matches an --exclude-paths pattern -
|
|
14217
|
+
// the directory to write `disabled` to, so one write covers everything
|
|
14218
|
+
// beneath it, even if that directory isn't itself a build root.
|
|
14219
|
+
function findExclusionRoot(relDir, ignorePatterns) {
|
|
14220
|
+
const segments = relDir.split('/');
|
|
14221
|
+
for (let depth = 1; depth <= segments.length; depth += 1) {
|
|
14222
|
+
const prefix = segments.slice(0, depth).join('/');
|
|
14223
|
+
if (vendor.micromatchExports.isMatch(prefix, ignorePatterns, {
|
|
14224
|
+
dot: true
|
|
14225
|
+
})) {
|
|
14226
|
+
return prefix;
|
|
14227
|
+
}
|
|
14228
|
+
}
|
|
14229
|
+
return relDir;
|
|
14230
|
+
}
|
|
14231
|
+
// Both the unfiltered and --exclude-paths-filtered scans, once, so ecosystem
|
|
14232
|
+
// detection and later include/exclude grouping (see discoverBuildRoots)
|
|
14233
|
+
// never re-walk the same tree.
|
|
14234
|
+
async function scanBuildRoots({
|
|
14235
|
+
cwd,
|
|
14236
|
+
excludePaths,
|
|
14237
|
+
sockJson
|
|
14238
|
+
}) {
|
|
14239
|
+
const [fullByTool, includedByTool] = await Promise.all([findBuildToolCandidates({
|
|
14240
|
+
cwd,
|
|
14241
|
+
sockJson
|
|
14242
|
+
}), findBuildToolCandidates({
|
|
14243
|
+
cwd,
|
|
14244
|
+
excludePaths,
|
|
14245
|
+
sockJson
|
|
14246
|
+
})]);
|
|
14247
|
+
return {
|
|
14248
|
+
fullByTool,
|
|
14249
|
+
includedByTool
|
|
14250
|
+
};
|
|
14251
|
+
}
|
|
14252
|
+
// Splits an already-scanned candidate set into `included` (the interactive
|
|
14253
|
+
// walk) and `excluded`, grouped by the shallowest --exclude-paths match (see
|
|
14254
|
+
// findExclusionRoot). `cwd` is excluded from both (it got its own wizard
|
|
14255
|
+
// pass) and realpath-resolved to match findBuildToolCandidates' own
|
|
14256
|
+
// resolution - otherwise macOS's /tmp -> /private/tmp breaks the comparison.
|
|
14257
|
+
async function discoverBuildRoots({
|
|
14258
|
+
cwd,
|
|
14259
|
+
excludePaths,
|
|
14260
|
+
fullByTool,
|
|
14261
|
+
includedByTool
|
|
14262
|
+
}) {
|
|
14263
|
+
const realCwd = await realpathOrResolved(cwd);
|
|
14264
|
+
const included = [];
|
|
14265
|
+
const ignorePatterns = (excludePaths ?? []).flatMap(excludePathToScanIgnores);
|
|
14266
|
+
const ecosystemsByRoot = new Map();
|
|
14267
|
+
for (const [ecosystem, fullDirs] of fullByTool) {
|
|
14268
|
+
const includedDirs = new Set(includedByTool.get(ecosystem) ?? []);
|
|
14269
|
+
for (const dir of fullDirs) {
|
|
14270
|
+
if (dir === realCwd) {
|
|
14271
|
+
continue;
|
|
14272
|
+
}
|
|
14273
|
+
if (includedDirs.has(dir)) {
|
|
14274
|
+
included.push({
|
|
14275
|
+
dir,
|
|
14276
|
+
ecosystem
|
|
14277
|
+
});
|
|
14278
|
+
continue;
|
|
14279
|
+
}
|
|
14280
|
+
const relDir = toPosixRelative(realCwd, dir);
|
|
14281
|
+
const rootRelDir = findExclusionRoot(relDir, ignorePatterns);
|
|
14282
|
+
const rootDir = path.join(realCwd, rootRelDir);
|
|
14283
|
+
const ecosystems = ecosystemsByRoot.get(rootDir) ?? new Set();
|
|
14284
|
+
ecosystems.add(ecosystem);
|
|
14285
|
+
ecosystemsByRoot.set(rootDir, ecosystems);
|
|
14286
|
+
}
|
|
14287
|
+
}
|
|
14288
|
+
return {
|
|
14289
|
+
excluded: [...ecosystemsByRoot].map(([dir, ecosystems]) => ({
|
|
14290
|
+
dir,
|
|
14291
|
+
ecosystems: [...ecosystems].sort()
|
|
14292
|
+
})),
|
|
14293
|
+
included
|
|
14294
|
+
};
|
|
14295
|
+
}
|
|
14296
|
+
|
|
14297
|
+
// Enumerates one build root's declared workspace members (see
|
|
14298
|
+
// enumerate-workspaces.mts) and folds them into `coveredByEcosystem`, so a
|
|
14299
|
+
// later candidate matching one is recognized as a reactor member rather than
|
|
14300
|
+
// independent. Called per-candidate right after its own prompt, not as a
|
|
14301
|
+
// bulk pass, so the build invocation never blocks candidates that don't need
|
|
14302
|
+
// it. A disabled candidate is skipped (no point invoking an off build tool);
|
|
14303
|
+
// otherwise this fails closed, same reasoning as generateRecursiveManifests -
|
|
14304
|
+
// if enumeration fails there's no way to tell covered from independent, so
|
|
14305
|
+
// the caller aborts rather than guess.
|
|
14306
|
+
async function markWorkspaceCoverage({
|
|
14307
|
+
candidate,
|
|
14308
|
+
coveredByEcosystem,
|
|
14309
|
+
cwd,
|
|
14310
|
+
excludePaths,
|
|
14311
|
+
rootSockJson
|
|
14312
|
+
}) {
|
|
14313
|
+
const cascade = utils.readSocketJsonCascade(candidate.dir, cwd, rootSockJson);
|
|
14314
|
+
const {
|
|
14315
|
+
bin,
|
|
14316
|
+
buildOpts,
|
|
14317
|
+
javaHome,
|
|
14318
|
+
skipReason
|
|
14319
|
+
} = resolveEcosystemConfig(candidate.ecosystem, candidate.dir, cascade);
|
|
14320
|
+
if (skipReason) {
|
|
14321
|
+
return {
|
|
14322
|
+
ok: true,
|
|
14323
|
+
data: undefined
|
|
14324
|
+
};
|
|
14325
|
+
}
|
|
14326
|
+
const excludePathsForCandidate = projectIgnorePathsToReachExcludePaths(excludePaths, {
|
|
14327
|
+
cwd,
|
|
14328
|
+
target: candidate.dir
|
|
14329
|
+
});
|
|
14330
|
+
const enumResult = await enumerateWorkspaces({
|
|
14331
|
+
bin,
|
|
14332
|
+
buildOpts,
|
|
14333
|
+
cwd: candidate.dir,
|
|
14334
|
+
ecosystem: candidate.ecosystem,
|
|
14335
|
+
excludePaths: excludePathsForCandidate,
|
|
14336
|
+
javaHome,
|
|
14337
|
+
verbose: false
|
|
14338
|
+
});
|
|
14339
|
+
if (!enumResult) {
|
|
14340
|
+
const relDir = path.relative(cwd, candidate.dir) || '.';
|
|
14341
|
+
return {
|
|
14342
|
+
ok: false,
|
|
14343
|
+
message: `Could not determine ${candidate.ecosystem} workspace layout for ${relDir}; aborting rather than risk misclassifying its members.`
|
|
14344
|
+
};
|
|
14345
|
+
}
|
|
14346
|
+
const set = coveredByEcosystem.get(candidate.ecosystem) ?? new Set();
|
|
14347
|
+
set.add(candidate.dir);
|
|
14348
|
+
const resolvedSubprojectDirs = await Promise.all(enumResult.projects.map(project => realpathOrResolved(path.resolve(candidate.dir, project.subprojectDir))));
|
|
14349
|
+
for (const subprojectDir of resolvedSubprojectDirs) {
|
|
14350
|
+
set.add(subprojectDir);
|
|
14351
|
+
}
|
|
14352
|
+
coveredByEcosystem.set(candidate.ecosystem, set);
|
|
14353
|
+
return {
|
|
14354
|
+
ok: true,
|
|
14355
|
+
data: undefined
|
|
14356
|
+
};
|
|
14357
|
+
}
|
|
14358
|
+
|
|
14359
|
+
// Marks one exclusion root's own socket.json `disabled: true` for whichever
|
|
14360
|
+
// of its ecosystems aren't already disabled via cascade (an already-disabled
|
|
14361
|
+
// ancestor from a prior run) - a no-op write is skipped entirely so re-running
|
|
14362
|
+
// the wizard doesn't keep rewriting already-disabled roots.
|
|
14363
|
+
async function disableExclusionRoot({
|
|
14364
|
+
cwd,
|
|
14365
|
+
dir,
|
|
14366
|
+
ecosystems,
|
|
14367
|
+
rootSockJson
|
|
14368
|
+
}) {
|
|
14369
|
+
const relDir = path.relative(cwd, dir) || '.';
|
|
14370
|
+
const cascade = utils.readSocketJsonCascade(dir, cwd, rootSockJson);
|
|
14371
|
+
const needsWrite = ecosystems.filter(ecosystem => getEcosystemSection(cascade, ecosystem)['disabled'] !== true);
|
|
14372
|
+
if (!needsWrite.length) {
|
|
14373
|
+
return notCanceled$1();
|
|
14374
|
+
}
|
|
14375
|
+
const ownSockJson = utils.readOrDefaultSocketJson(dir);
|
|
14376
|
+
if (!ownSockJson.defaults) {
|
|
14377
|
+
ownSockJson.defaults = {};
|
|
14378
|
+
}
|
|
14379
|
+
if (!ownSockJson.defaults.manifest) {
|
|
14380
|
+
ownSockJson.defaults.manifest = {};
|
|
14381
|
+
}
|
|
14382
|
+
const manifest = ownSockJson.defaults.manifest;
|
|
14383
|
+
for (const ecosystem of needsWrite) {
|
|
14384
|
+
manifest[ecosystem] = {
|
|
14385
|
+
...getEcosystemSection(ownSockJson, ecosystem),
|
|
14386
|
+
disabled: true
|
|
14387
|
+
};
|
|
14388
|
+
}
|
|
14389
|
+
const writeResult = await utils.writeSocketJson(dir, ownSockJson);
|
|
14390
|
+
if (!writeResult.ok) {
|
|
14391
|
+
return writeResult;
|
|
14392
|
+
}
|
|
14393
|
+
logger.logger.success(`Disabled ${relDir} (${needsWrite.join(', ')})`);
|
|
14394
|
+
return notCanceled$1();
|
|
14395
|
+
}
|
|
14396
|
+
|
|
14397
|
+
// Dispatches to the right ecosystem-specific wizard. `factsOnly` skips the
|
|
14398
|
+
// facts/pom question - dynamic-sbom-inference never generates a pom.xml, it
|
|
14399
|
+
// just skips a project resolved to `facts: false` (see
|
|
14400
|
+
// generateRecursiveManifests' skipReason), so offering that choice here
|
|
14401
|
+
// would be misleading.
|
|
14402
|
+
async function runEcosystemWizard(ecosystem, config) {
|
|
14403
|
+
if (ecosystem === 'gradle') {
|
|
14404
|
+
return await setupGradle(config, {
|
|
14405
|
+
factsOnly: true
|
|
14406
|
+
});
|
|
14407
|
+
}
|
|
14408
|
+
if (ecosystem === 'maven') {
|
|
14409
|
+
return await setupMaven(config);
|
|
14410
|
+
}
|
|
14411
|
+
return await setupSbt(config, {
|
|
14412
|
+
factsOnly: true
|
|
14413
|
+
});
|
|
14414
|
+
}
|
|
14415
|
+
|
|
14416
|
+
// Seeds the wizard with this candidate's effective (cascaded) value for any
|
|
14417
|
+
// field its own file doesn't already set, so accepting every prompt
|
|
14418
|
+
// unchanged preserves what it currently inherits, while an actual change
|
|
14419
|
+
// writes an explicit override.
|
|
14420
|
+
async function configureCandidate({
|
|
14421
|
+
cwd,
|
|
14422
|
+
dir,
|
|
14423
|
+
ecosystem,
|
|
14424
|
+
rootSockJson
|
|
14425
|
+
}) {
|
|
14426
|
+
const relDir = path.relative(cwd, dir) || '.';
|
|
14427
|
+
const ownSockJson = utils.readOrDefaultSocketJson(dir);
|
|
14428
|
+
if (!ownSockJson.defaults) {
|
|
14429
|
+
ownSockJson.defaults = {};
|
|
14430
|
+
}
|
|
14431
|
+
if (!ownSockJson.defaults.manifest) {
|
|
14432
|
+
ownSockJson.defaults.manifest = {};
|
|
14433
|
+
}
|
|
14434
|
+
|
|
14435
|
+
// Ancestor-only baseline (excludes dir's own file, unlike the cascade below)
|
|
14436
|
+
// so a field left exactly as shown can be told apart from one the user
|
|
14437
|
+
// actually set - otherwise every accepted default gets written verbatim,
|
|
14438
|
+
// permanently pinning it against future changes to an ancestor's value.
|
|
14439
|
+
const inherited = getEcosystemSection(utils.readSocketJsonCascade(path.dirname(dir), cwd, rootSockJson), ecosystem);
|
|
14440
|
+
const cascade = utils.readSocketJsonCascade(dir, cwd, rootSockJson);
|
|
14441
|
+
const seed = {
|
|
14442
|
+
...getEcosystemSection(cascade, ecosystem),
|
|
14443
|
+
...getEcosystemSection(ownSockJson, ecosystem)
|
|
14444
|
+
};
|
|
14445
|
+
const result = await runEcosystemWizard(ecosystem, seed);
|
|
14446
|
+
if (!result.ok || result.data.canceled) {
|
|
14447
|
+
return result;
|
|
14448
|
+
}
|
|
14449
|
+
const toWrite = {};
|
|
14450
|
+
for (const key of Object.keys(seed)) {
|
|
14451
|
+
if (seed[key] !== inherited[key]) {
|
|
14452
|
+
toWrite[key] = seed[key];
|
|
14453
|
+
}
|
|
14454
|
+
}
|
|
14455
|
+
// Every field equals what dir would already inherit - writing it would just
|
|
14456
|
+
// be noise (own file unaffected, dir keeps inheriting exactly as before).
|
|
14457
|
+
if (!Object.keys(toWrite).length) {
|
|
14458
|
+
logger.logger.log(`No changes for ${relDir} (${ecosystem}); nothing written.`);
|
|
14459
|
+
return notCanceled$1();
|
|
14460
|
+
}
|
|
14461
|
+
const manifest = ownSockJson.defaults.manifest;
|
|
14462
|
+
manifest[ecosystem] = toWrite;
|
|
14463
|
+
const writeResult = await utils.writeSocketJson(dir, ownSockJson);
|
|
14464
|
+
if (!writeResult.ok) {
|
|
14465
|
+
return writeResult;
|
|
14466
|
+
}
|
|
14467
|
+
logger.logger.success(`Configured ${relDir} (${ecosystem})`);
|
|
14468
|
+
return notCanceled$1();
|
|
14469
|
+
}
|
|
14470
|
+
// No disable choice here - that's --exclude-paths' job (see
|
|
14471
|
+
// findExclusionRoot), so a whole subtree still collapses to one write
|
|
14472
|
+
// instead of one per candidate.
|
|
14473
|
+
async function askCandidateAction(relDir, ecosystem) {
|
|
14474
|
+
return await prompts.select({
|
|
14475
|
+
message: `${relDir} (${ecosystem})`,
|
|
14476
|
+
choices: [{
|
|
14477
|
+
name: 'Leave as-is',
|
|
14478
|
+
value: 'inherit',
|
|
14479
|
+
description: "Make no change - keep this project's current effective configuration"
|
|
14480
|
+
}, {
|
|
14481
|
+
name: 'Configure',
|
|
14482
|
+
value: 'configure',
|
|
14483
|
+
description: 'Set bin/JDK/opts/etc. for this project specifically'
|
|
14484
|
+
}],
|
|
14485
|
+
default: 'inherit'
|
|
14486
|
+
});
|
|
14487
|
+
}
|
|
14488
|
+
// Asks the standard configure-or-leave-as-is question and applies the
|
|
14489
|
+
// answer. Shared by the normal path and the post-re-enable path below, so
|
|
14490
|
+
// re-enabling a candidate isn't a dead end that skips straight past its own
|
|
14491
|
+
// bin/JDK/opts configuration.
|
|
14492
|
+
async function askAndApplyAction({
|
|
14493
|
+
cwd,
|
|
14494
|
+
dir,
|
|
14495
|
+
ecosystem,
|
|
14496
|
+
relDir,
|
|
14497
|
+
rootSockJson
|
|
14498
|
+
}) {
|
|
14499
|
+
const action = await askCandidateAction(relDir, ecosystem);
|
|
14500
|
+
if (action === undefined || action === null) {
|
|
14501
|
+
canceledByUser$1();
|
|
14502
|
+
return {
|
|
14503
|
+
ok: true,
|
|
14504
|
+
data: {
|
|
14505
|
+
canceled: true,
|
|
14506
|
+
outcome: 'inherited'
|
|
14507
|
+
}
|
|
14508
|
+
};
|
|
14509
|
+
}
|
|
14510
|
+
if (action === 'configure') {
|
|
14511
|
+
const result = await configureCandidate({
|
|
14512
|
+
cwd,
|
|
14513
|
+
dir,
|
|
14514
|
+
ecosystem,
|
|
14515
|
+
rootSockJson
|
|
14516
|
+
});
|
|
14517
|
+
if (!result.ok) {
|
|
14518
|
+
return result;
|
|
14519
|
+
}
|
|
14520
|
+
return {
|
|
14521
|
+
ok: true,
|
|
14522
|
+
data: {
|
|
14523
|
+
...result.data,
|
|
14524
|
+
outcome: 'configured'
|
|
14525
|
+
}
|
|
14526
|
+
};
|
|
14527
|
+
}
|
|
14528
|
+
// 'inherit', or any unexpected value - the safe no-op default.
|
|
14529
|
+
return {
|
|
14530
|
+
ok: true,
|
|
14531
|
+
data: {
|
|
14532
|
+
canceled: false,
|
|
14533
|
+
outcome: 'inherited'
|
|
14534
|
+
}
|
|
14535
|
+
};
|
|
14536
|
+
}
|
|
14537
|
+
|
|
14538
|
+
// Prompts for configure/inherit, unless the cascade shows the candidate
|
|
14539
|
+
// disabled: offer to re-enable if that's its own file, then continue into
|
|
14540
|
+
// the normal prompt; stay silent if it's only inherited from an ancestor's
|
|
14541
|
+
// bulk write (that's noise for a large excluded subtree).
|
|
14542
|
+
async function processCandidate({
|
|
14543
|
+
cwd,
|
|
14544
|
+
dir,
|
|
14545
|
+
ecosystem,
|
|
14546
|
+
rootSockJson
|
|
14547
|
+
}) {
|
|
14548
|
+
const relDir = path.relative(cwd, dir) || '.';
|
|
14549
|
+
const cascade = utils.readSocketJsonCascade(dir, cwd, rootSockJson);
|
|
14550
|
+
if (getEcosystemSection(cascade, ecosystem)['disabled'] === true) {
|
|
14551
|
+
const ownSockJson = utils.readOrDefaultSocketJson(dir);
|
|
14552
|
+
if (getEcosystemSection(ownSockJson, ecosystem)['disabled'] !== true) {
|
|
14553
|
+
// Inherited from an ancestor, not this candidate's own file - stay silent.
|
|
14554
|
+
return {
|
|
14555
|
+
ok: true,
|
|
14556
|
+
data: {
|
|
14557
|
+
canceled: false,
|
|
14558
|
+
outcome: 'skipped',
|
|
14559
|
+
reenabled: false
|
|
14560
|
+
}
|
|
14561
|
+
};
|
|
14562
|
+
}
|
|
14563
|
+
const wantsReenable = await askYesNo(`${relDir} (${ecosystem}) is disabled - re-enable it?`);
|
|
14564
|
+
if (wantsReenable === undefined || wantsReenable === null) {
|
|
14565
|
+
canceledByUser$1();
|
|
14566
|
+
return {
|
|
14567
|
+
ok: true,
|
|
14568
|
+
data: {
|
|
14569
|
+
canceled: true,
|
|
14570
|
+
outcome: 'skipped',
|
|
14571
|
+
reenabled: false
|
|
14572
|
+
}
|
|
14573
|
+
};
|
|
14574
|
+
}
|
|
14575
|
+
if (!wantsReenable) {
|
|
14576
|
+
return {
|
|
14577
|
+
ok: true,
|
|
14578
|
+
data: {
|
|
14579
|
+
canceled: false,
|
|
14580
|
+
outcome: 'skipped',
|
|
14581
|
+
reenabled: false
|
|
14582
|
+
}
|
|
14583
|
+
};
|
|
14584
|
+
}
|
|
14585
|
+
if (!ownSockJson.defaults) {
|
|
14586
|
+
ownSockJson.defaults = {};
|
|
14587
|
+
}
|
|
14588
|
+
if (!ownSockJson.defaults.manifest) {
|
|
14589
|
+
ownSockJson.defaults.manifest = {};
|
|
14590
|
+
}
|
|
14591
|
+
const manifest = ownSockJson.defaults.manifest;
|
|
14592
|
+
manifest[ecosystem] = {
|
|
14593
|
+
...getEcosystemSection(ownSockJson, ecosystem),
|
|
14594
|
+
disabled: false
|
|
14595
|
+
};
|
|
14596
|
+
const writeResult = await utils.writeSocketJson(dir, ownSockJson);
|
|
14597
|
+
if (!writeResult.ok) {
|
|
14598
|
+
return writeResult;
|
|
14599
|
+
}
|
|
14600
|
+
logger.logger.success(`Re-enabled ${relDir} (${ecosystem})`);
|
|
14601
|
+
const result = await askAndApplyAction({
|
|
14602
|
+
cwd,
|
|
14603
|
+
dir,
|
|
14604
|
+
ecosystem,
|
|
14605
|
+
relDir,
|
|
14606
|
+
rootSockJson
|
|
14607
|
+
});
|
|
14608
|
+
if (!result.ok) {
|
|
14609
|
+
return result;
|
|
14610
|
+
}
|
|
14611
|
+
return {
|
|
14612
|
+
ok: true,
|
|
14613
|
+
data: {
|
|
14614
|
+
...result.data,
|
|
14615
|
+
reenabled: true
|
|
14616
|
+
}
|
|
14617
|
+
};
|
|
14618
|
+
}
|
|
14619
|
+
const result = await askAndApplyAction({
|
|
14620
|
+
cwd,
|
|
14621
|
+
dir,
|
|
14622
|
+
ecosystem,
|
|
14623
|
+
relDir,
|
|
14624
|
+
rootSockJson
|
|
14625
|
+
});
|
|
14626
|
+
if (!result.ok) {
|
|
14627
|
+
return result;
|
|
14628
|
+
}
|
|
14629
|
+
return {
|
|
14630
|
+
ok: true,
|
|
14631
|
+
data: {
|
|
14632
|
+
...result.data,
|
|
14633
|
+
reenabled: false
|
|
14634
|
+
}
|
|
14635
|
+
};
|
|
14636
|
+
}
|
|
14637
|
+
|
|
14638
|
+
// Accepting every prompt's default leaves an ecosystem's section genuinely
|
|
14639
|
+
// empty (see askForBin in setup-manifest-config.mts) - drop it so
|
|
14640
|
+
// `configuredAny` reflects what was actually configured, not just which
|
|
14641
|
+
// ecosystems the user said "yes" to.
|
|
14642
|
+
function dropIfEmpty(manifest, ecosystem) {
|
|
14643
|
+
const section = manifest[ecosystem];
|
|
14644
|
+
if (section && Object.keys(section).length) {
|
|
14645
|
+
return true;
|
|
14646
|
+
}
|
|
14647
|
+
delete manifest[ecosystem];
|
|
14648
|
+
return false;
|
|
14649
|
+
}
|
|
14650
|
+
async function askYesNo(message) {
|
|
14651
|
+
return await prompts.select({
|
|
14652
|
+
message,
|
|
14653
|
+
choices: [{
|
|
14654
|
+
name: 'Yes',
|
|
14655
|
+
value: true
|
|
14656
|
+
}, {
|
|
14657
|
+
name: 'No',
|
|
14658
|
+
value: false
|
|
14659
|
+
}]
|
|
14660
|
+
});
|
|
14661
|
+
}
|
|
14662
|
+
|
|
14663
|
+
// Unlike the plain single-project wizard, cwd here is often just a common
|
|
14664
|
+
// ancestor - so `ecosystemsAtCwd` (a subset of `ecosystems`) picks the
|
|
14665
|
+
// wording: cwd's own build root gets "configure this project's settings",
|
|
14666
|
+
// everything else gets "configure defaults for nested projects". A
|
|
14667
|
+
// pre-existing `disabled: true` gets a re-enable question first, otherwise
|
|
14668
|
+
// there'd be no way back from a prior disable. Declining everything is
|
|
14669
|
+
// normal, not an abort.
|
|
14670
|
+
async function setupRecursiveRootDefaults(cwd, defaultOnReadError, ecosystems, ecosystemsAtCwd) {
|
|
14671
|
+
const jsonPath = path.join(cwd, constants.SOCKET_JSON);
|
|
14672
|
+
if (fs$1.existsSync(jsonPath)) {
|
|
14673
|
+
logger.logger.info(`Found ${constants.SOCKET_JSON} at ${jsonPath}`);
|
|
14674
|
+
} else {
|
|
14675
|
+
logger.logger.info(`No ${constants.SOCKET_JSON} found at ${cwd}, will generate a new one`);
|
|
14676
|
+
}
|
|
14677
|
+
logger.logger.log('');
|
|
14678
|
+
logger.logger.log('Note: This tool will set up flag and argument defaults for certain');
|
|
14679
|
+
logger.logger.log(' CLI commands. You can still override them by explicitly');
|
|
14680
|
+
logger.logger.log(' setting the flag.');
|
|
14681
|
+
logger.logger.log('');
|
|
14682
|
+
logger.logger.log(`This command will generate a ${constants.SOCKET_JSON} file in ${cwd}.`);
|
|
14683
|
+
logger.logger.log('socket.json properties are inherited by nested paths.');
|
|
14684
|
+
logger.logger.log('');
|
|
14685
|
+
const sockJsonCResult = utils.readSocketJsonSync(cwd, defaultOnReadError);
|
|
14686
|
+
if (!sockJsonCResult.ok) {
|
|
14687
|
+
return sockJsonCResult;
|
|
14688
|
+
}
|
|
14689
|
+
const sockJson = sockJsonCResult.data;
|
|
14690
|
+
if (!sockJson.defaults) {
|
|
14691
|
+
sockJson.defaults = {};
|
|
14692
|
+
}
|
|
14693
|
+
if (!sockJson.defaults.manifest) {
|
|
14694
|
+
sockJson.defaults.manifest = {};
|
|
14695
|
+
}
|
|
14696
|
+
const manifest = sockJson.defaults.manifest;
|
|
14697
|
+
let configuredAny = false;
|
|
14698
|
+
for (const ecosystem of ecosystems) {
|
|
14699
|
+
const label = ECOSYSTEM_LABELS[ecosystem];
|
|
14700
|
+
const existingSection = manifest[ecosystem];
|
|
14701
|
+
if (existingSection?.['disabled'] === true) {
|
|
14702
|
+
// eslint-disable-next-line no-await-in-loop
|
|
14703
|
+
const wantsReenable = await askYesNo(`${label} is currently disabled here - re-enable it?`);
|
|
14704
|
+
if (wantsReenable === undefined || wantsReenable === null) {
|
|
14705
|
+
return canceledByUser$1();
|
|
14706
|
+
}
|
|
14707
|
+
if (!wantsReenable) {
|
|
14708
|
+
continue;
|
|
14709
|
+
}
|
|
14710
|
+
existingSection['disabled'] = false;
|
|
14711
|
+
configuredAny = true;
|
|
14712
|
+
}
|
|
14713
|
+
const message = ecosystemsAtCwd.includes(ecosystem) ? `Configure ${label} settings for this project?` : `Configure ${label} defaults for any nested projects?`;
|
|
14714
|
+
// eslint-disable-next-line no-await-in-loop
|
|
14715
|
+
const wants = await askYesNo(message);
|
|
14716
|
+
if (wants === undefined || wants === null) {
|
|
14717
|
+
return canceledByUser$1();
|
|
14718
|
+
}
|
|
14719
|
+
if (!wants) {
|
|
14720
|
+
continue;
|
|
14721
|
+
}
|
|
14722
|
+
if (!manifest[ecosystem]) {
|
|
14723
|
+
manifest[ecosystem] = {};
|
|
14724
|
+
}
|
|
14725
|
+
// eslint-disable-next-line no-await-in-loop
|
|
14726
|
+
const result = await runEcosystemWizard(ecosystem, manifest[ecosystem]);
|
|
14727
|
+
if (!result.ok || result.data.canceled) {
|
|
14728
|
+
return result;
|
|
14729
|
+
}
|
|
14730
|
+
if (dropIfEmpty(manifest, ecosystem)) {
|
|
14731
|
+
configuredAny = true;
|
|
14732
|
+
}
|
|
14733
|
+
}
|
|
14734
|
+
if (!configuredAny) {
|
|
14735
|
+
logger.logger.log('');
|
|
14736
|
+
logger.logger.log('No root-level defaults configured.');
|
|
14737
|
+
return notCanceled$1();
|
|
14738
|
+
}
|
|
14739
|
+
logger.logger.log('');
|
|
14740
|
+
logger.logger.log(`Writing ${constants.SOCKET_JSON} to ${jsonPath}`);
|
|
14741
|
+
logger.logger.log('');
|
|
14742
|
+
const writeResult = await utils.writeSocketJson(cwd, sockJson);
|
|
14743
|
+
if (!writeResult.ok) {
|
|
14744
|
+
return writeResult;
|
|
14745
|
+
}
|
|
14746
|
+
return notCanceled$1();
|
|
14747
|
+
}
|
|
14748
|
+
|
|
14749
|
+
// `socket manifest setup --dynamic-sbom-inference`: the up-front scan (see
|
|
14750
|
+
// scanBuildRoots) only ever answers "which build tools exist", never "which
|
|
14751
|
+
// projects to configure". `--exclude-paths` matches are bulk-disabled
|
|
14752
|
+
// unconditionally, independent of the interactive walk below, so re-running
|
|
14753
|
+
// later with one more entry (or none) never re-answers settled decisions.
|
|
14754
|
+
// Everything else gets a configure-or-inherit prompt (see processCandidate),
|
|
14755
|
+
// except a workspace member a parent build already declares - learned via
|
|
14756
|
+
// markWorkspaceCoverage right after the parent's own prompt, not a separate
|
|
14757
|
+
// bulk pass, so a reactor member is silently skipped instead of prompted.
|
|
14758
|
+
async function setupRecursiveManifestConfig(cwd, defaultOnReadError, excludePaths) {
|
|
14759
|
+
logger.logger.log('');
|
|
14760
|
+
logger.logger.log(`Configuring ${cwd} ...`);
|
|
14761
|
+
const initialSockJson = utils.readOrDefaultSocketJson(cwd);
|
|
14762
|
+
// Resolved once here for sortCandidatesForDisplay/disableExclusionRoot's
|
|
14763
|
+
// relative-path math, consistent with discoverBuildRoots' own internal
|
|
14764
|
+
// resolution (see its comment for why this matters).
|
|
14765
|
+
const realCwd = await realpathOrResolved(cwd);
|
|
14766
|
+
logger.logger.log('');
|
|
14767
|
+
logger.logger.log('Scanning for build roots ...');
|
|
14768
|
+
const {
|
|
14769
|
+
fullByTool,
|
|
14770
|
+
includedByTool
|
|
14771
|
+
} = await scanBuildRoots({
|
|
14772
|
+
cwd,
|
|
14773
|
+
excludePaths,
|
|
14774
|
+
sockJson: withoutDisabledFlags(initialSockJson)
|
|
14775
|
+
});
|
|
14776
|
+
// includedByTool (not the unfiltered fullByTool), so an ecosystem whose
|
|
14777
|
+
// only candidate is about to be excluded this same run isn't offered
|
|
14778
|
+
// either - there'd be nothing left for its root defaults to apply to.
|
|
14779
|
+
const detectedEcosystems = ROOT_ECOSYSTEMS.filter(ecosystem => (includedByTool.get(ecosystem)?.length ?? 0) > 0);
|
|
14780
|
+
if (detectedEcosystems.length) {
|
|
14781
|
+
logger.logger.log(`Detected: ${detectedEcosystems.map(ecosystem => ECOSYSTEM_LABELS[ecosystem]).join(', ')}.`);
|
|
14782
|
+
} else {
|
|
14783
|
+
logger.logger.log(`No gradle/maven/sbt build roots found beneath ${cwd}.`);
|
|
14784
|
+
}
|
|
14785
|
+
logger.logger.log('');
|
|
14786
|
+
|
|
14787
|
+
// Which detected ecosystems cwd is itself a build root for - see
|
|
14788
|
+
// setupRecursiveRootDefaults' wording split.
|
|
14789
|
+
const ecosystemsAtCwd = ROOT_ECOSYSTEMS.filter(ecosystem => (fullByTool.get(ecosystem) ?? []).includes(realCwd));
|
|
14790
|
+
const rootResult = await setupRecursiveRootDefaults(cwd, defaultOnReadError, detectedEcosystems, ecosystemsAtCwd);
|
|
14791
|
+
if (!rootResult.ok) {
|
|
14792
|
+
return rootResult;
|
|
14793
|
+
}
|
|
14794
|
+
if (rootResult.data.canceled) {
|
|
14795
|
+
return canceledByUser$1();
|
|
14796
|
+
}
|
|
14797
|
+
|
|
14798
|
+
// Re-read: the root wizard may have just written a new socket.json.
|
|
14799
|
+
const rootSockJson = utils.readOrDefaultSocketJson(cwd);
|
|
14800
|
+
const {
|
|
14801
|
+
excluded,
|
|
14802
|
+
included
|
|
14803
|
+
} = await discoverBuildRoots({
|
|
14804
|
+
cwd,
|
|
14805
|
+
excludePaths,
|
|
14806
|
+
fullByTool,
|
|
14807
|
+
includedByTool
|
|
14808
|
+
});
|
|
14809
|
+
|
|
14810
|
+
// Applies regardless of the interactive walk below - see the module doc
|
|
14811
|
+
// comment on why --exclude-paths is unconditional.
|
|
14812
|
+
if (excludePaths?.length) {
|
|
14813
|
+
if (!excluded.length) {
|
|
14814
|
+
logger.logger.log('None matched --exclude-paths; nothing disabled.');
|
|
14815
|
+
} else {
|
|
14816
|
+
const orderedExcluded = sortCandidatesForDisplay(excluded, realCwd);
|
|
14817
|
+
for (const candidate of orderedExcluded) {
|
|
14818
|
+
// eslint-disable-next-line no-await-in-loop
|
|
14819
|
+
const result = await disableExclusionRoot({
|
|
14820
|
+
cwd: realCwd,
|
|
14821
|
+
dir: candidate.dir,
|
|
14822
|
+
ecosystems: candidate.ecosystems,
|
|
14823
|
+
rootSockJson
|
|
14824
|
+
});
|
|
14825
|
+
if (!result.ok) {
|
|
14826
|
+
return result;
|
|
14827
|
+
}
|
|
14828
|
+
}
|
|
14829
|
+
}
|
|
13374
14830
|
}
|
|
13375
|
-
|
|
13376
|
-
|
|
14831
|
+
if (!included.length) {
|
|
14832
|
+
logger.logger.log('');
|
|
14833
|
+
logger.logger.success('Recursive setup complete.');
|
|
14834
|
+
return notCanceled$1();
|
|
14835
|
+
}
|
|
14836
|
+
logger.logger.log('');
|
|
14837
|
+
const wantsIndividual = await askYesNo('Configure other build roots found beneath this one, individually?');
|
|
14838
|
+
if (wantsIndividual === undefined || wantsIndividual === null) {
|
|
13377
14839
|
return canceledByUser$1();
|
|
13378
|
-
} else if (ignoreUnresolved === 'yes' || ignoreUnresolved === 'no') {
|
|
13379
|
-
config.ignoreUnresolved = ignoreUnresolved === 'yes';
|
|
13380
|
-
} else {
|
|
13381
|
-
delete config.ignoreUnresolved;
|
|
13382
14840
|
}
|
|
13383
|
-
|
|
13384
|
-
|
|
13385
|
-
|
|
14841
|
+
if (!wantsIndividual) {
|
|
14842
|
+
logger.logger.log('');
|
|
14843
|
+
logger.logger.success('Recursive setup complete.');
|
|
14844
|
+
return notCanceled$1();
|
|
14845
|
+
}
|
|
13386
14846
|
logger.logger.log('');
|
|
13387
|
-
logger.logger.
|
|
14847
|
+
logger.logger.log('For each one, choose to configure it or leave it as-is. To disable one,');
|
|
14848
|
+
logger.logger.log('re-run with --exclude-paths instead.');
|
|
13388
14849
|
logger.logger.log('');
|
|
13389
|
-
|
|
13390
|
-
|
|
13391
|
-
|
|
13392
|
-
|
|
14850
|
+
const coveredByEcosystem = new Map();
|
|
14851
|
+
// cwd never appears in `included`, but a reactor root often sits exactly
|
|
14852
|
+
// at cwd - seed coverage from it first so its declared members still get
|
|
14853
|
+
// recognized.
|
|
14854
|
+
for (const ecosystem of ecosystemsAtCwd) {
|
|
14855
|
+
// eslint-disable-next-line no-await-in-loop
|
|
14856
|
+
const coverageResult = await markWorkspaceCoverage({
|
|
14857
|
+
candidate: {
|
|
14858
|
+
dir: realCwd,
|
|
14859
|
+
ecosystem
|
|
14860
|
+
},
|
|
14861
|
+
coveredByEcosystem,
|
|
14862
|
+
cwd: realCwd,
|
|
14863
|
+
excludePaths,
|
|
14864
|
+
rootSockJson
|
|
14865
|
+
});
|
|
14866
|
+
if (!coverageResult.ok) {
|
|
14867
|
+
return coverageResult;
|
|
13393
14868
|
}
|
|
14869
|
+
}
|
|
14870
|
+
const counts = {
|
|
14871
|
+
configured: 0,
|
|
14872
|
+
covered: 0,
|
|
14873
|
+
inherited: 0,
|
|
14874
|
+
reenabled: 0,
|
|
14875
|
+
skipped: 0
|
|
13394
14876
|
};
|
|
13395
|
-
|
|
13396
|
-
|
|
13397
|
-
|
|
13398
|
-
|
|
13399
|
-
|
|
13400
|
-
|
|
14877
|
+
const orderedIncluded = sortCandidatesForDisplay(included, realCwd);
|
|
14878
|
+
for (const candidate of orderedIncluded) {
|
|
14879
|
+
const covered = coveredByEcosystem.get(candidate.ecosystem);
|
|
14880
|
+
if (covered?.has(candidate.dir)) {
|
|
14881
|
+
counts.covered += 1;
|
|
14882
|
+
continue;
|
|
13401
14883
|
}
|
|
13402
|
-
|
|
14884
|
+
|
|
14885
|
+
// eslint-disable-next-line no-await-in-loop
|
|
14886
|
+
const result = await processCandidate({
|
|
14887
|
+
cwd: realCwd,
|
|
14888
|
+
dir: candidate.dir,
|
|
14889
|
+
ecosystem: candidate.ecosystem,
|
|
14890
|
+
rootSockJson
|
|
14891
|
+
});
|
|
14892
|
+
if (!result.ok) {
|
|
14893
|
+
return result;
|
|
14894
|
+
}
|
|
14895
|
+
if (result.data.canceled) {
|
|
14896
|
+
// The cancellation itself (select Esc/Ctrl+C, or a sub-wizard's own
|
|
14897
|
+
// cancel) already logged "User canceled" - don't log it twice.
|
|
14898
|
+
return {
|
|
14899
|
+
ok: true,
|
|
14900
|
+
data: {
|
|
14901
|
+
canceled: true
|
|
14902
|
+
}
|
|
14903
|
+
};
|
|
14904
|
+
}
|
|
14905
|
+
counts[result.data.outcome] += 1;
|
|
14906
|
+
if (result.data.reenabled) {
|
|
14907
|
+
// A qualifier on the 'configured'/'inherited' bucket just incremented
|
|
14908
|
+
// above, not a separate bucket - avoids double-counting the candidate.
|
|
14909
|
+
counts.reenabled += 1;
|
|
14910
|
+
}
|
|
14911
|
+
|
|
14912
|
+
// Interleaved rather than a bulk pre-pass, so this (possibly slow)
|
|
14913
|
+
// build-tool invocation only runs for a project already prompted about.
|
|
14914
|
+
// eslint-disable-next-line no-await-in-loop
|
|
14915
|
+
const coverageResult = await markWorkspaceCoverage({
|
|
14916
|
+
candidate,
|
|
14917
|
+
coveredByEcosystem,
|
|
14918
|
+
cwd: realCwd,
|
|
14919
|
+
excludePaths,
|
|
14920
|
+
rootSockJson
|
|
14921
|
+
});
|
|
14922
|
+
if (!coverageResult.ok) {
|
|
14923
|
+
return coverageResult;
|
|
14924
|
+
}
|
|
14925
|
+
}
|
|
14926
|
+
logger.logger.log('');
|
|
14927
|
+
logger.logger.log(`${counts.configured} configured (${counts.reenabled} re-enabled), ${counts.inherited} left as-is, ${counts.skipped} left disabled, ${counts.covered} covered by a parent build.`);
|
|
14928
|
+
logger.logger.log('');
|
|
14929
|
+
logger.logger.success('Recursive setup complete.');
|
|
14930
|
+
return notCanceled$1();
|
|
13403
14931
|
}
|
|
13404
14932
|
|
|
13405
|
-
async function handleManifestSetup(cwd, defaultOnReadError) {
|
|
13406
|
-
const result = await setupManifestConfig(cwd, defaultOnReadError);
|
|
14933
|
+
async function handleManifestSetup(cwd, defaultOnReadError, dynamicSbomInference = false, excludePaths) {
|
|
14934
|
+
const result = dynamicSbomInference ? await setupRecursiveManifestConfig(cwd, defaultOnReadError, excludePaths) : await setupManifestConfig(cwd, defaultOnReadError);
|
|
13407
14935
|
await outputManifestSetup(result);
|
|
13408
14936
|
}
|
|
13409
14937
|
|
|
@@ -13413,9 +14941,21 @@ const config$8 = {
|
|
|
13413
14941
|
hidden: false,
|
|
13414
14942
|
flags: {
|
|
13415
14943
|
...flags.commonFlags,
|
|
14944
|
+
// Only meaningful alongside the hidden --dynamic-sbom-inference below; kept hidden too.
|
|
14945
|
+
excludePaths: {
|
|
14946
|
+
type: 'string',
|
|
14947
|
+
isMultiple: true,
|
|
14948
|
+
hidden: true,
|
|
14949
|
+
description: 'Build roots matching these glob patterns (and everything beneath them) are marked disabled. Patterns are anchored micromatch globs matched relative to CWD: `legacy` matches only `<cwd>/legacy`; use `**/legacy` to match at any depth. Negation patterns (`!path`) are not supported. Accepts a comma-separated value or multiple flags.'
|
|
14950
|
+
},
|
|
13416
14951
|
defaultOnReadError: {
|
|
13417
14952
|
type: 'boolean',
|
|
13418
14953
|
description: `If reading the ${constants.SOCKET_JSON} fails, just use a default config? Warning: This might override the existing json file!`
|
|
14954
|
+
},
|
|
14955
|
+
dynamicSbomInference: {
|
|
14956
|
+
type: 'boolean',
|
|
14957
|
+
hidden: true,
|
|
14958
|
+
description: 'Recursively scans for every gradle/sbt/maven build root beneath CWD first, so the CWD config step only asks about ecosystems actually found somewhere in the tree. A build root matching --exclude-paths is bulk-disabled with no prompt, applied unconditionally; eligible build roots found afterward can be configured individually'
|
|
13419
14959
|
}
|
|
13420
14960
|
},
|
|
13421
14961
|
help: (command, config) => `
|
|
@@ -13464,7 +15004,8 @@ async function run$z(argv, importMeta, {
|
|
|
13464
15004
|
parentName
|
|
13465
15005
|
});
|
|
13466
15006
|
const {
|
|
13467
|
-
defaultOnReadError = false
|
|
15007
|
+
defaultOnReadError = false,
|
|
15008
|
+
dynamicSbomInference = false
|
|
13468
15009
|
} = cli.flags;
|
|
13469
15010
|
const dryRun = !!cli.flags['dryRun'];
|
|
13470
15011
|
let [cwd = '.'] = cli.input;
|
|
@@ -13475,7 +15016,9 @@ async function run$z(argv, importMeta, {
|
|
|
13475
15016
|
logger.logger.log(constants.default.DRY_RUN_BAILING_NOW);
|
|
13476
15017
|
return;
|
|
13477
15018
|
}
|
|
13478
|
-
|
|
15019
|
+
const excludePaths = utils.cmdFlagValueToArray(cli.flags['excludePaths']);
|
|
15020
|
+
assertValidExcludePaths(excludePaths);
|
|
15021
|
+
await handleManifestSetup(cwd, Boolean(defaultOnReadError), Boolean(dynamicSbomInference), excludePaths);
|
|
13479
15022
|
}
|
|
13480
15023
|
|
|
13481
15024
|
const config$7 = {
|
|
@@ -13502,6 +15045,7 @@ async function run$y(argv, importMeta, {
|
|
|
13502
15045
|
bazel: cmdManifestBazel,
|
|
13503
15046
|
cdxgen: cmdManifestCdxgen,
|
|
13504
15047
|
conda: cmdManifestConda,
|
|
15048
|
+
'dynamic-sbom-inference': cmdManifestDynamicSbomInference,
|
|
13505
15049
|
gradle: cmdManifestGradle,
|
|
13506
15050
|
kotlin: cmdManifestKotlin,
|
|
13507
15051
|
maven: cmdManifestMaven,
|
|
@@ -17575,6 +19119,124 @@ const cmdRepository = {
|
|
|
17575
19119
|
}
|
|
17576
19120
|
};
|
|
17577
19121
|
|
|
19122
|
+
const reachabilityFlags = {
|
|
19123
|
+
dynamicSbomInference: {
|
|
19124
|
+
type: 'boolean',
|
|
19125
|
+
default: false,
|
|
19126
|
+
hidden: true,
|
|
19127
|
+
description: 'Internal: enables dynamic SBOM inference for full application reachability analysis. Passes --maven-use-only-root-socket-facts to Coana and implies --auto-manifest.'
|
|
19128
|
+
},
|
|
19129
|
+
reachVersion: {
|
|
19130
|
+
type: 'string',
|
|
19131
|
+
description: `Override the version of @coana-tech/cli used for reachability analysis. Default: ${constants.default.ENV.INLINED_SOCKET_CLI_COANA_TECH_CLI_VERSION}.`
|
|
19132
|
+
},
|
|
19133
|
+
reachAnalysisMemoryLimit: {
|
|
19134
|
+
type: 'string',
|
|
19135
|
+
default: '8192',
|
|
19136
|
+
description: 'The maximum memory for the reachability analysis as a whole number optionally followed by MB or GB (e.g. 512MB, 8GB). The default is 8GB.'
|
|
19137
|
+
},
|
|
19138
|
+
reachAnalysisTimeout: {
|
|
19139
|
+
type: 'string',
|
|
19140
|
+
default: '',
|
|
19141
|
+
description: 'Set the timeout for the reachability analysis as a whole number optionally followed by s, m or h (e.g. 90s, 10m, 1h). Defaults to 10m. Split analysis runs may cause the total scan time to exceed this timeout significantly.'
|
|
19142
|
+
},
|
|
19143
|
+
reachConcurrency: {
|
|
19144
|
+
type: 'number',
|
|
19145
|
+
default: 1,
|
|
19146
|
+
description: 'Set the maximum number of concurrent reachability analysis runs. It is recommended to choose a concurrency level that ensures each analysis run has at least the --reach-analysis-memory-limit amount of memory available.'
|
|
19147
|
+
},
|
|
19148
|
+
reachContinueOnAnalysisErrors: {
|
|
19149
|
+
type: 'boolean',
|
|
19150
|
+
default: false,
|
|
19151
|
+
description: 'Continue reachability analysis when errors occur (timeouts, OOM, parse errors, etc.), falling back to precomputed reachability results. By default, the CLI halts on analysis errors.'
|
|
19152
|
+
},
|
|
19153
|
+
reachContinueOnInstallErrors: {
|
|
19154
|
+
type: 'boolean',
|
|
19155
|
+
default: false,
|
|
19156
|
+
description: 'Continue reachability analysis when package installation fails, falling back to precomputed reachability results. By default, the CLI halts on installation errors.'
|
|
19157
|
+
},
|
|
19158
|
+
reachContinueOnMissingLockFiles: {
|
|
19159
|
+
type: 'boolean',
|
|
19160
|
+
default: false,
|
|
19161
|
+
description: 'Continue reachability analysis when a Gradle or SBT project is missing its lock file (or version catalog / pre-generated SBOM). By default, the CLI halts.'
|
|
19162
|
+
},
|
|
19163
|
+
reachContinueOnNoSourceFiles: {
|
|
19164
|
+
type: 'boolean',
|
|
19165
|
+
default: false,
|
|
19166
|
+
description: 'Continue reachability analysis when a workspace contains no source files for its ecosystem. By default, the CLI halts.'
|
|
19167
|
+
},
|
|
19168
|
+
reachDisableExternalToolChecks: {
|
|
19169
|
+
type: 'boolean',
|
|
19170
|
+
default: false,
|
|
19171
|
+
description: 'Disable external tool checks during reachability analysis.'
|
|
19172
|
+
},
|
|
19173
|
+
reachDebug: {
|
|
19174
|
+
type: 'boolean',
|
|
19175
|
+
default: false,
|
|
19176
|
+
description: 'Enable debug mode for reachability analysis. Provides verbose logging from the reachability CLI.'
|
|
19177
|
+
},
|
|
19178
|
+
reachDetailedAnalysisLogFile: {
|
|
19179
|
+
type: 'boolean',
|
|
19180
|
+
default: false,
|
|
19181
|
+
description: 'A log file with detailed analysis logs is written to root of each analyzed workspace.'
|
|
19182
|
+
},
|
|
19183
|
+
reachDisableAnalytics: {
|
|
19184
|
+
type: 'boolean',
|
|
19185
|
+
default: false,
|
|
19186
|
+
description: 'Disable reachability analytics sharing with Socket. Also disables caching-based optimizations.'
|
|
19187
|
+
},
|
|
19188
|
+
reachDisableAnalysisSplitting: {
|
|
19189
|
+
type: 'boolean',
|
|
19190
|
+
default: false,
|
|
19191
|
+
hidden: true,
|
|
19192
|
+
description: 'Deprecated: Analysis splitting is now disabled by default. This flag is a no-op.'
|
|
19193
|
+
},
|
|
19194
|
+
reachEnableAnalysisSplitting: {
|
|
19195
|
+
type: 'boolean',
|
|
19196
|
+
default: false,
|
|
19197
|
+
description: 'Allow the reachability analysis to partition CVEs into buckets that are processed in separate analysis runs. May improve accuracy, but not recommended by default.'
|
|
19198
|
+
},
|
|
19199
|
+
reachEcosystems: {
|
|
19200
|
+
type: 'string',
|
|
19201
|
+
isMultiple: true,
|
|
19202
|
+
description: `List of ecosystems to conduct reachability analysis on, as either a comma separated value or as multiple flags. Supported: ${utils.getReachabilityEcosystemChoices().join(', ')}. Defaults to all supported ecosystems.`
|
|
19203
|
+
},
|
|
19204
|
+
reachExcludePaths: {
|
|
19205
|
+
type: 'string',
|
|
19206
|
+
isMultiple: true,
|
|
19207
|
+
hidden: true,
|
|
19208
|
+
description: 'Deprecated: use --exclude-paths instead. List of paths to exclude from reachability analysis, as either a comma separated value or as multiple flags.'
|
|
19209
|
+
},
|
|
19210
|
+
reachLazyMode: {
|
|
19211
|
+
type: 'boolean',
|
|
19212
|
+
default: false,
|
|
19213
|
+
description: 'Enable lazy mode for reachability analysis.',
|
|
19214
|
+
hidden: true
|
|
19215
|
+
},
|
|
19216
|
+
reachRetainFactsFile: {
|
|
19217
|
+
type: 'boolean',
|
|
19218
|
+
default: false,
|
|
19219
|
+
description: 'Keep the `.socket.facts.json` reachability report that the analysis writes to the scan directory instead of deleting it after a successful scan. IMPORTANT: you must delete this file before running a fresh full application reachability scan. A stale `.socket.facts.json` left in place is picked up as a pre-generated input and silently overrides fresh analysis, so the new scan results will not be reliable.'
|
|
19220
|
+
},
|
|
19221
|
+
reachSkipCache: {
|
|
19222
|
+
type: 'boolean',
|
|
19223
|
+
default: false,
|
|
19224
|
+
description: 'Skip caching-based optimizations. By default, the reachability analysis will use cached configurations from previous runs to speed up the analysis.'
|
|
19225
|
+
},
|
|
19226
|
+
reachUseOnlyPregeneratedSboms: {
|
|
19227
|
+
type: 'boolean',
|
|
19228
|
+
default: false,
|
|
19229
|
+
description: 'When using this option, the scan is created based only on pre-generated CDX and SPDX files in your project.'
|
|
19230
|
+
}
|
|
19231
|
+
};
|
|
19232
|
+
const excludePathsFlag = {
|
|
19233
|
+
excludePaths: {
|
|
19234
|
+
type: 'string',
|
|
19235
|
+
isMultiple: true,
|
|
19236
|
+
description: 'List of glob patterns to exclude from the scan, including SCA/SBOM manifest discovery and (when --reach is enabled) full application reachability analysis. Patterns are anchored micromatch globs matched relative to the Socket scan root, which is the command working directory (`--cwd` if set), not the reachability target: `tests` matches only `<cwd>/tests`; use `**/tests` to match at any depth. Negation patterns (`!path`) are not supported. Accepts a comma-separated value or multiple flags.'
|
|
19237
|
+
}
|
|
19238
|
+
};
|
|
19239
|
+
|
|
17578
19240
|
async function suggestTarget() {
|
|
17579
19241
|
// We could prefill this with sub-dirs of the current
|
|
17580
19242
|
// dir ... but is that going to be useful?
|
|
@@ -18600,17 +20262,39 @@ async function createScanFromGithub({
|
|
|
18600
20262
|
return sure;
|
|
18601
20263
|
}
|
|
18602
20264
|
}
|
|
20265
|
+
return await runGithubScanLoop(targetRepos, repoSlug => scanRepo(repoSlug, {
|
|
20266
|
+
githubApiUrl,
|
|
20267
|
+
githubToken,
|
|
20268
|
+
orgSlug,
|
|
20269
|
+
orgGithub,
|
|
20270
|
+
outputKind,
|
|
20271
|
+
repos
|
|
20272
|
+
}));
|
|
20273
|
+
}
|
|
20274
|
+
|
|
20275
|
+
/**
|
|
20276
|
+
* Drive the per-repo scan loop and decide the overall run result.
|
|
20277
|
+
*
|
|
20278
|
+
* The loop stops early on a blocking GitHub error (rate limit / auth / abuse
|
|
20279
|
+
* detection) because every remaining repo would fail the same way. Previously
|
|
20280
|
+
* a rate-limited token made every repo fail its API calls, the loop swallowed
|
|
20281
|
+
* each failure, and the final "N repos / 0 manifests" summary misled users and
|
|
20282
|
+
* CI into thinking the scan succeeded when nothing was uploaded. A run where
|
|
20283
|
+
* every attempted repo failed for a non-blocking reason is also surfaced as an
|
|
20284
|
+
* error rather than a silent ok:true.
|
|
20285
|
+
*
|
|
20286
|
+
* `scanRepoFn` is injected so this decision logic can be tested without the
|
|
20287
|
+
* GitHub network path.
|
|
20288
|
+
*/
|
|
20289
|
+
async function runGithubScanLoop(targetRepos, scanRepoFn) {
|
|
18603
20290
|
let scansCreated = 0;
|
|
20291
|
+
let reposScanned = 0;
|
|
20292
|
+
let blockingError;
|
|
20293
|
+
const perRepoFailures = [];
|
|
18604
20294
|
for (const repoSlug of targetRepos) {
|
|
20295
|
+
reposScanned += 1;
|
|
18605
20296
|
// eslint-disable-next-line no-await-in-loop
|
|
18606
|
-
const scanCResult = await
|
|
18607
|
-
githubApiUrl,
|
|
18608
|
-
githubToken,
|
|
18609
|
-
orgSlug,
|
|
18610
|
-
orgGithub,
|
|
18611
|
-
outputKind,
|
|
18612
|
-
repos
|
|
18613
|
-
});
|
|
20297
|
+
const scanCResult = await scanRepoFn(repoSlug);
|
|
18614
20298
|
if (scanCResult.ok) {
|
|
18615
20299
|
const {
|
|
18616
20300
|
scanCreated
|
|
@@ -18618,9 +20302,43 @@ async function createScanFromGithub({
|
|
|
18618
20302
|
if (scanCreated) {
|
|
18619
20303
|
scansCreated += 1;
|
|
18620
20304
|
}
|
|
20305
|
+
continue;
|
|
18621
20306
|
}
|
|
20307
|
+
perRepoFailures.push({
|
|
20308
|
+
repo: repoSlug,
|
|
20309
|
+
message: scanCResult.message
|
|
20310
|
+
});
|
|
20311
|
+
// Stop on rate-limit / auth / abuse failures: every remaining repo will
|
|
20312
|
+
// fail for the same reason, and continuing only burns more quota while
|
|
20313
|
+
// delaying the real error.
|
|
20314
|
+
if (utils.isGitHubBlockingError(scanCResult.message)) {
|
|
20315
|
+
blockingError = {
|
|
20316
|
+
ok: false,
|
|
20317
|
+
message: scanCResult.message,
|
|
20318
|
+
cause: scanCResult.cause
|
|
20319
|
+
};
|
|
20320
|
+
break;
|
|
20321
|
+
}
|
|
20322
|
+
}
|
|
20323
|
+
if (blockingError) {
|
|
20324
|
+
logger.logger.fail(blockingError.message);
|
|
20325
|
+
return blockingError;
|
|
20326
|
+
}
|
|
20327
|
+
|
|
20328
|
+
// If every attempted repo failed (but not for a known blocking reason),
|
|
20329
|
+
// treat the run as an error so scripts do not infer success from an
|
|
20330
|
+
// ok:true with zero scans created. Checked before the success lines below
|
|
20331
|
+
// so an all-failed run never prints green "processed" output that a script
|
|
20332
|
+
// reading log lines could mistake for success.
|
|
20333
|
+
if (reposScanned > 0 && scansCreated === 0 && perRepoFailures.length === reposScanned) {
|
|
20334
|
+
const firstFailure = perRepoFailures[0];
|
|
20335
|
+
return {
|
|
20336
|
+
ok: false,
|
|
20337
|
+
message: 'All repos failed to scan',
|
|
20338
|
+
cause: `All ${reposScanned} repos failed to scan. First failure for ${firstFailure.repo}: ${firstFailure.message}. ` + 'See the log above for per-repo details.'
|
|
20339
|
+
};
|
|
18622
20340
|
}
|
|
18623
|
-
logger.logger.success(
|
|
20341
|
+
logger.logger.success(reposScanned, 'GitHub repos processed');
|
|
18624
20342
|
logger.logger.success(scansCreated, 'with supported Manifest files');
|
|
18625
20343
|
return {
|
|
18626
20344
|
ok: true,
|
|
@@ -18801,8 +20519,20 @@ async function testAndDownloadManifestFiles({
|
|
|
18801
20519
|
if (result.data.isManifest) {
|
|
18802
20520
|
fileCount += 1;
|
|
18803
20521
|
}
|
|
18804
|
-
} else
|
|
18805
|
-
|
|
20522
|
+
} else {
|
|
20523
|
+
// A blocking error (rate limit / auth / abuse detection) is token-wide:
|
|
20524
|
+
// every remaining download would fail the same way, and an earlier
|
|
20525
|
+
// successful download must not mask it into an ok:true scan of a
|
|
20526
|
+
// partial manifest set. Surface it immediately, the same way the repo
|
|
20527
|
+
// loop short-circuits on blocking errors.
|
|
20528
|
+
if (utils.isGitHubBlockingError(result.message)) {
|
|
20529
|
+
logger.logger.groupEnd();
|
|
20530
|
+
logger.logger.fail(result.message);
|
|
20531
|
+
return result;
|
|
20532
|
+
}
|
|
20533
|
+
if (!firstFailureResult) {
|
|
20534
|
+
firstFailureResult = result;
|
|
20535
|
+
}
|
|
18806
20536
|
}
|
|
18807
20537
|
}
|
|
18808
20538
|
logger.logger.groupEnd();
|
|
@@ -18873,22 +20603,17 @@ async function downloadManifestFile({
|
|
|
18873
20603
|
debug.debugDir('inspect', {
|
|
18874
20604
|
fileUrl
|
|
18875
20605
|
});
|
|
18876
|
-
utils.
|
|
18877
|
-
|
|
18878
|
-
|
|
18879
|
-
|
|
18880
|
-
|
|
18881
|
-
|
|
18882
|
-
|
|
18883
|
-
|
|
18884
|
-
});
|
|
18885
|
-
utils.debugApiResponse('GET', fileUrl, downloadUrlResponse.status);
|
|
18886
|
-
} catch (e) {
|
|
18887
|
-
utils.debugApiResponse('GET', fileUrl, undefined, e);
|
|
18888
|
-
throw e;
|
|
20606
|
+
const reqResult = await utils.githubApiRequest(fileUrl, {
|
|
20607
|
+
method: 'GET',
|
|
20608
|
+
headers: {
|
|
20609
|
+
Authorization: `Bearer ${githubToken}`
|
|
20610
|
+
}
|
|
20611
|
+
}, `fetching the download URL for ${file}`);
|
|
20612
|
+
if (!reqResult.ok) {
|
|
20613
|
+
return reqResult;
|
|
18889
20614
|
}
|
|
18890
20615
|
debug.debugFn('notice', 'complete: request');
|
|
18891
|
-
const downloadUrlText =
|
|
20616
|
+
const downloadUrlText = reqResult.data.bodyText;
|
|
18892
20617
|
debug.debugFn('inspect', 'response: raw download url', downloadUrlText);
|
|
18893
20618
|
let downloadUrl;
|
|
18894
20619
|
try {
|
|
@@ -18927,6 +20652,15 @@ async function streamDownloadWithFetch(localPath, downloadUrl) {
|
|
|
18927
20652
|
response = await utils.apiFetch(downloadUrl);
|
|
18928
20653
|
utils.debugApiResponse('GET', downloadUrl, response.status);
|
|
18929
20654
|
if (!response.ok) {
|
|
20655
|
+
// Surface rate-limit / auth failures on the raw download host too, so a
|
|
20656
|
+
// bulk run that trips the limit mid-download fails loudly instead of
|
|
20657
|
+
// being counted as just another skipped file. Header/status-only check;
|
|
20658
|
+
// the stream body is left unconsumed.
|
|
20659
|
+
const blocking = utils.classifyGitHubResponse(response.status, response.headers, '', 'downloading a manifest file');
|
|
20660
|
+
if (blocking) {
|
|
20661
|
+
logger.logger.fail(blocking.message);
|
|
20662
|
+
return blocking;
|
|
20663
|
+
}
|
|
18930
20664
|
const errorMsg = `Download failed due to bad server response: ${response.status} ${response.statusText} for ${downloadUrl}`;
|
|
18931
20665
|
logger.logger.fail(errorMsg);
|
|
18932
20666
|
return {
|
|
@@ -19008,20 +20742,15 @@ async function getLastCommitDetails({
|
|
|
19008
20742
|
logger.logger.info(`Requesting last commit for default branch ${defaultBranch} for ${orgGithub}/${repoSlug}...`);
|
|
19009
20743
|
const commitApiUrl = `${repoApiUrl}/commits?sha=${defaultBranch}&per_page=1`;
|
|
19010
20744
|
debug.debugFn('inspect', 'url: commit', commitApiUrl);
|
|
19011
|
-
utils.
|
|
19012
|
-
|
|
19013
|
-
|
|
19014
|
-
|
|
19015
|
-
|
|
19016
|
-
|
|
19017
|
-
|
|
19018
|
-
});
|
|
19019
|
-
utils.debugApiResponse('GET', commitApiUrl, commitResponse.status);
|
|
19020
|
-
} catch (e) {
|
|
19021
|
-
utils.debugApiResponse('GET', commitApiUrl, undefined, e);
|
|
19022
|
-
throw e;
|
|
20745
|
+
const reqResult = await utils.githubApiRequest(commitApiUrl, {
|
|
20746
|
+
headers: {
|
|
20747
|
+
Authorization: `Bearer ${githubToken}`
|
|
20748
|
+
}
|
|
20749
|
+
}, `fetching the latest commit for ${orgGithub}/${repoSlug}`);
|
|
20750
|
+
if (!reqResult.ok) {
|
|
20751
|
+
return reqResult;
|
|
19023
20752
|
}
|
|
19024
|
-
const commitText =
|
|
20753
|
+
const commitText = reqResult.data.bodyText;
|
|
19025
20754
|
debug.debugFn('inspect', 'response: commit', commitText);
|
|
19026
20755
|
let lastCommit;
|
|
19027
20756
|
try {
|
|
@@ -19112,22 +20841,17 @@ async function getRepoDetails({
|
|
|
19112
20841
|
debug.debugDir('inspect', {
|
|
19113
20842
|
repoApiUrl
|
|
19114
20843
|
});
|
|
19115
|
-
|
|
19116
|
-
|
|
19117
|
-
|
|
19118
|
-
|
|
19119
|
-
|
|
19120
|
-
|
|
19121
|
-
|
|
19122
|
-
|
|
19123
|
-
});
|
|
19124
|
-
utils.debugApiResponse('GET', repoApiUrl, repoDetailsResponse.status);
|
|
19125
|
-
} catch (e) {
|
|
19126
|
-
utils.debugApiResponse('GET', repoApiUrl, undefined, e);
|
|
19127
|
-
throw e;
|
|
20844
|
+
const reqResult = await utils.githubApiRequest(repoApiUrl, {
|
|
20845
|
+
method: 'GET',
|
|
20846
|
+
headers: {
|
|
20847
|
+
Authorization: `Bearer ${githubToken}`
|
|
20848
|
+
}
|
|
20849
|
+
}, `fetching repo details for ${orgGithub}/${repoSlug}`);
|
|
20850
|
+
if (!reqResult.ok) {
|
|
20851
|
+
return reqResult;
|
|
19128
20852
|
}
|
|
19129
20853
|
logger.logger.success(`Request completed.`);
|
|
19130
|
-
const repoDetailsText =
|
|
20854
|
+
const repoDetailsText = reqResult.data.bodyText;
|
|
19131
20855
|
debug.debugFn('inspect', 'response: repo', repoDetailsText);
|
|
19132
20856
|
let repoDetails;
|
|
19133
20857
|
try {
|
|
@@ -19168,21 +20892,16 @@ async function getRepoBranchTree({
|
|
|
19168
20892
|
logger.logger.info(`Requesting default branch file tree; branch \`${defaultBranch}\`, repo \`${orgGithub}/${repoSlug}\`...`);
|
|
19169
20893
|
const treeApiUrl = `${repoApiUrl}/git/trees/${defaultBranch}?recursive=1`;
|
|
19170
20894
|
debug.debugFn('inspect', 'url: tree', treeApiUrl);
|
|
19171
|
-
|
|
19172
|
-
|
|
19173
|
-
|
|
19174
|
-
|
|
19175
|
-
|
|
19176
|
-
|
|
19177
|
-
|
|
19178
|
-
|
|
19179
|
-
});
|
|
19180
|
-
utils.debugApiResponse('GET', treeApiUrl, treeResponse.status);
|
|
19181
|
-
} catch (e) {
|
|
19182
|
-
utils.debugApiResponse('GET', treeApiUrl, undefined, e);
|
|
19183
|
-
throw e;
|
|
20895
|
+
const reqResult = await utils.githubApiRequest(treeApiUrl, {
|
|
20896
|
+
method: 'GET',
|
|
20897
|
+
headers: {
|
|
20898
|
+
Authorization: `Bearer ${githubToken}`
|
|
20899
|
+
}
|
|
20900
|
+
}, `fetching the file tree for ${orgGithub}/${repoSlug}`);
|
|
20901
|
+
if (!reqResult.ok) {
|
|
20902
|
+
return reqResult;
|
|
19184
20903
|
}
|
|
19185
|
-
const treeText =
|
|
20904
|
+
const treeText = reqResult.data.bodyText;
|
|
19186
20905
|
debug.debugFn('inspect', 'response: tree', treeText);
|
|
19187
20906
|
let treeDetails;
|
|
19188
20907
|
try {
|
|
@@ -22168,5 +23887,5 @@ process.on('unhandledRejection', async (reason, promise) => {
|
|
|
22168
23887
|
// eslint-disable-next-line n/no-process-exit
|
|
22169
23888
|
process.exit(1);
|
|
22170
23889
|
});
|
|
22171
|
-
//# debugId=
|
|
23890
|
+
//# debugId=684e53fa-a85b-4a32-befc-11d50061b4b0
|
|
22172
23891
|
//# sourceMappingURL=cli.js.map
|