From b972ea2451c9e1ea7988d2f64602926c67fd7fbc Mon Sep 17 00:00:00 2001 From: David First Date: Mon, 15 Jun 2026 16:55:40 -0400 Subject: [PATCH 01/13] fix(install): clear error when a component depends on an unpublished update-dependent A visible workspace component can depend on a hidden lane update-dependent whose Ripple build failed (or never completed), so no package was published. bit install then failed with a cryptic pnpm 'No matching version found' for the unpublished 0.0.0-. Detect this up-front: for each checked-out component's snap dependency that is a lane update-dependent, not checked out, and whose local Version.buildStatus is not 'succeed', throw an actionable error suggesting 'bit import ' (which links it from source instead of fetching from the registry). --- .../lanes/update-dependents-cascade.e2e.ts | 67 +++++++++++++++++ scopes/workspace/install/exceptions/index.ts | 2 + .../update-dependent-build-failed.ts | 41 ++++++++++ .../workspace/install/install.main.runtime.ts | 75 ++++++++++++++++++- 4 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 scopes/workspace/install/exceptions/update-dependent-build-failed.ts diff --git a/e2e/harmony/lanes/update-dependents-cascade.e2e.ts b/e2e/harmony/lanes/update-dependents-cascade.e2e.ts index 4a577c44c760..035fbfc7ea0e 100644 --- a/e2e/harmony/lanes/update-dependents-cascade.e2e.ts +++ b/e2e/harmony/lanes/update-dependents-cascade.e2e.ts @@ -1179,4 +1179,71 @@ describe('local snap cascades updateDependents on the lane', function () { expect(bitMap).to.not.have.property('comp2'); }); }); + + // --------------------------------------------------------------------------------------------- + // Scenario 21: a visible workspace component depends on a hidden updateDependent whose build never + // succeeded (e.g. Ripple failed after "snap updates"), so it was never published to the registry. + // The updateDependent is not checked out, so it must be fetched from the registry as a package — + // but there is no package. `bit install` must fail early with an actionable error (and a `bit + // import` remediation) instead of letting pnpm error with the cryptic "No matching version found" + // for the unpublished `0.0.0-`. + // --------------------------------------------------------------------------------------------- + describe('scenario 21: install errors clearly when a workspace component depends on an unpublished updateDependent', () => { + let comp2UpdDepHash: string; + let comp1LaneHead: string; + before(async () => { + helper.scopeHelper.setWorkspaceWithRemoteScope(); + helper.fixtures.populateComponents(3); // comp1 -> comp2 -> comp3 + helper.command.tagAllWithoutBuild(); + helper.command.export(); + + // comp1 is a visible lane component (it depends on comp2). + helper.command.createLane(); + helper.command.snapComponentWithoutBuild('comp1', '--unmodified'); + helper.command.export(); + + // "snap updates": comp2 enters lane.updateDependents via the bare-scope cascade. build:false + // means the cascade snap is never built/published — the state Ripple leaves on a build failure. + // The cascade also re-snaps comp1 so its comp2 dep points at the new (unpublished) updateDependent. + const bareSnap = helper.scopeHelper.getNewBareScope('-bare-snap-updates'); + helper.scopeHelper.addRemoteScope(helper.scopes.remotePath, bareSnap.scopePath); + await helper.snapping.snapFromScope( + bareSnap.scopePath, + [{ componentId: `${helper.scopes.remote}/comp2`, message: 'snap updates' }], + { lane: `${helper.scopes.remote}/dev`, updateDependents: true, push: true } + ); + const lane = helper.command.catLane('dev', helper.scopes.remotePath); + comp2UpdDepHash = (lane.updateDependents[0] as string).split('@')[1]; + comp1LaneHead = lane.components.find((c) => c.id.name === 'comp1').head; + + // fresh workspace: importing the lane checks out the visible comp1 (comp2 stays hidden). + helper.scopeHelper.reInitWorkspace(); + helper.scopeHelper.addRemoteScope(helper.scopes.remotePath); + helper.command.importLane('dev', '-x'); + }); + + it('precondition: comp2 is hidden (not in bitmap) and the visible comp1 depends on its unpublished cascade snap', () => { + const bitMap = helper.bitMap.read(); + expect(bitMap).to.have.property('comp1'); + expect(bitMap).to.not.have.property('comp2'); + const comp1Obj = helper.command.catComponent(`${helper.scopes.remote}/comp1@${comp1LaneHead}`); + const comp2Dep = comp1Obj.dependencies.find((d) => d.id.name === 'comp2'); + expect(comp2Dep, 'comp1 should depend on comp2').to.exist; + expect(comp2Dep.id.version, 'comp1 should depend on the unpublished updateDependent snap').to.equal( + comp2UpdDepHash + ); + }); + + it('bit install fails with an actionable error naming the updateDependent and the bit import remediation', () => { + let output = ''; + try { + helper.command.install(); + } catch (err: any) { + output = `${err.message || ''}${err.stdout?.toString() || ''}${err.stderr?.toString() || ''}`; + } + expect(output, 'bit install should have failed').to.have.string('update-dependent'); + expect(output, 'error should name the problematic component').to.have.string('comp2'); + expect(output, 'error should suggest the bit import remediation').to.have.string('bit import'); + }); + }); }); diff --git a/scopes/workspace/install/exceptions/index.ts b/scopes/workspace/install/exceptions/index.ts index bba7c53394fb..54e4ed6db8d4 100644 --- a/scopes/workspace/install/exceptions/index.ts +++ b/scopes/workspace/install/exceptions/index.ts @@ -1 +1,3 @@ export { DependencyTypeNotSupportedInPolicy } from './dependency-type-not-supported-in-policy'; +export { UpdateDependentBuildFailed } from './update-dependent-build-failed'; +export type { FailedUpdateDependent } from './update-dependent-build-failed'; diff --git a/scopes/workspace/install/exceptions/update-dependent-build-failed.ts b/scopes/workspace/install/exceptions/update-dependent-build-failed.ts new file mode 100644 index 000000000000..54c50e5ed42a --- /dev/null +++ b/scopes/workspace/install/exceptions/update-dependent-build-failed.ts @@ -0,0 +1,41 @@ +import { BitError } from '@teambit/bit-error'; + +export type FailedUpdateDependent = { + /** the failed update-dependent component id (without version) */ + id: string; + /** the snap version (hash) that failed to build */ + version: string; + /** workspace component ids that depend on this update-dependent */ + dependents: string[]; +}; + +/** + * thrown during `bit install` when a workspace component depends on a hidden "update-dependent" + * of the current lane that was never published — its Ripple build failed or hasn't completed + * successfully. without a published package there is nothing to install and pnpm would otherwise + * fail with a cryptic "No matching version found" error. + */ +export class UpdateDependentBuildFailed extends BitError { + constructor(readonly failed: FailedUpdateDependent[]) { + super(UpdateDependentBuildFailed.formatMessage(failed)); + } + + private static formatMessage(failed: FailedUpdateDependent[]): string { + const list = failed + .map(({ id, version, dependents }) => { + const shortVersion = version.substring(0, 9); + const requiredBy = dependents.join(', '); + return ` ✖ ${id} (${shortVersion}) — required by: ${requiredBy}`; + }) + .join('\n'); + const importCommand = `bit import ${failed.map(({ id }) => id).join(' ')}`; + return `unable to install the following update-dependent component(s) of the current lane. +their build did not complete successfully (it failed, e.g. on Ripple after "snap updates", or is still pending), so they were never published to the registry and no package exists to install: + +${list} + +to resolve, import the component(s) into your workspace so they are linked from source instead of fetched from the registry: + + ${importCommand}`; + } +} diff --git a/scopes/workspace/install/install.main.runtime.ts b/scopes/workspace/install/install.main.runtime.ts index 29d89a3b0e2f..d373a6e4ac1e 100644 --- a/scopes/workspace/install/install.main.runtime.ts +++ b/scopes/workspace/install/install.main.runtime.ts @@ -22,6 +22,9 @@ import type { VariantsMain } from '@teambit/variants'; import { VariantsAspect } from '@teambit/variants'; import type { Component } from '@teambit/component'; import { ComponentID, ComponentMap } from '@teambit/component'; +import { ComponentIdList } from '@teambit/component-id'; +import { isSnap } from '@teambit/component-version'; +import { BuildStatus } from '@teambit/legacy.constants'; import { PackageJsonFile } from '@teambit/component.sources'; import { updateJsoncPreservingFormatting } from '@teambit/toolbox.json.jsonc-utils'; import { createLinks } from '@teambit/dependencies.fs.linked-dependencies'; @@ -67,7 +70,7 @@ import { BundlerAspect } from '@teambit/bundler'; import type { UiMain } from '@teambit/ui'; import { UIAspect } from '@teambit/ui'; import { EXTERNAL_PM_POSTINSTALL_SCRIPT } from '@teambit/host-initializer'; -import { DependencyTypeNotSupportedInPolicy } from './exceptions'; +import { DependencyTypeNotSupportedInPolicy, UpdateDependentBuildFailed } from './exceptions'; import { InstallAspect } from './install.aspect'; import { pickOutdatedPkgs } from './pick-outdated-pkgs'; import { LinkCommand } from './link'; @@ -392,6 +395,11 @@ export class InstallMain { } ); + // a workspace component may depend on a hidden "update-dependent" of the current lane that was never + // published (its Ripple build failed or hasn't completed), so there is nothing for pnpm to install. + // fail early with an actionable message instead of letting pnpm error with "No matching version found". + await this.throwOnUnpublishedUpdateDependentDeps(current.componentDirectoryMap.components); + const pmInstallOptions: PackageManagerInstallOptions = { ...calcManifestsOpts, autoInstallPeers: this.dependencyResolver.config.autoInstallPeers, @@ -516,6 +524,71 @@ export class InstallMain { return nonLoadedEnvs.length > 0; } + /** + * "update-dependents" are hidden entries of the current lane (components that depend on the lane's visible + * components and get re-snapped against the lane heads by "snap updates" / Ripple CI). they are not checked out + * into the workspace, so a visible component that depends on one must fetch it from the registry as a package. + * a package only exists once Ripple built the entry successfully; if its build failed (or hasn't completed) the + * snap was never published and there is no package to install. detect this up-front and throw an actionable + * error instead of letting pnpm fail with the cryptic "No matching version found" for a `0.0.0-` version. + */ + private async throwOnUnpublishedUpdateDependentDeps(components: Component[]): Promise { + const lane = await this.workspace.getCurrentLaneObject(); + const updateDependents = lane?.updateDependents; + if (!updateDependents?.length) return; + const updateDependentsList = ComponentIdList.fromArray(updateDependents); + // checked-out components are linked from source (filtered out of the install manifest), so they're never + // fetched from the registry and can't trigger this failure even when they weren't built successfully. + const workspaceIds = this.workspace.listIds(); + const legacyScope = this.workspace.scope.legacyScope; + // a successful build is the only state in which the package was published. anything else (failed / pending / + // not built) means there's no package. `null` means the version object isn't available locally, so we can't + // tell — in that case let the install proceed and surface the original error if there really is no package. + const buildStatusCache = new Map(); + const getBuildStatus = async (id: ComponentID): Promise => { + const key = id.toString(); + if (buildStatusCache.has(key)) return buildStatusCache.get(key); + let status: BuildStatus | undefined | null; + try { + status = (await legacyScope.getVersionInstance(id)).buildStatus; + } catch { + status = null; + } + buildStatusCache.set(key, status); + return status; + }; + + const unpublished = new Map }>(); + await Promise.all( + components.map(async (component) => { + const compDeps = this.dependencyResolver.getComponentDependencies(component); + await Promise.all( + compDeps.map(async (dep) => { + const depId = dep.componentId; + if (!depId.version || !isSnap(depId.version)) return; + if (workspaceIds.hasWithoutVersion(depId)) return; + if (!updateDependentsList.hasWithoutVersion(depId)) return; + const buildStatus = await getBuildStatus(depId); + // skip when published (succeed) or undeterminable (version object not local) + if (buildStatus === null || buildStatus === BuildStatus.Succeed) return; + const key = depId.toStringWithoutVersion(); + const entry = unpublished.get(key) ?? { id: depId, dependents: new Set() }; + entry.dependents.add(component.id.toStringWithoutVersion()); + unpublished.set(key, entry); + }) + ); + }) + ); + if (!unpublished.size) return; + throw new UpdateDependentBuildFailed( + [...unpublished.values()].map(({ id, dependents }) => ({ + id: id.toStringWithoutVersion(), + version: id.version as string, + dependents: [...dependents], + })) + ); + } + /** * This function is very important to fix some issues that might happen during the installation process. * The case is the following: From 16b5328efebad007053757b5089484864fe8ca70 Mon Sep 17 00:00:00 2001 From: David First Date: Tue, 16 Jun 2026 10:13:35 -0400 Subject: [PATCH 02/13] fix(install): refetch unbuilt update-dependents before failing; use shared CLI formatter Address review feedback: - A locally-stale pending/failed buildStatus could wrongly fail install when the remote already built/published the snap. Refresh the suspect versions from the remote (reFetchUnBuiltVersion) before deciding they're unpublished. - Use errorSymbol/formatItem from @teambit/cli instead of hardcoding the Unicode symbol, per the CLI output style guide. --- .../update-dependent-build-failed.ts | 3 +- .../workspace/install/install.main.runtime.ts | 66 +++++++++++-------- 2 files changed, 41 insertions(+), 28 deletions(-) diff --git a/scopes/workspace/install/exceptions/update-dependent-build-failed.ts b/scopes/workspace/install/exceptions/update-dependent-build-failed.ts index 54c50e5ed42a..907e44b29873 100644 --- a/scopes/workspace/install/exceptions/update-dependent-build-failed.ts +++ b/scopes/workspace/install/exceptions/update-dependent-build-failed.ts @@ -1,4 +1,5 @@ import { BitError } from '@teambit/bit-error'; +import { errorSymbol, formatItem } from '@teambit/cli'; export type FailedUpdateDependent = { /** the failed update-dependent component id (without version) */ @@ -25,7 +26,7 @@ export class UpdateDependentBuildFailed extends BitError { .map(({ id, version, dependents }) => { const shortVersion = version.substring(0, 9); const requiredBy = dependents.join(', '); - return ` ✖ ${id} (${shortVersion}) — required by: ${requiredBy}`; + return formatItem(`${id} (${shortVersion}) — required by: ${requiredBy}`, errorSymbol); }) .join('\n'); const importCommand = `bit import ${failed.map(({ id }) => id).join(' ')}`; diff --git a/scopes/workspace/install/install.main.runtime.ts b/scopes/workspace/install/install.main.runtime.ts index d373a6e4ac1e..ff99bc0e62ef 100644 --- a/scopes/workspace/install/install.main.runtime.ts +++ b/scopes/workspace/install/install.main.runtime.ts @@ -541,24 +541,19 @@ export class InstallMain { // fetched from the registry and can't trigger this failure even when they weren't built successfully. const workspaceIds = this.workspace.listIds(); const legacyScope = this.workspace.scope.legacyScope; - // a successful build is the only state in which the package was published. anything else (failed / pending / - // not built) means there's no package. `null` means the version object isn't available locally, so we can't - // tell — in that case let the install proceed and surface the original error if there really is no package. - const buildStatusCache = new Map(); - const getBuildStatus = async (id: ComponentID): Promise => { - const key = id.toString(); - if (buildStatusCache.has(key)) return buildStatusCache.get(key); - let status: BuildStatus | undefined | null; + const readBuildStatus = async (id: ComponentID): Promise => { + // `null` means the version object isn't available locally, so we can't determine its build status. try { - status = (await legacyScope.getVersionInstance(id)).buildStatus; + return (await legacyScope.getVersionInstance(id)).buildStatus; } catch { - status = null; + return null; } - buildStatusCache.set(key, status); - return status; }; - const unpublished = new Map }>(); + // collect update-dependent deps referenced by a checked-out component whose locally-known build status + // is not `succeed`. a successful build is the only state in which a package was published; anything else + // (failed / pending / not built) means there may be no package to install. + const candidates = new Map }>(); await Promise.all( components.map(async (component) => { const compDeps = this.dependencyResolver.getComponentDependencies(component); @@ -568,25 +563,42 @@ export class InstallMain { if (!depId.version || !isSnap(depId.version)) return; if (workspaceIds.hasWithoutVersion(depId)) return; if (!updateDependentsList.hasWithoutVersion(depId)) return; - const buildStatus = await getBuildStatus(depId); - // skip when published (succeed) or undeterminable (version object not local) - if (buildStatus === null || buildStatus === BuildStatus.Succeed) return; - const key = depId.toStringWithoutVersion(); - const entry = unpublished.get(key) ?? { id: depId, dependents: new Set() }; + if ((await readBuildStatus(depId)) === BuildStatus.Succeed) return; + const key = depId.toString(); + const entry = candidates.get(key) ?? { id: depId, dependents: new Set() }; entry.dependents.add(component.id.toStringWithoutVersion()); - unpublished.set(key, entry); + candidates.set(key, entry); }) ); }) ); - if (!unpublished.size) return; - throw new UpdateDependentBuildFailed( - [...unpublished.values()].map(({ id, dependents }) => ({ - id: id.toStringWithoutVersion(), - version: id.version as string, - dependents: [...dependents], - })) - ); + if (!candidates.size) return; + + // a locally-stale `pending`/`failed` snap may have since been built and published on the remote. refresh + // these specific versions from the remote (this is exactly what `reFetchUnBuiltVersion` is for) before + // deciding they're unpublished, so we don't fail an install for a snap that is actually installable. + const candidateIds = [...candidates.values()].map((entry) => entry.id); + try { + await this.workspace.scope.import(candidateIds, { + reFetchUnBuiltVersion: true, + useCache: false, + lane, + includeUpdateDependents: true, + reason: 'to check whether unbuilt update-dependents were already published', + }); + } catch { + // offline or remote unavailable — fall back to the locally-known build status. + } + + const unpublished: { id: string; version: string; dependents: string[] }[] = []; + for (const { id, dependents } of candidates.values()) { + const buildStatus = await readBuildStatus(id); + // skip when now known to be published (succeed) or undeterminable (version object not local). + if (buildStatus === null || buildStatus === BuildStatus.Succeed) continue; + unpublished.push({ id: id.toStringWithoutVersion(), version: id.version as string, dependents: [...dependents] }); + } + if (!unpublished.length) return; + throw new UpdateDependentBuildFailed(unpublished); } /** From 7d5916c354d8bb10e570849424bed6cbdeadff2c Mon Sep 17 00:00:00 2001 From: David First Date: Tue, 16 Jun 2026 10:31:21 -0400 Subject: [PATCH 03/13] fix(install): treat Skipped build status as built for update-dependent guard BuildStatus.Skipped is treated as 'built enough' elsewhere (onlyIfBuilt in scope-components-importer, sources.get), so the install guard now treats both Succeed and Skipped as built and won't report a Skipped update-dependent as unpublished. --- scopes/workspace/install/install.main.runtime.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/scopes/workspace/install/install.main.runtime.ts b/scopes/workspace/install/install.main.runtime.ts index ff99bc0e62ef..a5d767046f47 100644 --- a/scopes/workspace/install/install.main.runtime.ts +++ b/scopes/workspace/install/install.main.runtime.ts @@ -549,10 +549,14 @@ export class InstallMain { return null; } }; + // a package exists once the snap was built. `Succeed` and `Skipped` both count as "built" here, mirroring + // the rest of the scope (see `onlyIfBuilt` in scope-components-importer and `sources.get`); `null` means + // undeterminable (version object not local). anything else (failed / pending / not built) has no package. + const isBuilt = (status: BuildStatus | undefined | null): boolean => + status === null || status === BuildStatus.Succeed || status === BuildStatus.Skipped; // collect update-dependent deps referenced by a checked-out component whose locally-known build status - // is not `succeed`. a successful build is the only state in which a package was published; anything else - // (failed / pending / not built) means there may be no package to install. + // is not "built" — those are the ones that might have no package to install. const candidates = new Map }>(); await Promise.all( components.map(async (component) => { @@ -563,7 +567,7 @@ export class InstallMain { if (!depId.version || !isSnap(depId.version)) return; if (workspaceIds.hasWithoutVersion(depId)) return; if (!updateDependentsList.hasWithoutVersion(depId)) return; - if ((await readBuildStatus(depId)) === BuildStatus.Succeed) return; + if (isBuilt(await readBuildStatus(depId))) return; const key = depId.toString(); const entry = candidates.get(key) ?? { id: depId, dependents: new Set() }; entry.dependents.add(component.id.toStringWithoutVersion()); @@ -592,9 +596,8 @@ export class InstallMain { const unpublished: { id: string; version: string; dependents: string[] }[] = []; for (const { id, dependents } of candidates.values()) { - const buildStatus = await readBuildStatus(id); - // skip when now known to be published (succeed) or undeterminable (version object not local). - if (buildStatus === null || buildStatus === BuildStatus.Succeed) continue; + // skip when now known to be built (the refetch may have picked up a remote build) or undeterminable. + if (isBuilt(await readBuildStatus(id))) continue; unpublished.push({ id: id.toStringWithoutVersion(), version: id.version as string, dependents: [...dependents] }); } if (!unpublished.length) return; From 8b4c264b2bcbcd0dc301ba1c65155c52ce7439f5 Mon Sep 17 00:00:00 2001 From: David First Date: Tue, 16 Jun 2026 10:55:47 -0400 Subject: [PATCH 04/13] fix(install): enrich the unpublished-dependency error only on package-manager failure Replace the pre-emptive per-install scan (which ran a dependency walk + remote refetch on every install) with a catch around the package-manager step: only when it fails with 'No matching version found' do we check whether the failing package is a component-dependency snap that isn't checked out, and if so surface the actionable 'bit import' message. Match against the package-manager error rather than lane.updateDependents, since the install's import phase may have already dropped the entry from the local lane object. This also removes the buildStatus/Skipped/refetch logic (the failure itself proves the package is unpublished), so there is zero overhead on successful installs. The e2e now uses the npm CI registry so the unpublished snap genuinely 404s. --- .../lanes/update-dependents-cascade.e2e.ts | 124 ++++++++------- .../workspace/install/install.main.runtime.ts | 148 +++++++----------- 2 files changed, 123 insertions(+), 149 deletions(-) diff --git a/e2e/harmony/lanes/update-dependents-cascade.e2e.ts b/e2e/harmony/lanes/update-dependents-cascade.e2e.ts index 035fbfc7ea0e..a938f7bd7757 100644 --- a/e2e/harmony/lanes/update-dependents-cascade.e2e.ts +++ b/e2e/harmony/lanes/update-dependents-cascade.e2e.ts @@ -1183,67 +1183,77 @@ describe('local snap cascades updateDependents on the lane', function () { // --------------------------------------------------------------------------------------------- // Scenario 21: a visible workspace component depends on a hidden updateDependent whose build never // succeeded (e.g. Ripple failed after "snap updates"), so it was never published to the registry. - // The updateDependent is not checked out, so it must be fetched from the registry as a package — - // but there is no package. `bit install` must fail early with an actionable error (and a `bit - // import` remediation) instead of letting pnpm error with the cryptic "No matching version found" - // for the unpublished `0.0.0-`. + // The updateDependent is not checked out, so the package manager must fetch it from the registry — + // but there is no package, so it fails with the cryptic "No matching version found". `bit install` + // should instead surface an actionable error pointing at the `bit import` remediation. + // + // Uses the npm CI registry so the unpublished `0.0.0-` genuinely 404s (with a local file + // remote the dep is filtered out of the install manifest and never fetched, so it can't reproduce). // --------------------------------------------------------------------------------------------- - describe('scenario 21: install errors clearly when a workspace component depends on an unpublished updateDependent', () => { - let comp2UpdDepHash: string; - let comp1LaneHead: string; - before(async () => { - helper.scopeHelper.setWorkspaceWithRemoteScope(); - helper.fixtures.populateComponents(3); // comp1 -> comp2 -> comp3 - helper.command.tagAllWithoutBuild(); - helper.command.export(); + (supportNpmCiRegistryTesting ? describe : describe.skip)( + 'scenario 21: install errors clearly when a workspace component depends on an unpublished updateDependent', + () => { + let npmCiRegistry: NpmCiRegistry; + before(async () => { + helper.scopeHelper.destroy(); + helper = new Helper({ scopesOptions: { remoteScopeWithDot: true } }); + helper.scopeHelper.setWorkspaceWithRemoteScope(); + npmCiRegistry = new NpmCiRegistry(helper); + await npmCiRegistry.init(); + npmCiRegistry.configureCiInPackageJsonHarmony(); + helper.fixtures.populateComponents(2); // comp1 -> comp2 + helper.command.tagAllComponents(); // tag + publish comp1@0.0.1, comp2@0.0.1 to the registry + helper.command.export(); - // comp1 is a visible lane component (it depends on comp2). - helper.command.createLane(); - helper.command.snapComponentWithoutBuild('comp1', '--unmodified'); - helper.command.export(); + // comp1 becomes a visible lane component (it depends on comp2). + helper.scopeHelper.reInitWorkspace(); + helper.scopeHelper.addRemoteScope(helper.scopes.remotePath); + npmCiRegistry.setResolver(); + helper.command.createLane(); + helper.command.importComponent('comp1'); + helper.command.snapAllComponentsWithoutBuild('--unmodified'); + helper.command.export(); - // "snap updates": comp2 enters lane.updateDependents via the bare-scope cascade. build:false - // means the cascade snap is never built/published — the state Ripple leaves on a build failure. - // The cascade also re-snaps comp1 so its comp2 dep points at the new (unpublished) updateDependent. - const bareSnap = helper.scopeHelper.getNewBareScope('-bare-snap-updates'); - helper.scopeHelper.addRemoteScope(helper.scopes.remotePath, bareSnap.scopePath); - await helper.snapping.snapFromScope( - bareSnap.scopePath, - [{ componentId: `${helper.scopes.remote}/comp2`, message: 'snap updates' }], - { lane: `${helper.scopes.remote}/dev`, updateDependents: true, push: true } - ); - const lane = helper.command.catLane('dev', helper.scopes.remotePath); - comp2UpdDepHash = (lane.updateDependents[0] as string).split('@')[1]; - comp1LaneHead = lane.components.find((c) => c.id.name === 'comp1').head; + // "snap updates": comp2 enters lane.updateDependents via the bare-scope cascade. build:false + // means the cascade snap is never built/published — the state Ripple leaves on a build failure. + // The cascade also re-snaps comp1 so its comp2 dep points at the new (unpublished) updateDependent. + const bareSnap = helper.scopeHelper.getNewBareScope('-bare-snap-updates'); + helper.scopeHelper.addRemoteScope(helper.scopes.remotePath, bareSnap.scopePath); + await helper.snapping.snapFromScope( + bareSnap.scopePath, + [{ componentId: `${helper.scopes.remote}/comp2`, message: 'snap updates' }], + { lane: `${helper.scopes.remote}/dev`, updateDependents: true, push: true } + ); - // fresh workspace: importing the lane checks out the visible comp1 (comp2 stays hidden). - helper.scopeHelper.reInitWorkspace(); - helper.scopeHelper.addRemoteScope(helper.scopes.remotePath); - helper.command.importLane('dev', '-x'); - }); + // fresh workspace: importing the lane checks out the visible comp1 (comp2 stays hidden). + helper.scopeHelper.reInitWorkspace(); + helper.scopeHelper.addRemoteScope(helper.scopes.remotePath); + npmCiRegistry.setResolver(); + helper.command.importLane('dev', '-x'); + }); + after(() => { + npmCiRegistry.destroy(); + helper.scopeHelper.destroy(); + helper = new Helper(); + }); - it('precondition: comp2 is hidden (not in bitmap) and the visible comp1 depends on its unpublished cascade snap', () => { - const bitMap = helper.bitMap.read(); - expect(bitMap).to.have.property('comp1'); - expect(bitMap).to.not.have.property('comp2'); - const comp1Obj = helper.command.catComponent(`${helper.scopes.remote}/comp1@${comp1LaneHead}`); - const comp2Dep = comp1Obj.dependencies.find((d) => d.id.name === 'comp2'); - expect(comp2Dep, 'comp1 should depend on comp2').to.exist; - expect(comp2Dep.id.version, 'comp1 should depend on the unpublished updateDependent snap').to.equal( - comp2UpdDepHash - ); - }); + it('comp2 stays a hidden updateDependent — only comp1 is in the bitmap', () => { + const bitMap = helper.bitMap.read(); + expect(bitMap).to.have.property('comp1'); + expect(bitMap).to.not.have.property('comp2'); + }); - it('bit install fails with an actionable error naming the updateDependent and the bit import remediation', () => { - let output = ''; - try { - helper.command.install(); - } catch (err: any) { - output = `${err.message || ''}${err.stdout?.toString() || ''}${err.stderr?.toString() || ''}`; - } - expect(output, 'bit install should have failed').to.have.string('update-dependent'); - expect(output, 'error should name the problematic component').to.have.string('comp2'); - expect(output, 'error should suggest the bit import remediation').to.have.string('bit import'); - }); - }); + it('bit install fails with an actionable error naming the updateDependent and the bit import remediation', () => { + let output = ''; + try { + helper.command.install(); + } catch (err: any) { + output = `${err.message || ''}${err.stdout?.toString() || ''}${err.stderr?.toString() || ''}`; + } + expect(output, 'bit install should have failed').to.have.string('update-dependent'); + expect(output, 'error should name the problematic component').to.have.string('comp2'); + expect(output, 'error should suggest the bit import remediation').to.have.string('bit import'); + }); + } + ); }); diff --git a/scopes/workspace/install/install.main.runtime.ts b/scopes/workspace/install/install.main.runtime.ts index a5d767046f47..9e7960246ec5 100644 --- a/scopes/workspace/install/install.main.runtime.ts +++ b/scopes/workspace/install/install.main.runtime.ts @@ -22,9 +22,7 @@ import type { VariantsMain } from '@teambit/variants'; import { VariantsAspect } from '@teambit/variants'; import type { Component } from '@teambit/component'; import { ComponentID, ComponentMap } from '@teambit/component'; -import { ComponentIdList } from '@teambit/component-id'; import { isSnap } from '@teambit/component-version'; -import { BuildStatus } from '@teambit/legacy.constants'; import { PackageJsonFile } from '@teambit/component.sources'; import { updateJsoncPreservingFormatting } from '@teambit/toolbox.json.jsonc-utils'; import { createLinks } from '@teambit/dependencies.fs.linked-dependencies'; @@ -395,11 +393,6 @@ export class InstallMain { } ); - // a workspace component may depend on a hidden "update-dependent" of the current lane that was never - // published (its Ripple build failed or hasn't completed), so there is nothing for pnpm to install. - // fail early with an actionable message instead of letting pnpm error with "No matching version found". - await this.throwOnUnpublishedUpdateDependentDeps(current.componentDirectoryMap.components); - const pmInstallOptions: PackageManagerInstallOptions = { ...calcManifestsOpts, autoInstallPeers: this.dependencyResolver.config.autoInstallPeers, @@ -438,18 +431,28 @@ export class InstallMain { // are not added to the manifests. // This is an issue when installation is done using root components. hasMissingLocalComponents = hasRootComponents && hasComponentsFromWorkspaceInMissingDeps(current); - const { dependenciesChanged } = await installer.installComponents( - this.workspace.path, - current.manifests, - mergedRootPolicy, - current.componentDirectoryMap, - { - linkedDependencies, - installTeambitBit: false, - forcedHarmonyVersion, - }, - pmInstallOptions - ); + let installResult: { dependenciesChanged: boolean }; + try { + installResult = await installer.installComponents( + this.workspace.path, + current.manifests, + mergedRootPolicy, + current.componentDirectoryMap, + { + linkedDependencies, + installTeambitBit: false, + forcedHarmonyVersion, + }, + pmInstallOptions + ); + } catch (err: any) { + // when the package manager can't find a version, the culprit is usually a component dependency that + // resolves to a snap which was never published (e.g. a hidden lane update-dependent whose Ripple build + // failed or hasn't completed). replace the cryptic "No matching version found" with an actionable + // message. re-throws the original error otherwise. + throw this.enrichUnpublishedSnapDepError(err, current.componentDirectoryMap.components); + } + const { dependenciesChanged } = installResult; this.workspace.inInstallAfterPmContext = true; let cacheCleared = false; await this.linkCodemods(compDirMap); @@ -525,83 +528,44 @@ export class InstallMain { } /** - * "update-dependents" are hidden entries of the current lane (components that depend on the lane's visible - * components and get re-snapped against the lane heads by "snap updates" / Ripple CI). they are not checked out - * into the workspace, so a visible component that depends on one must fetch it from the registry as a package. - * a package only exists once Ripple built the entry successfully; if its build failed (or hasn't completed) the - * snap was never published and there is no package to install. detect this up-front and throw an actionable - * error instead of letting pnpm fail with the cryptic "No matching version found" for a `0.0.0-` version. + * Called only when the package manager install failed. A "No matching version found" failure usually points + * at a component dependency that resolves to a snap which was never published — typically a hidden lane + * "update-dependent" (re-snapped by "snap updates" / Ripple CI) whose build failed or hasn't completed. Such a + * dep isn't checked out, so it can't be linked from source and there's no package to fetch. When the failing + * package matches one of those deps, return an actionable error (pointing at `bit import`); otherwise return + * the original error unchanged. + * + * Note: we deliberately match against the package manager error rather than the lane's `updateDependents`, + * because the install's import phase may have already dropped the entry from the local lane object. */ - private async throwOnUnpublishedUpdateDependentDeps(components: Component[]): Promise { - const lane = await this.workspace.getCurrentLaneObject(); - const updateDependents = lane?.updateDependents; - if (!updateDependents?.length) return; - const updateDependentsList = ComponentIdList.fromArray(updateDependents); - // checked-out components are linked from source (filtered out of the install manifest), so they're never - // fetched from the registry and can't trigger this failure even when they weren't built successfully. + private enrichUnpublishedSnapDepError(err: Error, components: Component[]): Error { + // checked-out components are linked from source, so they're never fetched from the registry. const workspaceIds = this.workspace.listIds(); - const legacyScope = this.workspace.scope.legacyScope; - const readBuildStatus = async (id: ComponentID): Promise => { - // `null` means the version object isn't available locally, so we can't determine its build status. - try { - return (await legacyScope.getVersionInstance(id)).buildStatus; - } catch { - return null; + const errMessage = err.message || ''; + + const unpublished = new Map }>(); + for (const component of components) { + for (const dep of this.dependencyResolver.getComponentDependencies(component)) { + const depId = dep.componentId; + if (!depId.version || !isSnap(depId.version)) continue; + if (workspaceIds.hasWithoutVersion(depId)) continue; + // only the dependency the package manager actually failed on: its package + snap version appear in the + // error (the manifest version is `0.0.0-`, so the raw hash is a substring). + if (!errMessage.includes(dep.packageName) || !errMessage.includes(depId.version)) continue; + const key = depId.toStringWithoutVersion(); + const entry = unpublished.get(key) ?? { id: depId, dependents: new Set() }; + entry.dependents.add(component.id.toStringWithoutVersion()); + unpublished.set(key, entry); } - }; - // a package exists once the snap was built. `Succeed` and `Skipped` both count as "built" here, mirroring - // the rest of the scope (see `onlyIfBuilt` in scope-components-importer and `sources.get`); `null` means - // undeterminable (version object not local). anything else (failed / pending / not built) has no package. - const isBuilt = (status: BuildStatus | undefined | null): boolean => - status === null || status === BuildStatus.Succeed || status === BuildStatus.Skipped; - - // collect update-dependent deps referenced by a checked-out component whose locally-known build status - // is not "built" — those are the ones that might have no package to install. - const candidates = new Map }>(); - await Promise.all( - components.map(async (component) => { - const compDeps = this.dependencyResolver.getComponentDependencies(component); - await Promise.all( - compDeps.map(async (dep) => { - const depId = dep.componentId; - if (!depId.version || !isSnap(depId.version)) return; - if (workspaceIds.hasWithoutVersion(depId)) return; - if (!updateDependentsList.hasWithoutVersion(depId)) return; - if (isBuilt(await readBuildStatus(depId))) return; - const key = depId.toString(); - const entry = candidates.get(key) ?? { id: depId, dependents: new Set() }; - entry.dependents.add(component.id.toStringWithoutVersion()); - candidates.set(key, entry); - }) - ); - }) - ); - if (!candidates.size) return; - - // a locally-stale `pending`/`failed` snap may have since been built and published on the remote. refresh - // these specific versions from the remote (this is exactly what `reFetchUnBuiltVersion` is for) before - // deciding they're unpublished, so we don't fail an install for a snap that is actually installable. - const candidateIds = [...candidates.values()].map((entry) => entry.id); - try { - await this.workspace.scope.import(candidateIds, { - reFetchUnBuiltVersion: true, - useCache: false, - lane, - includeUpdateDependents: true, - reason: 'to check whether unbuilt update-dependents were already published', - }); - } catch { - // offline or remote unavailable — fall back to the locally-known build status. } - - const unpublished: { id: string; version: string; dependents: string[] }[] = []; - for (const { id, dependents } of candidates.values()) { - // skip when now known to be built (the refetch may have picked up a remote build) or undeterminable. - if (isBuilt(await readBuildStatus(id))) continue; - unpublished.push({ id: id.toStringWithoutVersion(), version: id.version as string, dependents: [...dependents] }); - } - if (!unpublished.length) return; - throw new UpdateDependentBuildFailed(unpublished); + if (!unpublished.size) return err; + return new UpdateDependentBuildFailed( + [...unpublished.values()].map(({ id, dependents }) => ({ + id: id.toStringWithoutVersion(), + version: id.version as string, + dependents: [...dependents], + })) + ); } /** From 704078f33f88b95869ff60c6e83287d853d9f8ce Mon Sep 17 00:00:00 2001 From: David First Date: Tue, 16 Jun 2026 13:07:56 -0400 Subject: [PATCH 05/13] docs(install): correct rationale comment for matching the package-manager error bit import does not remove entries from lane.updateDependents (only snap/merge/reset and the explicit removeUpdateDependents command do, via addVersion). Fix the comment that wrongly attributed a missing entry to the import phase; the real reason to match the package-manager error is that it pinpoints the failing version and a consumer can keep pinning a never-published snap after the entry was promoted out of updateDependents. --- scopes/workspace/install/install.main.runtime.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scopes/workspace/install/install.main.runtime.ts b/scopes/workspace/install/install.main.runtime.ts index 9e7960246ec5..d267b71ba194 100644 --- a/scopes/workspace/install/install.main.runtime.ts +++ b/scopes/workspace/install/install.main.runtime.ts @@ -535,8 +535,9 @@ export class InstallMain { * package matches one of those deps, return an actionable error (pointing at `bit import`); otherwise return * the original error unchanged. * - * Note: we deliberately match against the package manager error rather than the lane's `updateDependents`, - * because the install's import phase may have already dropped the entry from the local lane object. + * Note: we match against the package manager error rather than the lane's `updateDependents` because the + * error pinpoints the exact failing version, and a consumer can keep pinning a never-published snap even after + * the entry was promoted out of `updateDependents` (e.g. the producer later re-snapped that component). */ private enrichUnpublishedSnapDepError(err: Error, components: Component[]): Error { // checked-out components are linked from source, so they're never fetched from the registry. From dff5520082d513829b58bd19ff58265830ecf108 Mon Sep 17 00:00:00 2001 From: David First Date: Tue, 16 Jun 2026 16:23:59 -0400 Subject: [PATCH 06/13] fix(install): don't assert 'update-dependent' in the unpublished-snap error The matcher keys off the package-manager 'No matching version found' error, not lane.updateDependents membership, so it can legitimately fire for any snap dependency that isn't checked out and was never published (e.g. after a reset that rewound a component to a never-published snap). Reword the message to name that as the likely cause rather than asserting the component is an update-dependent. Rename the payload type FailedUpdateDependent -> UnpublishedSnapDependency accordingly. --- scopes/workspace/install/exceptions/index.ts | 2 +- .../update-dependent-build-failed.ts | 30 +++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/scopes/workspace/install/exceptions/index.ts b/scopes/workspace/install/exceptions/index.ts index 54e4ed6db8d4..6b3a31249e44 100644 --- a/scopes/workspace/install/exceptions/index.ts +++ b/scopes/workspace/install/exceptions/index.ts @@ -1,3 +1,3 @@ export { DependencyTypeNotSupportedInPolicy } from './dependency-type-not-supported-in-policy'; export { UpdateDependentBuildFailed } from './update-dependent-build-failed'; -export type { FailedUpdateDependent } from './update-dependent-build-failed'; +export type { UnpublishedSnapDependency } from './update-dependent-build-failed'; diff --git a/scopes/workspace/install/exceptions/update-dependent-build-failed.ts b/scopes/workspace/install/exceptions/update-dependent-build-failed.ts index 907e44b29873..b5432dc0ac5f 100644 --- a/scopes/workspace/install/exceptions/update-dependent-build-failed.ts +++ b/scopes/workspace/install/exceptions/update-dependent-build-failed.ts @@ -1,40 +1,40 @@ import { BitError } from '@teambit/bit-error'; import { errorSymbol, formatItem } from '@teambit/cli'; -export type FailedUpdateDependent = { - /** the failed update-dependent component id (without version) */ +export type UnpublishedSnapDependency = { + /** the depended-on component id (without version) */ id: string; - /** the snap version (hash) that failed to build */ + /** the snap version (hash) that has no published package */ version: string; - /** workspace component ids that depend on this update-dependent */ + /** workspace component ids that depend on it */ dependents: string[]; }; /** - * thrown during `bit install` when a workspace component depends on a hidden "update-dependent" - * of the current lane that was never published — its Ripple build failed or hasn't completed - * successfully. without a published package there is nothing to install and pnpm would otherwise - * fail with a cryptic "No matching version found" error. + * thrown during `bit install` when a workspace component depends on another component pinned to a snap + * that was never published to the registry, so the package manager can't find it. the usual cause is a + * hidden lane "update-dependent" (created by "snap updates") whose build failed or hasn't completed, but + * it can also be any snap dependency that isn't checked out and was never published. */ export class UpdateDependentBuildFailed extends BitError { - constructor(readonly failed: FailedUpdateDependent[]) { - super(UpdateDependentBuildFailed.formatMessage(failed)); + constructor(readonly unpublished: UnpublishedSnapDependency[]) { + super(UpdateDependentBuildFailed.formatMessage(unpublished)); } - private static formatMessage(failed: FailedUpdateDependent[]): string { - const list = failed + private static formatMessage(unpublished: UnpublishedSnapDependency[]): string { + const list = unpublished .map(({ id, version, dependents }) => { const shortVersion = version.substring(0, 9); const requiredBy = dependents.join(', '); return formatItem(`${id} (${shortVersion}) — required by: ${requiredBy}`, errorSymbol); }) .join('\n'); - const importCommand = `bit import ${failed.map(({ id }) => id).join(' ')}`; - return `unable to install the following update-dependent component(s) of the current lane. -their build did not complete successfully (it failed, e.g. on Ripple after "snap updates", or is still pending), so they were never published to the registry and no package exists to install: + const importCommand = `bit import ${unpublished.map(({ id }) => id).join(' ')}`; + return `unable to install the following component(s) — they are pinned to a snap that was never published to the registry, so there is no package to install: ${list} +this usually happens with a lane "update-dependent" (created by "snap updates") whose build failed or hasn't completed. to resolve, import the component(s) into your workspace so they are linked from source instead of fetched from the registry: ${importCommand}`; From 89493534dfda9dd29bb44e937ddc1abb22e74462 Mon Sep 17 00:00:00 2001 From: David First Date: Wed, 17 Jun 2026 09:30:28 -0400 Subject: [PATCH 07/13] fix(install): only enrich the package-manager error when it's ERR_PNPM_NO_MATCHING_VERSION MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously enrichUnpublishedSnapDepError rewrote any install failure whose message mentioned the package+snap-hash, which could mask auth/network/FETCH_404/outage failures. Gate on the pnpm error code (carried on err.cause by pnpmErrorToBitError) — only ERR_PNPM_NO_MATCHING_VERSION is the unpublished-snap signal, matching the repo convention in lockfile-deps-graph-converter. Also drop the hardcoded em dash from the error message. --- .../exceptions/update-dependent-build-failed.ts | 2 +- scopes/workspace/install/install.main.runtime.ts | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/scopes/workspace/install/exceptions/update-dependent-build-failed.ts b/scopes/workspace/install/exceptions/update-dependent-build-failed.ts index b5432dc0ac5f..f5e2347206f7 100644 --- a/scopes/workspace/install/exceptions/update-dependent-build-failed.ts +++ b/scopes/workspace/install/exceptions/update-dependent-build-failed.ts @@ -26,7 +26,7 @@ export class UpdateDependentBuildFailed extends BitError { .map(({ id, version, dependents }) => { const shortVersion = version.substring(0, 9); const requiredBy = dependents.join(', '); - return formatItem(`${id} (${shortVersion}) — required by: ${requiredBy}`, errorSymbol); + return formatItem(`${id} (${shortVersion}) required by: ${requiredBy}`, errorSymbol); }) .join('\n'); const importCommand = `bit import ${unpublished.map(({ id }) => id).join(' ')}`; diff --git a/scopes/workspace/install/install.main.runtime.ts b/scopes/workspace/install/install.main.runtime.ts index d267b71ba194..5f223ad93eda 100644 --- a/scopes/workspace/install/install.main.runtime.ts +++ b/scopes/workspace/install/install.main.runtime.ts @@ -540,9 +540,18 @@ export class InstallMain { * the entry was promoted out of `updateDependents` (e.g. the producer later re-snapped that component). */ private enrichUnpublishedSnapDepError(err: Error, components: Component[]): Error { + // only act on the package manager's "no matching version" failure. other codes (auth, network, FETCH_404, + // registry outages) can mention the same package but signal a real problem we must not mask. the repo treats + // only ERR_PNPM_NO_MATCHING_VERSION as the "unpublished snap" signal (see lockfile-deps-graph-converter). + // pnpm errors are wrapped by pnpmErrorToBitError, which keeps the original error (with its code) on `cause`. + const pnpmCode = (err as any)?.cause?.code ?? (err as any)?.code; + const errMessage = err.message || ''; + const isNoMatchingVersion = + pnpmCode === 'ERR_PNPM_NO_MATCHING_VERSION' || (!pnpmCode && errMessage.includes('No matching version found')); + if (!isNoMatchingVersion) return err; + // checked-out components are linked from source, so they're never fetched from the registry. const workspaceIds = this.workspace.listIds(); - const errMessage = err.message || ''; const unpublished = new Map }>(); for (const component of components) { From f16463dcd6efe19a0ac41d4653a94f04879810c8 Mon Sep 17 00:00:00 2001 From: David First Date: Mon, 22 Jun 2026 09:42:05 -0400 Subject: [PATCH 08/13] refactor(install): generalize unpublished-snap-dependency install error rename UpdateDependentBuildFailed -> UnpublishedComponentDependency and drop the update-dependent wording from the message. the failing snap may no longer be tracked in lane.updateDependents (forking a lane drops it, reset moves it out), so the message now states the true condition: a component dependency that isn't checked out and is pinned to a snap that was never published. detection still matches the package-manager error against resolved component deps (the only place to recover the dep's component-id for the bit import remediation; reads in-memory data, no fetch). --- .../lanes/update-dependents-cascade.e2e.ts | 4 ++-- scopes/workspace/install/exceptions/index.ts | 4 ++-- ...ts => unpublished-component-dependency.ts} | 19 +++++++-------- .../workspace/install/install.main.runtime.ts | 23 +++++++++++-------- 4 files changed, 27 insertions(+), 23 deletions(-) rename scopes/workspace/install/exceptions/{update-dependent-build-failed.ts => unpublished-component-dependency.ts} (51%) diff --git a/e2e/harmony/lanes/update-dependents-cascade.e2e.ts b/e2e/harmony/lanes/update-dependents-cascade.e2e.ts index a938f7bd7757..6b1f51615d04 100644 --- a/e2e/harmony/lanes/update-dependents-cascade.e2e.ts +++ b/e2e/harmony/lanes/update-dependents-cascade.e2e.ts @@ -1243,14 +1243,14 @@ describe('local snap cascades updateDependents on the lane', function () { expect(bitMap).to.not.have.property('comp2'); }); - it('bit install fails with an actionable error naming the updateDependent and the bit import remediation', () => { + it('bit install fails with an actionable error naming the component and the bit import remediation', () => { let output = ''; try { helper.command.install(); } catch (err: any) { output = `${err.message || ''}${err.stdout?.toString() || ''}${err.stderr?.toString() || ''}`; } - expect(output, 'bit install should have failed').to.have.string('update-dependent'); + expect(output, 'bit install should have failed').to.have.string('never published'); expect(output, 'error should name the problematic component').to.have.string('comp2'); expect(output, 'error should suggest the bit import remediation').to.have.string('bit import'); }); diff --git a/scopes/workspace/install/exceptions/index.ts b/scopes/workspace/install/exceptions/index.ts index 6b3a31249e44..7cfeee5d060e 100644 --- a/scopes/workspace/install/exceptions/index.ts +++ b/scopes/workspace/install/exceptions/index.ts @@ -1,3 +1,3 @@ export { DependencyTypeNotSupportedInPolicy } from './dependency-type-not-supported-in-policy'; -export { UpdateDependentBuildFailed } from './update-dependent-build-failed'; -export type { UnpublishedSnapDependency } from './update-dependent-build-failed'; +export { UnpublishedComponentDependency } from './unpublished-component-dependency'; +export type { UnpublishedSnapDependency } from './unpublished-component-dependency'; diff --git a/scopes/workspace/install/exceptions/update-dependent-build-failed.ts b/scopes/workspace/install/exceptions/unpublished-component-dependency.ts similarity index 51% rename from scopes/workspace/install/exceptions/update-dependent-build-failed.ts rename to scopes/workspace/install/exceptions/unpublished-component-dependency.ts index f5e2347206f7..ef237b782129 100644 --- a/scopes/workspace/install/exceptions/update-dependent-build-failed.ts +++ b/scopes/workspace/install/exceptions/unpublished-component-dependency.ts @@ -11,14 +11,15 @@ export type UnpublishedSnapDependency = { }; /** - * thrown during `bit install` when a workspace component depends on another component pinned to a snap - * that was never published to the registry, so the package manager can't find it. the usual cause is a - * hidden lane "update-dependent" (created by "snap updates") whose build failed or hasn't completed, but - * it can also be any snap dependency that isn't checked out and was never published. + * thrown during `bit install` when a checked-out workspace component depends on another component that + * isn't checked out and is pinned to a snap that was never published to the registry, so the package + * manager can't find it. the usual cause is a build that failed or hasn't completed yet (e.g. a hidden + * lane "update-dependent" re-snapped by "snap updates"), but the message stays generic on purpose since + * the snap may no longer be tracked as an update-dependent (e.g. after the lane was forked). */ -export class UpdateDependentBuildFailed extends BitError { +export class UnpublishedComponentDependency extends BitError { constructor(readonly unpublished: UnpublishedSnapDependency[]) { - super(UpdateDependentBuildFailed.formatMessage(unpublished)); + super(UnpublishedComponentDependency.formatMessage(unpublished)); } private static formatMessage(unpublished: UnpublishedSnapDependency[]): string { @@ -30,12 +31,12 @@ export class UpdateDependentBuildFailed extends BitError { }) .join('\n'); const importCommand = `bit import ${unpublished.map(({ id }) => id).join(' ')}`; - return `unable to install the following component(s) — they are pinned to a snap that was never published to the registry, so there is no package to install: + return `unable to install the following component(s) — they're not checked out in your workspace and are pinned to a snap that was never published to the registry, so there's no package to fetch and no source to link: ${list} -this usually happens with a lane "update-dependent" (created by "snap updates") whose build failed or hasn't completed. -to resolve, import the component(s) into your workspace so they are linked from source instead of fetched from the registry: +this usually means the component's build failed or hasn't completed yet. +to resolve, import it into your workspace so it's linked from source instead of fetched from the registry: ${importCommand}`; } diff --git a/scopes/workspace/install/install.main.runtime.ts b/scopes/workspace/install/install.main.runtime.ts index 5f223ad93eda..77bc67f0d252 100644 --- a/scopes/workspace/install/install.main.runtime.ts +++ b/scopes/workspace/install/install.main.runtime.ts @@ -68,7 +68,7 @@ import { BundlerAspect } from '@teambit/bundler'; import type { UiMain } from '@teambit/ui'; import { UIAspect } from '@teambit/ui'; import { EXTERNAL_PM_POSTINSTALL_SCRIPT } from '@teambit/host-initializer'; -import { DependencyTypeNotSupportedInPolicy, UpdateDependentBuildFailed } from './exceptions'; +import { DependencyTypeNotSupportedInPolicy, UnpublishedComponentDependency } from './exceptions'; import { InstallAspect } from './install.aspect'; import { pickOutdatedPkgs } from './pick-outdated-pkgs'; import { LinkCommand } from './link'; @@ -529,15 +529,18 @@ export class InstallMain { /** * Called only when the package manager install failed. A "No matching version found" failure usually points - * at a component dependency that resolves to a snap which was never published — typically a hidden lane - * "update-dependent" (re-snapped by "snap updates" / Ripple CI) whose build failed or hasn't completed. Such a - * dep isn't checked out, so it can't be linked from source and there's no package to fetch. When the failing - * package matches one of those deps, return an actionable error (pointing at `bit import`); otherwise return - * the original error unchanged. + * at a component dependency that resolves to a snap which was never published — its build failed or hasn't + * completed yet (commonly a hidden lane "update-dependent" re-snapped by "snap updates" / Ripple CI, but not + * necessarily). Such a dep isn't checked out, so it can't be linked from source and there's no package to + * fetch. When the failing package matches one of those deps, return an actionable error (pointing at + * `bit import`); otherwise return the original error unchanged. * - * Note: we match against the package manager error rather than the lane's `updateDependents` because the - * error pinpoints the exact failing version, and a consumer can keep pinning a never-published snap even after - * the entry was promoted out of `updateDependents` (e.g. the producer later re-snapped that component). + * We resolve the culprit by matching the package manager error against the resolved component dependencies + * rather than the lane's `updateDependents`, for two reasons: (1) the failing dep is not a workspace component, + * so its component-id (needed for the `bit import` remediation) is only recoverable from the resolved deps of + * the checked-out components — there's no package-name→id map for it; (2) `updateDependents` is unreliable — + * forking a lane drops it and `reset` moves entries out of it, so a never-published snap can still be pinned + * while no longer tracked there. The scan reads already-loaded in-memory dep data (no fetch). */ private enrichUnpublishedSnapDepError(err: Error, components: Component[]): Error { // only act on the package manager's "no matching version" failure. other codes (auth, network, FETCH_404, @@ -569,7 +572,7 @@ export class InstallMain { } } if (!unpublished.size) return err; - return new UpdateDependentBuildFailed( + return new UnpublishedComponentDependency( [...unpublished.values()].map(({ id, dependents }) => ({ id: id.toStringWithoutVersion(), version: id.version as string, From 7157c75ef873bb320c0141af71b445d57727aa9b Mon Sep 17 00:00:00 2001 From: David First Date: Mon, 22 Jun 2026 10:53:19 -0400 Subject: [PATCH 09/13] test(e2e): scope verdaccio registry to workspace config (#10435) E2E tests set the npm registry with `bit config set registry `, which defaults to Bit's **global** config store. When the suite runs locally, the local verdaccio URL leaks into every other Bit workspace, so unrelated installs try to reach the (now-dead) local registry. Pass `--local-track` so the registry is written to the test workspace's `workspace.jsonc` (origin `workspace`) instead of the global config. `getConfig` still reads it during install, but it's confined to the throwaway test workspace. Also removed the now-redundant `delConfig('registry')` cleanup calls. --- .../legacy/e2e-helper/npm-ci-registry.ts | 10 +++++ e2e/harmony/dependencies/allow-scripts.e2e.ts | 37 ++++++++++--------- .../never-built-dependencies.e2e.ts | 6 +-- e2e/harmony/deps-graph-isolation.e2e.ts | 7 ++-- e2e/harmony/deps-graph-reimport.e2e.ts | 10 +++-- e2e/harmony/deps-graph.e2e.ts | 13 ++++--- e2e/harmony/install.e2e.ts | 6 +-- 7 files changed, 50 insertions(+), 39 deletions(-) diff --git a/components/legacy/e2e-helper/npm-ci-registry.ts b/components/legacy/e2e-helper/npm-ci-registry.ts index 02e97117f004..4d4fa60bf6a1 100644 --- a/components/legacy/e2e-helper/npm-ci-registry.ts +++ b/components/legacy/e2e-helper/npm-ci-registry.ts @@ -49,6 +49,16 @@ export class NpmCiRegistry { this._registerScopes(scopes); } + /** + * set the default registry in the workspace config (workspace.jsonc) rather than the global bit + * config, so running the tests locally doesn't leak the local Verdaccio registry into other + * workspaces. + * note: must be re-applied after `reInitWorkspace()`, which wipes the workspace dir (workspace.jsonc). + */ + setRegistry() { + this.helper.command.setConfig('registry', this.getRegistryUrl(), '--local-track'); + } + /** * makes sure to kill the server process, otherwise, the tests will continue forever and never exit */ diff --git a/e2e/harmony/dependencies/allow-scripts.e2e.ts b/e2e/harmony/dependencies/allow-scripts.e2e.ts index 3269d1d4c584..79dcd8e24b8b 100644 --- a/e2e/harmony/dependencies/allow-scripts.e2e.ts +++ b/e2e/harmony/dependencies/allow-scripts.e2e.ts @@ -16,11 +16,10 @@ chai.use(chaiFs); npmCiRegistry = new NpmCiRegistry(helper); await npmCiRegistry.init(); - helper.command.setConfig('registry', npmCiRegistry.getRegistryUrl()); + npmCiRegistry.setRegistry(); helper.command.install('@pnpm.e2e/pre-and-postinstall-scripts-example'); }); after(() => { - helper.command.delConfig('registry'); npmCiRegistry.destroy(); helper.scopeHelper.destroy(); }); @@ -48,7 +47,7 @@ chai.use(chaiFs); npmCiRegistry = new NpmCiRegistry(helper); await npmCiRegistry.init(); - helper.command.setConfig('registry', npmCiRegistry.getRegistryUrl()); + npmCiRegistry.setRegistry(); helper.extensions.workspaceJsonc.addKeyValToDependencyResolver('allowScripts', { '@pnpm.e2e/failing-postinstall': false, '@pnpm.e2e/pre-and-postinstall-scripts-example': true, @@ -58,7 +57,6 @@ chai.use(chaiFs); helper.command.install('@pnpm.e2e/failing-postinstall @pnpm.e2e/pre-and-postinstall-scripts-example'); }); after(() => { - helper.command.delConfig('registry'); npmCiRegistry.destroy(); helper.scopeHelper.destroy(); }); @@ -87,14 +85,15 @@ chai.use(chaiFs); npmCiRegistry = new NpmCiRegistry(helper); await npmCiRegistry.init(); - helper.command.setConfig('registry', npmCiRegistry.getRegistryUrl()); + npmCiRegistry.setRegistry(); // The installation below would fail if we didn't explicitly disallow // @pnpm.e2e/failing-postinstall in allowScripts. - helper.command.install('@pnpm.e2e/failing-postinstall @pnpm.e2e/pre-and-postinstall-scripts-example --disallow-scripts=@pnpm.e2e/failing-postinstall --allow-scripts=@pnpm.e2e/pre-and-postinstall-scripts-example'); + helper.command.install( + '@pnpm.e2e/failing-postinstall @pnpm.e2e/pre-and-postinstall-scripts-example --disallow-scripts=@pnpm.e2e/failing-postinstall --allow-scripts=@pnpm.e2e/pre-and-postinstall-scripts-example' + ); workspaceJsonc = helper.workspaceJsonc.read(); }); after(() => { - helper.command.delConfig('registry'); npmCiRegistry.destroy(); helper.scopeHelper.destroy(); }); @@ -129,25 +128,29 @@ chai.use(chaiFs); npmCiRegistry = new NpmCiRegistry(helper); await npmCiRegistry.init(); - helper.command.setConfig('registry', npmCiRegistry.getRegistryUrl()); + npmCiRegistry.setRegistry(); helper.extensions.workspaceJsonc.addKeyValToDependencyResolver('allowScripts', { '@pnpm.e2e/failing-postinstall': true, '@pnpm.e2e/pre-and-postinstall-scripts-example': false, }); // The installation below would fail if we didn't explicitly disallow // @pnpm.e2e/failing-postinstall in allowScripts. - helper.command.install('@pnpm.e2e/failing-postinstall @pnpm.e2e/pre-and-postinstall-scripts-example', undefined, undefined, { - envVariables: { - BIT_ALLOW_SCRIPTS: JSON.stringify({ - '@pnpm.e2e/failing-postinstall': false, - '@pnpm.e2e/pre-and-postinstall-scripts-example': true, - }), - }, - }); + helper.command.install( + '@pnpm.e2e/failing-postinstall @pnpm.e2e/pre-and-postinstall-scripts-example', + undefined, + undefined, + { + envVariables: { + BIT_ALLOW_SCRIPTS: JSON.stringify({ + '@pnpm.e2e/failing-postinstall': false, + '@pnpm.e2e/pre-and-postinstall-scripts-example': true, + }), + }, + } + ); workspaceJsonc = helper.workspaceJsonc.read(); }); after(() => { - helper.command.delConfig('registry'); npmCiRegistry.destroy(); helper.scopeHelper.destroy(); }); diff --git a/e2e/harmony/dependencies/never-built-dependencies.e2e.ts b/e2e/harmony/dependencies/never-built-dependencies.e2e.ts index 54ac8c03b1ac..405464984c05 100644 --- a/e2e/harmony/dependencies/never-built-dependencies.e2e.ts +++ b/e2e/harmony/dependencies/never-built-dependencies.e2e.ts @@ -16,7 +16,7 @@ chai.use(chaiFs); npmCiRegistry = new NpmCiRegistry(helper); await npmCiRegistry.init(); - helper.command.setConfig('registry', npmCiRegistry.getRegistryUrl()); + npmCiRegistry.setRegistry(); helper.extensions.workspaceJsonc.addKeyValToDependencyResolver('dangerouslyAllowAllScripts', true); helper.extensions.workspaceJsonc.addKeyValToDependencyResolver('neverBuiltDependencies', [ '@pnpm.e2e/pre-and-postinstall-scripts-example', @@ -24,7 +24,6 @@ chai.use(chaiFs); helper.command.install('@pnpm.e2e/pre-and-postinstall-scripts-example'); }); after(() => { - helper.command.delConfig('registry'); npmCiRegistry.destroy(); helper.scopeHelper.destroy(); }); @@ -56,14 +55,13 @@ chai.use(chaiFs); npmCiRegistry = new NpmCiRegistry(helper); await npmCiRegistry.init(); - helper.command.setConfig('registry', npmCiRegistry.getRegistryUrl()); + npmCiRegistry.setRegistry(); helper.extensions.workspaceJsonc.addKeyValToDependencyResolver('neverBuiltDependencies', [ '@pnpm.e2e/pre-and-postinstall-scripts-example', ]); helper.command.install('@pnpm.e2e/pre-and-postinstall-scripts-example'); }); after(() => { - helper.command.delConfig('registry'); npmCiRegistry.destroy(); helper.scopeHelper.destroy(); }); diff --git a/e2e/harmony/deps-graph-isolation.e2e.ts b/e2e/harmony/deps-graph-isolation.e2e.ts index e16ddcfe31ce..9dba0ae56adc 100644 --- a/e2e/harmony/deps-graph-isolation.e2e.ts +++ b/e2e/harmony/deps-graph-isolation.e2e.ts @@ -45,7 +45,7 @@ chai.use(chaiFs); npmCiRegistry = new NpmCiRegistry(helper); npmCiRegistry.configureCustomNameInPackageJsonHarmony(name); await npmCiRegistry.init(); - helper.command.setConfig('registry', npmCiRegistry.getRegistryUrl()); + npmCiRegistry.setRegistry(); helper.env.setCustomNewEnv( undefined, undefined, @@ -80,13 +80,13 @@ chai.use(chaiFs); helper.scopeHelper.reInitWorkspace(); helper.scopeHelper.addRemoteScope(); + npmCiRegistry.setRegistry(); helper.extensions.workspaceJsonc.addKeyValToDependencyResolver('rootComponents', true); helper.command.import(`${helper.scopes.remote}/comp1@latest`); lockfile = yaml.load(fs.readFileSync(path.join(helper.scopes.localPath, 'pnpm-lock.yaml'), 'utf8')); }); after(() => { npmCiRegistry.destroy(); - helper.command.delConfig('registry'); helper.scopeHelper.destroy(); }); it('should record the dev-only dependency in the graph with lifecycle=dev', () => { @@ -207,7 +207,7 @@ chai.use(chaiFs); npmCiRegistry = new NpmCiRegistry(helper); npmCiRegistry.configureCustomNameInPackageJsonHarmony(name); await npmCiRegistry.init(); - helper.command.setConfig('registry', npmCiRegistry.getRegistryUrl()); + npmCiRegistry.setRegistry(); helper.fixtures.populateComponents(1); helper.extensions.workspaceJsonc.addKeyValToDependencyResolver('rootComponents', true); helper.command.install(); @@ -222,7 +222,6 @@ chai.use(chaiFs); }); after(() => { npmCiRegistry.destroy(); - helper.command.delConfig('registry'); helper.scopeHelper.destroy(); }); it('should attach the dependencies graph to the snap by default', () => { diff --git a/e2e/harmony/deps-graph-reimport.e2e.ts b/e2e/harmony/deps-graph-reimport.e2e.ts index bcc119cf4885..593460ab315b 100644 --- a/e2e/harmony/deps-graph-reimport.e2e.ts +++ b/e2e/harmony/deps-graph-reimport.e2e.ts @@ -36,7 +36,7 @@ chai.use(chaiFs); npmCiRegistry = new NpmCiRegistry(helper); npmCiRegistry.configureCustomNameInPackageJsonHarmony(name); await npmCiRegistry.init(); - helper.command.setConfig('registry', npmCiRegistry.getRegistryUrl()); + npmCiRegistry.setRegistry(); helper.env.setCustomNewEnv( undefined, undefined, @@ -64,6 +64,7 @@ chai.use(chaiFs); helper.scopeHelper.reInitWorkspace(); helper.scopeHelper.addRemoteScope(); + npmCiRegistry.setRegistry(); helper.extensions.workspaceJsonc.addKeyValToDependencyResolver('rootComponents', true); helper.command.import(`${helper.scopes.remote}/comp1@0.0.1 ${helper.scopes.remote}/comp2@latest`); @@ -74,7 +75,6 @@ chai.use(chaiFs); }); after(() => { npmCiRegistry.destroy(); - helper.command.delConfig('registry'); helper.scopeHelper.destroy(); }); // Regression coverage: re-importing comp1 must not regenerate the lockfile from @@ -95,7 +95,7 @@ chai.use(chaiFs); npmCiRegistry = new NpmCiRegistry(helper); npmCiRegistry.configureCustomNameInPackageJsonHarmony(name); await npmCiRegistry.init(); - helper.command.setConfig('registry', npmCiRegistry.getRegistryUrl()); + npmCiRegistry.setRegistry(); helper.env.setCustomNewEnv( undefined, undefined, @@ -127,6 +127,7 @@ chai.use(chaiFs); await addDistTag({ package: '@pnpm.e2e/peer-a', version: '1.0.1', distTag: 'latest' }); helper.scopeHelper.reInitWorkspace(); helper.scopeHelper.addRemoteScope(); + npmCiRegistry.setRegistry(); helper.fs.createFile('foo', 'foo.js', `require("@pnpm.e2e/abc"); require("@ci/${randomStr}.bar");`); helper.command.addComponent('foo'); helper.extensions.addExtensionToVariant('foo', `${helper.scopes.remote}/custom-env/env@0.0.1`, {}); @@ -141,6 +142,7 @@ chai.use(chaiFs); await addDistTag({ package: '@pnpm.e2e/peer-a', version: '1.0.0', distTag: 'latest' }); helper.scopeHelper.reInitWorkspace(); helper.scopeHelper.addRemoteScope(); + npmCiRegistry.setRegistry(); helper.fs.createFile('baz', 'baz.js', `require("@pnpm.e2e/abc"); require("@ci/${randomStr}.bar");`); helper.command.addComponent('baz'); helper.extensions.addExtensionToVariant('baz', `${helper.scopes.remote}/custom-env/env@0.0.1`, {}); @@ -150,6 +152,7 @@ chai.use(chaiFs); helper.scopeHelper.reInitWorkspace(); helper.scopeHelper.addRemoteScope(); + npmCiRegistry.setRegistry(); helper.command.import( `${helper.scopes.remote}/foo@latest ${helper.scopes.remote}/bar@latest ${helper.scopes.remote}/baz@latest` ); @@ -157,7 +160,6 @@ chai.use(chaiFs); }); after(() => { npmCiRegistry.destroy(); - helper.command.delConfig('registry'); helper.scopeHelper.destroy(); }); it('should restore the lockfile from the merged graphs', () => { diff --git a/e2e/harmony/deps-graph.e2e.ts b/e2e/harmony/deps-graph.e2e.ts index 075cf09a1759..0ff3a760f25d 100644 --- a/e2e/harmony/deps-graph.e2e.ts +++ b/e2e/harmony/deps-graph.e2e.ts @@ -33,7 +33,7 @@ chai.use(chaiFs); npmCiRegistry = new NpmCiRegistry(helper); npmCiRegistry.configureCustomNameInPackageJsonHarmony(name); await npmCiRegistry.init(); - helper.command.setConfig('registry', npmCiRegistry.getRegistryUrl()); + npmCiRegistry.setRegistry(); helper.env.setCustomNewEnv( undefined, @@ -99,7 +99,6 @@ chai.use(chaiFs); }); after(() => { npmCiRegistry.destroy(); - helper.command.delConfig('registry'); helper.scopeHelper.destroy(); }); it('should save dependencies graph to the model of comp1', () => { @@ -149,6 +148,7 @@ chai.use(chaiFs); before(async () => { helper.scopeHelper.reInitWorkspace(); helper.scopeHelper.addRemoteScope(); + npmCiRegistry.setRegistry(); await addDistTag({ package: '@pnpm.e2e/foo', version: '100.1.0', distTag: 'latest' }); await addDistTag({ package: '@pnpm.e2e/bar', version: '100.1.0', distTag: 'latest' }); helper.command.import(`${helper.scopes.remote}/comp2@latest`); @@ -175,7 +175,7 @@ chai.use(chaiFs); npmCiRegistry = new NpmCiRegistry(helper); npmCiRegistry.configureCustomNameInPackageJsonHarmony(name); await npmCiRegistry.init(); - helper.command.setConfig('registry', npmCiRegistry.getRegistryUrl()); + npmCiRegistry.setRegistry(); helper.env.setCustomNewEnv( undefined, undefined, @@ -207,6 +207,7 @@ chai.use(chaiFs); await addDistTag({ package: '@pnpm.e2e/peer-a', version: '1.0.0', distTag: 'latest' }); helper.scopeHelper.reInitWorkspace(); helper.scopeHelper.addRemoteScope(); + npmCiRegistry.setRegistry(); helper.fs.createFile('foo', 'foo.js', `require("@pnpm.e2e/abc"); require("@ci/${randomStr}.bar");`); helper.command.addComponent('foo'); helper.extensions.addExtensionToVariant('foo', `${helper.scopes.remote}/custom-env/env@0.0.1`, {}); @@ -216,6 +217,7 @@ chai.use(chaiFs); helper.scopeHelper.reInitWorkspace(); helper.scopeHelper.addRemoteScope(); + npmCiRegistry.setRegistry(); helper.command.import(`${helper.scopes.remote}/foo@latest ${helper.scopes.remote}/bar@latest`); }); let lockfile: any; @@ -234,7 +236,6 @@ chai.use(chaiFs); }); after(() => { npmCiRegistry.destroy(); - helper.command.delConfig('registry'); helper.scopeHelper.destroy(); }); }); @@ -254,7 +255,7 @@ chai.use(chaiFs); npmCiRegistry = new NpmCiRegistry(helper); npmCiRegistry.configureCustomNameInPackageJsonHarmony(name); await npmCiRegistry.init(); - helper.command.setConfig('registry', npmCiRegistry.getRegistryUrl()); + npmCiRegistry.setRegistry(); helper.env.setCustomNewEnv( undefined, undefined, @@ -278,6 +279,7 @@ chai.use(chaiFs); helper.scopeHelper.reInitWorkspace(); helper.scopeHelper.addRemoteScope(); + npmCiRegistry.setRegistry(); helper.extensions.workspaceJsonc.addKeyValToDependencyResolver('rootComponents', true); helper.command.import(`${helper.scopes.remote}/comp1@latest`); initialLockfile = yaml.load(fs.readFileSync(path.join(helper.scopes.localPath, 'pnpm-lock.yaml'), 'utf8')); @@ -291,7 +293,6 @@ chai.use(chaiFs); }); after(() => { npmCiRegistry.destroy(); - helper.command.delConfig('registry'); helper.scopeHelper.destroy(); }); it('first import should restore the lockfile from comp1 graph', () => { diff --git a/e2e/harmony/install.e2e.ts b/e2e/harmony/install.e2e.ts index b686ed158748..84d2a26535ea 100644 --- a/e2e/harmony/install.e2e.ts +++ b/e2e/harmony/install.e2e.ts @@ -132,11 +132,10 @@ describe('install generator configured envs', function () { npmCiRegistry = new NpmCiRegistry(helper); await npmCiRegistry.init(); - helper.command.setConfig('registry', npmCiRegistry.getRegistryUrl()); + npmCiRegistry.setRegistry(); helper.command.install('@pnpm.e2e/pkg-with-good-optional --no-optional'); }); after(() => { - helper.command.delConfig('registry'); npmCiRegistry.destroy(); helper.scopeHelper.destroy(); }); @@ -160,7 +159,7 @@ describe('install generator configured envs', function () { npmCiRegistry = new NpmCiRegistry(helper); await npmCiRegistry.init(); - helper.command.setConfig('registry', npmCiRegistry.getRegistryUrl()); + npmCiRegistry.setRegistry(); await addDistTag({ package: '@pnpm.e2e/pkg-with-1-dep', version: '100.0.0', distTag: 'latest' }); await addDistTag({ package: '@pnpm.e2e/dep-of-pkg-with-1-dep', version: '100.0.0', distTag: 'latest' }); helper.command.install('@pnpm.e2e/dep-of-pkg-with-1-dep @pnpm.e2e/parent-of-pkg-with-1-dep'); @@ -169,7 +168,6 @@ describe('install generator configured envs', function () { helper.command.install('--update'); }); after(() => { - helper.command.delConfig('registry'); npmCiRegistry.destroy(); helper.scopeHelper.destroy(); }); From cf0add9090224619727eb1e53eafa5f782294821 Mon Sep 17 00:00:00 2001 From: Bit CI Date: Mon, 22 Jun 2026 15:29:29 +0000 Subject: [PATCH 10/13] bump teambit version to 1.13.230 [skip ci] --- .bitmap | 202 +++++++++--------- .../cli-reference/cli-reference.docs.mdx | 2 +- 2 files changed, 102 insertions(+), 102 deletions(-) diff --git a/.bitmap b/.bitmap index 6d12cb0639e9..d944f9c60c69 100644 --- a/.bitmap +++ b/.bitmap @@ -19,21 +19,21 @@ "api-reference": { "name": "api-reference", "scope": "teambit.api-reference", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/api-reference/api-reference" }, "api-server": { "name": "api-server", "scope": "teambit.harmony", - "version": "1.0.1053", + "version": "1.0.1054", "mainFile": "index.ts", "rootDir": "scopes/harmony/api-server" }, "application": { "name": "application", "scope": "teambit.harmony", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/harmony/application" }, @@ -47,7 +47,7 @@ "aspect": { "name": "aspect", "scope": "teambit.harmony", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/harmony/aspect" }, @@ -194,14 +194,14 @@ "aspect-loader": { "name": "aspect-loader", "scope": "teambit.harmony", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/harmony/aspect-loader" }, "babel": { "name": "babel", "scope": "teambit.compilation", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/compilation/babel" }, @@ -215,7 +215,7 @@ "bit": { "name": "bit", "scope": "teambit.harmony", - "version": "1.13.229", + "version": "1.13.230", "mainFile": "index.ts", "rootDir": "scopes/harmony/bit" }, @@ -229,7 +229,7 @@ "builder": { "name": "builder", "scope": "teambit.pipelines", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/pipelines/builder" }, @@ -243,7 +243,7 @@ "bundler": { "name": "bundler", "scope": "teambit.compilation", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/compilation/bundler" }, @@ -257,21 +257,21 @@ "changelog": { "name": "changelog", "scope": "teambit.component", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/component/changelog" }, "checkout": { "name": "checkout", "scope": "teambit.component", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/component/checkout" }, "ci": { "name": "ci", "scope": "teambit.git", - "version": "1.0.409", + "version": "1.0.410", "mainFile": "index.ts", "rootDir": "scopes/git/ci" }, @@ -292,7 +292,7 @@ "cli-mcp-server": { "name": "cli-mcp-server", "scope": "teambit.mcp", - "version": "0.0.197", + "version": "0.0.198", "mainFile": "index.ts", "rootDir": "scopes/mcp/cli-mcp-server" }, @@ -320,21 +320,21 @@ "cloud": { "name": "cloud", "scope": "teambit.cloud", - "version": "0.0.1319", + "version": "0.0.1320", "mainFile": "index.ts", "rootDir": "scopes/cloud/cloud" }, "code": { "name": "code", "scope": "teambit.component", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/component/code" }, "command-bar": { "name": "command-bar", "scope": "teambit.explorer", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/explorer/command-bar" }, @@ -348,21 +348,21 @@ "compiler": { "name": "compiler", "scope": "teambit.compilation", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/compilation/compiler" }, "component": { "name": "component", "scope": "teambit.component", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/component/component" }, "component-compare": { "name": "component-compare", "scope": "teambit.component", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/component/component-compare" }, @@ -397,7 +397,7 @@ "component-log": { "name": "component-log", "scope": "teambit.component", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/component/component-log" }, @@ -411,21 +411,21 @@ "component-sizer": { "name": "component-sizer", "scope": "teambit.component", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/component/component-sizer" }, "component-tree": { "name": "component-tree", "scope": "teambit.component", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/component/component-tree" }, "component-writer": { "name": "component-writer", "scope": "teambit.component", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/component/component-writer" }, @@ -439,7 +439,7 @@ "compositions": { "name": "compositions", "scope": "teambit.compositions", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/compositions/compositions" }, @@ -453,7 +453,7 @@ "config-merger": { "name": "config-merger", "scope": "teambit.workspace", - "version": "0.0.890", + "version": "0.0.891", "mainFile": "index.ts", "rootDir": "scopes/workspace/config-merger" }, @@ -495,7 +495,7 @@ "content/cli-reference": { "name": "content/cli-reference", "scope": "teambit.harmony", - "version": "2.0.1089", + "version": "2.0.1090", "mainFile": "index.ts", "rootDir": "scopes/harmony/cli-reference" }, @@ -509,7 +509,7 @@ "dependencies": { "name": "dependencies", "scope": "teambit.dependencies", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/dependencies/dependencies" }, @@ -523,28 +523,28 @@ "dependency-resolver": { "name": "dependency-resolver", "scope": "teambit.dependencies", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/dependencies/dependency-resolver" }, "deprecation": { "name": "deprecation", "scope": "teambit.component", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/component/deprecation" }, "dev-files": { "name": "dev-files", "scope": "teambit.component", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/component/dev-files" }, "diagnostic": { "name": "diagnostic", "scope": "teambit.harmony", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/harmony/diagnostic" }, @@ -558,28 +558,28 @@ "docs": { "name": "docs", "scope": "teambit.docs", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/docs/docs" }, "doctor": { "name": "doctor", "scope": "teambit.harmony", - "version": "0.0.708", + "version": "0.0.709", "mainFile": "index.ts", "rootDir": "scopes/harmony/doctor" }, "e2e-helper": { "name": "e2e-helper", "scope": "teambit.legacy", - "version": "0.0.172", + "version": "0.0.173", "mainFile": "index.ts", "rootDir": "components/legacy/e2e-helper" }, "eject": { "name": "eject", "scope": "teambit.workspace", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/workspace/eject" }, @@ -607,21 +607,21 @@ "env": { "name": "env", "scope": "teambit.envs", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/envs/env" }, "envs": { "name": "envs", "scope": "teambit.envs", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/envs/envs" }, "eslint": { "name": "eslint", "scope": "teambit.defender", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/defender/eslint" }, @@ -642,7 +642,7 @@ "export": { "name": "export", "scope": "teambit.scope", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/scope/export" }, @@ -663,14 +663,14 @@ "forking": { "name": "forking", "scope": "teambit.component", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/component/forking" }, "formatter": { "name": "formatter", "scope": "teambit.defender", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/defender/formatter" }, @@ -726,7 +726,7 @@ "generator": { "name": "generator", "scope": "teambit.generator", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/generator/generator" }, @@ -740,7 +740,7 @@ "git": { "name": "git", "scope": "teambit.git", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/git/git" }, @@ -754,21 +754,21 @@ "graph": { "name": "graph", "scope": "teambit.component", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/component/graph" }, "graphql": { "name": "graphql", "scope": "teambit.harmony", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/harmony/graphql" }, "harmony-ui-app": { "name": "harmony-ui-app", "scope": "teambit.ui-foundation", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/ui-foundation/harmony-ui-app/harmony-ui-app" }, @@ -831,63 +831,63 @@ "host-initializer": { "name": "host-initializer", "scope": "teambit.harmony", - "version": "0.0.736", + "version": "0.0.737", "mainFile": "index.ts", "rootDir": "scopes/harmony/host-initializer" }, "importer": { "name": "importer", "scope": "teambit.scope", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/scope/importer" }, "insights": { "name": "insights", "scope": "teambit.explorer", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/explorer/insights" }, "install": { "name": "install", "scope": "teambit.workspace", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/workspace/install" }, "internalize": { "name": "internalize", "scope": "teambit.component", - "version": "0.0.22", + "version": "0.0.23", "mainFile": "index.ts", "rootDir": "scopes/component/internalize" }, "ipc-events": { "name": "ipc-events", "scope": "teambit.harmony", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/harmony/ipc-events" }, "isolator": { "name": "isolator", "scope": "teambit.component", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/component/isolator" }, "issues": { "name": "issues", "scope": "teambit.component", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/component/issues" }, "jest": { "name": "jest", "scope": "teambit.defender", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/defender/jest" }, @@ -901,7 +901,7 @@ "lanes": { "name": "lanes", "scope": "teambit.lanes", - "version": "1.0.1042", + "version": "1.0.1043", "mainFile": "index.ts", "rootDir": "scopes/lanes/lanes" }, @@ -915,14 +915,14 @@ "linter": { "name": "linter", "scope": "teambit.defender", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/defender/linter" }, "lister": { "name": "lister", "scope": "teambit.component", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/component/lister" }, @@ -943,21 +943,21 @@ "mdx": { "name": "mdx", "scope": "teambit.mdx", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/mdx/mdx" }, "merge-lanes": { "name": "merge-lanes", "scope": "teambit.lanes", - "version": "1.0.1042", + "version": "1.0.1043", "mainFile": "index.ts", "rootDir": "scopes/lanes/merge-lanes" }, "merging": { "name": "merging", "scope": "teambit.component", - "version": "1.0.1026", + "version": "1.0.1027", "mainFile": "index.ts", "rootDir": "scopes/component/merging" }, @@ -1251,21 +1251,21 @@ "mover": { "name": "mover", "scope": "teambit.component", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/component/mover" }, "multi-compiler": { "name": "multi-compiler", "scope": "teambit.compilation", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/compilation/multi-compiler" }, "multi-tester": { "name": "multi-tester", "scope": "teambit.defender", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/defender/multi-tester" }, @@ -1286,28 +1286,28 @@ "new-component-helper": { "name": "new-component-helper", "scope": "teambit.component", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/component/new-component-helper" }, "node": { "name": "node", "scope": "teambit.harmony", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/harmony/node" }, "notifications": { "name": "notifications", "scope": "teambit.ui-foundation", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/ui-foundation/notifications/aspect" }, "objects": { "name": "objects", "scope": "teambit.scope", - "version": "0.0.530", + "version": "0.0.531", "mainFile": "index.ts", "rootDir": "scopes/scope/objects" }, @@ -1363,7 +1363,7 @@ "pkg": { "name": "pkg", "scope": "teambit.pkg", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/pkg/pkg" }, @@ -1384,14 +1384,14 @@ "pnpm": { "name": "pnpm", "scope": "teambit.dependencies", - "version": "1.0.1056", + "version": "1.0.1057", "mainFile": "index.ts", "rootDir": "scopes/dependencies/pnpm" }, "prettier": { "name": "prettier", "scope": "teambit.defender", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/defender/prettier" }, @@ -1405,7 +1405,7 @@ "preview": { "name": "preview", "scope": "teambit.preview", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/preview/preview" }, @@ -1419,35 +1419,35 @@ "pubsub": { "name": "pubsub", "scope": "teambit.harmony", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/harmony/pubsub" }, "react": { "name": "react", "scope": "teambit.react", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/react/react" }, "react-router": { "name": "react-router", "scope": "teambit.ui-foundation", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/ui-foundation/react-router/react-router" }, "readme": { "name": "readme", "scope": "teambit.mdx", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/mdx/readme" }, "refactoring": { "name": "refactoring", "scope": "teambit.component", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/component/refactoring" }, @@ -1468,14 +1468,14 @@ "remove": { "name": "remove", "scope": "teambit.component", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/component/remove" }, "renaming": { "name": "renaming", "scope": "teambit.component", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/component/renaming" }, @@ -1496,14 +1496,14 @@ "ripple": { "name": "ripple", "scope": "teambit.cloud", - "version": "0.0.105", + "version": "0.0.106", "mainFile": "index.ts", "rootDir": "scopes/cloud/ripple" }, "schema": { "name": "schema", "scope": "teambit.semantics", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/semantics/schema" }, @@ -1517,14 +1517,14 @@ "scripts": { "name": "scripts", "scope": "teambit.workspace", - "version": "0.0.242", + "version": "0.0.243", "mainFile": "index.ts", "rootDir": "scopes/workspace/scripts" }, "sidebar": { "name": "sidebar", "scope": "teambit.ui-foundation", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/ui-foundation/sidebar" }, @@ -1538,7 +1538,7 @@ "snapping": { "name": "snapping", "scope": "teambit.component", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/component/snapping" }, @@ -1552,14 +1552,14 @@ "stash": { "name": "stash", "scope": "teambit.component", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/component/stash" }, "status": { "name": "status", "scope": "teambit.component", - "version": "1.0.1046", + "version": "1.0.1047", "mainFile": "index.ts", "rootDir": "scopes/component/status" }, @@ -1636,14 +1636,14 @@ "teambit.scope/scope": { "name": "scope", "scope": "teambit.scope", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/scope/scope" }, "tester": { "name": "tester", "scope": "teambit.defender", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/defender/tester" }, @@ -1664,7 +1664,7 @@ "testing/mock-workspace": { "name": "testing/mock-workspace", "scope": "teambit.workspace", - "version": "0.0.199", + "version": "0.0.200", "mainFile": "index.ts", "rootDir": "scopes/workspace/testing/mock-workspace" }, @@ -1678,7 +1678,7 @@ "tracker": { "name": "tracker", "scope": "teambit.component", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/component/tracker" }, @@ -1699,14 +1699,14 @@ "typescript": { "name": "typescript", "scope": "teambit.typescript", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/typescript/typescript" }, "ui": { "name": "ui", "scope": "teambit.ui-foundation", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/ui-foundation/ui" }, @@ -2119,7 +2119,7 @@ "user-agent": { "name": "user-agent", "scope": "teambit.ui-foundation", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/ui-foundation/user-agent" }, @@ -2133,7 +2133,7 @@ "validator": { "name": "validator", "scope": "teambit.defender", - "version": "0.0.259", + "version": "0.0.260", "mainFile": "index.ts", "rootDir": "scopes/defender/validator" }, @@ -2147,28 +2147,28 @@ "version-history": { "name": "version-history", "scope": "teambit.scope", - "version": "0.0.815", + "version": "0.0.816", "mainFile": "index.ts", "rootDir": "scopes/scope/version-history" }, "vue-aspect": { "name": "vue-aspect", "scope": "teambit.vue", - "version": "0.0.389", + "version": "0.0.390", "mainFile": "index.ts", "rootDir": "scopes/vue/vue" }, "watcher": { "name": "watcher", "scope": "teambit.workspace", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/workspace/watcher" }, "webpack": { "name": "webpack", "scope": "teambit.webpack", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/webpack/webpack" }, @@ -2182,21 +2182,21 @@ "workspace": { "name": "workspace", "scope": "teambit.workspace", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/workspace/workspace" }, "workspace-config-files": { "name": "workspace-config-files", "scope": "teambit.workspace", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/workspace/workspace-config-files" }, "yarn": { "name": "yarn", "scope": "teambit.dependencies", - "version": "1.0.1023", + "version": "1.0.1024", "mainFile": "index.ts", "rootDir": "scopes/dependencies/yarn" }, diff --git a/scopes/harmony/cli-reference/cli-reference.docs.mdx b/scopes/harmony/cli-reference/cli-reference.docs.mdx index 0d63be1a1e3d..1ffe484a3353 100644 --- a/scopes/harmony/cli-reference/cli-reference.docs.mdx +++ b/scopes/harmony/cli-reference/cli-reference.docs.mdx @@ -1,4 +1,4 @@ --- -description: 'Bit command synopses. Bit version: 1.13.228' +description: 'Bit command synopses. Bit version: 1.13.229' labels: ['cli', 'mdx', 'docs'] --- From 0d90feadb0705a65c57b145b7e3e56620b0b0710 Mon Sep 17 00:00:00 2001 From: David First Date: Mon, 22 Jun 2026 13:20:58 -0400 Subject: [PATCH 11/13] fix(diff): avoid phantom peer-dependency change for component-level peers (#10437) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem `bit diff` printed a phantom `+ @` under `peerDependencies` for component-level peers, even though `bit status` reported no modification and re-tagging didn't help. ## Root cause Component-level peer dependencies are never persisted in the legacy `Version` model — `Version.fromComponent` doesn't serialize them; they live only in the dependency-resolver aspect data. So a component reconstructed from the model (`toConsumerComponent`) comes back with an empty legacy `peerDependencies` array, while the workspace component has it populated by the dependencies-loader. `bit diff` compared the legacy arrays directly → empty (model) vs `[peer]` (workspace) → phantom `+`. `bit status`/`tag` were unaffected because modification detection relies on the dep-resolver aspect data, which is identical on both sides. ## Fix In the diff layer, the `peerDependencies` field now derives component peers from the authoritative dependency-resolver aspect data (present on both the model and workspace component), unioned with the legacy array for backward compatibility. Symmetric data → no phantom, while real peer changes still show. ## Test Added an e2e test reproducing the scenario via `set-peer`; it fails without the fix (identical phantom output) and passes with it, plus asserts `bit status` shows the component as unmodified. --- .../component-diff/components-object-diff.ts | 43 +++++++++++++++++-- e2e/harmony/diff-component-peer.e2e.ts | 34 +++++++++++++++ 2 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 e2e/harmony/diff-component-peer.e2e.ts diff --git a/components/legacy/component-diff/components-object-diff.ts b/components/legacy/component-diff/components-object-diff.ts index 9d4dd08213ba..ba2ccd94cb42 100644 --- a/components/legacy/component-diff/components-object-diff.ts +++ b/components/legacy/component-diff/components-object-diff.ts @@ -1,7 +1,7 @@ import arrayDifference from 'array-difference'; import tempy from 'tempy'; import chalk from 'chalk'; -import type { ComponentIdList } from '@teambit/component-id'; +import { ComponentID, ComponentIdList } from '@teambit/component-id'; import Table from 'cli-table'; import normalize from 'normalize-path'; import diff from 'object-diff'; @@ -143,6 +143,37 @@ function componentToPrintableForDiffCommand(component: Component, verbose = fals return comp; } +const DEPENDENCY_RESOLVER_ASPECT_ID = 'teambit.dependencies/dependency-resolver'; + +/** + * Component-level peer dependencies are not persisted in the legacy `peerDependencies` array of the + * Version model (`Version.fromComponent` doesn't serialize them) - they live only in the + * dependency-resolver aspect data. As a result, a component reconstructed from the model has an + * empty legacy peer array, while the same component loaded from the workspace has it populated by + * the dependencies-loader. Comparing the legacy arrays directly therefore produces a phantom + * "peer added" diff even when nothing actually changed (and `bit status` correctly shows no diff). + * + * To avoid that, derive the component peers from the authoritative dependency-resolver aspect data + * (present on both the model and the workspace component) and union it with the legacy array (still + * relevant for old components tagged before peers were moved to the aspect data). + */ +function getComponentPeerDepsIds(component: Component): ComponentIdList { + const legacyPeerIds = component.depsIdsGroupedByType.peerDependencies; + const depResolverData = component.extensions?.findCoreExtension(DEPENDENCY_RESOLVER_ASPECT_ID)?.data; + const serializedDeps: Array<{ id: string; version?: string; __type?: string; lifecycle?: string }> = + depResolverData?.dependencies || []; + const peerComponentIds = serializedDeps + .filter((dep) => dep.__type === 'component' && dep.lifecycle === 'peer') + .map((dep) => { + // the `id` string may be versionless; `version` is the authoritative field. apply it so that + // peer version upgrades/downgrades are surfaced by the version comparison in + // `componentDependenciesOutput()`. + const componentId = ComponentID.fromString(dep.id); + return dep.version ? componentId.changeVersion(dep.version) : componentId; + }); + return ComponentIdList.uniqFromArray([...legacyPeerIds, ...peerComponentIds]); +} + export async function diffBetweenComponentsObjects( componentLeft: Component, componentRight: Component, @@ -260,8 +291,14 @@ export async function diffBetweenComponentsObjects( }; const componentDependenciesOutput = (fieldName: string): string | null => { - const dependenciesLeft: ComponentIdList = componentLeft.depsIdsGroupedByType[fieldName]; - const dependenciesRight: ComponentIdList = componentRight.depsIdsGroupedByType[fieldName]; + const dependenciesLeft: ComponentIdList = + fieldName === 'peerDependencies' + ? getComponentPeerDepsIds(componentLeft) + : componentLeft.depsIdsGroupedByType[fieldName]; + const dependenciesRight: ComponentIdList = + fieldName === 'peerDependencies' + ? getComponentPeerDepsIds(componentRight) + : componentRight.depsIdsGroupedByType[fieldName]; if (isEmpty(dependenciesLeft) && isEmpty(dependenciesRight)) return null; const diffsLeft = dependenciesLeft.reduce((acc, dependencyLeft) => { const dependencyRight = dependenciesRight.searchWithoutVersion(dependencyLeft); diff --git a/e2e/harmony/diff-component-peer.e2e.ts b/e2e/harmony/diff-component-peer.e2e.ts new file mode 100644 index 000000000000..b4d066a77a92 --- /dev/null +++ b/e2e/harmony/diff-component-peer.e2e.ts @@ -0,0 +1,34 @@ +import { expect } from 'chai'; +import { Helper } from '@teambit/legacy.e2e-helper'; + +// A component-level peer dependency (a bit-component set as a peer of another component) is stored +// only in the dependency-resolver aspect data, not in the legacy `peerDependencies` array of the +// Version model. As a result, a component reconstructed from the model has an empty legacy peer +// array, while the workspace component has it populated. `bit diff` used to compare the legacy +// arrays directly and therefore printed a phantom "peer added" line even though nothing changed +// (and `bit status` correctly reported no modification). +describe('bit diff with a component-level peer dependency', function () { + this.timeout(0); + let helper: Helper; + before(() => { + helper = new Helper(); + helper.scopeHelper.reInitWorkspace(); + helper.fixtures.populateComponents(2); + // mark comp2 as a peer. since comp1 depends on comp2, comp2 becomes a component-level peer + // dependency of comp1 (lifecycle=peer, type=component), which is stored only in the + // dependency-resolver aspect data and not in the legacy peerDependencies array. + helper.command.setPeer('comp2', '0'); + helper.command.install(); + helper.command.tagAllWithoutBuild(); + }); + after(() => { + helper.scopeHelper.destroy(); + }); + it('bit status should not show the component as modified', () => { + expect(helper.command.statusComponentIsModified(`${helper.scopes.remote}/comp1`)).to.be.false; + }); + it('bit diff should not show a phantom peer-dependency change', () => { + const diff = helper.command.diff('comp1'); + expect(diff).to.not.include('peerDependencies'); + }); +}); From 8a217c7d61dfc8e4018400f5aec45e0cf6661e2b Mon Sep 17 00:00:00 2001 From: Bit CI Date: Mon, 22 Jun 2026 18:01:31 +0000 Subject: [PATCH 12/13] bump teambit version to 1.13.231 [skip ci] --- .bitmap | 200 +++++++++--------- .../cli-reference/cli-reference.docs.mdx | 2 +- 2 files changed, 101 insertions(+), 101 deletions(-) diff --git a/.bitmap b/.bitmap index d944f9c60c69..6eda57e031e2 100644 --- a/.bitmap +++ b/.bitmap @@ -19,21 +19,21 @@ "api-reference": { "name": "api-reference", "scope": "teambit.api-reference", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/api-reference/api-reference" }, "api-server": { "name": "api-server", "scope": "teambit.harmony", - "version": "1.0.1054", + "version": "1.0.1055", "mainFile": "index.ts", "rootDir": "scopes/harmony/api-server" }, "application": { "name": "application", "scope": "teambit.harmony", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/harmony/application" }, @@ -47,7 +47,7 @@ "aspect": { "name": "aspect", "scope": "teambit.harmony", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/harmony/aspect" }, @@ -194,14 +194,14 @@ "aspect-loader": { "name": "aspect-loader", "scope": "teambit.harmony", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/harmony/aspect-loader" }, "babel": { "name": "babel", "scope": "teambit.compilation", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/compilation/babel" }, @@ -215,7 +215,7 @@ "bit": { "name": "bit", "scope": "teambit.harmony", - "version": "1.13.230", + "version": "1.13.231", "mainFile": "index.ts", "rootDir": "scopes/harmony/bit" }, @@ -229,7 +229,7 @@ "builder": { "name": "builder", "scope": "teambit.pipelines", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/pipelines/builder" }, @@ -243,7 +243,7 @@ "bundler": { "name": "bundler", "scope": "teambit.compilation", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/compilation/bundler" }, @@ -257,21 +257,21 @@ "changelog": { "name": "changelog", "scope": "teambit.component", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/component/changelog" }, "checkout": { "name": "checkout", "scope": "teambit.component", - "version": "1.0.1025", + "version": "1.0.1026", "mainFile": "index.ts", "rootDir": "scopes/component/checkout" }, "ci": { "name": "ci", "scope": "teambit.git", - "version": "1.0.410", + "version": "1.0.411", "mainFile": "index.ts", "rootDir": "scopes/git/ci" }, @@ -320,21 +320,21 @@ "cloud": { "name": "cloud", "scope": "teambit.cloud", - "version": "0.0.1320", + "version": "0.0.1321", "mainFile": "index.ts", "rootDir": "scopes/cloud/cloud" }, "code": { "name": "code", "scope": "teambit.component", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/component/code" }, "command-bar": { "name": "command-bar", "scope": "teambit.explorer", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/explorer/command-bar" }, @@ -348,21 +348,21 @@ "compiler": { "name": "compiler", "scope": "teambit.compilation", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/compilation/compiler" }, "component": { "name": "component", "scope": "teambit.component", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/component/component" }, "component-compare": { "name": "component-compare", "scope": "teambit.component", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/component/component-compare" }, @@ -376,7 +376,7 @@ "component-diff": { "name": "component-diff", "scope": "teambit.legacy", - "version": "0.0.184", + "version": "0.0.185", "mainFile": "index.ts", "rootDir": "components/legacy/component-diff" }, @@ -397,7 +397,7 @@ "component-log": { "name": "component-log", "scope": "teambit.component", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/component/component-log" }, @@ -411,21 +411,21 @@ "component-sizer": { "name": "component-sizer", "scope": "teambit.component", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/component/component-sizer" }, "component-tree": { "name": "component-tree", "scope": "teambit.component", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/component/component-tree" }, "component-writer": { "name": "component-writer", "scope": "teambit.component", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/component/component-writer" }, @@ -439,7 +439,7 @@ "compositions": { "name": "compositions", "scope": "teambit.compositions", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/compositions/compositions" }, @@ -453,7 +453,7 @@ "config-merger": { "name": "config-merger", "scope": "teambit.workspace", - "version": "0.0.891", + "version": "0.0.892", "mainFile": "index.ts", "rootDir": "scopes/workspace/config-merger" }, @@ -495,7 +495,7 @@ "content/cli-reference": { "name": "content/cli-reference", "scope": "teambit.harmony", - "version": "2.0.1090", + "version": "2.0.1091", "mainFile": "index.ts", "rootDir": "scopes/harmony/cli-reference" }, @@ -509,7 +509,7 @@ "dependencies": { "name": "dependencies", "scope": "teambit.dependencies", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/dependencies/dependencies" }, @@ -523,28 +523,28 @@ "dependency-resolver": { "name": "dependency-resolver", "scope": "teambit.dependencies", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/dependencies/dependency-resolver" }, "deprecation": { "name": "deprecation", "scope": "teambit.component", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/component/deprecation" }, "dev-files": { "name": "dev-files", "scope": "teambit.component", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/component/dev-files" }, "diagnostic": { "name": "diagnostic", "scope": "teambit.harmony", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/harmony/diagnostic" }, @@ -558,14 +558,14 @@ "docs": { "name": "docs", "scope": "teambit.docs", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/docs/docs" }, "doctor": { "name": "doctor", "scope": "teambit.harmony", - "version": "0.0.709", + "version": "0.0.710", "mainFile": "index.ts", "rootDir": "scopes/harmony/doctor" }, @@ -579,7 +579,7 @@ "eject": { "name": "eject", "scope": "teambit.workspace", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/workspace/eject" }, @@ -607,21 +607,21 @@ "env": { "name": "env", "scope": "teambit.envs", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/envs/env" }, "envs": { "name": "envs", "scope": "teambit.envs", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/envs/envs" }, "eslint": { "name": "eslint", "scope": "teambit.defender", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/defender/eslint" }, @@ -642,7 +642,7 @@ "export": { "name": "export", "scope": "teambit.scope", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/scope/export" }, @@ -663,14 +663,14 @@ "forking": { "name": "forking", "scope": "teambit.component", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/component/forking" }, "formatter": { "name": "formatter", "scope": "teambit.defender", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/defender/formatter" }, @@ -726,7 +726,7 @@ "generator": { "name": "generator", "scope": "teambit.generator", - "version": "1.0.1025", + "version": "1.0.1026", "mainFile": "index.ts", "rootDir": "scopes/generator/generator" }, @@ -740,7 +740,7 @@ "git": { "name": "git", "scope": "teambit.git", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/git/git" }, @@ -754,21 +754,21 @@ "graph": { "name": "graph", "scope": "teambit.component", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/component/graph" }, "graphql": { "name": "graphql", "scope": "teambit.harmony", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/harmony/graphql" }, "harmony-ui-app": { "name": "harmony-ui-app", "scope": "teambit.ui-foundation", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/ui-foundation/harmony-ui-app/harmony-ui-app" }, @@ -831,63 +831,63 @@ "host-initializer": { "name": "host-initializer", "scope": "teambit.harmony", - "version": "0.0.737", + "version": "0.0.738", "mainFile": "index.ts", "rootDir": "scopes/harmony/host-initializer" }, "importer": { "name": "importer", "scope": "teambit.scope", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/scope/importer" }, "insights": { "name": "insights", "scope": "teambit.explorer", - "version": "1.0.1025", + "version": "1.0.1026", "mainFile": "index.ts", "rootDir": "scopes/explorer/insights" }, "install": { "name": "install", "scope": "teambit.workspace", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/workspace/install" }, "internalize": { "name": "internalize", "scope": "teambit.component", - "version": "0.0.23", + "version": "0.0.24", "mainFile": "index.ts", "rootDir": "scopes/component/internalize" }, "ipc-events": { "name": "ipc-events", "scope": "teambit.harmony", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/harmony/ipc-events" }, "isolator": { "name": "isolator", "scope": "teambit.component", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/component/isolator" }, "issues": { "name": "issues", "scope": "teambit.component", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/component/issues" }, "jest": { "name": "jest", "scope": "teambit.defender", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/defender/jest" }, @@ -901,7 +901,7 @@ "lanes": { "name": "lanes", "scope": "teambit.lanes", - "version": "1.0.1043", + "version": "1.0.1044", "mainFile": "index.ts", "rootDir": "scopes/lanes/lanes" }, @@ -915,14 +915,14 @@ "linter": { "name": "linter", "scope": "teambit.defender", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/defender/linter" }, "lister": { "name": "lister", "scope": "teambit.component", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/component/lister" }, @@ -943,21 +943,21 @@ "mdx": { "name": "mdx", "scope": "teambit.mdx", - "version": "1.0.1025", + "version": "1.0.1026", "mainFile": "index.ts", "rootDir": "scopes/mdx/mdx" }, "merge-lanes": { "name": "merge-lanes", "scope": "teambit.lanes", - "version": "1.0.1043", + "version": "1.0.1044", "mainFile": "index.ts", "rootDir": "scopes/lanes/merge-lanes" }, "merging": { "name": "merging", "scope": "teambit.component", - "version": "1.0.1027", + "version": "1.0.1028", "mainFile": "index.ts", "rootDir": "scopes/component/merging" }, @@ -1055,7 +1055,7 @@ "modules/diff": { "name": "modules/diff", "scope": "teambit.lanes", - "version": "0.0.632", + "version": "0.0.633", "mainFile": "index.ts", "rootDir": "scopes/lanes/diff" }, @@ -1251,21 +1251,21 @@ "mover": { "name": "mover", "scope": "teambit.component", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/component/mover" }, "multi-compiler": { "name": "multi-compiler", "scope": "teambit.compilation", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/compilation/multi-compiler" }, "multi-tester": { "name": "multi-tester", "scope": "teambit.defender", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/defender/multi-tester" }, @@ -1286,28 +1286,28 @@ "new-component-helper": { "name": "new-component-helper", "scope": "teambit.component", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/component/new-component-helper" }, "node": { "name": "node", "scope": "teambit.harmony", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/harmony/node" }, "notifications": { "name": "notifications", "scope": "teambit.ui-foundation", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/ui-foundation/notifications/aspect" }, "objects": { "name": "objects", "scope": "teambit.scope", - "version": "0.0.531", + "version": "0.0.532", "mainFile": "index.ts", "rootDir": "scopes/scope/objects" }, @@ -1363,7 +1363,7 @@ "pkg": { "name": "pkg", "scope": "teambit.pkg", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/pkg/pkg" }, @@ -1384,14 +1384,14 @@ "pnpm": { "name": "pnpm", "scope": "teambit.dependencies", - "version": "1.0.1057", + "version": "1.0.1058", "mainFile": "index.ts", "rootDir": "scopes/dependencies/pnpm" }, "prettier": { "name": "prettier", "scope": "teambit.defender", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/defender/prettier" }, @@ -1405,7 +1405,7 @@ "preview": { "name": "preview", "scope": "teambit.preview", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/preview/preview" }, @@ -1419,35 +1419,35 @@ "pubsub": { "name": "pubsub", "scope": "teambit.harmony", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/harmony/pubsub" }, "react": { "name": "react", "scope": "teambit.react", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/react/react" }, "react-router": { "name": "react-router", "scope": "teambit.ui-foundation", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/ui-foundation/react-router/react-router" }, "readme": { "name": "readme", "scope": "teambit.mdx", - "version": "1.0.1025", + "version": "1.0.1026", "mainFile": "index.ts", "rootDir": "scopes/mdx/readme" }, "refactoring": { "name": "refactoring", "scope": "teambit.component", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/component/refactoring" }, @@ -1468,14 +1468,14 @@ "remove": { "name": "remove", "scope": "teambit.component", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/component/remove" }, "renaming": { "name": "renaming", "scope": "teambit.component", - "version": "1.0.1025", + "version": "1.0.1026", "mainFile": "index.ts", "rootDir": "scopes/component/renaming" }, @@ -1496,14 +1496,14 @@ "ripple": { "name": "ripple", "scope": "teambit.cloud", - "version": "0.0.106", + "version": "0.0.107", "mainFile": "index.ts", "rootDir": "scopes/cloud/ripple" }, "schema": { "name": "schema", "scope": "teambit.semantics", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/semantics/schema" }, @@ -1517,14 +1517,14 @@ "scripts": { "name": "scripts", "scope": "teambit.workspace", - "version": "0.0.243", + "version": "0.0.244", "mainFile": "index.ts", "rootDir": "scopes/workspace/scripts" }, "sidebar": { "name": "sidebar", "scope": "teambit.ui-foundation", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/ui-foundation/sidebar" }, @@ -1538,7 +1538,7 @@ "snapping": { "name": "snapping", "scope": "teambit.component", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/component/snapping" }, @@ -1552,14 +1552,14 @@ "stash": { "name": "stash", "scope": "teambit.component", - "version": "1.0.1025", + "version": "1.0.1026", "mainFile": "index.ts", "rootDir": "scopes/component/stash" }, "status": { "name": "status", "scope": "teambit.component", - "version": "1.0.1047", + "version": "1.0.1048", "mainFile": "index.ts", "rootDir": "scopes/component/status" }, @@ -1636,14 +1636,14 @@ "teambit.scope/scope": { "name": "scope", "scope": "teambit.scope", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/scope/scope" }, "tester": { "name": "tester", "scope": "teambit.defender", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/defender/tester" }, @@ -1678,7 +1678,7 @@ "tracker": { "name": "tracker", "scope": "teambit.component", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/component/tracker" }, @@ -1699,14 +1699,14 @@ "typescript": { "name": "typescript", "scope": "teambit.typescript", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/typescript/typescript" }, "ui": { "name": "ui", "scope": "teambit.ui-foundation", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/ui-foundation/ui" }, @@ -2119,7 +2119,7 @@ "user-agent": { "name": "user-agent", "scope": "teambit.ui-foundation", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/ui-foundation/user-agent" }, @@ -2133,7 +2133,7 @@ "validator": { "name": "validator", "scope": "teambit.defender", - "version": "0.0.260", + "version": "0.0.261", "mainFile": "index.ts", "rootDir": "scopes/defender/validator" }, @@ -2147,28 +2147,28 @@ "version-history": { "name": "version-history", "scope": "teambit.scope", - "version": "0.0.816", + "version": "0.0.817", "mainFile": "index.ts", "rootDir": "scopes/scope/version-history" }, "vue-aspect": { "name": "vue-aspect", "scope": "teambit.vue", - "version": "0.0.390", + "version": "0.0.391", "mainFile": "index.ts", "rootDir": "scopes/vue/vue" }, "watcher": { "name": "watcher", "scope": "teambit.workspace", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/workspace/watcher" }, "webpack": { "name": "webpack", "scope": "teambit.webpack", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/webpack/webpack" }, @@ -2182,21 +2182,21 @@ "workspace": { "name": "workspace", "scope": "teambit.workspace", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/workspace/workspace" }, "workspace-config-files": { "name": "workspace-config-files", "scope": "teambit.workspace", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/workspace/workspace-config-files" }, "yarn": { "name": "yarn", "scope": "teambit.dependencies", - "version": "1.0.1024", + "version": "1.0.1025", "mainFile": "index.ts", "rootDir": "scopes/dependencies/yarn" }, diff --git a/scopes/harmony/cli-reference/cli-reference.docs.mdx b/scopes/harmony/cli-reference/cli-reference.docs.mdx index 1ffe484a3353..7ae105383fd6 100644 --- a/scopes/harmony/cli-reference/cli-reference.docs.mdx +++ b/scopes/harmony/cli-reference/cli-reference.docs.mdx @@ -1,4 +1,4 @@ --- -description: 'Bit command synopses. Bit version: 1.13.229' +description: 'Bit command synopses. Bit version: 1.13.230' labels: ['cli', 'mdx', 'docs'] --- From fe8470eada849b88ccad465edbe95ebc9e8c2993 Mon Sep 17 00:00:00 2001 From: David First Date: Mon, 22 Jun 2026 15:26:39 -0400 Subject: [PATCH 13/13] refactor(install): simplify unpublished-snap culprit matching match the failing dep by its globally-unique snap hash alone (drop the redundant package-name substring check), and accumulate culprits directly into the exception's shape (drop the intermediate Map + re-projection). behavior unchanged. --- .../workspace/install/install.main.runtime.ts | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/scopes/workspace/install/install.main.runtime.ts b/scopes/workspace/install/install.main.runtime.ts index 77bc67f0d252..f43999444eb6 100644 --- a/scopes/workspace/install/install.main.runtime.ts +++ b/scopes/workspace/install/install.main.runtime.ts @@ -69,6 +69,7 @@ import type { UiMain } from '@teambit/ui'; import { UIAspect } from '@teambit/ui'; import { EXTERNAL_PM_POSTINSTALL_SCRIPT } from '@teambit/host-initializer'; import { DependencyTypeNotSupportedInPolicy, UnpublishedComponentDependency } from './exceptions'; +import type { UnpublishedSnapDependency } from './exceptions'; import { InstallAspect } from './install.aspect'; import { pickOutdatedPkgs } from './pick-outdated-pkgs'; import { LinkCommand } from './link'; @@ -556,29 +557,24 @@ export class InstallMain { // checked-out components are linked from source, so they're never fetched from the registry. const workspaceIds = this.workspace.listIds(); - const unpublished = new Map }>(); + const unpublished = new Map(); for (const component of components) { for (const dep of this.dependencyResolver.getComponentDependencies(component)) { const depId = dep.componentId; if (!depId.version || !isSnap(depId.version)) continue; if (workspaceIds.hasWithoutVersion(depId)) continue; - // only the dependency the package manager actually failed on: its package + snap version appear in the - // error (the manifest version is `0.0.0-`, so the raw hash is a substring). - if (!errMessage.includes(dep.packageName) || !errMessage.includes(depId.version)) continue; + // pinpoint the exact dependency the package manager failed on: its snap hash (globally unique) appears + // verbatim in the error (the manifest pins `0.0.0-`, so the raw hash is a substring). + if (!errMessage.includes(depId.version)) continue; const key = depId.toStringWithoutVersion(); - const entry = unpublished.get(key) ?? { id: depId, dependents: new Set() }; - entry.dependents.add(component.id.toStringWithoutVersion()); - unpublished.set(key, entry); + const dependent = component.id.toStringWithoutVersion(); + const entry = unpublished.get(key); + if (entry) entry.dependents.push(dependent); + else unpublished.set(key, { id: key, version: depId.version, dependents: [dependent] }); } } if (!unpublished.size) return err; - return new UnpublishedComponentDependency( - [...unpublished.values()].map(({ id, dependents }) => ({ - id: id.toStringWithoutVersion(), - version: id.version as string, - dependents: [...dependents], - })) - ); + return new UnpublishedComponentDependency([...unpublished.values()]); } /**