Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const result = await inspect(rootPath, targetFile, options);
| `allProjects` | boolean | `false` | Include all projects in multi-module builds |
| `mavenAggregateProject` | boolean | `false` | Treat as Maven aggregate project |
| `mavenVerboseIncludeAllVersions` | boolean | `false` | Include all dependency versions in verbose mode |
| `mavenDebugOutput` | boolean | `false` | Log raw Maven command output to debug |
| `includeProvenance` | boolean | `false` | Generate cryptographic fingerprints for artifacts to prove origin |
| `fingerprintAlgorithm` | string | `'sha1'` | Hash algorithm ('sha1', 'sha256', 'sha512') |
| `mavenRepository` | string | - | Custom Maven repository path |
Expand Down
7 changes: 6 additions & 1 deletion lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export interface MavenOptions extends legacyPlugin.BaseInspectOptions {
mavenRepository?: string;
showMavenBuildScope?: boolean;
mavenSkipWrapper?: boolean;
mavenDebugOutput?: boolean;
}

function buildFingerprintOptions(
Expand Down Expand Up @@ -79,6 +80,7 @@ export async function inspect(
scanAllUnmanaged: false,
'print-graph': false,
mavenVerboseIncludeAllVersions: false,
mavenDebugOutput: false,
};
}
const fingerprintOptions = buildFingerprintOptions(options);
Expand Down Expand Up @@ -138,6 +140,8 @@ export async function inspect(
args.includes('-Dverbose=true') ||
!!options['print-graph'];

const logMavenOutput = !!options.mavenDebugOutput;

let executionResult;
try {
// Execute Maven pipeline (resolve + tree)
Expand All @@ -146,6 +150,7 @@ export async function inspect(
options.mavenAggregateProject,
verboseEnabled,
args,
logMavenOutput,
);
debug(
`Verbose enabled with all versions: ${options.mavenVerboseIncludeAllVersions}`,
Expand Down Expand Up @@ -193,7 +198,7 @@ export async function inspect(
...{ scannedProjects },
};
} catch (err) {
if (executionResult) {
if (executionResult && !logMavenOutput) {
debug(`>>> Output from mvn: ${executionResult.dependencyTreeResult}`);
}

Expand Down
24 changes: 20 additions & 4 deletions lib/maven/dependency-resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ import { debug } from '../index';
import { MavenContext } from './context';
import { MAVEN_DEPENDENCY_PLUGIN_VERSION } from './version';

export interface MavenDependencyResolveResult {
dependencyResolveResult: string;
command: string;
args: string[];
}

export function buildArgs(
context: MavenContext,
mavenArgs: string[],
Expand Down Expand Up @@ -55,7 +61,7 @@ export async function executeMavenDependencyResolve(
mavenAggregateProject: boolean,
args: string[],
pluginVersion: string = MAVEN_DEPENDENCY_PLUGIN_VERSION,
): Promise<string> {
): Promise<MavenDependencyResolveResult> {
const mvnArgs = buildArgs(
context,
args,
Expand All @@ -67,9 +73,19 @@ export async function executeMavenDependencyResolve(
debug(`Maven working directory: ${context.workingDirectory}`);

try {
return await subProcess.execute(context.command, mvnArgs, {
cwd: context.workingDirectory,
});
const dependencyResolveResult = await subProcess.execute(
context.command,
mvnArgs,
{
cwd: context.workingDirectory,
},
);

return {
dependencyResolveResult,
command: context.command,
args: mvnArgs,
};
} catch (error) {
debug(
`dependency:resolve execution failed - command: ${
Expand Down
60 changes: 51 additions & 9 deletions lib/maven/executor.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { executeMavenDependencyResolve } from './dependency-resolve';
import {
executeMavenDependencyResolve,
MavenDependencyResolveResult,
} from './dependency-resolve';
import { executeMavenDependencyTree } from './dependency-tree';
import { MavenContext } from './context';
import { getMavenVersion, selectPluginVersion } from './version';
Expand Down Expand Up @@ -33,6 +36,22 @@ export interface MavenExecutionResult {
args: string[];
}

function logMavenCommandOutput(
phase: string,
command: string,
args: string[],
output: string,
) {
if (!output) {
debug(
`>>> Maven ${phase} output (${command} ${args.join(' ')}): <no output>`,
);
return;
}

debug(`>>> Maven ${phase} output (${command} ${args.join(' ')}):`, output);
}

/**
* Execute the optimal Maven pipeline: dependency:tree (required) + dependency:resolve (conditional)
*
Expand All @@ -44,6 +63,7 @@ export async function executeMavenPipeline(
mavenAggregateProject = false,
verboseEnabled: boolean,
args: string[],
logMavenOutput = false,
): Promise<MavenExecutionResult> {
// Get Maven version to select appropriate maven-dependency-plugin version
// This is used for verbose mode (dependency:tree) and dependency:resolve
Expand All @@ -61,6 +81,15 @@ export async function executeMavenPipeline(
explicitPluginVersion,
);

if (logMavenOutput) {
logMavenCommandOutput(
'dependency:tree',
treeResult.command,
treeResult.args,
treeResult.dependencyTreeResult,
);
}

const hasMetaversions = detectMetaversions(treeResult.dependencyTreeResult);
debug(`Metaversions detected: ${hasMetaversions}`);

Expand All @@ -69,16 +98,29 @@ export async function executeMavenPipeline(
if (hasMetaversions) {
try {
debug('Running dependency:resolve for metaversion resolution');
const resolveResult = await executeMavenDependencyResolve(
context,
mavenAggregateProject,
args,
explicitPluginVersion,
);
debug(`Resolve result: ${resolveResult}`);
const resolveResult: MavenDependencyResolveResult =
await executeMavenDependencyResolve(
context,
mavenAggregateProject,
args,
explicitPluginVersion,
);

if (logMavenOutput) {
logMavenCommandOutput(
'dependency:resolve',
resolveResult.command,
resolveResult.args,
resolveResult.dependencyResolveResult,
);
} else {
debug(`Resolve result: ${resolveResult.dependencyResolveResult}`);
}

// Parse immediately and fail fast if there's an issue
versionResolver = createVersionResolver(resolveResult);
versionResolver = createVersionResolver(
resolveResult.dependencyResolveResult,
);
} catch (err) {
debug(`Version resolution failed: ${err}`);
// Graceful degradation using no-op version resolver
Expand Down
53 changes: 49 additions & 4 deletions tests/jest/functional/maven/executor.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { executeMavenPipeline } from '../../../../lib/maven/executor';
import * as plugin from '../../../../lib';
import * as dependencyTree from '../../../../lib/maven/dependency-tree';
import * as dependencyResolve from '../../../../lib/maven/dependency-resolve';
import * as version from '../../../../lib/maven/version';
Expand Down Expand Up @@ -98,9 +99,13 @@ describe('executeMavenPipeline conditional logic', () => {
});

// Mock resolve result with resolved versions
mockExecuteMavenDependencyResolve.mockResolvedValue(`[INFO] The following files have been resolved:
mockExecuteMavenDependencyResolve.mockResolvedValue({
dependencyResolveResult: `[INFO] The following files have been resolved:
[INFO] junit:junit:jar:4.13.2:test -- module junit [auto]
[INFO] org.slf4j:slf4j-api:jar:1.7.36:compile -- module org.slf4j.api`);
[INFO] org.slf4j:slf4j-api:jar:1.7.36:compile -- module org.slf4j.api`,
command: 'mvn',
args: ['dependency:resolve', '--batch-mode'],
});

const result = await executeMavenPipeline(context, false, false, []);

Expand Down Expand Up @@ -167,8 +172,12 @@ describe('executeMavenPipeline conditional logic', () => {
args: ['test-compile', 'dependency:tree', '--batch-mode'],
});

mockExecuteMavenDependencyResolve.mockResolvedValue(`[INFO] The following files have been resolved:
[INFO] com.example:module-a:jar:1.0.0:compile -- module com.example.module.a`);
mockExecuteMavenDependencyResolve.mockResolvedValue({
dependencyResolveResult: `[INFO] The following files have been resolved:
[INFO] com.example:module-a:jar:1.0.0:compile -- module com.example.module.a`,
command: 'mvn',
args: ['dependency:resolve', '--batch-mode'],
});

const result = await executeMavenPipeline(context, true, false, [
'-Pprofile',
Expand All @@ -192,4 +201,40 @@ describe('executeMavenPipeline conditional logic', () => {

expect(result.versionResolver).not.toBe(NO_OP_VERSION_RESOLVER);
});

test('should log mvn output when requested', async () => {
mockExecuteMavenDependencyTree.mockResolvedValue({
dependencyTreeResult: `digraph "com.example:test:jar:1.0.0" {
"com.example:test:jar:1.0.0" -> "junit:junit:jar:RELEASE:test" ;
}`,
command: 'mvn',
args: ['dependency:tree', '--batch-mode'],
});

mockExecuteMavenDependencyResolve.mockResolvedValue({
dependencyResolveResult: `[INFO] Resolved artifacts
[INFO] junit:junit:jar:4.13.2:test`,
command: 'mvn',
args: ['dependency:resolve', '--batch-mode'],
});

const debugSpy = jest
.spyOn(plugin, 'debug')
.mockImplementation(() => undefined);

try {
await executeMavenPipeline(context, false, false, [], true);

expect(debugSpy).toHaveBeenCalledWith(
expect.stringContaining('dependency:tree output'),
expect.stringContaining('digraph "com.example:test:jar:1.0.0"'),
);
expect(debugSpy).toHaveBeenCalledWith(
expect.stringContaining('dependency:resolve output'),
expect.stringContaining('Resolved artifacts'),
);
} finally {
debugSpy.mockRestore();
}
});
});