`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
47 lines
1.8 KiB
TypeScript
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()
|
|
})
|