fix(marketplace): honour producer tag_pattern in consumer semver range resolution - #2366
Open
sergio-sisternes-epam wants to merge 2 commits into
Open
fix(marketplace): honour producer tag_pattern in consumer semver range resolution#2366sergio-sisternes-epam wants to merge 2 commits into
sergio-sisternes-epam wants to merge 2 commits into
Conversation
When a consumer resolves a marketplace dependency with a semver range
(e.g. version: "~2.1.0"), APM now reads the tag naming pattern declared
by the marketplace producer instead of always falling back to the
hardcoded "{name}--v{version}" convention.
Changes:
- builder.py: add effective_tag_pattern to ResolvedPackage; thread it
from _resolve_entry through _resolve_explicit_ref and
_resolve_version_range.
- output_mappers.py: emit tag_pattern in the remote source dict so
marketplace.json carries the producer's chosen pattern.
- models.py: parse tag_pattern from the source dict into
MarketplacePlugin.
- resolver.py: pass plugin.tag_pattern (or DEFAULT_TAG_PATTERN
fallback) to resolve_version_constraint.
- Update golden fixture and existing unit tests to reflect the new
tag_pattern field in source dicts.
- Add unit and integration tests covering the full producer-to-consumer
closure.
- Update docs and CHANGELOG.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR fixes marketplace dependency semver-range resolution by propagating the producer-declared tag naming pattern (tagPattern / tag_pattern) into marketplace.json and ensuring apm install uses it when resolving version constraints, restoring producer/consumer symmetry and preventing “no matching tag” failures for non-default tag layouts.
Changes:
- Producer: thread an
effective_tag_patternthrough the marketplace build pipeline and emitsource.tag_patternintomarketplace.json. - Consumer: parse
tag_patternintoMarketplacePluginand pass it intoresolve_version_constraint()during version constraint resolution. - Tests/docs: extend unit + integration coverage for end-to-end closure, update golden fixture, and refresh dependency docs + changelog.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
src/apm_cli/marketplace/builder.py |
Tracks and carries an effective tag pattern alongside resolved package data. |
src/apm_cli/marketplace/output_mappers.py |
Emits tag_pattern into marketplace JSON source objects. |
src/apm_cli/marketplace/models.py |
Parses tag_pattern from marketplace.json into the consumer model. |
src/apm_cli/marketplace/resolver.py |
Uses parsed tag_pattern when resolving semver ranges to git tags. |
tests/unit/marketplace/test_version_resolver.py |
Adds unit coverage for custom slash tag patterns and default behavior. |
tests/unit/marketplace/test_versioned_resolver.py |
Ensures resolver forwards tag_pattern into version constraint resolution. |
tests/unit/marketplace/test_marketplace_resolver.py |
Updates expected call args for version constraint resolution. |
tests/unit/marketplace/test_builder.py |
Updates assertions around which keys may appear in emitted source dicts. |
tests/integration/marketplace/test_build_integration.py |
Adds producer→consumer integration tests validating tag_pattern closure. |
tests/fixtures/marketplace/golden.json |
Updates golden fixture to include emitted tag_pattern. |
packages/apm-guide/.apm/skills/apm-usage/dependencies.md |
Updates dependency docs to describe tag-pattern-based resolution. |
docs/src/content/docs/consumer/manage-dependencies.md |
Notes that marketplace dict dependencies resolve using marketplace tag naming patterns. |
CHANGELOG.md |
Adds an Unreleased Fixed entry describing the bugfix. |
Comments suppressed due to low confidence (1)
src/apm_cli/marketplace/resolver.py:989
- The fallback debug log still hardcodes the old
"%s--v*"tag layout, but this resolver now triesplugin.tag_patternwhich can be something else (e.g.{name}/{version}). Updating the message to reflect the actual pattern attempted would make debugging tag resolution failures less misleading.
tag_pattern=plugin.tag_pattern or DEFAULT_TAG_PATTERN,
host=source.host,
token=token,
auth_scheme=auth_scheme,
auth_resolver=auth_resolver,
…eld comment - CHANGELOG: condense to one line, reference PR #2366 (not issue #2319), resolve merge conflict with main's #2069 entry - dependencies.md: distinguish producer default (v{version}) from legacy consumer fallback ({name}--v{version}) per reviewer feedback - builder.py: clarify effective_tag_pattern comment -- it is for consumer-side semver resolution closure, not used for explicit ref entries Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
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.
fix(marketplace): honour producer tag_pattern in consumer semver range resolution
TL;DR
When a consumer installs a marketplace dependency with a semver range (e.g.
version: "~2.1.0"), APM now uses the tag-naming pattern declared by the marketplace producer instead of silently hardcoding{name}--v{version}. The bug caused silent install failures for any marketplace that ships tags under a different convention. The fix threadseffective_tag_patternfromapm packthroughmarketplace.jsontoapm install.Note
Closes #2319. Backward-compatible: old
marketplace.jsonfiles withouttag_patternfall back to{name}--v{version}(unchanged behaviour).Problem (WHY)
resolve_marketplace_plugin()calledresolve_version_constraint()with notag_patternkwarg, so the consumer always resolved tags using the hardcoded default"{name}--v{version}"— regardless of what pattern the producer declared inbuild.tagPattern. Any marketplace using a different convention (e.g.{name}/{version},releases/{name}-v{version}) would produce zero tag matches and fail silently.apm packalready honouredbuild.tagPatternwhen producingmarketplace.json, but the resulting file never carried the resolved pattern forward. The producer-to-consumer contract had a one-field gap.v{version}) and the consumer default ({name}--v{version}) meant that even the built-in defaults were asymmetric — a marketplace built with no explicittagPatternwould produce tags likev1.0.0but the consumer would search formy-plugin--v1.0.0.Approach (WHAT)
effective_tag_patterntoResolvedPackage; compute it in_resolve_entry()asentry.tag_pattern or yml.build.tag_pattern."tag_pattern"in the remote source dict ofmarketplace.json(producer side).tag_patternfrom the source dict intoMarketplacePlugin(consumer model).plugin.tag_pattern or DEFAULT_TAG_PATTERNtoresolve_version_constraint()(consumer resolver).Implementation (HOW)
src/apm_cli/marketplace/builder.py— addseffective_tag_pattern: str = ""to the frozenResolvedPackagedataclass;_resolve_entry()computes the pattern from the entry or build-level config and forwards it through_resolve_explicit_ref()(4 return sites) and_resolve_version_range()(1 return site). Local-path packages retain the default"".src/apm_cli/marketplace/output_mappers.py— emits"tag_pattern"in the remote source object only whenpkg.effective_tag_patternis non-empty, keeping the diff minimal and old marketplace files stable.src/apm_cli/marketplace/models.py— addstag_pattern: str | None = NonetoMarketplacePlugin;_parse_plugin_entry()reads it from the source dict at parse time.src/apm_cli/marketplace/resolver.py— importsDEFAULT_TAG_PATTERNfromversion_resolverand passestag_pattern=plugin.tag_pattern or DEFAULT_TAG_PATTERNtoresolve_version_constraint(), closing the call-site gap.tests/fixtures/marketplace/golden.json) and three existing tests that assertedtag_patternmust NOT appear in source dicts; added 5 new tests covering the full producer→consumer closure.packages/apm-guide/.apm/skills/apm-usage/dependencies.mdcorrects the stale{name}--v{version}reference;docs/src/content/docs/consumer/manage-dependencies.mdnotes pattern propagation; CHANGELOG adds[Fixed]entry.Diagrams
The sequence below shows the old (broken) and new (fixed) resolution path for a marketplace semver range.
sequenceDiagram participant C as apm install participant R as marketplace/resolver.py participant VR as marketplace/version_resolver.py participant Git as Git remote C->>R: resolve_marketplace_plugin("my-plugin", "acme-mkt", "~2.1.0") R->>R: fetch marketplace.json rect rgb(255, 247, 200) Note over R,VR: NEW: read tag_pattern from plugin source dict R->>VR: resolve_version_constraint(..., tag_pattern="v{version}") VR->>Git: list refs matching "v*" Git-->>VR: ["v2.0.0", "v2.1.3"] VR-->>R: "v2.1.3" end R-->>C: resolved ref = "v2.1.3"The flow below shows the producer path that now emits
tag_patternintomarketplace.json.flowchart LR subgraph Producer["apm pack"] E["_resolve_entry()"] RP["ResolvedPackage\n(effective_tag_pattern)"]:::new OM["output_mappers.py\nemit tag_pattern"]:::new MJ["marketplace.json\nsource.tag_pattern"]:::new end subgraph Consumer["apm install"] MP["MarketplacePlugin\n(tag_pattern field)"]:::new RS["resolver.py\npass tag_pattern kwarg"]:::new VC["resolve_version_constraint"] end E --> RP --> OM --> MJ --> MP --> RS --> VC classDef new stroke-dasharray: 5 5; class RP,OM,MJ,MP,RS new;Trade-offs
tag_patternfor all non-local packages, not only when it differs from the default. Chosen because the consumer has no way to know what the original default was at pack time; always emitting the resolved value is unambiguous. Rejected "emit only when non-default" because it would require the mapper to know the consumer's fallback, coupling two modules.DEFAULT_TAG_PATTERNwhenplugin.tag_patternisNone. Ensures oldmarketplace.jsonfiles (produced before this fix) continue to work with the existing{name}--v{version}convention — no forced migration.tag_patternmust not appear in source dicts were incorrect post-fix and have been updated.Benefits
apm installwith a semver range now resolves to the correct git tag regardless of the tag-naming convention the marketplace producer chose — eliminating silent install failures.marketplace.jsoncarries a self-describingtag_patternfield that future tooling (CLImarketplace inspect, drift detection) can use without re-readingapm.yml.tag_patterncontinue to install unchanged — zero forced migration.Validation
pytest (1522 tests, 0 failures)
Lint (ruff + pylint R0801 + auth-signal check)
Scenario Evidence
apm installresolves a semver range against tags matching the marketplace's declaredtagPattern, not a hardcoded defaulttests/unit/marketplace/test_version_resolver.py::TestResolveVersionConstraint::test_custom_slash_tag_pattern_resolvestests/unit/marketplace/test_version_resolver.py::TestResolveVersionConstraint::test_custom_slash_pattern_caret_picks_highest(regression-trap for #2319)tagPatternpasses that pattern to the consumer resolvertests/unit/marketplace/test_versioned_resolver.py::TestResolveMarketplacePlugin::test_semver_range_uses_custom_tag_patternapm packembedstag_patterninmarketplace.jsonsource dictstests/integration/marketplace/test_build_integration.py::TestTagPatternClosure::test_producer_emits_tag_pattern_in_sourcetag_patternfrommarketplace.jsonintoMarketplacePlugintests/integration/marketplace/test_build_integration.py::TestTagPatternClosure::test_consumer_parses_tag_pattern_from_sourcetests/integration/marketplace/test_build_integration.py::TestTagPatternClosure::test_consumer_resolver_uses_tag_pattern_from_marketplace_jsontag_patterncontinue to resolve using the original defaulttests/unit/marketplace/test_version_resolver.py::TestResolveVersionConstraint::test_default_pattern_unaffected_by_fixHow to test
git checkout sergio-sisternes-epam-fix-2319-semver-tag-pattern && uv run --extra dev pytest tests/unit/marketplace/ tests/integration/marketplace/ -q --ignore=tests/integration/marketplace/test_live_e2e.py— expect 1522 passed, 0 failed.tests/fixtures/marketplace/golden.json— each plugin'ssourceobject should contain"tag_pattern": "v{version}".tests/integration/marketplace/test_build_integration.py, search forTestTagPatternClosure— three integration tests exercise the producer-to-consumer closure with a custom{name}/{version}pattern.packages/apm-guide/.apm/skills/apm-usage/dependencies.mdlines 211-224 — the stale{name}--v{version}reference is replaced with the correct dynamic description.CHANGELOG.mdunder[Unreleased]for the[Fixed]entry describing this change.Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com