diff --git a/.changeset/tough-jars-study.md b/.changeset/tough-jars-study.md new file mode 100644 index 0000000000..3bdb07658a --- /dev/null +++ b/.changeset/tough-jars-study.md @@ -0,0 +1,17 @@ +--- +"@pnpm/hooks.read-package-hook": minor +"pnpm": minor +--- + +Added the ability for `overrides` to remove dependencies by specifying `"-"` as the field value [#8572](https://github.com/pnpm/pnpm/issues/8572). For example, to remove `lodash` from the dependencies, use this configuration in `package.json`: + +```json +{ + "pnpm": { + "overrides": { + "lodash": "-" + } + } +} +``` + diff --git a/hooks/read-package-hook/src/createVersionsOverrider.ts b/hooks/read-package-hook/src/createVersionsOverrider.ts index abfc6cae75..789e86ab6b 100644 --- a/hooks/read-package-hook/src/createVersionsOverrider.ts +++ b/hooks/read-package-hook/src/createVersionsOverrider.ts @@ -97,6 +97,11 @@ function overrideDeps ( ) if (!versionOverride) continue + if (versionOverride.newPref === '-') { + delete deps[versionOverride.targetPkg.name] + continue + } + if (versionOverride.localTarget) { deps[versionOverride.targetPkg.name] = `${versionOverride.localTarget.protocol}${resolveLocalOverride(versionOverride.localTarget, dir)}` continue diff --git a/hooks/read-package-hook/test/createVersionOverrider.test.ts b/hooks/read-package-hook/test/createVersionOverrider.test.ts index 88dcc4ea59..e25002c692 100644 --- a/hooks/read-package-hook/test/createVersionOverrider.test.ts +++ b/hooks/read-package-hook/test/createVersionOverrider.test.ts @@ -1,5 +1,5 @@ import path from 'path' -import { createVersionsOverrider } from '../lib/createVersionsOverrider' +import { createVersionsOverrider } from '../src/createVersionsOverrider' test('createVersionsOverrider() matches sub-ranges', () => { const overrider = createVersionsOverrider([ @@ -615,3 +615,77 @@ test('createVersionsOverrider() overrides peerDependencies of another dependency }, }) }) + +test('createVersionOverrider() removes dependencies', () => { + const overrider = createVersionsOverrider([ + { + targetPkg: { + name: 'foo', + }, + newPref: '-', + }, + { + parentPkg: { + name: 'bar', + }, + targetPkg: { + name: 'baz', + }, + newPref: '-', + }, + { + targetPkg: { + name: 'qux', + pref: '2', + }, + newPref: '-', + }, + ], process.cwd()) + expect( + overrider({ + dependencies: { + foo: '0.1.2', + bar: '1.2.3', + baz: '1.0.0', + qux: '2.1.0', + }, + }) + ).toStrictEqual({ + dependencies: { + bar: '1.2.3', + baz: '1.0.0', + }, + }) + expect( + overrider({ + name: 'bar', + dependencies: { + foo: '0.1.2', + bar: '1.2.3', + baz: '1.0.0', + qux: '2.1.0', + }, + }) + ).toStrictEqual({ + name: expect.anything(), + dependencies: { + bar: '1.2.3', + }, + }) + expect( + overrider({ + dependencies: { + foo: '0.1.2', + bar: '1.2.3', + baz: '1.0.0', + qux: '3.2.1', + }, + }) + ).toStrictEqual({ + dependencies: { + bar: '1.2.3', + baz: '1.0.0', + qux: '3.2.1', + }, + }) +}) diff --git a/pkg-manager/core/test/install/overrides.ts b/pkg-manager/core/test/install/overrides.ts index 8746508c39..dea4c41c05 100644 --- a/pkg-manager/core/test/install/overrides.ts +++ b/pkg-manager/core/test/install/overrides.ts @@ -2,15 +2,13 @@ import path from 'path' import fs from 'fs' import { sync as readYamlFile } from 'read-yaml-file' import { PnpmError } from '@pnpm/error' -import { prepareEmpty, preparePackages } from '@pnpm/prepare' +import { prepare, prepareEmpty, preparePackages } from '@pnpm/prepare' import { addDistTag } from '@pnpm/registry-mock' import { WANTED_LOCKFILE } from '@pnpm/constants' import { type MutatedProject, type ProjectOptions, addDependenciesToPackage, mutateModulesInSingleProject, mutateModules } from '@pnpm/core' import { type LockfileFileV9 } from '@pnpm/lockfile.types' import { type ProjectRootDir, type ProjectManifest } from '@pnpm/types' -import { - testDefaults, -} from '../utils' +import { testDefaults } from '../utils' test('versions are replaced with versions specified through overrides option', async () => { const project = prepareEmpty() @@ -252,3 +250,47 @@ test('overrides with local file and link specs', async () => { expect(fs.realpathSync(path.join(indirectPrefix, '@pnpm.e2e/pkg-c'))).toBe(path.resolve('overrides/pkg')) expect(fs.realpathSync(path.join(indirectPrefix, '@pnpm.e2e/pkg-d'))).toBe(path.resolve('overrides/pkg')) }) + +test('overrides remove dependencies', async () => { + const manifest: ProjectManifest = { + dependencies: { + '@pnpm.e2e/pkg-with-good-optional': '1.0.0', + }, + pnpm: { + overrides: { + '@pnpm.e2e/pkg-with-good-optional>is-positive': '-', + }, + }, + } + + const project = prepare(manifest) + + await mutateModulesInSingleProject({ + manifest, + mutation: 'install', + rootDir: process.cwd() as ProjectRootDir, + }, testDefaults({ + overrides: manifest.pnpm?.overrides, + })) + + // assert that @pnpm.e2e/pkg-with-good-optional@1.0.0 depends on is-positive@1.0.0 + expect(project.requireModule('@pnpm.e2e/pkg-with-good-optional/package.json')).toMatchObject({ + version: '1.0.0', + optionalDependencies: { + 'is-positive': '1.0.0', + }, + }) + + // yet because of the overrides, it installs @pnpm.e2e/pkg-with-good-optional@1.0.0 without is-positive@1.0.0 + const lockfile = project.readLockfile() + expect(lockfile.snapshots).toHaveProperty(['@pnpm.e2e/pkg-with-good-optional@1.0.0']) + expect(lockfile.snapshots['@pnpm.e2e/pkg-with-good-optional@1.0.0']).not.toHaveProperty(['optionalDependencies', 'is-positive']) + expect(lockfile.snapshots).not.toHaveProperty(['is-positive@1.0.0']) + expect(lockfile.packages).toHaveProperty(['@pnpm.e2e/pkg-with-good-optional@1.0.0']) + expect(lockfile.packages).not.toHaveProperty(['is-positive@1.0.0']) + expect( + fs.existsSync('node_modules/.pnpm/@pnpm.e2e+pkg-with-good-optional@1.0.0/node_modules/is-positive') + ).toBe(false) + const currentLockfile = project.readCurrentLockfile() + expect(lockfile.overrides).toStrictEqual(currentLockfile.overrides) +})