Skip to content

fix(deps): keep env.jsonc component peers after lane merge, support +^/+~ versions - #10815

Open
davidfirst wants to merge 2 commits into
masterfrom
fix/env-jsonc-lane-peer-deps
Open

davidfirst wants to merge 2 commits into
masterfrom
fix/env-jsonc-lane-peer-deps

Conversation

@davidfirst

Copy link
Copy Markdown
Member

An env.jsonc peer can be a component that is also on the lane. On the lane, the component dependency is a peer with the lane snap. When the lane is released from a bare scope, the snap/tag recalculates the dependency-resolver data from the legacy fields. The legacy fields do not keep component peers. Thus, the peers are removed on main, and each new lane shows them as new dependencies. Also, the released package.json keeps the env.jsonc version, which can be older than the version that is released.

This PR:

  • Restores the component peers from the dependency-resolver data when a component is loaded from the model (toConsumerComponent). The bare-scope snap/tag now keeps them.
  • Adds +^ and +~ to the + placeholder in env.jsonc (version and supportedRange). The placeholder uses the resolved version of the dependency: the snap on a lane, and the new tag after the release (for example, +^ gives ^1.2.3). A prefix is not added to a snap.

Tests: unit tests for the new helper and the policy validation. An e2e test snaps on a lane, merges, releases from a bare scope, and makes sure that the peer stays and the package.json has ^<new-tag>.

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Preserve component peers on lane release and resolve +^/+~ versions

🐞 Bug fix ✨ Enhancement 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Preserve component peers when merged lanes are tagged from bare scopes.
• Resolve env.jsonc +^ and +~ placeholders against current snaps or released tags.
• Cover policy validation, version resolution, and lane-to-release behavior with tests.
Diagram

graph TD
  Lane["Merged lane"] --> Model[("Version model")] --> Consumer["Consumer component"] --> Resolver["Dependency resolver"] --> Manifest["Generated manifests"] --> Package["Released package"]
  Policy["Env peer policy"] --> Resolver
  Policy --> Plus["Plus resolver"] --> Manifest
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Persist peers in legacy Version fields
  • ➕ All consumers of legacy dependency fields could see component peers directly.
  • ➖ Duplicates dependency-resolver data and requires handling existing versions that lack those fields.
2. Recompute env peers during bare-scope tagging
  • ➕ Avoids restoring peers when loading every model component.
  • ➖ Depends on env policy and dependency resolution being available during remote release; does not recover peers from stored resolver data.

Recommendation: Keep the PR’s model-loading restoration: it recovers peers from data already stored in historical versions and supports bare-scope tagging without requiring the env to be resolved again. Centralizing placeholder resolution is preferable to separate +^/+~ rules in each manifest path.

Files changed (19) +294 / -33

Enhancement (12) +89 / -27
component-overrides.tsTreat plus-prefixed overrides as placeholders +2/-1

Treat plus-prefixed overrides as placeholders

• Excludes +, +^, and +~ override values from concrete-version handling.

components/legacy/consumer-config/component-overrides.ts

apply-overrides.tsResolve env peer placeholders for package dependencies +9/-4

Resolve env peer placeholders for package dependencies

• Resolves +, +^, and +~ against discovered package versions while retaining component peer ranges for later manifest generation.

scopes/dependencies/dependencies/dependencies-loader/apply-overrides.ts

dependencies-versions-resolver.tsRetain plus-prefixed component version ranges +2/-1

Retain plus-prefixed component version ranges

• Prevents dependency version updates from clearing +^ and +~ placeholders.

scopes/dependencies/dependencies/dependencies-loader/dependencies-versions-resolver.ts

overrides-dependencies.tsApply plus placeholders to package.json overrides +5/-3

Apply plus placeholders to package.json overrides

• Derives versions for +, +^, and +~ overrides from package.json dependencies.

scopes/dependencies/dependencies/dependencies-loader/overrides-dependencies.ts

dependency-resolver.main.runtime.tsAllow supported plus versions in policy validation +2/-1

Allow supported plus versions in policy validation

• Recognizes the shared set of plus placeholders as valid special policy versions.

scopes/dependencies/dependency-resolver/dependency-resolver.main.runtime.ts

index.tsExport plus-version helpers +2/-0

Export plus-version helpers

• Makes placeholder detection, resolution, and types available to dependency consumers.

scopes/dependencies/dependency-resolver/index.ts

hoist-dependencies.tsAccept plus placeholders during range classification +7/-1

Accept plus placeholders during range classification

• Avoids rejecting +^ and +~ as invalid semver ranges during dependency hoisting.

scopes/dependencies/dependency-resolver/manifest/deduping/hoist-dependencies.ts

