diff --git a/.changeset/long-brooms-look.md b/.changeset/long-brooms-look.md new file mode 100644 index 0000000000..ffbcf4ddec --- /dev/null +++ b/.changeset/long-brooms-look.md @@ -0,0 +1,18 @@ +--- +"supi": patch +--- + +Allow to specify the overriden dependency's parent package. + +For example, if `foo` should be overriden only in dependencies of bar v2, this configuration may be used: + +```json +{ + ... + "pnpm": { + "overriden": { + "bar@2>foo": "1.0.0" + } + } +} +``` diff --git a/packages/supi/src/install/createVersionsOverrider.ts b/packages/supi/src/install/createVersionsOverrider.ts index be5e6a2bbb..59e630abdf 100644 --- a/packages/supi/src/install/createVersionsOverrider.ts +++ b/packages/supi/src/install/createVersionsOverrider.ts @@ -1,20 +1,42 @@ import { Dependencies, PackageManifest, ReadPackageHook } from '@pnpm/types' import parseWantedDependency from '@pnpm/parse-wanted-dependency' +import semver = require('semver') export default function (overrides: Record): ReadPackageHook { - const versionOverrides = Object.entries(overrides) - .map(([rawWantedDependency, newPref]) => ({ - newPref, - wantedDependency: parseWantedDependency(rawWantedDependency), - } as VersionOverride)) + const genericVersionOverrides = [] as VersionOverride[] + const versionOverrides = [] as VersionOverrideWithParent[] + Object.entries(overrides) + .forEach(([selector, newPref]) => { + if (selector.includes('>')) { + const [parentSelector, childSelector] = selector.split('>') + versionOverrides.push({ + newPref, + parentWantedDependency: parseWantedDependency(parentSelector), + wantedDependency: parseWantedDependency(childSelector), + } as VersionOverrideWithParent) + return + } + genericVersionOverrides.push({ + newPref, + wantedDependency: parseWantedDependency(selector), + } as VersionOverride) + }) return ((pkg: PackageManifest) => { - if (pkg.dependencies) overrideDeps(versionOverrides, pkg.dependencies) - if (pkg.optionalDependencies) overrideDeps(versionOverrides, pkg.optionalDependencies) + overrideDepsOfPkg(pkg, versionOverrides.filter(({ parentWantedDependency }) => { + return parentWantedDependency.alias === pkg.name && ( + !parentWantedDependency.pref || semver.satisfies(pkg.version, parentWantedDependency.pref) + ) + })) + overrideDepsOfPkg(pkg, genericVersionOverrides) return pkg }) as ReadPackageHook } interface VersionOverride { + parentWantedDependency?: { + alias: string + pref?: string + } wantedDependency: { alias: string pref?: string @@ -22,6 +44,19 @@ interface VersionOverride { newPref: string } +interface VersionOverrideWithParent extends VersionOverride { + parentWantedDependency: { + alias: string + pref?: string + } +} + +function overrideDepsOfPkg (pkg: PackageManifest, versionOverrides: VersionOverride[]) { + if (pkg.dependencies) overrideDeps(versionOverrides, pkg.dependencies) + if (pkg.optionalDependencies) overrideDeps(versionOverrides, pkg.optionalDependencies) + return pkg +} + function overrideDeps (versionOverrides: VersionOverride[], deps: Dependencies) { for (const versionOverride of versionOverrides) { if ( diff --git a/packages/supi/test/createVersionOverrider.test.ts b/packages/supi/test/createVersionOverrider.test.ts new file mode 100644 index 0000000000..421419bd98 --- /dev/null +++ b/packages/supi/test/createVersionOverrider.test.ts @@ -0,0 +1,38 @@ +import createVersionOverrider from 'supi/lib/install/createVersionsOverrider' +import promisifyTape from 'tape-promise' +import tape = require('tape') + +const test = promisifyTape(tape) + +test('createVersionsOverrider() overrides dependencies of specified packages only', (t: tape.Test) => { + const overrider = createVersionOverrider({ + 'foo@1>bar@^1.2.0': '3.0.0', + }) + t.deepEqual(overrider({ + name: 'foo', + version: '1.2.0', + dependencies: { + bar: '^1.2.0', + }, + }), { + name: 'foo', + version: '1.2.0', + dependencies: { + bar: '3.0.0', + }, + }) + t.deepEqual(overrider({ + name: 'foo', + version: '2.0.0', + dependencies: { + bar: '^1.2.0', + }, + }), { + name: 'foo', + version: '2.0.0', + dependencies: { + bar: '^1.2.0', + }, + }) + t.end() +}) diff --git a/packages/supi/test/index.ts b/packages/supi/test/index.ts index 3e55778413..3417c96362 100644 --- a/packages/supi/test/index.ts +++ b/packages/supi/test/index.ts @@ -3,6 +3,7 @@ import './allProjectsAreUpToDate.test' import './api' import './breakingChanges' import './cache' +import './createVersionOverrider.test' import './install' import './link' import './lockfile' diff --git a/packages/supi/test/install/overrides.ts b/packages/supi/test/install/overrides.ts index a4efc3ec0e..5fb0a8e763 100644 --- a/packages/supi/test/install/overrides.ts +++ b/packages/supi/test/install/overrides.ts @@ -13,21 +13,26 @@ test('versions are replaced with versions specified through pnpm.overrides field const project = prepareEmpty(t) await addDistTag({ package: 'bar', version: '100.0.0', distTag: 'latest' }) + await addDistTag({ package: 'foo', version: '100.0.0', distTag: 'latest' }) const manifest = await addDependenciesToPackage({ pnpm: { overrides: { + 'foobarqar>foo': 'npm:qar@100.0.0', 'bar@^100.0.0': '100.1.0', 'dep-of-pkg-with-1-dep': '101.0.0', }, }, - }, ['pkg-with-1-dep@100.0.0', 'foobar@100.0.0'], await testDefaults()) + }, ['pkg-with-1-dep@100.0.0', 'foobar@100.0.0', 'foobarqar@1.0.0'], await testDefaults()) { const lockfile = await project.readLockfile() + t.equal(lockfile.packages['/foobarqar/1.0.0'].dependencies['foo'], '/qar/100.0.0') + t.equal(lockfile.packages['/foobar/100.0.0'].dependencies['foo'], '100.0.0') t.ok(lockfile.packages['/dep-of-pkg-with-1-dep/101.0.0']) t.ok(lockfile.packages['/bar/100.1.0']) t.deepEqual(lockfile.overrides, { + 'foobarqar>foo': 'npm:qar@100.0.0', 'bar@^100.0.0': '100.1.0', 'dep-of-pkg-with-1-dep': '101.0.0', }) @@ -49,6 +54,7 @@ test('versions are replaced with versions specified through pnpm.overrides field t.ok(lockfile.packages['/dep-of-pkg-with-1-dep/101.0.0']) t.ok(lockfile.packages['/bar/100.0.0']) t.deepEqual(lockfile.overrides, { + 'foobarqar>foo': 'npm:qar@100.0.0', 'bar@^100.0.0': '100.0.0', 'dep-of-pkg-with-1-dep': '101.0.0', })