fix(manifest): resolve Maven dependencies through Maven's own resolver - #1519
Merged
Jeppe Fredsgaard Blaabjerg (jfblaa) merged 3 commits intoAug 28, 2026
Merged
Conversation
The Maven facts extension collected the dependency graph with maven-dependency-tree, then re-resolved each artifact itself, passing the root module's repository list for every node in the tree. Maven resolves each node against the repositories that node's own descriptor lineage contributes, so a dependency served only by a repository declared in one module of a reactor could not be materialized for any other module that reached it. Aether's local repository also records which repository each cached file came from, so not even an already-downloaded copy counted as available, and --with-files aborted the scan on a dependency the build itself resolves without trouble. Resolution now goes through ProjectDependenciesResolver, the component Maven's own lifecycle uses to build a project's classpath. Per-node repositories, dependency management, scope derivation and reactor substitution are Maven's rather than a re-implementation of them, and failures are reported from Maven's own per-dependency errors. Which artifacts get fetched is expressed as a DependencyFilter, so a plain --facts run collects without downloading anything and a reactor sibling's jar is never requested at the validate phase the CLI runs, where nothing has been packaged. A filtered-out node yields no ArtifactResult, so it can never be mistaken for a resolution failure. Coordinate ids keep Maven's `type` rather than Aether's file extension, and versions use the base version so a resolved remote snapshot cannot leak a timestamped coordinate no manifest names. Conflict-losing nodes, which a verbose collect leaves in the graph, are skipped. Records are byte-identical to the previous output on the projects exercised here. Drops the bundled maven-dependency-tree; the extension jar goes 67K -> 26K. Adds two compat fixtures. repo-inheritance covers a dependency reachable only through a repository a sibling module declares, plus the fail-closed half: an unresolvable dependency must still be reported, since a silently missing jar leaves reachability blind to what it contains. duplicate-failure covers several modules failing on the same dependency, whose identical failures collapse in the value-equality accumulator shared across the reactor. The Maven matrix now spans 3.2.5 through 4.0.0-rc-6, the range the extension claims to support.
…tion 1.1.161 never reached npm — 1.1.160 is still latest there — so no user can install what that section describes. Its tag and GitHub release are immutable and stay; the changelog documents the published package, and GitHub generates its own release notes from the PR list, so the two need not agree. The next bump derives 1.1.162 from the reachable tag either way, so nothing here affects the release tooling. How the section got there: #1516 wrote its heading as `## [Unreleased] - 2026-08-27`, and unreleasedRange() in scripts/release/changelog.mts locates the block by comparing the trimmed, lowercased heading for equality with `## [unreleased]`. The trailing date made it miss, so the release found nothing accrued, fell back to the commit-derived section, and inserted its own heading above the block it could not see — stranding `[Unreleased]` below a released version. - Drops the 1.1.161 section and returns the Coana 15.10.25 note to `## [Unreleased]`, to be promoted by the next release that ships. - Puts `## [Unreleased]` back at the top, without a date. - Leaves out "stop the coana bump from hand-writing versions": a release-workflow change with nothing for a user of the package to act on, which only appeared because the commit-derived fallback ran. - Files the Maven resolver fix under `[Unreleased]`. The locator's intolerance of a trailing date is left alone here; it wants its own change.
Jeppe Fredsgaard Blaabjerg (jfblaa)
force-pushed
the
fix/maven-resolve-via-maven-resolver
branch
from
August 28, 2026 10:08
43769b8 to
0b44351
Compare
The rule against writing a `## [<version>]` heading did not say what the heading may look like when it is recreated, and #1516 shows why that matters. The bump wrote `## [1.1.161](...) - 2026-08-27`; the follow-up correction changed the version to `Unreleased` but kept the date, leaving `## [Unreleased] - 2026-08-27`. unreleasedRange() in scripts/release/changelog.mts matches that heading for equality — case-insensitively, but otherwise exactly — so the dated form is invisible to it. The release promoted nothing, fell back to the section derived from the commits in range, and inserted its own heading above the block it could not see. The note sat below a released version where no release would pick it up, and the version it named never reached npm. Pins the recreated heading to exactly `## [Unreleased]` and says why a date breaks promotion, so the next correction of a malformed heading lands on the form the release can actually find.
Jeppe Fredsgaard Blaabjerg (jfblaa)
requested review from
Benjamin Barslev Nielsen (barslev) and
Martin Torp (mtorp)
August 28, 2026 10:54
Benjamin Barslev Nielsen (barslev)
approved these changes
Aug 28, 2026
Jeppe Fredsgaard Blaabjerg (jfblaa)
deleted the
fix/maven-resolve-via-maven-resolver
branch
August 28, 2026 10:55
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The Maven facts extension collected the dependency graph with
maven-dependency-tree, then re-resolved each artifact itself, passing the root module's repository list for every node in the tree. Maven resolves each node against the repositories that node's own descriptor lineage contributes, so a dependency served only by a repository declared in one module of a reactor could not be materialized for any other module that reached it.Aether's local repository also tracks which repository each cached file came from, so not even an already-downloaded copy counted as available: the resolver went to the network, missed, and
--with-filesaborted the scan on a dependencymvn compileresolves without trouble.Resolution now goes through
ProjectDependenciesResolver— the componentLifecycleDependencyResolverholds, i.e. the one Maven's own lifecycle uses to build every project's classpath.What that changes
DefaultDependencyCollectorBuilderoverrode the session'ssetDependencySelector/setDependencyGraphTransformerwith the tree tool's own.getCollectionErrors,getUnresolvedDependencies+getResolutionErrors, and the partial result onDependencyResolutionException), each carrying Maven's message and the dependency's own scope. Fail-closed is kept: an unresolvable dependency still aborts, because a silently missing jar leaves reachability blind to what that artifact contains and under-reports.DependencyFilter, carrying over the old guard. A plain--factsrun rejects everything, so it collects without downloading; a reactor sibling's jar is never requested at thevalidatephase the CLI runs, where nothing has been packaged and nothing need be installed. A filtered-out node produces noArtifactResult, so it lands in neitherresolvedDependenciesnorunresolvedDependenciesand can never be mistaken for a failure.typerather than Aether's file extension, and versions use the base version so a resolved remote snapshot can't leak a timestamped coordinate no manifest names. Conflict-losing nodes, which a verbose collect (mvn -X) leaves in the graph, are skipped.maven-dependency-treeis dropped; the extension jar goes 67K → 26K.Tests
Two new compat fixtures, both verified to fail on the pre-change jar and pass after:
repo-inheritance—libadeclares the only repository servingdemo.scoped:widget;libbreaches it transitively. Pre-change it fails withCould not find artifact … in central. A second phase mirrors central to a widget-less repo and asserts the failure record is emitted, so fail-closed itself is guarded.duplicate-failure— several modules failing on the same dependency. Their identical failures collapse in the value-equality accumulator shared across the reactor, which a naive "did this module report anything?" size check reads as silence. Runs offline, so it needs no stub repo and never touches the network.The Maven matrix now spans 3.2.5 → 4.0.0-rc-6, the range the extension claims to support: 30 assertions (5 versions × 6) all pass. Records verified byte-identical to the previous jar on two real multi-module projects; a plain
--factsrun against a cold local repository fetches poms only, zero jars.Second commit
docs(changelog)repairs a pre-existing problem the merge surfaced.1.1.161 never reached npm (1.1.160 is still
latest), so nobody can install what that section described. Its tag and GitHub release are immutable and stay as they are — GitHub generates those notes from the PR list, independently of this file — whileCHANGELOG.mddocuments the published package. The next bump derives 1.1.162 from the reachable tag regardless, so this touches no release tooling.How the section got there: #1516 wrote its heading as
## [Unreleased] - 2026-08-27, andunreleasedRange()inscripts/release/changelog.mtslocates the block by comparing the trimmed, lowercased heading for equality with## [unreleased]. The trailing date made it miss, so the release found nothing accrued, fell back to commit-derived notes, and inserted its heading above the block it couldn't see — stranding[Unreleased]below a released version.The commit drops the burned 1.1.161 section, returns the Coana 15.10.25 note to
## [Unreleased]for the next release that actually ships, restores## [Unreleased]at the top without a date, and leaves out "stop the coana bump from hand-writing versions" — a release-workflow change with nothing for a package user to act on, which only appeared because the commit-derived fallback ran.The locator's intolerance of a trailing date is left for its own change.
Third commit
docs(bump-coana)pins the recreated[Unreleased]heading to exactly that spelling, with the reason attached.Tracing #1516: the bump wrote
## [1.1.161](…) - 2026-08-27— a version heading, which the skill already forbids — and the follow-up correction changed the version toUnreleasedbut kept the date. The skill said what not to write, not what the heading must look like once recreated, so the partial fix landed on a form the release cannot see.Note the limit: the bump PR was opened by
app/socket-pr-bot, which lives outside this repo, so this commit only governs the in-repo procedure and whoever corrects a malformed heading by hand. The bot still writes a## [<version>]heading and apackage.jsonversion — both of which #1515 forbade — and that needs fixing where the bot lives.