update-dependency-version.tsExclude plus placeholders from concrete variant versions +2/-1

Exclude plus placeholders from concrete variant versions

• Treats +^ and +~ like + when choosing a dependency version from variant policy.

scopes/dependencies/dependency-resolver/manifest/update-dependency-version.ts

workspace-manifest-factory.tsHandle plus ranges in workspace peer resolution +9/-8

Handle plus ranges in workspace peer resolution

• Accounts for all plus placeholders during peer conflict selection and override injection, then resolves them against current dependency versions.

scopes/dependencies/dependency-resolver/manifest/workspace-manifest-factory.ts

workspace-manifest.tsResolve env peer ranges in workspace manifests +6/-4

Resolve env peer ranges in workspace manifests

• Expands +^ and +~ using already resolved workspace dependencies, retaining the existing wildcard fallback when no version is found.

scopes/dependencies/dependency-resolver/manifest/workspace-manifest.ts

plus-version.tsCentralize plus-version placeholder semantics +37/-0

Centralize plus-version placeholder semantics

• Defines +, +^, and +~ and resolves them against dependency versions. Adds caret or tilde only for tags, not snaps or existing ranges.

scopes/dependencies/dependency-resolver/plus-version.ts

install.main.runtime.tsResolve plus ranges during env dependency installation +6/-3

Resolve plus ranges during env dependency installation

• Expands env peer placeholders against workspace dependency versions while retaining the wildcard fallback.

scopes/workspace/install/install.main.runtime.ts

Bug fix (2) +32 / -6
isolator.main.runtime.tsResolve plus ranges in capsule manifests +9/-4

Resolve plus ranges in capsule manifests

• Uses the shared placeholder resolver when writing component dependency ranges and capsule package versions.

scopes/component/isolator/isolator.main.runtime.ts

model-component.tsRestore component peers when loading model versions +23/-2

Restore component peers when loading model versions

• Reconstructs component peer dependencies from dependency-resolver extension data when creating a ConsumerComponent. This keeps them available when bare-scope tagging recalculates dependency data.

scopes/scope/objects/models/model-component.ts

Tests (5) +173 / -0
e2e-snapping-helper.tsExpose bare-scope tagging options to e2e tests +2/-0

Expose bare-scope tagging options to e2e tests

• Adds tag and aspect-loading options to the snapping helper for release scenarios.

components/legacy/e2e-helper/e2e-snapping-helper.ts

snap-from-scope-runner.tsPass release options to the bare-scope runner +4/-0

Pass release options to the bare-scope runner

• Forwards tagging and optionally skips loading component aspects when packages are unavailable in a registry.

components/legacy/e2e-helper/snap-from-scope-runner.ts

lane-env-jsonc-dep.e2e.tsTest component peers through lane merge and release +94/-0

Test component peers through lane merge and release

• Exercises snapping, merging, and bare-scope tagging of an env.jsonc component peer. Checks that the peer survives in resolver data and the capsule package.json uses the new tag with a caret range.

e2e/harmony/lanes/lane-env-jsonc-dep.e2e.ts

dependency-resolver.main.runtime.spec.tsTest component policy placeholder validation +27/-0

Test component policy placeholder validation

• Confirms that +, +^, and +~ are accepted in component policies and an unknown plus-prefixed value is rejected.

scopes/dependencies/dependency-resolver/dependency-resolver.main.runtime.spec.ts

plus-version.spec.tsTest plus-version detection and resolution +46/-0

Test plus-version detection and resolution

• Covers supported placeholders, tags, prereleases, snaps, existing ranges, and non-placeholder values.

scopes/dependencies/dependency-resolver/plus-version.spec.ts

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. One environment loses its peer package 🐞 Bug ≡ Correctness ⭐ New
Description
mergeEnvPeersToRoot() skips +^ and +~ in a non-singleton version conflict without adding a
root entry or a per-component override for the environment that declared the placeholder. When
another environment requests a concrete version of the same package and the workspace has no
independent root dependency for it, only that other environment receives an injected dependency; the
placeholder environment has no peer installed.
Code

scopes/dependencies/dependency-resolver/manifest/workspace-manifest-factory.ts[228]

+          if (isPlusVersion(version)) continue;
Evidence
The changed condition drops the newly supported placeholders during non-singleton conflicts. That
branch adds neither a resolved root entry nor an override for the skipped environment; the root
manifest only adds peers supplied through the resulting root policy.

scopes/dependencies/dependency-resolver/manifest/workspace-manifest-factory.ts[218-257]
scopes/dependencies/dependency-resolver/manifest/workspace-manifest-factory.ts[267-278]
scopes/dependencies/dependency-resolver/manifest/workspace-manifest.ts[36-49]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
In a non-singleton conflict, skipping a `+^` or `+~` env peer can leave its components without that package when the workspace has no existing root dependency.

