Skip to content

Commit 767bebd

Browse files
committed
fix(install): resolve '+' version in env.jsonc for env capsules
When generating .bit_roots package.json for env capsules, the '+' version placeholder from env.jsonc was passed directly to pnpm, causing "isn't supported by any available resolver" errors. The fix resolves '+' by looking up the already-resolved version from the env component's dependencies (via getDependencies), leveraging the existing resolution infrastructure in apply-overrides.
1 parent 9cc57c1 commit 767bebd

File tree

3 files changed

+70
-2
lines changed

3 files changed

+70
-2
lines changed

e2e/harmony/lanes/lane-import.e2e.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,56 @@ describe('bit lane import operations', function () {
312312
});
313313
});
314314

315+
describe('lane import with env using "+" version for component peer dependency (snaps)', () => {
316+
before(() => {
317+
helper.scopeHelper.setWorkspaceWithRemoteScope();
318+
// Create comp1 which will be used as a peer dependency in the env
319+
helper.fs.outputFile('comp1/index.js', 'module.exports = function() { return "comp1"; };');
320+
helper.command.addComponent('comp1');
321+
// Create an env with "+" version for comp1 in the peers
322+
helper.env.setEmptyEnv();
323+
const comp1PkgName = helper.general.getPackageNameByCompName('comp1', false);
324+
helper.fs.outputFile(
325+
'empty-env/env.jsonc',
326+
`{
327+
"policy": {
328+
"peers": [
329+
{
330+
"name": "${comp1PkgName}",
331+
"version": "+",
332+
"supportedRange": "^0.0.1"
333+
}
334+
]
335+
}
336+
}
337+
`
338+
);
339+
// Create comp2 (independent) that uses the env
340+
helper.fs.outputFile('comp2/index.js', 'module.exports = function() { return "comp2"; };');
341+
helper.command.addComponent('comp2');
342+
helper.command.setEnv('comp2', 'empty-env');
343+
// Create a lane and snap (not tag) - this creates hash-based versions
344+
helper.command.createLane('dev');
345+
helper.command.snapAllComponentsWithoutBuild();
346+
helper.command.export();
347+
});
348+
// This test verifies the fix for the bug where "+" version resolution
349+
// was not properly resolving to the actual version from workspace/bitmap,
350+
// causing pnpm to receive "+" as a version which it cannot handle
351+
it('should resolve "+" version and not pass it directly to pnpm', () => {
352+
helper.scopeHelper.reInitWorkspace();
353+
helper.scopeHelper.addRemoteScope();
354+
// Import the component using the env - this triggers installation
355+
// where the "+" version must be resolved to actual version
356+
const output = helper.command.importLane('dev', `--pattern ${helper.scopes.remote}/comp2`);
357+
// The "+" should be resolved to actual version, not passed to pnpm directly
358+
// The specific error we're fixing is "isn't supported by any available resolver"
359+
// which happens when "+" is passed as a version to pnpm
360+
expect(output).to.not.have.string("isn't supported by any available resolver");
361+
expect(output).to.not.have.string('@+'); // No "@+" should appear in error messages
362+
});
363+
});
364+
315365
describe('getting new components from the lane', () => {
316366
let firstWorkspaceAfterExport: string;
317367
let secondWorkspace: string;

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,8 @@ export class WorkspaceManifestFactory {
258258
const currentDeps = this.dependencyResolver.getDependencies(component);
259259
const found = currentDeps.findByPkgNameOrCompId(name);
260260
// If not found, use '*' as fallback
261-
return [name, found?.version || '*'];
261+
// Use snapToSemver to convert raw hash versions to valid semver format (0.0.0-{hash})
262+
return [name, found?.version ? snapToSemver(found.version) : '*'];
262263
});
263264
return fromPairs(resolved);
264265
}

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

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -951,10 +951,27 @@ export class InstallMain {
951951
private async _getEnvDependencies(envId: ComponentID): Promise<Record<string, string>> {
952952
const policy = await this.dependencyResolver.getEnvPolicyFromEnvId(envId);
953953
if (!policy) return {};
954+
955+
// Get the env component to access its resolved dependencies
956+
// The "+" versions are resolved during dependency detection in apply-overrides.resolveEnvPeerDepVersion
957+
const envComponent = await this.envs.getEnvComponentByEnvId(envId.toString(), envId.toString());
958+
const envDeps = envComponent ? this.dependencyResolver.getDependencies(envComponent) : undefined;
959+
954960
return Object.fromEntries(
955961
policy.selfPolicy.entries
956962
.filter(({ force, value }) => force && value.version !== '-')
957-
.map(({ dependencyId, value }) => [dependencyId, value.version])
963+
.map(({ dependencyId, value }) => {
964+
let version = value.version;
965+
// Resolve "+" version placeholders by looking up the already resolved version
966+
// from the env component's dependencies (resolved in apply-overrides.resolveEnvPeerDepVersion)
967+
if (version === '+' && envDeps) {
968+
const found = envDeps.findByPkgNameOrCompId(dependencyId);
969+
version = found?.version ? snapToSemver(found.version) : '*';
970+
} else if (version === '+') {
971+
version = '*';
972+
}
973+
return [dependencyId, version];
974+
})
958975
);
959976
}
960977

0 commit comments

Comments
 (0)