-
Notifications
You must be signed in to change notification settings - Fork 947
Description
Symptom
After running bit new vue-hello-world <name> --aspect teambit.community/starters/hello-world-vue, bit status shows:
⚠ environment with ID: bitdev.general/envs/bit-env@3.0.7 configured on component org.scope-name/envs/my-vue-env was not loaded (run "bit install")
...
components with issues (1)
> org.scope-name/envs/my-vue-env ...
missing packages that were manually set (run "bit install"):
@vitest/coverage-v8, @mdx-js/mdx
Running bit install again works as a temporary workaround. The issue does not occur on version ≤1.13.42.
Bisection
| Version | Status |
|---|---|
| 1.13.21 | ✅ Works |
| 1.13.35 | ✅ Works |
| 1.13.39 | ✅ Works |
| 1.13.42 | ✅ Works |
| 1.13.44 | ❌ Broken |
| 1.13.46 | ❌ Broken |
The regression was introduced by PR #10150 (0f3672651), which landed between 1.13.42 and 1.13.43.
PR #10150 Change
File: scopes/dependencies/dependency-resolver/manifest/workspace-manifest-factory.ts
- depManifest.dependencies = {
- ...defaultPeerDependencies,
+ const usedPeerDependencies = pickBy(defaultPeerDependencies, (_val, pkgName) => {
+ return (
+ depManifestBeforeFiltering.dependencies[pkgName] ||
+ depManifestBeforeFiltering.devDependencies[pkgName] ||
+ depManifestBeforeFiltering.peerDependencies[pkgName]
+ );
+ });
+
+ depManifest.dependencies = {
+ ...usedPeerDependencies,
...unresolvedRuntimeMissingRootDeps,Intent: Stop env peer-dependencies from being written into the dependencies of user components (visible in external-package-manager mode).
Root Cause: Two Interacting Bugs
Bug 1 — Forking copies stale "+" dependency markers
When bit new forks bitdev.vue/examples/my-vue-env@1.1.18, the forking process copies the dep-resolver aspect config verbatim from the forked version. In my-vue-env@1.1.18 the packages @vitest/coverage-v8 and @mdx-js/mdx existed in the dep config with version "+" (the MANUALLY_ADD_DEPENDENCY sentinel), meaning "include this dep but resolve the version from the workspace package.json."
The current (latest) version of my-vue-env removed these entries — they are no longer needed — but the forked component inherits them from @1.1.18.
The source: "env-own" label seen in bit deps debug comes from getPoliciesFromEnvForItself(): because the forked component has an env.jsonc with an extends directive, the extended parent env's peers section is resolved and folded in as the component's own self-policy. Gilad's suspicion about the env.jsonc extends bug is on point here.
Code path:
workspace-component-loader.ts:921—mergeVariantPolicies(…, envExtendsDeps)dependency-resolver.main.runtime.ts:1291—getPoliciesFromEnvForItself(id, legacyFiles, envExtendsDeps)dependency-resolver.main.runtime.ts:1308— readsenvPolicy.selfPolicyfrom the resolvedenv.jsoncextends chain- Result: packages from the parent env's
peerssection end up in the forked component's effective policy withsource: "env-own"
Bug 2 — PR #10150 created a chicken-and-egg installation problem
usedPeerDependencies is a subset of defaultPeerDependencies (env self-policy) filtered to packages already present in depManifestBeforeFiltering. That snapshot is computed from getDependencies(component, { includeHidden: true }), which reads the component's stored serialized dep data.
For packages with version "+" in the dep config:
_manuallyAddPackage()(overrides-dependencies.ts:71) tries to resolve the version by looking in the workspace rootpackage.json.- On a fresh workspace the package is not yet installed → not in
package.json→versionToAdd = null→ added tomissingPackageDependencies, not added topackageDependencies. - Not in
packageDependencies→ not returned byfromLegacyComponent()→ not ingetDependencies()output → not indepManifestBeforeFiltering. - The
usedPeerDependenciesfilter excludes the package. - PNPM does not install it.
- On every subsequent
bit status/bit installthe cycle repeats.
Before PR #10150 the unconditional ...defaultPeerDependencies spread broke this cycle on the first install, so the problem was never visible.
Why both manuallyAddedDependencies AND missingPackageDependencies appear for the same package (apply-overrides.ts:328-338):
const addedPkg = this.overridesDependencies._manuallyAddPackage(
depField, dependency, dependencyValue, packageJson
);
if (addedPkg) {
packages[depField] = Object.assign(packages[depField] || {}, addedPkg);
if (componentData && !componentData.packageName) { // package NOT in node_modules
this.overridesDependencies.missingPackageDependencies.push(dependency);
}
}If an explicit version IS found (e.g. "^3.1.2" stored from a partial state), addedPkg is set and the entry goes into manuallyAddedDependencies. But because the package is not in node_modules, _resolvePackageData() returns undefined, componentData.packageName is undefined, and the package is also pushed into missingPackageDependencies. This is the state visible in the bit deps debug output from the issue.
Why bit deps unset did not work
bit deps unset removes entries from componentOverridesData (the component's explicit dep-resolver config stored in .bitmap). If the packages entered the policy purely through the env.jsonc extends chain (not written directly into the bitmap entry), there is nothing for unset to remove.
Data from bit deps debug Explained
"manuallyAddedDependencies": {
"dependencies": ["@vitest/coverage-v8@^3.1.2", "@mdx-js/mdx@1.6.22", ...]
},
"missingPackageDependencies": ["@vitest/coverage-v8", "@mdx-js/mdx"],
"sources": [
{ "id": "@vitest/coverage-v8", "source": "env-own" },
{ "id": "@mdx-js/mdx", "source": "env-own" }
]| Field | Meaning |
|---|---|
manuallyAddedDependencies |
Version was found (from componentOverridesData explicit value or from a partially-updated package.json); package added to the legacy dep list |
missingPackageDependencies |
Package is not in node_modules; _resolvePackageData() returned undefined |
source: "env-own" |
Entry attributed to the env's self-policy (peers section of the resolved env.jsonc extends chain) |
Key Files
| File | Relevance |
|---|---|
scopes/dependencies/dependency-resolver/manifest/workspace-manifest-factory.ts:215 |
usedPeerDependencies filter introduced by PR #10150 |
scopes/dependencies/dependency-resolver/manifest/workspace-manifest-factory.ts:262 |
_getDefaultPeerDependencies() — reads env's selfPolicy |
scopes/dependencies/dependencies/dependencies-loader/overrides-dependencies.ts:65 |
_manuallyAddPackage() — version lookup logic for "+" marker |
scopes/dependencies/dependencies/dependencies-loader/apply-overrides.ts:328 |
Sets both manuallyAddedDependencies and missingPackageDependencies |
scopes/dependencies/dependency-resolver/dependency-resolver.main.runtime.ts:1308 |
getPoliciesFromEnvForItself() — includes extended env's peers as env-own |
scopes/dependencies/dependency-resolver/policy/env-policy/env-policy.ts:70 |
selfPeersEntries created with source: 'env-own' from peers key |
scopes/envs/envs/environments.main.runtime.ts:335 |
calculateEnvManifest() — recursive extends resolution |
scopes/workspace/workspace/workspace-component/workspace-component-loader.ts:915 |
envExtendsDeps collection and passing to mergeVariantPolicies |
scopes/component/forking/forking.main.runtime.ts |
Forking copies dep-resolver aspect config verbatim from source version |
Proposed Fixes
Three approaches, in order of surgical precision:
Option A — Fix the forking process (addresses Bug 1)
During forking, filter out dep-resolver policy entries whose version is "+" and whose source in the original component was "env-own". These are transient, env-provided values and should not be hardcoded into the new component's config.
Option B — Fix the usedPeerDependencies filter (addresses the PR #10150 regression)
Widen the filter to also include packages that are explicitly listed in the component's dep-resolver config, even if they are not yet reflected in depManifestBeforeFiltering (the chicken-and-egg case):
// Pseudocode
const componentExplicitPkgs = new Set(
Object.keys(component.config.extensions
.findExtension(DependencyResolverAspect.id)?.config?.policy?.dependencies ?? {})
);
const usedPeerDependencies = pickBy(defaultPeerDependencies, (_val, pkgName) => {
return (
depManifestBeforeFiltering.dependencies[pkgName] ||
depManifestBeforeFiltering.devDependencies[pkgName] ||
depManifestBeforeFiltering.peerDependencies[pkgName] ||
componentExplicitPkgs.has(pkgName) // <-- new condition
);
});Option C — Targeted fix in the "+" resolution path (addresses Bug 1 differently)
In _manuallyAddPackage(), when dependencyValue === "+" and the package cannot be found in the workspace package.json, instead of immediately pushing to missingPackageDependencies, check whether the package exists in the env's self-policy and, if so, defer to installation via that path rather than flagging it as missing.
Workaround (user-facing)
Run bit install a second time after bit new. The first run partially populates the workspace policy; the second run finds the version and completes the installation.