## Fix Focus Areas
- scopes/dependencies/dependency-resolver/manifest/workspace-manifest-factory.ts[222-257]
- scopes/dependencies/dependency-resolver/manifest/workspace-manifest-factory.ts[267-278]

## Recommended Fix
In the non-singleton branch, resolve placeholder peers against an existing workspace dependency when one exists; otherwise provide a valid fallback dependency for the affected environment rather than discarding its entry. Add a test with two envs requesting the same peer as `+^` and a concrete version, with no independent root dependency.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Uninstalled peers lose their tag range ✓ Resolved
Description
applyAutoDetectedPeersFromEnvOnEnvItSelf() now resolves +^ and +~ through
resolveEnvPeerDepVersion(), but that fallback checks the model's regular dependencies and not its
restored component peers. When the peer is available only from the model, with no installed package
or workspace policy entry, resolution returns '*' rather than a range based on the peer's tag.
Code

scopes/dependencies/dependencies/dependencies-loader/apply-overrides.ts[762]

+      acc[pkgName] = resolvePlusVersion(version, this.resolveEnvPeerDepVersion(pkgName));
Evidence
The new resolution path calls the fallback for +^ and +~. Model loading now restores component
peers into a separate peer-dependency list, but the fallback searches only regular model
dependencies and package dependencies before returning '*'; resolvePlusVersion() leaves that
wildcard unchanged.

scopes/dependencies/dependencies/dependencies-loader/apply-overrides.ts[755-764]
scopes/dependencies/dependencies/dependencies-loader/apply-overrides.ts[780-805]
scopes/scope/objects/models/model-component.ts[1250-1261]
scopes/dependencies/dependency-resolver/plus-version.ts[26-36]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
An env peer using `+^` or `+~` resolves to `'*'` when its component version is available only in the model's restored peer dependencies.
## Fix Focus Areas
- scopes/dependencies/dependencies/dependencies-loader/apply-overrides.ts[780-805]
- scopes/scope/objects/models/model-component.ts[1250-1261]
## Recommended Fix
Have `resolveEnvPeerDepVersion()` also look up the matching component in `componentFromModel.peerDependencies` before falling back to `'*'`. Add a test for a peer absent from installed packages and workspace policy.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
Review mode: Auto: 🧠 Deep: Many independent dependency-resolution paths and subtle lane-release behavior changed.

Grey Divider

Tip of the day
💡 Did you know, you can hide the parts of a finding you never read, like the evidence or the agent prompt

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Previous reviews

Review updated until commit d48c359 🧠 Deep

Results up to commit 2d7d6b4


🐞 Bugs (1) 📘 Rule violations (0) 📜 Skill insights (0)


Remediation recommended
1. Uninstalled peers lose their tag range 🐞 Bug ≡ Correctness
Description
applyAutoDetectedPeersFromEnvOnEnvItSelf() now resolves +^ and +~ through
resolveEnvPeerDepVersion(), but that fallback checks the model's regular dependencies and not its
restored component peers. When the peer is available only from the model, with no installed package
or workspace policy entry, resolution returns '*' rather than a range based on the peer's tag.
Code

scopes/dependencies/dependencies/dependencies-loader/apply-overrides.ts[762]

+      acc[pkgName] = resolvePlusVersion(version, this.resolveEnvPeerDepVersion(pkgName));
Evidence
The new resolution path calls the fallback for +^ and +~. Model loading now restores component
peers into a separate peer-dependency list, but the fallback searches only regular model
dependencies and package dependencies before returning '*'; resolvePlusVersion() leaves that
wildcard unchanged.

scopes/dependencies/dependencies/dependencies-loader/apply-overrides.ts[755-764]
scopes/dependencies/dependencies/dependencies-loader/apply-overrides.ts[780-805]
scopes/scope/objects/models/model-component.ts[1250-1261]
scopes/dependencies/dependency-resolver/plus-version.ts[26-36]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
An env peer using `+^` or `+~` resolves to `'*'` when its component version is available only in the model's restored peer dependencies.

## Fix Focus Areas
- scopes/dependencies/dependencies/dependencies-loader/apply-overrides.ts[780-805]
- scopes/scope/objects/models/model-component.ts[1250-1261]

## Recommended Fix
Have `resolveEnvPeerDepVersion()` also look up the matching component in `componentFromModel.peerDependencies` before falling back to `'*'`. Add a test for a peer absent from installed packages and workspace policy.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Context sources
Review mode: Auto: 🧠 Deep: Many independent dependency-resolution paths introduce subtle, cross-cutting behavioral risks.

Grey Divider

Qodo Logo

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit d48c359

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants