Files
pnpm/pnpm11/installing/commands/test/parseYarnPatchSpecifier.test.ts
Altay f168f8514a fix(import): convert yarn patch: dependencies into patchedDependencies (#15449)
`pnpm import` copied a Yarn `patch:` specifier, such as
`patch:foo@npm%3A1.2.3#~/.yarn/patches/foo.patch`, into the manifest and
the lockfile as a `link:` to a directory that does not exist, so the
install that followed had no package.

Import now rewrites each such specifier in the imported projects to the
range it patches and records the patch file in the `patchedDependencies`
of pnpm-workspace.yaml, keyed `name@version` for a semver range and by
name otherwise. A `~/` patch path is relative to the Yarn project root,
any other to the declaring project. An existing `patchedDependencies`
entry wins, with a warning naming the skipped patch. When the patch file is missing, import warns and keeps the
unpatched dependency. Yarn's builtin compatibility patches are skipped,
and a dependency with several patch files is imported unpatched with a
warning, because pnpm applies one patch per dependency.

Closes pnpm/pnpm#10278
2026-09-24 09:27:27 +02:00

47 lines
1.8 KiB
TypeScript

import { expect, test } from '@jest/globals'
import { parseYarnPatchSpecifier } from '../src/import/yarnPatches.js'
test('parses a yarn 4 patch of a registry version', () => {
expect(parseYarnPatchSpecifier('patch:jest-runtime@npm%3A29.7.0#~/.yarn/patches/jest-runtime-npm-29.7.0-120fa64128.patch')).toStrictEqual({
specifier: '29.7.0',
patchKey: 'jest-runtime@29.7.0',
patchPaths: ['~/.yarn/patches/jest-runtime-npm-29.7.0-120fa64128.patch'],
})
})
test('parses a scoped patch with yarn 3 parameters', () => {
expect(parseYarnPatchSpecifier('patch:@scope/pkg@npm%3A%5E1.2.0#./.yarn/patches/pkg.patch::version=1.2.3&hash=abc&locator=root%40workspace%3A.')).toStrictEqual({
specifier: '^1.2.0',
patchKey: '@scope/pkg@^1.2.0',
patchPaths: ['./.yarn/patches/pkg.patch'],
})
})
test('keys an aliased patch by the real package', () => {
expect(parseYarnPatchSpecifier('patch:foo@npm%3A@scope/bar@1.0.0#~/foo.patch')).toStrictEqual({
specifier: 'npm:@scope/bar@1.0.0',
patchKey: '@scope/bar@1.0.0',
patchPaths: ['~/foo.patch'],
})
})
test('keys a non-registry patch by name', () => {
expect(parseYarnPatchSpecifier('patch:foo@https%3A//example.com/foo.tgz#~/foo.patch')).toStrictEqual({
specifier: 'https://example.com/foo.tgz',
patchKey: 'foo',
patchPaths: ['~/foo.patch'],
})
})
test('splits several patches and skips yarn builtins', () => {
expect(parseYarnPatchSpecifier('patch:typescript@npm%3A5.0.0#optional!builtin<compat/typescript>&./a.patch&./b.patch')?.patchPaths)
.toStrictEqual(['./a.patch', './b.patch'])
expect(parseYarnPatchSpecifier('patch:resolve@npm%3A1.22.0#builtin<compat/resolve>')?.patchPaths).toStrictEqual([])
})
test('ignores other protocols', () => {
expect(parseYarnPatchSpecifier('npm:foo@1.0.0')).toBeUndefined()
expect(parseYarnPatchSpecifier('^1.0.0')).toBeUndefined()
})