feat(core): add a new function for finding missing peer dependencies (#4092)

This commit is contained in:
Zoltan Kochan
2021-12-09 00:15:41 +02:00
committed by GitHub
parent ae32d313e4
commit 25f0fa9fa7
9 changed files with 147 additions and 25 deletions

View File

@@ -0,0 +1,5 @@
---
"@pnpm/resolve-dependencies": minor
---
`resolveDependencies()` should return `peerDependenciesIssues`.

View File

@@ -0,0 +1,5 @@
---
"@pnpm/get-context": minor
---
Export `GetContextOptions`.

View File

@@ -0,0 +1,5 @@
---
"@pnpm/core": minor
---
New function added to the core API: `listMissingPeers()`.

View File

@@ -2,6 +2,7 @@ import link from './link'
export * from './install'
export * from './link'
export * from './listMissingPeers'
export {
link,
}

View File

@@ -511,7 +511,7 @@ export function createObjectChecksum (obj: Object) {
return crypto.createHash('md5').update(s).digest('hex')
}
function createReadPackageHook (
export function createReadPackageHook (
{
lockfileDir,
overrides,
@@ -523,7 +523,7 @@ function createReadPackageHook (
packageExtensions?: Record<string, PackageExtension>
readPackageHook?: ReadPackageHook
}
) {
): ReadPackageHook | undefined {
const hooks: ReadPackageHook[] = []
if (!isEmpty(overrides ?? {})) {
hooks.push(createVersionsOverrider(overrides!, lockfileDir))

View File

@@ -0,0 +1,83 @@
import resolveDependencies from '@pnpm/resolve-dependencies'
import getWantedDependencies from '@pnpm/resolve-dependencies/lib/getWantedDependencies'
import getContext, { GetContextOptions, ProjectOptions } from '@pnpm/get-context'
import { createReadPackageHook } from './install'
import { getPreferredVersionsFromLockfile } from './install/getPreferredVersions'
import { InstallOptions } from './install/extendInstallOptions'
import { DEFAULT_REGISTRIES } from '@pnpm/normalize-registries'
export type ListMissingPeersOptions = Partial<GetContextOptions>
& Pick<InstallOptions, 'hooks'
| 'linkWorkspacePackagesDepth'
| 'nodeVersion'
| 'overrides'
| 'packageExtensions'
| 'preferWorkspacePackages'
| 'saveWorkspaceProtocol'
| 'storeController'
| 'workspacePackages'
>
& Pick<GetContextOptions, 'storeDir'>
export async function listMissingPeers (
projects: ProjectOptions[],
opts: ListMissingPeersOptions
) {
const lockfileDir = opts.lockfileDir ?? process.cwd()
const ctx = await getContext(projects, {
force: false,
forceSharedLockfile: false,
extraBinPaths: [],
lockfileDir,
registries: DEFAULT_REGISTRIES,
useLockfile: true,
...opts,
})
const projectsToResolve = ctx.projects.map((project) => ({
...project,
updatePackageManifest: false,
wantedDependencies: getWantedDependencies(project.manifest),
}))
const preferredVersions = ctx.wantedLockfile.packages ? getPreferredVersionsFromLockfile(ctx.wantedLockfile.packages) : undefined
const {
peerDependencyIssues,
waitTillAllFetchingsFinish,
} = await resolveDependencies(
projectsToResolve,
{
currentLockfile: ctx.currentLockfile,
defaultUpdateDepth: -1,
dryRun: true,
engineStrict: false,
force: false,
forceFullResolution: true,
hooks: {
readPackage: createReadPackageHook({
lockfileDir,
overrides: opts.overrides,
packageExtensions: opts.packageExtensions,
readPackageHook: opts.hooks?.readPackage,
}),
},
linkWorkspacePackagesDepth: opts.linkWorkspacePackagesDepth ?? (opts.saveWorkspaceProtocol ? 0 : -1),
lockfileDir,
nodeVersion: opts.nodeVersion ?? process.version,
pnpmVersion: '',
preferWorkspacePackages: opts.preferWorkspacePackages,
preferredVersions,
preserveWorkspaceProtocol: false,
registries: ctx.registries,
saveWorkspaceProtocol: false, // this doesn't matter in our case. We won't write changes to package.json files
storeController: opts.storeController,
strictPeerDependencies: false,
tag: 'latest',
virtualStoreDir: ctx.virtualStoreDir,
wantedLockfile: ctx.wantedLockfile,
workspacePackages: opts.workspacePackages ?? {},
}
)
await waitTillAllFetchingsFinish()
return peerDependencyIssues
}

View File

@@ -0,0 +1,20 @@
import { listMissingPeers } from '@pnpm/core'
import { prepareEmpty } from '@pnpm/prepare'
import { testDefaults } from './utils'
test('cannot resolve peer dependency for top-level dependency', async () => {
prepareEmpty()
const peerDependencyIssues = await listMissingPeers([
{
manifest: {
dependencies: {
'ajv-keywords': '1.5.0',
},
},
rootDir: process.cwd(),
},
], await testDefaults())
expect(peerDependencyIssues.length).toBe(1)
})

View File

@@ -65,31 +65,33 @@ interface HookOptions {
originalManifest?: ProjectManifest
}
export interface GetContextOptions {
force: boolean
forceNewModules?: boolean
forceSharedLockfile: boolean
frozenLockfile?: boolean
extraBinPaths: string[]
lockfileDir: string
modulesDir?: string
hooks?: {
readPackage?: ReadPackageHook
}
include?: IncludedDependencies
registries: Registries
storeDir: string
useLockfile: boolean
virtualStoreDir?: string
hoistPattern?: string[] | undefined
forceHoistPattern?: boolean
publicHoistPattern?: string[] | undefined
forcePublicHoistPattern?: boolean
}
export default async function getContext<T> (
projects: Array<ProjectOptions & HookOptions & T>,
opts: {
force: boolean
forceNewModules?: boolean
forceSharedLockfile: boolean
frozenLockfile?: boolean
extraBinPaths: string[]
lockfileDir: string
modulesDir?: string
hooks?: {
readPackage?: ReadPackageHook
}
include?: IncludedDependencies
registries: Registries
storeDir: string
useLockfile: boolean
virtualStoreDir?: string
hoistPattern?: string[] | undefined
forceHoistPattern?: boolean
publicHoistPattern?: string[] | undefined
forcePublicHoistPattern?: boolean
}
opts: GetContextOptions
): Promise<PnpmContext<T>> {
const modulesDir = opts.modulesDir ?? 'node_modules'
let importersContext = await readProjectsContext(projects, { lockfileDir: opts.lockfileDir, modulesDir })

View File

@@ -220,6 +220,7 @@ export default async function (
outdatedDependencies,
linkedDependenciesByProjectId,
newLockfile,
peerDependencyIssues,
waitTillAllFetchingsFinish,
wantedToBeSkippedPackageIds,
}