mirror of
https://github.com/pnpm/pnpm.git
synced 2026-03-31 21:42:15 -04:00
fix(manifest-utils): normalize peer specs for protocol deps (#10442)
close #10417
This commit is contained in:
committed by
Zoltan Kochan
parent
15e83b35a2
commit
d58bdaf73f
7
.changeset/sharp-planes-boil.md
Normal file
7
.changeset/sharp-planes-boil.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@pnpm/manifest-utils": patch
|
||||
"@pnpm/resolve-dependencies": patch
|
||||
pnpm: patch
|
||||
---
|
||||
|
||||
Fix `--save-peer` to write valid semver ranges to `peerDependencies` for protocol-based installs (e.g. `jsr:`) by deriving from resolved versions when available and falling back to `*` if none is available [#10417](https://github.com/pnpm/pnpm/issues/10417).
|
||||
1
.eslintcache
Normal file
1
.eslintcache
Normal file
File diff suppressed because one or more lines are too long
@@ -61,6 +61,24 @@ test('pnpm add jsr:@<scope>/<name>', async () => {
|
||||
} as Partial<LockfileFile>)
|
||||
})
|
||||
|
||||
test('pnpm add jsr:@<scope>/<name> --save-peer writes a valid peer range', async () => {
|
||||
prepare()
|
||||
|
||||
await add.handler({
|
||||
...createOptions(),
|
||||
savePeer: true,
|
||||
}, ['jsr:@pnpm-e2e/foo'])
|
||||
|
||||
expect(loadJsonFileSync('package.json')).toMatchObject({
|
||||
devDependencies: {
|
||||
'@pnpm-e2e/foo': 'jsr:^0.1.0',
|
||||
},
|
||||
peerDependencies: {
|
||||
'@pnpm-e2e/foo': '^0.1.0',
|
||||
},
|
||||
} as ProjectManifest)
|
||||
})
|
||||
|
||||
test('pnpm add jsr:@<scope>/<name>@latest', async () => {
|
||||
const project = prepare({
|
||||
name: 'test-add-jsr',
|
||||
|
||||
@@ -26,6 +26,8 @@ export async function updateProjectManifest (
|
||||
nodeExecPath: wantedDep.nodeExecPath,
|
||||
peer: importer.peer,
|
||||
bareSpecifier: rdd.catalogLookup?.userSpecifiedBareSpecifier ?? rdd.normalizedBareSpecifier ?? wantedDep.bareSpecifier,
|
||||
resolvedVersion: rdd.version,
|
||||
pinnedVersion: importer.pinnedVersion,
|
||||
saveType: importer.targetDependenciesField,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -34,14 +34,17 @@
|
||||
"dependencies": {
|
||||
"@pnpm/core-loggers": "workspace:*",
|
||||
"@pnpm/error": "workspace:*",
|
||||
"@pnpm/types": "workspace:*"
|
||||
"@pnpm/semver.peer-range": "workspace:*",
|
||||
"@pnpm/types": "workspace:*",
|
||||
"semver": "catalog:"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@pnpm/logger": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@pnpm/logger": "workspace:*",
|
||||
"@pnpm/manifest-utils": "workspace:*"
|
||||
"@pnpm/manifest-utils": "workspace:*",
|
||||
"@types/semver": "catalog:"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.12"
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { packageManifestLogger } from '@pnpm/core-loggers'
|
||||
import { isValidPeerRange } from '@pnpm/semver.peer-range'
|
||||
import semver from 'semver'
|
||||
import {
|
||||
type DependenciesOrPeersField,
|
||||
type DependenciesField,
|
||||
DEPENDENCIES_FIELDS,
|
||||
DEPENDENCIES_OR_PEER_FIELDS,
|
||||
type PinnedVersion,
|
||||
type ProjectManifest,
|
||||
} from '@pnpm/types'
|
||||
|
||||
@@ -12,9 +15,36 @@ export interface PackageSpecObject {
|
||||
nodeExecPath?: string
|
||||
peer?: boolean
|
||||
bareSpecifier?: string
|
||||
resolvedVersion?: string
|
||||
pinnedVersion?: PinnedVersion
|
||||
saveType?: DependenciesField
|
||||
}
|
||||
|
||||
function getPeerSpecifier (spec: string, resolvedVersion?: string, pinnedVersion?: PinnedVersion): string {
|
||||
if (isValidPeerRange(spec)) return spec
|
||||
|
||||
const rangeFromResolved = resolvedVersion ? createVersionSpecFromResolvedVersion(resolvedVersion, pinnedVersion) : null
|
||||
return rangeFromResolved ?? '*'
|
||||
}
|
||||
|
||||
function createVersionSpecFromResolvedVersion (resolvedVersion: string, pinnedVersion?: PinnedVersion): string | null {
|
||||
const parsed = semver.parse(resolvedVersion)
|
||||
if (!parsed) return null
|
||||
if (parsed.prerelease.length) return resolvedVersion
|
||||
|
||||
switch (pinnedVersion ?? 'major') {
|
||||
case 'none':
|
||||
case 'major':
|
||||
return `^${resolvedVersion}`
|
||||
case 'minor':
|
||||
return `~${resolvedVersion}`
|
||||
case 'patch':
|
||||
return resolvedVersion
|
||||
default:
|
||||
return `^${resolvedVersion}`
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateProjectManifestObject (
|
||||
prefix: string,
|
||||
packageManifest: ProjectManifest,
|
||||
@@ -33,7 +63,11 @@ export async function updateProjectManifestObject (
|
||||
}
|
||||
if (packageSpec.peer === true) {
|
||||
packageManifest.peerDependencies = packageManifest.peerDependencies ?? {}
|
||||
packageManifest.peerDependencies[packageSpec.alias] = spec
|
||||
packageManifest.peerDependencies[packageSpec.alias] = getPeerSpecifier(
|
||||
spec,
|
||||
packageSpec.resolvedVersion,
|
||||
packageSpec.pinnedVersion
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if (packageSpec.bareSpecifier) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { guessDependencyType } from '@pnpm/manifest-utils'
|
||||
import { guessDependencyType, updateProjectManifestObject } from '@pnpm/manifest-utils'
|
||||
|
||||
test('guessDependencyType()', () => {
|
||||
expect(
|
||||
@@ -23,3 +23,144 @@ test('guessDependencyType()', () => {
|
||||
})
|
||||
).toEqual('dependencies')
|
||||
})
|
||||
|
||||
test('peer dependencies fall back to "*" when resolved version is unavailable (git)', async () => {
|
||||
const manifest = await updateProjectManifestObject('/project', {}, [
|
||||
{
|
||||
alias: 'foo',
|
||||
bareSpecifier: 'https://github.com/kevva/is-negative',
|
||||
peer: true,
|
||||
saveType: 'devDependencies',
|
||||
},
|
||||
])
|
||||
|
||||
expect(manifest.devDependencies).toStrictEqual({
|
||||
foo: 'https://github.com/kevva/is-negative',
|
||||
})
|
||||
expect(manifest.peerDependencies).toStrictEqual({
|
||||
foo: '*',
|
||||
})
|
||||
})
|
||||
|
||||
test('peer dependencies fall back to "*" when resolved version is unavailable (tarball)', async () => {
|
||||
const manifest = await updateProjectManifestObject('/project', {}, [
|
||||
{
|
||||
alias: 'foo',
|
||||
bareSpecifier: 'https://github.com/hegemonic/taffydb/tarball/master',
|
||||
peer: true,
|
||||
saveType: 'devDependencies',
|
||||
},
|
||||
])
|
||||
|
||||
expect(manifest.devDependencies).toStrictEqual({
|
||||
foo: 'https://github.com/hegemonic/taffydb/tarball/master',
|
||||
})
|
||||
expect(manifest.peerDependencies).toStrictEqual({
|
||||
foo: '*',
|
||||
})
|
||||
})
|
||||
|
||||
test('peer dependencies use derived range when resolved version is available (git)', async () => {
|
||||
const manifest = await updateProjectManifestObject('/project', {}, [
|
||||
{
|
||||
alias: 'foo',
|
||||
bareSpecifier: 'https://github.com/kevva/is-negative',
|
||||
resolvedVersion: '2.1.0',
|
||||
peer: true,
|
||||
saveType: 'devDependencies',
|
||||
},
|
||||
])
|
||||
|
||||
expect(manifest.devDependencies).toStrictEqual({
|
||||
foo: 'https://github.com/kevva/is-negative',
|
||||
})
|
||||
expect(manifest.peerDependencies).toStrictEqual({
|
||||
foo: '^2.1.0',
|
||||
})
|
||||
})
|
||||
|
||||
test('peer dependencies honor pinned version when resolved version is available (tarball)', async () => {
|
||||
const manifest = await updateProjectManifestObject('/project', {}, [
|
||||
{
|
||||
alias: 'foo',
|
||||
bareSpecifier: 'https://github.com/hegemonic/taffydb/tarball/master',
|
||||
resolvedVersion: '1.4.0',
|
||||
pinnedVersion: 'minor',
|
||||
peer: true,
|
||||
saveType: 'devDependencies',
|
||||
},
|
||||
])
|
||||
|
||||
expect(manifest.devDependencies).toStrictEqual({
|
||||
foo: 'https://github.com/hegemonic/taffydb/tarball/master',
|
||||
})
|
||||
expect(manifest.peerDependencies).toStrictEqual({
|
||||
foo: '~1.4.0',
|
||||
})
|
||||
})
|
||||
|
||||
test('peer dependencies derive range from resolved version for jsr protocol', async () => {
|
||||
const manifest = await updateProjectManifestObject('/project', {}, [
|
||||
{
|
||||
alias: 'foo',
|
||||
bareSpecifier: 'jsr:^0.1.0',
|
||||
resolvedVersion: '0.1.0',
|
||||
peer: true,
|
||||
saveType: 'devDependencies',
|
||||
},
|
||||
])
|
||||
|
||||
expect(manifest.devDependencies).toStrictEqual({
|
||||
foo: 'jsr:^0.1.0',
|
||||
})
|
||||
expect(manifest.peerDependencies).toStrictEqual({
|
||||
foo: '^0.1.0',
|
||||
})
|
||||
})
|
||||
|
||||
test('peer dependencies keep prerelease resolved version without prefix', async () => {
|
||||
const manifest = await updateProjectManifestObject('/project', {}, [
|
||||
{
|
||||
alias: 'foo',
|
||||
bareSpecifier: 'https://github.com/kevva/is-negative',
|
||||
resolvedVersion: '2.1.0-rc.1',
|
||||
pinnedVersion: 'minor',
|
||||
peer: true,
|
||||
saveType: 'devDependencies',
|
||||
},
|
||||
])
|
||||
|
||||
expect(manifest.devDependencies).toStrictEqual({
|
||||
foo: 'https://github.com/kevva/is-negative',
|
||||
})
|
||||
expect(manifest.peerDependencies).toStrictEqual({
|
||||
foo: '2.1.0-rc.1',
|
||||
})
|
||||
})
|
||||
|
||||
test('peer dependencies respect pinned version "patch" and "none"', async () => {
|
||||
const cases = [
|
||||
{ pinnedVersion: 'patch' as const, expected: '3.2.1' },
|
||||
{ pinnedVersion: 'none' as const, expected: '^3.2.1' },
|
||||
]
|
||||
|
||||
await Promise.all(cases.map(async ({ pinnedVersion, expected }) => {
|
||||
const manifest = await updateProjectManifestObject('/project', {}, [
|
||||
{
|
||||
alias: 'foo',
|
||||
bareSpecifier: 'https://github.com/kevva/is-negative',
|
||||
resolvedVersion: '3.2.1',
|
||||
pinnedVersion,
|
||||
peer: true,
|
||||
saveType: 'devDependencies',
|
||||
},
|
||||
])
|
||||
|
||||
expect(manifest.devDependencies).toStrictEqual({
|
||||
foo: 'https://github.com/kevva/is-negative',
|
||||
})
|
||||
expect(manifest.peerDependencies).toStrictEqual({
|
||||
foo: expected,
|
||||
})
|
||||
}))
|
||||
})
|
||||
|
||||
@@ -20,6 +20,9 @@
|
||||
},
|
||||
{
|
||||
"path": "../../packages/types"
|
||||
},
|
||||
{
|
||||
"path": "../../semver/peer-range"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
9
pnpm-lock.yaml
generated
9
pnpm-lock.yaml
generated
@@ -6286,9 +6286,15 @@ importers:
|
||||
'@pnpm/error':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/error
|
||||
'@pnpm/semver.peer-range':
|
||||
specifier: workspace:*
|
||||
version: link:../../semver/peer-range
|
||||
'@pnpm/types':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/types
|
||||
semver:
|
||||
specifier: 'catalog:'
|
||||
version: 7.7.2
|
||||
devDependencies:
|
||||
'@pnpm/logger':
|
||||
specifier: workspace:*
|
||||
@@ -6296,6 +6302,9 @@ importers:
|
||||
'@pnpm/manifest-utils':
|
||||
specifier: workspace:*
|
||||
version: 'link:'
|
||||
'@types/semver':
|
||||
specifier: 'catalog:'
|
||||
version: 7.5.3
|
||||
|
||||
pkg-manifest/read-package-json:
|
||||
dependencies:
|
||||
|
||||
Reference in New Issue
Block a user