diff --git a/.changeset/good-rings-carry.md b/.changeset/good-rings-carry.md new file mode 100644 index 0000000000..daf697176e --- /dev/null +++ b/.changeset/good-rings-carry.md @@ -0,0 +1,5 @@ +--- +"supi": patch +--- + +Don't remove skipped optional dependencies from the current lockfile on partial installation. diff --git a/packages/supi/src/install/link.ts b/packages/supi/src/install/link.ts index b58e691d78..c5c9e88430 100644 --- a/packages/supi/src/install/link.ts +++ b/packages/supi/src/install/link.ts @@ -296,6 +296,7 @@ export default async function linkPackages ( Object.keys(projects), { ...filterOpts, failOnMissingDependencies: false, + skipped: new Set(), } ) } else if ( diff --git a/packages/supi/test/install/optionalDependencies.ts b/packages/supi/test/install/optionalDependencies.ts index 866bb60c6c..8028a8c345 100644 --- a/packages/supi/test/install/optionalDependencies.ts +++ b/packages/supi/test/install/optionalDependencies.ts @@ -1,4 +1,5 @@ import { WANTED_LOCKFILE } from '@pnpm/constants' +import { Lockfile } from '@pnpm/lockfile-file' import { prepareEmpty, preparePackages } from '@pnpm/prepare' import { REGISTRY_MOCK_PORT } from '@pnpm/registry-mock' import rimraf = require('@zkochan/rimraf') @@ -8,7 +9,12 @@ import exists = require('path-exists') import R = require('ramda') import readYamlFile from 'read-yaml-file' import sinon = require('sinon') -import { addDependenciesToPackage, install, mutateModules } from 'supi' +import { + addDependenciesToPackage, + install, + MutatedProject, + mutateModules, +} from 'supi' import tape = require('tape') import promisifyTape from 'tape-promise' import { testDefaults } from '../utils' @@ -202,6 +208,69 @@ test('don\'t skip optional dependency that does not support the current OS when await project.storeHas('not-compatible-with-any-os', '1.0.0') }) +// Covers https://github.com/pnpm/pnpm/issues/2636 +test('optional subdependency is not removed from current lockfile when new dependency added', async (t: tape.Test) => { + const projects = preparePackages(t, [ + { + location: 'project-1', + package: { name: 'project-1' }, + }, + { + location: 'project-2', + package: { name: 'project-2' }, + }, + ]) + + const importers: MutatedProject[] = [ + { + buildIndex: 0, + manifest: { + name: 'project-1', + version: '1.0.0', + + dependencies: { + 'pkg-with-optional': '1.0.0', + }, + }, + mutation: 'install', + rootDir: path.resolve('project-1'), + }, + { + buildIndex: 0, + manifest: { + name: 'project-2', + version: '1.0.0', + }, + mutation: 'install', + rootDir: path.resolve('project-2'), + }, + ] + await mutateModules(importers, + await testDefaults({ hoistPattern: ['*'] }) + ) + + { + const modulesInfo = await readYamlFile<{ skipped: string[] }>(path.join('node_modules', '.modules.yaml')) + t.deepEqual(modulesInfo.skipped, ['/dep-of-optional-pkg/1.0.0', '/not-compatible-with-any-os/1.0.0'], 'optional subdep skipped') + + const currentLockfile = await readYamlFile(path.resolve('node_modules/.pnpm/lock.yaml')) + t.ok(currentLockfile.packages?.['/not-compatible-with-any-os/1.0.0']) + } + + await mutateModules([ + { + ...importers[0], + dependencySelectors: ['is-positive@1.0.0'], + mutation: 'installSome', + }, + ], await testDefaults({ fastUnpack: false, hoistPattern: ['*'] })) + + { + const currentLockfile = await readYamlFile(path.resolve('node_modules/.pnpm/lock.yaml')) + t.ok(currentLockfile.packages?.['/not-compatible-with-any-os/1.0.0']) + } +}) + test('optional subdependency is skipped', async (t: tape.Test) => { const project = prepareEmpty(t) const reporter = sinon.spy()