From 25f0fa9fa7c28473d2e0b9e0916c408b4b3cc433 Mon Sep 17 00:00:00 2001 From: Zoltan Kochan Date: Thu, 9 Dec 2021 00:15:41 +0200 Subject: [PATCH] feat(core): add a new function for finding missing peer dependencies (#4092) --- .changeset/fifty-wolves-bathe.md | 5 ++ .changeset/quick-apes-fetch.md | 5 ++ .changeset/tasty-kiwis-boil.md | 5 ++ packages/core/src/api.ts | 1 + packages/core/src/install/index.ts | 4 +- packages/core/src/listMissingPeers.ts | 83 +++++++++++++++++++++ packages/core/test/listMissingPeers.test.ts | 20 +++++ packages/get-context/src/index.ts | 48 ++++++------ packages/resolve-dependencies/src/index.ts | 1 + 9 files changed, 147 insertions(+), 25 deletions(-) create mode 100644 .changeset/fifty-wolves-bathe.md create mode 100644 .changeset/quick-apes-fetch.md create mode 100644 .changeset/tasty-kiwis-boil.md create mode 100644 packages/core/src/listMissingPeers.ts create mode 100644 packages/core/test/listMissingPeers.test.ts diff --git a/.changeset/fifty-wolves-bathe.md b/.changeset/fifty-wolves-bathe.md new file mode 100644 index 0000000000..ea217e7a2c --- /dev/null +++ b/.changeset/fifty-wolves-bathe.md @@ -0,0 +1,5 @@ +--- +"@pnpm/resolve-dependencies": minor +--- + +`resolveDependencies()` should return `peerDependenciesIssues`. diff --git a/.changeset/quick-apes-fetch.md b/.changeset/quick-apes-fetch.md new file mode 100644 index 0000000000..994e6d4b4a --- /dev/null +++ b/.changeset/quick-apes-fetch.md @@ -0,0 +1,5 @@ +--- +"@pnpm/get-context": minor +--- + +Export `GetContextOptions`. diff --git a/.changeset/tasty-kiwis-boil.md b/.changeset/tasty-kiwis-boil.md new file mode 100644 index 0000000000..80c4dc175d --- /dev/null +++ b/.changeset/tasty-kiwis-boil.md @@ -0,0 +1,5 @@ +--- +"@pnpm/core": minor +--- + +New function added to the core API: `listMissingPeers()`. diff --git a/packages/core/src/api.ts b/packages/core/src/api.ts index ff629790d2..f0a143f3d0 100644 --- a/packages/core/src/api.ts +++ b/packages/core/src/api.ts @@ -2,6 +2,7 @@ import link from './link' export * from './install' export * from './link' +export * from './listMissingPeers' export { link, } diff --git a/packages/core/src/install/index.ts b/packages/core/src/install/index.ts index e40f16c34f..390bd8e27a 100644 --- a/packages/core/src/install/index.ts +++ b/packages/core/src/install/index.ts @@ -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 readPackageHook?: ReadPackageHook } -) { +): ReadPackageHook | undefined { const hooks: ReadPackageHook[] = [] if (!isEmpty(overrides ?? {})) { hooks.push(createVersionsOverrider(overrides!, lockfileDir)) diff --git a/packages/core/src/listMissingPeers.ts b/packages/core/src/listMissingPeers.ts new file mode 100644 index 0000000000..10bf6e6779 --- /dev/null +++ b/packages/core/src/listMissingPeers.ts @@ -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 +& Pick +& Pick + +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 +} diff --git a/packages/core/test/listMissingPeers.test.ts b/packages/core/test/listMissingPeers.test.ts new file mode 100644 index 0000000000..a547dcdcb7 --- /dev/null +++ b/packages/core/test/listMissingPeers.test.ts @@ -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) +}) diff --git a/packages/get-context/src/index.ts b/packages/get-context/src/index.ts index d11cc54f59..9b8d563026 100644 --- a/packages/get-context/src/index.ts +++ b/packages/get-context/src/index.ts @@ -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 ( projects: Array, - 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> { const modulesDir = opts.modulesDir ?? 'node_modules' let importersContext = await readProjectsContext(projects, { lockfileDir: opts.lockfileDir, modulesDir }) diff --git a/packages/resolve-dependencies/src/index.ts b/packages/resolve-dependencies/src/index.ts index d8972ebaba..95f3f8d710 100644 --- a/packages/resolve-dependencies/src/index.ts +++ b/packages/resolve-dependencies/src/index.ts @@ -220,6 +220,7 @@ export default async function ( outdatedDependencies, linkedDependenciesByProjectId, newLockfile, + peerDependencyIssues, waitTillAllFetchingsFinish, wantedToBeSkippedPackageIds, }