fix: warn when linking a package with peerDeps (#6956)

close #615
This commit is contained in:
Nacho Aldama
2023-08-22 11:58:38 +02:00
committed by GitHub
parent f432cb11ac
commit e0474bc4c3
3 changed files with 130 additions and 10 deletions

View File

@@ -0,0 +1,6 @@
---
"@pnpm/plugin-commands-installation": patch
"pnpm": patch
---
Warn when linking a package with peerDependencies [615](https://github.com/pnpm/pnpm/issues/615).

View File

@@ -21,6 +21,7 @@ import {
type LinkFunctionOptions,
type WorkspacePackages,
} from '@pnpm/core'
import { logger } from '@pnpm/logger'
import pLimit from 'p-limit'
import pathAbsolute from 'path-absolute'
import pick from 'ramda/src/pick'
@@ -35,6 +36,17 @@ const isWindows = process.platform === 'win32' || global['FAKE_WINDOWS']
const isFilespec = isWindows ? /^(?:[.]|~[/]|[/\\]|[a-zA-Z]:)/ : /^(?:[.]|~[/]|[/]|[a-zA-Z]:)/
const installLimit = pLimit(4)
type LinkOpts = CreateStoreControllerOptions & Pick<Config,
| 'bin'
| 'cliOptions'
| 'engineStrict'
| 'saveDev'
| 'saveOptional'
| 'saveProd'
| 'workspaceDir'
| 'sharedWorkspaceLockfile'
> & Partial<Pick<Config, 'linkWorkspacePackages'>>
export const rcOptionsTypes = cliOptionsTypes
export function cliOptionsTypes () {
@@ -82,17 +94,29 @@ export function help () {
})
}
async function checkPeerDeps (linkCwdDir: string, opts: LinkOpts) {
const { manifest } = await tryReadProjectManifest(linkCwdDir, opts)
if (manifest?.peerDependencies && Object.keys(manifest.peerDependencies).length > 0) {
const packageName = manifest.name ?? path.basename(linkCwdDir) // Assuming the name property exists in newManifest
const peerDeps = Object.entries(manifest.peerDependencies)
.map(([key, value]) => ` - ${key}@${value}`)
.join(', ')
logger.warn({
message: `The package ${packageName}, which you have just pnpm linked, has the following peerDependencies specified in its package.json:
${peerDeps}
The linked in dependency will not resolve the peer dependencies from the target node_modules.
This might cause issues in your project. To resolve this, you may use the "file:" protocol to reference the local dependency.`,
prefix: opts.dir,
})
}
}
export async function handler (
opts: CreateStoreControllerOptions & Pick<Config,
| 'bin'
| 'cliOptions'
| 'engineStrict'
| 'saveDev'
| 'saveOptional'
| 'saveProd'
| 'workspaceDir'
| 'sharedWorkspaceLockfile'
> & Partial<Pick<Config, 'linkWorkspacePackages'>>,
opts: LinkOpts,
params?: string[]
) {
const cwd = process.cwd()
@@ -122,6 +146,9 @@ export async function handler (
if (path.relative(linkOpts.dir, cwd) === '') {
throw new PnpmError('LINK_BAD_PARAMS', 'You must provide a parameter')
}
await checkPeerDeps(linkCwdDir, opts)
const { manifest, writeProjectManifest } = await tryReadProjectManifest(opts.dir, opts)
const newManifest = await addDependenciesToPackage(
manifest ?? {},
@@ -185,6 +212,12 @@ export async function handler (
const { manifest, writeProjectManifest } = await readProjectManifest(linkCwdDir, opts)
await Promise.all(
pkgPaths.map(async (dir) => {
await checkPeerDeps(dir, opts)
})
)
const linkConfig = await getConfig(
{ ...opts.cliOptions, dir: cwd },
{

View File

@@ -5,6 +5,7 @@ import { install, link } from '@pnpm/plugin-commands-installation'
import { prepare, preparePackages } from '@pnpm/prepare'
import { assertProject, isExecutable } from '@pnpm/assert-project'
import { fixtures } from '@pnpm/test-fixtures'
import { logger } from '@pnpm/logger'
import { sync as loadJsonFile } from 'load-json-file'
import PATH from 'path-name'
import writePkg from 'write-pkg'
@@ -276,3 +277,83 @@ test('link fails if nothing is linked', async () => {
}, [])
).rejects.toThrow(/You must provide a parameter/)
})
test('logger warns about peer dependencies when linking', async () => {
prepare()
const warnMock = jest.spyOn(logger, 'warn')
process.chdir('..')
const globalDir = path.resolve('global')
await writePkg('linked-with-peer-deps', {
name: 'linked-with-peer-deps',
version: '1.0.0',
peerDependencies: {
'some-peer-dependency': '1.0.0',
},
})
process.chdir('linked-with-peer-deps')
const linkOpts = {
...DEFAULT_OPTS,
bin: path.join(globalDir, 'bin'),
dir: globalDir,
}
await link.handler({
...linkOpts,
})
process.chdir('..')
process.chdir('project')
await link.handler({
...linkOpts,
}, ['linked-with-peer-deps'])
expect(warnMock).toHaveBeenCalledWith(expect.objectContaining({
message: expect.stringContaining('has the following peerDependencies specified in its package.json'),
}))
warnMock.mockRestore()
})
test('logger should not warn about peer dependencies when it is an empty object', async () => {
prepare()
const warnMock = jest.spyOn(logger, 'warn')
process.chdir('..')
const globalDir = path.resolve('global')
await writePkg('linked-with-empty-peer-deps', {
name: 'linked-with-empty-peer-deps',
version: '1.0.0',
peerDependencies: {},
})
process.chdir('linked-with-empty-peer-deps')
const linkOpts = {
...DEFAULT_OPTS,
bin: path.join(globalDir, 'bin'),
dir: globalDir,
}
await link.handler({
...linkOpts,
})
process.chdir('..')
process.chdir('project')
await link.handler({
...linkOpts,
}, ['linked-with-empty-peer-deps'])
expect(warnMock).not.toHaveBeenCalledWith(expect.objectContaining({
message: expect.stringContaining('has the following peerDependencies specified in its package.json'),
}))
warnMock.mockRestore()
})