mirror of
https://github.com/pnpm/pnpm.git
synced 2025-12-23 23:29:17 -05:00
7
.changeset/fair-sloths-roll.md
Normal file
7
.changeset/fair-sloths-roll.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@pnpm/lockfile-types": patch
|
||||
"supi": patch
|
||||
"@pnpm/types": patch
|
||||
---
|
||||
|
||||
Use pnpm.overrides instead of resolutions. Still support resolutions for partial compatibility with Yarn and for avoiding a breaking change.
|
||||
@@ -2,7 +2,7 @@ export interface Lockfile {
|
||||
importers: Record<string, ProjectSnapshot>
|
||||
lockfileVersion: number
|
||||
packages?: PackageSnapshots
|
||||
resolutions?: Record<string, string>
|
||||
overrides?: Record<string, string>
|
||||
}
|
||||
|
||||
export interface ProjectSnapshot {
|
||||
|
||||
@@ -19,7 +19,7 @@ import semver = require('semver')
|
||||
export default function allProjectsAreUpToDate (
|
||||
projects: Array<ProjectOptions & { id: string }>,
|
||||
opts: {
|
||||
resolutions?: Record<string, string>
|
||||
overrides?: Record<string, string>
|
||||
linkWorkspacePackages: boolean
|
||||
wantedLockfile: Lockfile
|
||||
workspacePackages: WorkspacePackages
|
||||
@@ -32,7 +32,7 @@ export default function allProjectsAreUpToDate (
|
||||
manifestsByDir,
|
||||
workspacePackages: opts.workspacePackages,
|
||||
})
|
||||
return R.equals(opts.wantedLockfile.resolutions ?? {}, opts.resolutions ?? {}) && pEvery(projects, (project) => {
|
||||
return R.equals(opts.wantedLockfile.overrides ?? {}, opts.overrides ?? {}) && pEvery(projects, (project) => {
|
||||
const importer = opts.wantedLockfile.importers[project.id]
|
||||
return !hasLocalTarballDepsInRoot(importer) &&
|
||||
_satisfiesPackageManifest(project.manifest, project.id) &&
|
||||
|
||||
37
packages/supi/src/install/createVersionsOverrider.ts
Normal file
37
packages/supi/src/install/createVersionsOverrider.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { Dependencies, PackageManifest, ReadPackageHook } from '@pnpm/types'
|
||||
import parseWantedDependency from '@pnpm/parse-wanted-dependency'
|
||||
|
||||
export default function (overrides: Record<string, string>): ReadPackageHook {
|
||||
const versionOverrides = Object.entries(overrides)
|
||||
.map(([rawWantedDependency, newPref]) => ({
|
||||
newPref,
|
||||
wantedDependency: parseWantedDependency(rawWantedDependency),
|
||||
} as VersionOverride))
|
||||
return ((pkg: PackageManifest) => {
|
||||
if (pkg.dependencies) overrideDeps(versionOverrides, pkg.dependencies)
|
||||
if (pkg.optionalDependencies) overrideDeps(versionOverrides, pkg.optionalDependencies)
|
||||
return pkg
|
||||
}) as ReadPackageHook
|
||||
}
|
||||
|
||||
interface VersionOverride {
|
||||
wantedDependency: {
|
||||
alias: string
|
||||
pref?: string
|
||||
}
|
||||
newPref: string
|
||||
}
|
||||
|
||||
function overrideDeps (versionOverrides: VersionOverride[], deps: Dependencies) {
|
||||
for (const versionOverride of versionOverrides) {
|
||||
if (
|
||||
deps[versionOverride.wantedDependency.alias] &&
|
||||
(
|
||||
!versionOverride.wantedDependency.pref ||
|
||||
deps[versionOverride.wantedDependency.alias] === versionOverride.wantedDependency.pref
|
||||
)
|
||||
) {
|
||||
deps[versionOverride.wantedDependency.alias] = versionOverride.newPref
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
import { Dependencies, PackageManifest, ReadPackageHook } from '@pnpm/types'
|
||||
import parseWantedDependency from '@pnpm/parse-wanted-dependency'
|
||||
|
||||
export default function (resolutions: Record<string, string>): ReadPackageHook {
|
||||
const replacements = Object.entries(resolutions)
|
||||
.map(([rawWantedDependency, newPref]) => ({
|
||||
newPref,
|
||||
wantedDependency: parseWantedDependency(rawWantedDependency),
|
||||
} as VersionReplacement))
|
||||
return ((pkg: PackageManifest) => {
|
||||
if (pkg.dependencies) replaceDeps(replacements, pkg.dependencies)
|
||||
if (pkg.optionalDependencies) replaceDeps(replacements, pkg.optionalDependencies)
|
||||
return pkg
|
||||
}) as ReadPackageHook
|
||||
}
|
||||
|
||||
interface VersionReplacement {
|
||||
wantedDependency: {
|
||||
alias: string
|
||||
pref?: string
|
||||
}
|
||||
newPref: string
|
||||
}
|
||||
|
||||
function replaceDeps (replacements: VersionReplacement[], deps: Dependencies) {
|
||||
for (const replacement of replacements) {
|
||||
if (
|
||||
deps[replacement.wantedDependency.alias] &&
|
||||
(
|
||||
!replacement.wantedDependency.pref ||
|
||||
deps[replacement.wantedDependency.alias] === replacement.wantedDependency.pref
|
||||
)
|
||||
) {
|
||||
deps[replacement.wantedDependency.alias] = replacement.newPref
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,7 @@ import parseWantedDependencies from '../parseWantedDependencies'
|
||||
import safeIsInnerLink from '../safeIsInnerLink'
|
||||
import removeDeps from '../uninstall/removeDeps'
|
||||
import allProjectsAreUpToDate from './allProjectsAreUpToDate'
|
||||
import createVersionsReplacer from './createVersionsReplacer'
|
||||
import createVersionsOverrider from './createVersionsOverrider'
|
||||
import extendOptions, {
|
||||
InstallOptions,
|
||||
StrictInstallOptions,
|
||||
@@ -138,15 +138,21 @@ export async function mutateModules (
|
||||
opts['forceNewModules'] = installsOnly
|
||||
const ctx = await getContext(projects, opts)
|
||||
const rootProject = ctx.projects.find(({ id }) => id === '.')
|
||||
if (!R.isEmpty(rootProject?.manifest.resolutions ?? {})) {
|
||||
const versionsReplacer = createVersionsReplacer(rootProject!.manifest.resolutions!)
|
||||
// We read Yarn's resolutions field for compatibility
|
||||
// but we really replace the version specs to any other version spec, not only to exact versions,
|
||||
// so we cannot call it resolutions
|
||||
const overrides = rootProject
|
||||
? rootProject.manifest.pnpm?.overrides ?? rootProject.manifest.resolutions
|
||||
: undefined
|
||||
if (!R.isEmpty(overrides ?? {})) {
|
||||
const versionsOverrider = createVersionsOverrider(overrides!)
|
||||
if (opts.hooks.readPackage) {
|
||||
opts.hooks.readPackage = R.pipe(
|
||||
opts.hooks.readPackage,
|
||||
versionsReplacer
|
||||
versionsOverrider
|
||||
) as ReadPackageHook
|
||||
} else {
|
||||
opts.hooks.readPackage = versionsReplacer
|
||||
opts.hooks.readPackage = versionsOverrider
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,8 +184,8 @@ export async function mutateModules (
|
||||
ctx.existsWantedLockfile &&
|
||||
ctx.wantedLockfile.lockfileVersion === LOCKFILE_VERSION &&
|
||||
await allProjectsAreUpToDate(ctx.projects, {
|
||||
resolutions: rootProject?.manifest.resolutions,
|
||||
linkWorkspacePackages: opts.linkWorkspacePackagesDepth >= 0,
|
||||
overrides,
|
||||
wantedLockfile: ctx.wantedLockfile,
|
||||
workspacePackages: opts.workspacePackages,
|
||||
})
|
||||
@@ -417,7 +423,7 @@ export async function mutateModules (
|
||||
...opts,
|
||||
currentLockfileIsUpToDate: !ctx.existsWantedLockfile || ctx.currentLockfileIsUpToDate,
|
||||
makePartialCurrentLockfile,
|
||||
resolutions: rootProject?.manifest.resolutions,
|
||||
overrides,
|
||||
update: opts.update || !installsOnly,
|
||||
updateLockfileMinorVersion: true,
|
||||
})
|
||||
@@ -562,9 +568,9 @@ async function installInContext (
|
||||
ctx: PnpmContext<DependenciesMutation>,
|
||||
opts: StrictInstallOptions & {
|
||||
makePartialCurrentLockfile: boolean
|
||||
overrides?: Record<string, string>
|
||||
updateLockfileMinorVersion: boolean
|
||||
preferredVersions?: PreferredVersions
|
||||
resolutions?: Record<string, string>
|
||||
currentLockfileIsUpToDate: boolean
|
||||
}
|
||||
) {
|
||||
@@ -589,11 +595,14 @@ async function installInContext (
|
||||
}
|
||||
}
|
||||
}
|
||||
const resolutionsChanged = !R.equals(opts.resolutions ?? {}, ctx.wantedLockfile.resolutions ?? {})
|
||||
if (!R.isEmpty(opts.resolutions ?? {})) {
|
||||
ctx.wantedLockfile.resolutions = opts.resolutions
|
||||
const overridesChanged = !R.equals(opts.overrides ?? {}, ctx.wantedLockfile.overrides ?? {})
|
||||
if (!R.isEmpty(opts.overrides ?? {})) {
|
||||
ctx.wantedLockfile.overrides = opts.overrides
|
||||
} else {
|
||||
delete ctx.wantedLockfile.resolutions
|
||||
delete ctx.wantedLockfile.overrides
|
||||
// We were only setting the resolutions field in pnpm v5.10.0, which was never latest,
|
||||
// so we can probably remove this line safely in future pnpm versions.
|
||||
delete ctx.wantedLockfile['resolutions']
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
@@ -623,7 +632,7 @@ async function installInContext (
|
||||
const forceFullResolution = ctx.wantedLockfile.lockfileVersion !== LOCKFILE_VERSION ||
|
||||
!opts.currentLockfileIsUpToDate ||
|
||||
opts.force ||
|
||||
resolutionsChanged
|
||||
overridesChanged
|
||||
const _toResolveImporter = toResolveImporter.bind(null, {
|
||||
defaultUpdateDepth: (opts.update || opts.updateMatching) ? opts.depth : -1,
|
||||
lockfileOnly: opts.lockfileOnly,
|
||||
|
||||
@@ -16,9 +16,9 @@ import './modulesDir'
|
||||
import './multipleImporters'
|
||||
import './only'
|
||||
import './optionalDependencies'
|
||||
import './overrides'
|
||||
import './peerDependencies'
|
||||
import './reporting'
|
||||
import './resolutions'
|
||||
import './sideEffects'
|
||||
import './store'
|
||||
import './update'
|
||||
|
||||
100
packages/supi/test/install/overrides.ts
Normal file
100
packages/supi/test/install/overrides.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { prepareEmpty } from '@pnpm/prepare'
|
||||
import { addDistTag } from '@pnpm/registry-mock'
|
||||
import { addDependenciesToPackage, mutateModules } from 'supi'
|
||||
import promisifyTape from 'tape-promise'
|
||||
import {
|
||||
testDefaults,
|
||||
} from '../utils'
|
||||
import tape = require('tape')
|
||||
|
||||
const test = promisifyTape(tape)
|
||||
|
||||
test('versions are replaced with versions specified through pnpm.overrides field', async (t: tape.Test) => {
|
||||
const project = prepareEmpty(t)
|
||||
|
||||
await addDistTag({ package: 'bar', version: '100.0.0', distTag: 'latest' })
|
||||
|
||||
const manifest = await addDependenciesToPackage({
|
||||
pnpm: {
|
||||
overrides: {
|
||||
'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())
|
||||
|
||||
{
|
||||
const lockfile = await project.readLockfile()
|
||||
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, {
|
||||
'bar@^100.0.0': '100.1.0',
|
||||
'dep-of-pkg-with-1-dep': '101.0.0',
|
||||
})
|
||||
}
|
||||
|
||||
// The lockfile is updated if the overrides are changed
|
||||
manifest.pnpm.overrides['bar@^100.0.0'] = '100.0.0'
|
||||
await mutateModules([
|
||||
{
|
||||
buildIndex: 0,
|
||||
manifest,
|
||||
mutation: 'install',
|
||||
rootDir: process.cwd(),
|
||||
},
|
||||
], await testDefaults())
|
||||
|
||||
{
|
||||
const lockfile = await project.readLockfile()
|
||||
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, {
|
||||
'bar@^100.0.0': '100.0.0',
|
||||
'dep-of-pkg-with-1-dep': '101.0.0',
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
test('versions are replaced with versions specified through "resolutions" field (for Yarn compatibility)', async (t: tape.Test) => {
|
||||
const project = prepareEmpty(t)
|
||||
|
||||
await addDistTag({ package: 'bar', version: '100.0.0', distTag: 'latest' })
|
||||
|
||||
const manifest = await addDependenciesToPackage({
|
||||
resolutions: {
|
||||
'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())
|
||||
|
||||
{
|
||||
const lockfile = await project.readLockfile()
|
||||
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, {
|
||||
'bar@^100.0.0': '100.1.0',
|
||||
'dep-of-pkg-with-1-dep': '101.0.0',
|
||||
})
|
||||
}
|
||||
|
||||
// The lockfile is updated if the resolutions are changed
|
||||
manifest.resolutions['bar@^100.0.0'] = '100.0.0'
|
||||
await mutateModules([
|
||||
{
|
||||
buildIndex: 0,
|
||||
manifest,
|
||||
mutation: 'install',
|
||||
rootDir: process.cwd(),
|
||||
},
|
||||
], await testDefaults())
|
||||
|
||||
{
|
||||
const lockfile = await project.readLockfile()
|
||||
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, {
|
||||
'bar@^100.0.0': '100.0.0',
|
||||
'dep-of-pkg-with-1-dep': '101.0.0',
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -1,54 +0,0 @@
|
||||
import { prepareEmpty } from '@pnpm/prepare'
|
||||
import { addDistTag } from '@pnpm/registry-mock'
|
||||
import { addDependenciesToPackage, mutateModules } from 'supi'
|
||||
import promisifyTape from 'tape-promise'
|
||||
import {
|
||||
testDefaults,
|
||||
} from '../utils'
|
||||
import tape = require('tape')
|
||||
|
||||
const test = promisifyTape(tape)
|
||||
|
||||
test('versions are replaced with versions specified through resolutions field', async (t: tape.Test) => {
|
||||
const project = prepareEmpty(t)
|
||||
|
||||
await addDistTag({ package: 'bar', version: '100.0.0', distTag: 'latest' })
|
||||
|
||||
const manifest = await addDependenciesToPackage({
|
||||
resolutions: {
|
||||
'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())
|
||||
|
||||
{
|
||||
const lockfile = await project.readLockfile()
|
||||
t.ok(lockfile.packages['/dep-of-pkg-with-1-dep/101.0.0'])
|
||||
t.ok(lockfile.packages['/bar/100.1.0'])
|
||||
t.deepEqual(lockfile.resolutions, {
|
||||
'bar@^100.0.0': '100.1.0',
|
||||
'dep-of-pkg-with-1-dep': '101.0.0',
|
||||
})
|
||||
}
|
||||
|
||||
// The lockfile is updated if the resolutions are changed
|
||||
manifest.resolutions['bar@^100.0.0'] = '100.0.0'
|
||||
await mutateModules([
|
||||
{
|
||||
buildIndex: 0,
|
||||
manifest,
|
||||
mutation: 'install',
|
||||
rootDir: process.cwd(),
|
||||
},
|
||||
], await testDefaults())
|
||||
|
||||
{
|
||||
const lockfile = await project.readLockfile()
|
||||
t.ok(lockfile.packages['/dep-of-pkg-with-1-dep/101.0.0'])
|
||||
t.ok(lockfile.packages['/bar/100.0.0'])
|
||||
t.deepEqual(lockfile.resolutions, {
|
||||
'bar@^100.0.0': '100.0.0',
|
||||
'dep-of-pkg-with-1-dep': '101.0.0',
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -82,6 +82,9 @@ interface BaseManifest {
|
||||
export type DependencyManifest = BaseManifest & Required<Pick<BaseManifest, 'name' | 'version'>>
|
||||
|
||||
export type ProjectManifest = BaseManifest & {
|
||||
pnpm?: {
|
||||
overrides?: Record<string, string>
|
||||
}
|
||||
private?: boolean
|
||||
resolutions?: Record<string, string>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user