fix: resolve catalogs in overrides (#8321)

close #8303
This commit is contained in:
Zoltan Kochan
2024-07-22 01:35:30 +02:00
committed by GitHub
parent b0e8ae8761
commit 0f0e44101d
19 changed files with 420 additions and 123 deletions

View File

@@ -0,0 +1,9 @@
---
"@pnpm/core": minor
"@pnpm/hooks.read-package-hook": major
"@pnpm/outdated": minor
"@pnpm/parse-overrides": minor
"pnpm": minor
---
Overrides now support catalogs [#8303](https://github.com/pnpm/pnpm/issues/8303).

View File

@@ -29,6 +29,8 @@
"homepage": "https://github.com/pnpm/pnpm/blob/main/config/parse-overrides#readme",
"funding": "https://opencollective.com/pnpm",
"dependencies": {
"@pnpm/catalogs.resolver": "workspace:*",
"@pnpm/catalogs.types": "workspace:*",
"@pnpm/error": "workspace:*",
"@pnpm/parse-wanted-dependency": "workspace:*"
},

View File

@@ -1,9 +1,12 @@
import { PnpmError } from '@pnpm/error'
import { parseWantedDependency } from '@pnpm/parse-wanted-dependency'
import { matchCatalogResolveResult, resolveFromCatalog } from '@pnpm/catalogs.resolver'
import { type Catalogs } from '@pnpm/catalogs.types'
const DELIMITER_REGEX = /[^ |@]>/
export interface VersionOverride {
selector: string
parentPkg?: PackageSelector
targetPkg: PackageSelector
newPref: string
@@ -15,28 +18,47 @@ export interface PackageSelector {
}
export function parseOverrides (
overrides: Record<string, string>
overrides: Record<string, string>,
catalogs?: Catalogs
): VersionOverride[] {
const _resolveFromCatalog = resolveFromCatalog.bind(null, catalogs ?? {})
return Object.entries(overrides)
.map(([selector, newPref]) => {
let delimiterIndex = selector.search(DELIMITER_REGEX)
if (delimiterIndex !== -1) {
delimiterIndex++
const parentSelector = selector.substring(0, delimiterIndex)
const childSelector = selector.substring(delimiterIndex + 1)
return {
newPref,
parentPkg: parsePkgSelector(parentSelector),
targetPkg: parsePkgSelector(childSelector),
}
}
const result = parsePkgAndParentSelector(selector)
const resolvedCatalog = matchCatalogResolveResult(_resolveFromCatalog({
pref: newPref,
alias: result.targetPkg.name,
}), {
found: ({ resolution }) => resolution.specifier,
unused: () => undefined,
misconfiguration: ({ error }) => {
throw new PnpmError('CATALOG_IN_OVERRIDES', `Could not resolve a catalog in the overrides: ${error.message}`)
},
})
return {
newPref,
targetPkg: parsePkgSelector(selector),
selector,
newPref: resolvedCatalog ?? newPref,
...result,
}
})
}
function parsePkgAndParentSelector (selector: string): Pick<VersionOverride, 'parentPkg' | 'targetPkg'> {
let delimiterIndex = selector.search(DELIMITER_REGEX)
if (delimiterIndex !== -1) {
delimiterIndex++
const parentSelector = selector.substring(0, delimiterIndex)
const childSelector = selector.substring(delimiterIndex + 1)
return {
parentPkg: parsePkgSelector(parentSelector),
targetPkg: parsePkgSelector(childSelector),
}
}
return {
targetPkg: parsePkgSelector(selector),
}
}
function parsePkgSelector (selector: string): PackageSelector {
const wantedDep = parseWantedDependency(selector)
if (!wantedDep.alias) {

View File

@@ -3,11 +3,11 @@ import { parseOverrides } from '@pnpm/parse-overrides'
test.each([
[
{ foo: '1' },
[{ newPref: '1', targetPkg: { name: 'foo' } }],
[{ selector: 'foo', newPref: '1', targetPkg: { name: 'foo' } }],
],
[
{ 'foo@2': '1' },
[{ newPref: '1', targetPkg: { name: 'foo', pref: '2' } }],
[{ selector: 'foo@2', newPref: '1', targetPkg: { name: 'foo', pref: '2' } }],
],
[
{
@@ -15,8 +15,8 @@ test.each([
'foo@3 || >=2': '1',
},
[
{ newPref: '1', targetPkg: { name: 'foo', pref: '>2' } },
{ newPref: '1', targetPkg: { name: 'foo', pref: '3 || >=2' } },
{ selector: 'foo@>2', newPref: '1', targetPkg: { name: 'foo', pref: '>2' } },
{ selector: 'foo@3 || >=2', newPref: '1', targetPkg: { name: 'foo', pref: '3 || >=2' } },
],
],
[
@@ -27,10 +27,10 @@ test.each([
'bar@1>foo@1': '2',
},
[
{ newPref: '2', parentPkg: { name: 'bar' }, targetPkg: { name: 'foo' } },
{ newPref: '2', parentPkg: { name: 'bar', pref: '1' }, targetPkg: { name: 'foo' } },
{ newPref: '2', parentPkg: { name: 'bar' }, targetPkg: { name: 'foo', pref: '1' } },
{ newPref: '2', parentPkg: { name: 'bar', pref: '1' }, targetPkg: { name: 'foo', pref: '1' } },
{ selector: 'bar>foo', newPref: '2', parentPkg: { name: 'bar' }, targetPkg: { name: 'foo' } },
{ selector: 'bar@1>foo', newPref: '2', parentPkg: { name: 'bar', pref: '1' }, targetPkg: { name: 'foo' } },
{ selector: 'bar>foo@1', newPref: '2', parentPkg: { name: 'bar' }, targetPkg: { name: 'foo', pref: '1' } },
{ selector: 'bar@1>foo@1', newPref: '2', parentPkg: { name: 'bar', pref: '1' }, targetPkg: { name: 'foo', pref: '1' } },
],
],
[
@@ -39,14 +39,15 @@ test.each([
'foo@3 || >=2>bar@3 || >=2': '1',
},
[
{ newPref: '1', parentPkg: { name: 'foo', pref: '>2' }, targetPkg: { name: 'bar', pref: '>2' } },
{ newPref: '1', parentPkg: { name: 'foo', pref: '3 || >=2' }, targetPkg: { name: 'bar', pref: '3 || >=2' } },
{ selector: 'foo@>2>bar@>2', newPref: '1', parentPkg: { name: 'foo', pref: '>2' }, targetPkg: { name: 'bar', pref: '>2' } },
{ selector: 'foo@3 || >=2>bar@3 || >=2', newPref: '1', parentPkg: { name: 'foo', pref: '3 || >=2' }, targetPkg: { name: 'bar', pref: '3 || >=2' } },
],
],
])('parseOverrides()', (overrides, expectedResult) => {
expect(parseOverrides(overrides)).toEqual(expectedResult)
expect(parseOverrides(overrides, {})).toEqual(expectedResult)
})
test('parseOverrides() throws an exception on invalid selector', () => {
expect(() => parseOverrides({ '%': '2' })).toThrow('Cannot parse the "%" selector')
expect(() => parseOverrides({ '%': '2' }, {})).toThrow('Cannot parse the "%" selector')
expect(() => parseOverrides({ 'foo > bar': '2' }, {})).toThrow('Cannot parse the "foo > bar" selector')
})

View File

@@ -9,6 +9,12 @@
"../../__typings__/**/*.d.ts"
],
"references": [
{
"path": "../../catalogs/resolver"
},
{
"path": "../../catalogs/types"
},
{
"path": "../../packages/error"
},

View File

@@ -9,7 +9,7 @@ import isEmpty from 'ramda/src/isEmpty'
import pipeWith from 'ramda/src/pipeWith'
import { createOptionalDependenciesRemover } from './createOptionalDependenciesRemover'
import { createPackageExtender } from './createPackageExtender'
import { createVersionsOverrider } from './createVersionsOverrider'
import { createVersionsOverrider, type VersionOverrideWithoutRawSelector } from './createVersionsOverrider'
export function createReadPackageHook (
{
@@ -22,7 +22,7 @@ export function createReadPackageHook (
}: {
ignoreCompatibilityDb?: boolean
lockfileDir: string
overrides?: Record<string, string>
overrides?: VersionOverrideWithoutRawSelector[]
ignoredOptionalDependencies?: string[]
packageExtensions?: Record<string, PackageExtension>
readPackageHook?: ReadPackageHook[] | ReadPackageHook

View File

@@ -2,18 +2,18 @@ import path from 'path'
import semver from 'semver'
import partition from 'ramda/src/partition'
import { type Dependencies, type PackageManifest, type ReadPackageHook } from '@pnpm/types'
import { PnpmError } from '@pnpm/error'
import { parseOverrides, type VersionOverride as VersionOverrideBase } from '@pnpm/parse-overrides'
import { type VersionOverride as VersionOverrideBase } from '@pnpm/parse-overrides'
import normalizePath from 'normalize-path'
import { isIntersectingRange } from './isIntersectingRange'
export type VersionOverrideWithoutRawSelector = Omit<VersionOverrideBase, 'selector'>
export function createVersionsOverrider (
overrides: Record<string, string>,
overrides: VersionOverrideWithoutRawSelector[],
rootDir: string
): ReadPackageHook {
const parsedOverrides = tryParseOverrides(overrides)
const [versionOverrides, genericVersionOverrides] = partition(({ parentPkg }) => parentPkg != null,
parsedOverrides
overrides
.map((override) => {
return {
...override,
@@ -34,14 +34,6 @@ export function createVersionsOverrider (
}) as ReadPackageHook
}
function tryParseOverrides (overrides: Record<string, string>): VersionOverrideBase[] {
try {
return parseOverrides(overrides)
} catch (e) {
throw new PnpmError('INVALID_OVERRIDES_SELECTOR', `${(e as PnpmError).message} in pnpm.overrides`)
}
}
interface LocalTarget {
protocol: LocalProtocol
absolutePath: string
@@ -50,7 +42,7 @@ interface LocalTarget {
type LocalProtocol = 'link:' | 'file:'
function createLocalTarget (override: VersionOverrideBase, rootDir: string): LocalTarget | undefined {
function createLocalTarget (override: VersionOverrideWithoutRawSelector, rootDir: string): LocalTarget | undefined {
let protocol: LocalProtocol | undefined
if (override.newPref.startsWith('file:')) {
protocol = 'file:'
@@ -69,12 +61,7 @@ interface VersionOverride extends VersionOverrideBase {
localTarget?: LocalTarget
}
interface VersionOverrideWithParent extends VersionOverride {
parentPkg: {
name: string
pref?: string
}
}
type VersionOverrideWithParent = VersionOverride & Required<Pick<VersionOverride, 'parentPkg'>>
function overrideDepsOfPkg (
{ manifest, dir }: { manifest: PackageManifest, dir: string | undefined },

View File

@@ -27,9 +27,14 @@ test('createReadPackageHook() runs the custom hook before the version overrider'
ignoreCompatibilityDb: true,
lockfileDir: '/foo',
readPackageHook: [hook],
overrides: {
react: '16',
},
overrides: [
{
targetPkg: {
name: 'react',
},
newPref: '16',
},
],
})
const manifest = {}
const dir = '/bar'

View File

@@ -2,10 +2,22 @@ import path from 'path'
import { createVersionsOverrider } from '../lib/createVersionsOverrider'
test('createVersionsOverrider() matches sub-ranges', () => {
const overrider = createVersionsOverrider({
'foo@2': '2.12.0',
'qar@>2': '1.0.0',
}, process.cwd())
const overrider = createVersionsOverrider([
{
targetPkg: {
name: 'foo',
pref: '2',
},
newPref: '2.12.0',
},
{
targetPkg: {
name: 'qar',
pref: '>2',
},
newPref: '1.0.0',
},
], process.cwd())
expect(
overrider({
dependencies: { foo: '^2.10.0' },
@@ -18,10 +30,22 @@ test('createVersionsOverrider() matches sub-ranges', () => {
})
test('createVersionsOverrider() does not fail on non-range selectors', () => {
const overrider = createVersionsOverrider({
'foo@2': '2.12.0',
'bar@github:org/bar': '2.12.0',
}, process.cwd())
const overrider = createVersionsOverrider([
{
targetPkg: {
name: 'foo',
pref: '2',
},
newPref: '2.12.0',
},
{
targetPkg: {
name: 'bar',
pref: 'github:org/bar',
},
newPref: '2.12.0',
},
], process.cwd())
expect(
overrider({
dependencies: {
@@ -38,10 +62,30 @@ test('createVersionsOverrider() does not fail on non-range selectors', () => {
})
test('createVersionsOverrider() overrides dependencies of specified packages only', () => {
const overrider = createVersionsOverrider({
'foo@1>bar@^1.2.0': '3.0.0',
'qar@1>bar@>4': '3.0.0',
}, process.cwd())
const overrider = createVersionsOverrider([
{
parentPkg: {
name: 'foo',
pref: '1',
},
targetPkg: {
name: 'bar',
pref: '^1.2.0',
},
newPref: '3.0.0',
},
{
parentPkg: {
name: 'qar',
pref: '1',
},
targetPkg: {
name: 'bar',
pref: '>4',
},
newPref: '3.0.0',
},
], process.cwd())
expect(overrider({
name: 'foo',
version: '1.2.0',
@@ -97,11 +141,26 @@ test('createVersionsOverrider() overrides dependencies of specified packages onl
})
test('createVersionsOverrider() overrides all types of dependencies', () => {
const overrider = createVersionsOverrider({
foo: '3.0.0',
bar: '3.0.0',
qar: '3.0.0',
}, process.cwd())
const overrider = createVersionsOverrider([
{
targetPkg: {
name: 'foo',
},
newPref: '3.0.0',
},
{
targetPkg: {
name: 'bar',
},
newPref: '3.0.0',
},
{
targetPkg: {
name: 'qar',
},
newPref: '3.0.0',
},
], process.cwd())
expect(overrider({
name: 'foo',
version: '1.2.0',
@@ -130,9 +189,14 @@ test('createVersionsOverrider() overrides all types of dependencies', () => {
})
test('createVersionsOverrider() overrides dependencies with links', () => {
const overrider = createVersionsOverrider({
qar: 'link:../qar',
}, process.cwd())
const overrider = createVersionsOverrider([
{
targetPkg: {
name: 'qar',
},
newPref: 'link:../qar',
},
], process.cwd())
expect(overrider({
name: 'foo',
version: '1.2.0',
@@ -150,9 +214,14 @@ test('createVersionsOverrider() overrides dependencies with links', () => {
test('createVersionsOverrider() overrides dependencies with absolute links', () => {
const qarAbsolutePath = path.resolve(process.cwd(), './qar')
const overrider = createVersionsOverrider({
qar: `link:${qarAbsolutePath}`,
}, process.cwd())
const overrider = createVersionsOverrider([
{
targetPkg: {
name: 'qar',
},
newPref: `link:${qarAbsolutePath}`,
},
], process.cwd())
expect(overrider({
name: 'foo',
@@ -170,9 +239,18 @@ test('createVersionsOverrider() overrides dependencies with absolute links', ()
})
test('createVersionsOverrider() overrides dependency of pkg matched by name and version', () => {
const overrider = createVersionsOverrider({
'yargs@^7.1.0>yargs-parser': '^20.0.0',
}, process.cwd())
const overrider = createVersionsOverrider([
{
parentPkg: {
name: 'yargs',
pref: '^7.1.0',
},
targetPkg: {
name: 'yargs-parser',
},
newPref: '^20.0.0',
},
], process.cwd())
expect(
overrider({
name: 'yargs',
@@ -191,9 +269,18 @@ test('createVersionsOverrider() overrides dependency of pkg matched by name and
})
test('createVersionsOverrider() does not override dependency of pkg matched by name and version', () => {
const overrider = createVersionsOverrider({
'yargs@^8.1.0>yargs-parser': '^20.0.0',
}, process.cwd())
const overrider = createVersionsOverrider([
{
parentPkg: {
name: 'yargs',
pref: '^8.1.0',
},
targetPkg: {
name: 'yargs-parser',
},
newPref: '^20.0.0',
},
], process.cwd())
expect(
overrider({
name: 'yargs',
@@ -212,9 +299,17 @@ test('createVersionsOverrider() does not override dependency of pkg matched by n
})
test('createVersionsOverrider() should work for scoped parent and unscoped child', () => {
const overrider = createVersionsOverrider({
'@scoped/package>unscoped-package': 'workspace:*',
}, process.cwd())
const overrider = createVersionsOverrider([
{
parentPkg: {
name: '@scoped/package',
},
targetPkg: {
name: 'unscoped-package',
},
newPref: 'workspace:*',
},
], process.cwd())
expect(
overrider({
name: '@scoped/package',
@@ -233,9 +328,17 @@ test('createVersionsOverrider() should work for scoped parent and unscoped child
})
test('createVersionsOverrider() should work for unscoped parent and scoped child', () => {
const overrider = createVersionsOverrider({
'unscoped-package>@scoped/package': 'workspace:*',
}, process.cwd())
const overrider = createVersionsOverrider([
{
parentPkg: {
name: 'unscoped-package',
},
targetPkg: {
name: '@scoped/package',
},
newPref: 'workspace:*',
},
], process.cwd())
expect(
overrider({
name: 'unscoped-package',
@@ -254,9 +357,17 @@ test('createVersionsOverrider() should work for unscoped parent and scoped child
})
test('createVersionsOverrider() should work for scoped parent and scoped child', () => {
const overrider = createVersionsOverrider({
'@scoped/package>@scoped/package2': 'workspace:*',
}, process.cwd())
const overrider = createVersionsOverrider([
{
parentPkg: {
name: '@scoped/package',
},
targetPkg: {
name: '@scoped/package2',
},
newPref: 'workspace:*',
},
], process.cwd())
expect(
overrider({
name: '@scoped/package',
@@ -276,9 +387,14 @@ test('createVersionsOverrider() should work for scoped parent and scoped child',
test('createVersionsOverrider() overrides dependencies with file with relative path for root package', () => {
const rootDir = process.cwd()
const overrider = createVersionsOverrider({
qar: 'file:../qar',
}, rootDir)
const overrider = createVersionsOverrider([
{
targetPkg: {
name: 'qar',
},
newPref: 'file:../qar',
},
], rootDir)
expect(overrider({
name: 'foo',
version: '1.2.0',
@@ -296,9 +412,14 @@ test('createVersionsOverrider() overrides dependencies with file with relative p
test('createVersionsOverrider() overrides dependencies with file with relative path for workspace package', () => {
const rootDir = process.cwd()
const overrider = createVersionsOverrider({
qar: 'file:../qar',
}, rootDir)
const overrider = createVersionsOverrider([
{
targetPkg: {
name: 'qar',
},
newPref: 'file:../qar',
},
], rootDir)
expect(overrider({
name: 'foo',
version: '1.2.0',
@@ -316,9 +437,14 @@ test('createVersionsOverrider() overrides dependencies with file with relative p
test('createVersionsOverrider() overrides dependencies with file specified with absolute path', () => {
const absolutePath = path.join(__dirname, 'qar')
const overrider = createVersionsOverrider({
qar: `file:${absolutePath}`,
}, process.cwd())
const overrider = createVersionsOverrider([
{
targetPkg: {
name: 'qar',
},
newPref: `file:${absolutePath}`,
},
], process.cwd())
expect(overrider({
name: 'foo',
version: '1.2.0',
@@ -335,13 +461,48 @@ test('createVersionsOverrider() overrides dependencies with file specified with
})
test('createVersionOverride() should use the most specific rule when both override rules match the same target', () => {
const overrider = createVersionsOverrider({
foo: '3.0.0',
'foo@3': '4.0.0',
'foo@2': '2.12.0',
'bar>foo@2': 'github:org/foo',
'bar>foo@3': '5.0.0',
}, process.cwd())
const overrider = createVersionsOverrider([
{
targetPkg: {
name: 'foo',
},
newPref: '3.0.0',
},
{
targetPkg: {
name: 'foo',
pref: '3',
},
newPref: '4.0.0',
},
{
targetPkg: {
name: 'foo',
pref: '2',
},
newPref: '2.12.0',
},
{
parentPkg: {
name: 'bar',
},
targetPkg: {
name: 'foo',
pref: '2',
},
newPref: 'github:org/foo',
},
{
parentPkg: {
name: 'bar',
},
targetPkg: {
name: 'foo',
pref: '3',
},
newPref: '5.0.0',
},
], process.cwd())
expect(
overrider({
dependencies: {
@@ -407,16 +568,16 @@ test('createVersionOverride() should use the most specific rule when both overri
})
})
test('createVersionOverrider() throws error when supplied an invalid selector', () => {
expect(() => createVersionsOverrider({
'foo > bar': '2',
}, process.cwd())).toThrowError('Cannot parse the "foo > bar" selector in pnpm.overrides')
})
test('createVersionsOverrider() matches intersections', () => {
const overrider = createVersionsOverrider({
'foo@<1.2.4': '>=1.2.4',
}, process.cwd())
const overrider = createVersionsOverrider([
{
targetPkg: {
name: 'foo',
pref: '<1.2.4',
},
newPref: '>=1.2.4',
},
], process.cwd())
expect(
overrider({
dependencies: { foo: '^1.2.3' },
@@ -427,9 +588,17 @@ test('createVersionsOverrider() matches intersections', () => {
})
test('createVersionsOverrider() overrides peerDependencies of another dependency', () => {
const overrider = createVersionsOverrider({
'react-dom>react': '18.1.0',
}, process.cwd())
const overrider = createVersionsOverrider([
{
parentPkg: {
name: 'react-dom',
},
targetPkg: {
name: 'react',
},
newPref: '18.1.0',
},
], process.cwd())
expect(
overrider({
name: 'react-dom',

View File

@@ -47,6 +47,7 @@
"@pnpm/normalize-registries": "workspace:*",
"@pnpm/npm-package-arg": "catalog:",
"@pnpm/package-requester": "workspace:*",
"@pnpm/parse-overrides": "workspace:*",
"@pnpm/parse-wanted-dependency": "workspace:*",
"@pnpm/pkg-manager.direct-dep-linker": "workspace:*",
"@pnpm/prune-lockfile": "workspace:*",

View File

@@ -3,8 +3,9 @@ import { resolveDependencies, getWantedDependencies } from '@pnpm/resolve-depend
import { type PeerDependencyIssuesByProjects } from '@pnpm/types'
import { getContext, type GetContextOptions, type ProjectOptions } from '@pnpm/get-context'
import { createReadPackageHook } from '@pnpm/hooks.read-package-hook'
import { type InstallOptions } from './install/extendInstallOptions'
import { DEFAULT_REGISTRIES } from '@pnpm/normalize-registries'
import { parseOverrides } from '@pnpm/parse-overrides'
import { type InstallOptions } from './install/extendInstallOptions'
export type ListMissingPeersOptions = Partial<GetContextOptions>
& Pick<InstallOptions, 'hooks'
@@ -51,6 +52,7 @@ export async function getPeerDependencyIssues (
ctx.wantedLockfile.packages,
Object.values(ctx.projects).map(({ manifest }) => manifest)
)
const overrides = parseOverrides(opts.overrides ?? {}, opts.catalogs ?? {})
const {
peerDependencyIssuesByProjects,
waitTillAllFetchingsFinish,
@@ -71,7 +73,7 @@ export async function getPeerDependencyIssues (
readPackage: createReadPackageHook({
ignoreCompatibilityDb: opts.ignoreCompatibilityDb,
lockfileDir,
overrides: opts.overrides,
overrides,
packageExtensions: opts.packageExtensions,
readPackageHook: opts.hooks?.readPackage,
ignoredOptionalDependencies: opts.ignoredOptionalDependencies,

View File

@@ -17,6 +17,7 @@ import {
type Registries,
type PrepareExecutionEnv,
} from '@pnpm/types'
import { parseOverrides, type VersionOverride } from '@pnpm/parse-overrides'
import { pnpmPkgJson } from '../pnpmPkgJson'
import { type ReporterFunction } from '../types'
import { type PreResolutionHookContext } from '@pnpm/hooks.types'
@@ -252,6 +253,7 @@ const defaults = (opts: InstallOptions): StrictInstallOptions => {
export type ProcessedInstallOptions = StrictInstallOptions & {
readPackageHook?: ReadPackageHook
parsedOverrides: VersionOverride[]
}
export function extendOptions (
@@ -272,11 +274,12 @@ export function extendOptions (
...defaultOpts,
...opts,
storeDir: defaultOpts.storeDir,
parsedOverrides: parseOverrides(opts.overrides ?? {}, opts.catalogs ?? {}),
}
extendedOpts.readPackageHook = createReadPackageHook({
ignoreCompatibilityDb: extendedOpts.ignoreCompatibilityDb,
readPackageHook: extendedOpts.hooks?.readPackage,
overrides: extendedOpts.overrides,
overrides: extendedOpts.parsedOverrides,
lockfileDir: extendedOpts.lockfileDir,
packageExtensions: extendedOpts.packageExtensions,
ignoredOptionalDependencies: extendedOpts.ignoredOptionalDependencies,

View File

@@ -340,12 +340,13 @@ export async function mutateModules (
const frozenLockfile = opts.frozenLockfile ||
opts.frozenLockfileIfExists && ctx.existsNonEmptyWantedLockfile
let outdatedLockfileSettings = false
const overridesMap = Object.fromEntries((opts.parsedOverrides ?? []).map(({ selector, newPref }) => [selector, newPref]))
if (!opts.ignorePackageManifest) {
const outdatedLockfileSettingName = getOutdatedLockfileSetting(ctx.wantedLockfile, {
autoInstallPeers: opts.autoInstallPeers,
excludeLinksFromLockfile: opts.excludeLinksFromLockfile,
peersSuffixMaxLength: opts.peersSuffixMaxLength,
overrides: opts.overrides,
overrides: overridesMap,
ignoredOptionalDependencies: opts.ignoredOptionalDependencies?.sort(),
packageExtensionsChecksum,
patchedDependencies,
@@ -367,7 +368,7 @@ export async function mutateModules (
excludeLinksFromLockfile: opts.excludeLinksFromLockfile,
peersSuffixMaxLength: opts.peersSuffixMaxLength,
}
ctx.wantedLockfile.overrides = opts.overrides
ctx.wantedLockfile.overrides = overridesMap
ctx.wantedLockfile.packageExtensionsChecksum = packageExtensionsChecksum
ctx.wantedLockfile.ignoredOptionalDependencies = opts.ignoredOptionalDependencies
ctx.wantedLockfile.pnpmfileChecksum = pnpmfileChecksum

View File

@@ -1,6 +1,7 @@
import { createPeersDirSuffix } from '@pnpm/dependency-path'
import { type ProjectRootDir, type ProjectId, type ProjectManifest } from '@pnpm/types'
import { prepareEmpty } from '@pnpm/prepare'
import { addDistTag } from '@pnpm/registry-mock'
import { type MutatedProject, mutateModules, type ProjectOptions, type MutateModulesOptions, addDependenciesToPackage } from '@pnpm/core'
import path from 'path'
import { testDefaults } from './utils'
@@ -678,3 +679,71 @@ describe('update', () => {
expect(Object.keys(readLockfile().snapshots)).toEqual(['is-positive@1.0.0'])
})
})
test('catalogs work in overrides', async () => {
await addDistTag({ package: '@pnpm.e2e/bar', version: '100.0.0', distTag: 'latest' })
await addDistTag({ package: '@pnpm.e2e/foo', version: '100.0.0', distTag: 'latest' })
const overrides: Record<string, string> = {
'@pnpm.e2e/foobarqar>@pnpm.e2e/foo': 'catalog:',
'@pnpm.e2e/bar@^100.0.0': 'catalog:',
'@pnpm.e2e/dep-of-pkg-with-1-dep': 'catalog:',
}
const { options, projects, readLockfile } = preparePackagesAndReturnObjects([
{
name: 'project1',
dependencies: {
'@pnpm.e2e/pkg-with-1-dep': '100.0.0',
'@pnpm.e2e/foobar': '100.0.0',
'@pnpm.e2e/foobarqar': '1.0.0',
},
},
// Empty second project to create a multi-package workspace.
{
name: 'project2',
},
])
const catalogs = {
default: {
'@pnpm.e2e/foo': 'npm:@pnpm.e2e/qar@100.0.0',
'@pnpm.e2e/bar': '100.1.0',
'@pnpm.e2e/dep-of-pkg-with-1-dep': '101.0.0',
},
}
await mutateModules(installProjects(projects), {
...options,
lockfileOnly: true,
catalogs,
overrides,
})
let lockfile = readLockfile()
expect(lockfile.snapshots['@pnpm.e2e/foobarqar@1.0.0'].dependencies?.['@pnpm.e2e/foo']).toBe('@pnpm.e2e/qar@100.0.0')
expect(lockfile.snapshots['@pnpm.e2e/foobar@100.0.0'].dependencies?.['@pnpm.e2e/foo']).toBe('100.0.0')
expect(lockfile.packages).toHaveProperty(['@pnpm.e2e/dep-of-pkg-with-1-dep@101.0.0'])
expect(lockfile.packages).toHaveProperty(['@pnpm.e2e/bar@100.1.0'])
expect(lockfile.overrides).toStrictEqual({
'@pnpm.e2e/foobarqar>@pnpm.e2e/foo': 'npm:@pnpm.e2e/qar@100.0.0',
'@pnpm.e2e/bar@^100.0.0': '100.1.0',
'@pnpm.e2e/dep-of-pkg-with-1-dep': '101.0.0',
})
catalogs.default['@pnpm.e2e/bar'] = '100.0.0'
await mutateModules(installProjects(projects), {
...options,
lockfileOnly: true,
catalogs,
overrides,
})
lockfile = readLockfile()
expect(lockfile.packages).toHaveProperty(['@pnpm.e2e/bar@100.0.0'])
expect(lockfile.packages).not.toHaveProperty(['@pnpm.e2e/bar@100.1.0'])
expect(lockfile.overrides).toStrictEqual({
'@pnpm.e2e/foobarqar>@pnpm.e2e/foo': 'npm:@pnpm.e2e/qar@100.0.0',
'@pnpm.e2e/bar@^100.0.0': '100.0.0',
'@pnpm.e2e/dep-of-pkg-with-1-dep': '101.0.0',
})
})

View File

@@ -36,6 +36,9 @@
{
"path": "../../config/normalize-registries"
},
{
"path": "../../config/parse-overrides"
},
{
"path": "../../crypto/object-hasher"
},

12
pnpm-lock.yaml generated
View File

@@ -1432,6 +1432,12 @@ importers:
config/parse-overrides:
dependencies:
'@pnpm/catalogs.resolver':
specifier: workspace:*
version: link:../../catalogs/resolver
'@pnpm/catalogs.types':
specifier: workspace:*
version: link:../../catalogs/types
'@pnpm/error':
specifier: workspace:*
version: link:../../packages/error
@@ -3848,6 +3854,9 @@ importers:
'@pnpm/package-requester':
specifier: workspace:*
version: link:../package-requester
'@pnpm/parse-overrides':
specifier: workspace:*
version: link:../../config/parse-overrides
'@pnpm/parse-wanted-dependency':
specifier: workspace:*
version: link:../../packages/parse-wanted-dependency
@@ -6270,6 +6279,9 @@ importers:
'@pnpm/npm-resolver':
specifier: workspace:*
version: link:../../resolving/npm-resolver
'@pnpm/parse-overrides':
specifier: workspace:*
version: link:../../config/parse-overrides
'@pnpm/pick-registry-for-package':
specifier: workspace:*
version: link:../../config/pick-registry-for-package

View File

@@ -46,6 +46,7 @@
"@pnpm/matcher": "workspace:*",
"@pnpm/modules-yaml": "workspace:*",
"@pnpm/npm-resolver": "workspace:*",
"@pnpm/parse-overrides": "workspace:*",
"@pnpm/pick-registry-for-package": "workspace:*",
"@pnpm/types": "workspace:*",
"ramda": "catalog:",

View File

@@ -28,6 +28,7 @@ import * as dp from '@pnpm/dependency-path'
import semver from 'semver'
import { createMatcher } from '@pnpm/matcher'
import { createReadPackageHook } from '@pnpm/hooks.read-package-hook'
import { parseOverrides } from '@pnpm/parse-overrides'
export * from './createManifestGetter'
@@ -69,7 +70,7 @@ export async function outdated (
if (overrides) {
const readPackageHook = createReadPackageHook({
lockfileDir: opts.lockfileDir,
overrides,
overrides: parseOverrides(overrides, opts.catalogs ?? {}),
})
const manifest = await readPackageHook?.(opts.manifest, opts.lockfileDir)
if (manifest) return manifest

View File

@@ -18,6 +18,9 @@
{
"path": "../../config/matcher"
},
{
"path": "../../config/parse-overrides"
},
{
"path": "../../config/pick-registry-for-package"
},