diff --git a/lib/modules/manager/npm/extract/index.spec.ts b/lib/modules/manager/npm/extract/index.spec.ts
index 8d596855174d1946b6c3f864503d07dd13cfe2e1..9d1ebbb87611c35dd9aa96ca6307312ea17fc9fa 100644
--- a/lib/modules/manager/npm/extract/index.spec.ts
+++ b/lib/modules/manager/npm/extract/index.spec.ts
@@ -880,6 +880,53 @@ describe('modules/manager/npm/extract/index', () => {
     });
   });
 
+  describe('.extractAllPackageFiles()', () => {
+    it('runs', async () => {
+      fs.readLocalFile.mockResolvedValueOnce(input02Content);
+      const res = await npmExtract.extractAllPackageFiles(
+        defaultExtractConfig,
+        ['package.json']
+      );
+      expect(res).toEqual([
+        {
+          deps: [
+            {
+              currentValue: '7.0.0',
+              datasource: 'npm',
+              depName: '@babel/core',
+              depType: 'dependencies',
+              prettyDepType: 'dependency',
+            },
+            {
+              currentValue: '1.21.0',
+              datasource: 'npm',
+              depName: 'config',
+              depType: 'dependencies',
+              prettyDepType: 'dependency',
+            },
+          ],
+          extractedConstraints: {},
+          managerData: {
+            hasPackageManager: false,
+            lernaClient: undefined,
+            lernaJsonFile: undefined,
+            lernaPackages: undefined,
+            npmLock: undefined,
+            packageJsonName: 'renovate',
+            pnpmShrinkwrap: undefined,
+            workspacesPackages: undefined,
+            yarnLock: undefined,
+            yarnZeroInstall: false,
+          },
+          npmrc: undefined,
+          packageFile: 'package.json',
+          packageFileVersion: '1.0.0',
+          skipInstalls: true,
+        },
+      ]);
+    });
+  });
+
   describe('.postExtract()', () => {
     it('runs', async () => {
       await expect(npmExtract.postExtract([])).resolves.not.toThrow();
diff --git a/lib/modules/manager/npm/extract/npm.ts b/lib/modules/manager/npm/extract/npm.ts
index 862cabfcbe63ae56610718d2bc45a7a9822bd5b9..db86b5288115eb91921b4de5bed431ca71d48c7e 100644
--- a/lib/modules/manager/npm/extract/npm.ts
+++ b/lib/modules/manager/npm/extract/npm.ts
@@ -1,6 +1,6 @@
 import { logger } from '../../../../logger';
 import { readLocalFile } from '../../../../util/fs';
-import { PackageLock } from './schema';
+import { PackageLock } from '../schema';
 import type { LockFile } from './types';
 
 export async function getNpmLock(filePath: string): Promise<LockFile> {
diff --git a/lib/modules/manager/npm/post-update/lerna.spec.ts b/lib/modules/manager/npm/post-update/lerna.spec.ts
index 96426a0f899ddfa2ff250c8d88780da7b00f85d7..f061b4344f78d1385ae4c020b0b2f0b934094d5d 100644
--- a/lib/modules/manager/npm/post-update/lerna.spec.ts
+++ b/lib/modules/manager/npm/post-update/lerna.spec.ts
@@ -1,5 +1,5 @@
 import { envMock, mockExecAll } from '../../../../../test/exec-util';
-import { env, mockedFunction, partial } from '../../../../../test/util';
+import { env, fs, mockedFunction, partial } from '../../../../../test/util';
 import { GlobalConfig } from '../../../../config/global';
 import type { RepoGlobalConfig } from '../../../../config/types';
 import type { PackageFileContent, PostUpdateConfig } from '../../types';
@@ -7,6 +7,7 @@ import * as lernaHelper from './lerna';
 import { getNodeToolConstraint } from './node-version';
 
 jest.mock('../../../../util/exec/env');
+jest.mock('../../../../util/fs');
 jest.mock('./node-version');
 jest.mock('../../../datasource');
 
@@ -14,20 +15,11 @@ process.env.CONTAINERBASE = 'true';
 
 function lernaPkgFile(lernaClient: string): Partial<PackageFileContent> {
   return {
-    deps: [{ depName: 'lerna', currentValue: '2.0.0' }],
     managerData: { lernaClient },
   };
 }
 
-function lernaPkgFileWithoutLernaDep(
-  lernaClient: string
-): Partial<PackageFileContent> {
-  return {
-    managerData: { lernaClient },
-  };
-}
-
-const config = partial<PostUpdateConfig>();
+const config = partial<PostUpdateConfig>({ constraints: { lerna: '2.0.0' } });
 
 describe('modules/manager/npm/post-update/lerna', () => {
   const globalConfig: RepoGlobalConfig = {
@@ -97,10 +89,13 @@ describe('modules/manager/npm/post-update/lerna', () => {
 
     it('generates yarn.lock files', async () => {
       const execSnapshots = mockExecAll();
+      fs.readLocalFile.mockResolvedValueOnce(
+        '{"packageManager":"yarn@^1.10.0"}'
+      );
       const res = await lernaHelper.generateLockFiles(
         lernaPkgFile('yarn'),
         'some-dir',
-        { ...config, extractedConstraints: { yarn: '^1.10.0' } },
+        { ...config },
         {}
       );
       expect(execSnapshots).toMatchSnapshot();
@@ -110,9 +105,9 @@ describe('modules/manager/npm/post-update/lerna', () => {
     it('defaults to latest and skips bootstrap if lerna version unspecified', async () => {
       const execSnapshots = mockExecAll();
       const res = await lernaHelper.generateLockFiles(
-        lernaPkgFileWithoutLernaDep('npm'),
+        lernaPkgFile('npm'),
         'some-dir',
-        config,
+        { ...config, constraints: null },
         {}
       );
       expect(res.error).toBeFalse();
@@ -125,7 +120,7 @@ describe('modules/manager/npm/post-update/lerna', () => {
       const res = await lernaHelper.generateLockFiles(
         lernaPkgFile('npm'),
         'some-dir',
-        { ...config, constraints: { npm: '^6.0.0' } },
+        { ...config, constraints: { ...config.constraints, npm: '^6.0.0' } },
         {}
       );
       expect(res.error).toBeFalse();
@@ -143,7 +138,7 @@ describe('modules/manager/npm/post-update/lerna', () => {
       const res = await lernaHelper.generateLockFiles(
         lernaPkgFile('npm'),
         'some-dir',
-        { ...config, constraints: { npm: '6.0.0' } },
+        { ...config, constraints: { ...config.constraints, npm: '6.0.0' } },
         {}
       );
       expect(execSnapshots).toMatchObject([
@@ -189,7 +184,7 @@ describe('modules/manager/npm/post-update/lerna', () => {
       const res = await lernaHelper.generateLockFiles(
         lernaPkgFile('npm'),
         'some-dir',
-        { ...config, constraints: { npm: '6.0.0' } },
+        { ...config, constraints: { ...config.constraints, npm: '6.0.0' } },
         {}
       );
       expect(res.error).toBeFalse();
@@ -222,42 +217,36 @@ describe('modules/manager/npm/post-update/lerna', () => {
 
   describe('getLernaVersion()', () => {
     it('returns specified version', () => {
-      const pkg = {
-        deps: [
-          { depName: 'lerna', currentValue: '^2.0.0', currentVersion: '2.0.0' },
-        ],
-      };
-      expect(lernaHelper.getLernaVersion(pkg)).toBe('2.0.0');
+      const pkg = {};
+      expect(
+        lernaHelper.getLernaVersion(pkg, { engines: { lerna: '2.0.0' } })
+      ).toBe('2.0.0');
     });
 
     it('returns specified range', () => {
-      const pkg = {
-        deps: [
-          { depName: 'lerna', currentValue: '1.x || >=2.5.0 || 5.0.0 - 7.2.3' },
-        ],
-      };
-      expect(lernaHelper.getLernaVersion(pkg)).toBe(
-        '1.x || >=2.5.0 || 5.0.0 - 7.2.3'
-      );
+      const pkg = {};
+      expect(
+        lernaHelper.getLernaVersion(pkg, {
+          engines: { lerna: '1.x || >=2.5.0 || 5.0.0 - 7.2.3' },
+        })
+      ).toBe('1.x || >=2.5.0 || 5.0.0 - 7.2.3');
     });
 
     it('returns latest if no lerna dep is specified', () => {
-      const pkg = {
-        deps: [{ depName: 'something-else', currentValue: '1.2.3' }],
-      };
-      expect(lernaHelper.getLernaVersion(pkg)).toBeNull();
+      const pkg = {};
+      expect(lernaHelper.getLernaVersion(pkg, {})).toBeNull();
     });
 
     it('returns latest if pkg has no deps at all', () => {
       const pkg = {};
-      expect(lernaHelper.getLernaVersion(pkg)).toBeNull();
+      expect(lernaHelper.getLernaVersion(pkg, {})).toBeNull();
     });
 
     it('returns latest if specified lerna version is not a valid semVer range', () => {
-      const pkg = {
-        deps: [{ depName: 'lerna', currentValue: '[a.b.c;' }],
-      };
-      expect(lernaHelper.getLernaVersion(pkg)).toBeNull();
+      const pkg = {};
+      expect(
+        lernaHelper.getLernaVersion(pkg, { engines: { lerna: '[a.b.c;' } })
+      ).toBeNull();
     });
   });
 });
diff --git a/lib/modules/manager/npm/post-update/lerna.ts b/lib/modules/manager/npm/post-update/lerna.ts
index b2f5bf21c0279690787d2c06e35bd4a96d815916..f65a052d477177cff0ef13dbeeb681b264b69853 100644
--- a/lib/modules/manager/npm/post-update/lerna.ts
+++ b/lib/modules/manager/npm/post-update/lerna.ts
@@ -15,16 +15,19 @@ import type {
   PackageFileContent,
   PostUpdateConfig,
 } from '../../types';
+import type { PackageJsonSchema } from '../schema';
 import type { NpmManagerData } from '../types';
 import { getNodeToolConstraint } from './node-version';
 import type { GenerateLockFileResult } from './types';
+import { getPackageManagerVersion, lazyLoadPackageJson } from './utils';
 
 // Exported for testability
 export function getLernaVersion(
-  lernaPackageFile: Partial<PackageFile<NpmManagerData>>
+  lernaPackageFile: Partial<PackageFile<NpmManagerData>>,
+  lazyPgkJson: PackageJsonSchema
 ): string | null {
-  const lernaDep = lernaPackageFile.deps?.find((d) => d.depName === 'lerna');
-  if (!lernaDep?.currentValue || !semver.validRange(lernaDep.currentValue)) {
+  const constraint = getPackageManagerVersion('lerna', lazyPgkJson);
+  if (!constraint || !semver.validRange(constraint)) {
     logger.warn(
       // TODO: types (#7154)
       // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
@@ -32,7 +35,7 @@ export function getLernaVersion(
     );
     return null;
   }
-  return lernaDep.currentVersion ?? lernaDep.currentValue;
+  return constraint;
 }
 
 export async function generateLockFiles(
@@ -48,19 +51,22 @@ export async function generateLockFiles(
     return { error: false };
   }
   logger.debug(`Spawning lerna with ${lernaClient} to create lock files`);
-  const toolConstraints: ToolConstraint[] = [
-    await getNodeToolConstraint(config, [], lockFileDir),
-  ];
+
   const cmd: string[] = [];
   let cmdOptions = '';
   try {
+    const lazyPgkJson = lazyLoadPackageJson(lockFileDir);
+    const toolConstraints: ToolConstraint[] = [
+      await getNodeToolConstraint(config, [], lockFileDir, lazyPgkJson),
+    ];
     if (lernaClient === 'yarn') {
       const yarnTool: ToolConstraint = {
         toolName: 'yarn',
         constraint: '^1.22.18', // needs to be a v1 yarn, otherwise v2 will be installed
       };
       const yarnCompatibility =
-        config.constraints?.yarn ?? config.extractedConstraints?.yarn;
+        config.constraints?.yarn ??
+        getPackageManagerVersion('yarn', await lazyPgkJson.getValue());
       if (semver.validRange(yarnCompatibility)) {
         yarnTool.constraint = yarnCompatibility;
       }
@@ -73,7 +79,8 @@ export async function generateLockFiles(
     } else if (lernaClient === 'npm') {
       const npmTool: ToolConstraint = { toolName: 'npm' };
       const npmCompatibility =
-        config.constraints?.npm ?? config.extractedConstraints?.npm;
+        config.constraints?.npm ??
+        getPackageManagerVersion('npm', await lazyPgkJson.getValue());
       if (semver.validRange(npmCompatibility)) {
         npmTool.constraint = npmCompatibility;
       }
@@ -107,7 +114,9 @@ export async function generateLockFiles(
       extraEnv.NPM_AUTH = env.NPM_AUTH;
       extraEnv.NPM_EMAIL = env.NPM_EMAIL;
     }
-    const lernaVersion = getLernaVersion(lernaPackageFile);
+    const lernaVersion =
+      config.constraints?.lerna ??
+      getLernaVersion(lernaPackageFile, await lazyPgkJson.getValue());
     if (
       !is.string(lernaVersion) ||
       (semver.valid(lernaVersion) && semver.gte(lernaVersion, '7.0.0'))
@@ -115,7 +124,7 @@ export async function generateLockFiles(
       logger.debug('Skipping lerna bootstrap');
       cmd.push(`${lernaClient} install ${cmdOptions}`);
     } else {
-      logger.debug(`Using lerna version ${String(lernaVersion)}`);
+      logger.debug(`Using lerna version ${lernaVersion}`);
       toolConstraints.push({ toolName: 'lerna', constraint: lernaVersion });
       cmd.push('lerna info || echo "Ignoring lerna info failure"');
       cmd.push(`${lernaClient} install ${cmdOptions}`);
diff --git a/lib/modules/manager/npm/post-update/node-version.spec.ts b/lib/modules/manager/npm/post-update/node-version.spec.ts
index b8c95ddcb7ec1bea0e625d44ba30c78f32dcf8d2..a951be3807fdd7dc94160711b997fc6f5bfbab94 100644
--- a/lib/modules/manager/npm/post-update/node-version.spec.ts
+++ b/lib/modules/manager/npm/post-update/node-version.spec.ts
@@ -1,4 +1,5 @@
 import { fs } from '../../../../../test/util';
+import { Lazy } from '../../../../util/lazy';
 import {
   getNodeConstraint,
   getNodeToolConstraint,
@@ -14,38 +15,60 @@ describe('modules/manager/npm/post-update/node-version', () => {
   };
 
   describe('getNodeConstraint()', () => {
-    it('returns package.json range', async () => {
-      fs.readLocalFile.mockResolvedValueOnce(null as never);
-      fs.readLocalFile.mockResolvedValueOnce(null as never);
-      const res = await getNodeConstraint(config, '');
+    it('returns from user constraints', async () => {
+      const res = await getNodeConstraint(
+        config,
+        [],
+        '',
+        new Lazy(() => Promise.resolve({}))
+      );
       expect(res).toBe('^12.16.0');
+      expect(fs.readLocalFile).not.toHaveBeenCalled();
     });
 
     it('returns .node-version value', async () => {
-      fs.readLocalFile.mockResolvedValueOnce(null as never);
+      fs.readLocalFile.mockResolvedValueOnce(null);
       fs.readLocalFile.mockResolvedValueOnce('12.16.1\n');
-      const res = await getNodeConstraint(config, '');
+      const res = await getNodeConstraint(
+        {},
+        [],
+        '',
+        new Lazy(() => Promise.resolve({}))
+      );
       expect(res).toBe('12.16.1');
     });
 
     it('returns .nvmrc value', async () => {
       fs.readLocalFile.mockResolvedValueOnce('12.16.2\n');
-      const res = await getNodeConstraint(config, '');
+      const res = await getNodeConstraint(
+        {},
+        [],
+        '',
+        new Lazy(() => Promise.resolve({}))
+      );
       expect(res).toBe('12.16.2');
     });
 
     it('ignores unusable ranges in dotfiles', async () => {
       fs.readLocalFile.mockResolvedValueOnce('latest');
       fs.readLocalFile.mockResolvedValueOnce('lts');
-      const res = await getNodeConstraint(config, '');
-      expect(res).toBe('^12.16.0');
+      const res = await getNodeConstraint(
+        {},
+        [],
+        '',
+        new Lazy(() => Promise.resolve({}))
+      );
+      expect(res).toBeNull();
     });
 
-    it('returns no constraint', async () => {
-      fs.readLocalFile.mockResolvedValueOnce(null as never);
-      fs.readLocalFile.mockResolvedValueOnce(null as never);
-      const res = await getNodeConstraint({ ...config, constraints: null }, '');
-      expect(res).toBeNull();
+    it('returns from package.json', async () => {
+      const res = await getNodeConstraint(
+        {},
+        [],
+        '',
+        new Lazy(() => Promise.resolve({ engines: { node: '^12.16.3' } }))
+      );
+      expect(res).toBe('^12.16.3');
     });
   });
 
@@ -67,7 +90,8 @@ describe('modules/manager/npm/post-update/node-version', () => {
         await getNodeToolConstraint(
           config,
           [{ depName: 'node', newValue: '16.15.0' }],
-          ''
+          '',
+          new Lazy(() => Promise.resolve({}))
         )
       ).toEqual({
         toolName: 'node',
@@ -76,7 +100,14 @@ describe('modules/manager/npm/post-update/node-version', () => {
     });
 
     it('returns getNodeConstraint', async () => {
-      expect(await getNodeToolConstraint(config, [], '')).toEqual({
+      expect(
+        await getNodeToolConstraint(
+          config,
+          [],
+          '',
+          new Lazy(() => Promise.resolve({}))
+        )
+      ).toEqual({
         toolName: 'node',
         constraint: '^12.16.0',
       });
diff --git a/lib/modules/manager/npm/post-update/node-version.ts b/lib/modules/manager/npm/post-update/node-version.ts
index 213d0dd615add162bc5b737b9891b3d2d86f5f2c..4fb88b4cf3f21da80503e30548f7e5fd46993f29 100644
--- a/lib/modules/manager/npm/post-update/node-version.ts
+++ b/lib/modules/manager/npm/post-update/node-version.ts
@@ -5,6 +5,7 @@ import type { ToolConstraint } from '../../../../util/exec/types';
 import { readLocalFile } from '../../../../util/fs';
 import { newlineRegex, regEx } from '../../../../util/regex';
 import type { PostUpdateConfig, Upgrade } from '../../types';
+import type { LazyPackageJson } from './utils';
 
 async function getNodeFile(filename: string): Promise<string | null> {
   try {
@@ -22,11 +23,10 @@ async function getNodeFile(filename: string): Promise<string | null> {
   return null;
 }
 
-function getPackageJsonConstraint(
-  config: Partial<PostUpdateConfig>
-): string | null {
-  const constraint: string =
-    config.constraints?.node ?? config.extractedConstraints?.node;
+async function getPackageJsonConstraint(
+  pkg: LazyPackageJson
+): Promise<string | null> {
+  const constraint = (await pkg.getValue()).engines?.node;
   if (constraint && semver.validRange(constraint)) {
     logger.debug(`Using node constraint "${constraint}" from package.json`);
     return constraint;
@@ -34,15 +34,19 @@ function getPackageJsonConstraint(
   return null;
 }
 
+// export only for testing
 export async function getNodeConstraint(
   config: Partial<PostUpdateConfig>,
-  lockFileDir: string
+  upgrades: Upgrade[],
+  lockFileDir: string,
+  pkg: LazyPackageJson
 ): Promise<string | null> {
-  // TODO: fix types (#7154)
   const constraint =
+    getNodeUpdate(upgrades) ??
+    config.constraints?.node ??
     (await getNodeFile(upath.join(lockFileDir, '.nvmrc'))) ??
     (await getNodeFile(upath.join(lockFileDir, '.node-version'))) ??
-    getPackageJsonConstraint(config);
+    (await getPackageJsonConstraint(pkg));
   if (!constraint) {
     logger.debug('No node constraint found - using latest');
   }
@@ -56,10 +60,15 @@ export function getNodeUpdate(upgrades: Upgrade[]): string | undefined {
 export async function getNodeToolConstraint(
   config: Partial<PostUpdateConfig>,
   upgrades: Upgrade[],
-  lockFileDir: string
+  lockFileDir: string,
+  pkg: LazyPackageJson
 ): Promise<ToolConstraint> {
-  const constraint =
-    getNodeUpdate(upgrades) ?? (await getNodeConstraint(config, lockFileDir));
+  const constraint = await getNodeConstraint(
+    config,
+    upgrades,
+    lockFileDir,
+    pkg
+  );
 
   return {
     toolName: 'node',
diff --git a/lib/modules/manager/npm/post-update/npm.spec.ts b/lib/modules/manager/npm/post-update/npm.spec.ts
index 833a7ad9b8c3b6c36f2e2ef50dc16e4787c6a8a8..bebc23d702860933ea3e47d423c07ced5b3d6da8 100644
--- a/lib/modules/manager/npm/post-update/npm.spec.ts
+++ b/lib/modules/manager/npm/post-update/npm.spec.ts
@@ -26,6 +26,8 @@ describe('modules/manager/npm/post-update/npm', () => {
 
   it('generates lock files', async () => {
     const execSnapshots = mockExecAll();
+    // package.json
+    fs.readLocalFile.mockResolvedValueOnce('{}');
     fs.readLocalFile.mockResolvedValueOnce('package-lock-contents');
     const skipInstalls = true;
     const postUpdateOptions = ['npmDedupe'];
@@ -39,7 +41,7 @@ describe('modules/manager/npm/post-update/npm', () => {
       { skipInstalls, postUpdateOptions },
       updates
     );
-    expect(fs.readLocalFile).toHaveBeenCalledTimes(1);
+    expect(fs.readLocalFile).toHaveBeenCalledTimes(2);
     expect(res.error).toBeUndefined();
     expect(res.lockFile).toBe('package-lock-contents');
     expect(execSnapshots).toMatchSnapshot();
@@ -56,7 +58,7 @@ describe('modules/manager/npm/post-update/npm', () => {
       'some-dir',
       {},
       'package-lock.json',
-      { skipInstalls },
+      { skipInstalls, constraints: { npm: '^6.0.0' } },
       updates
     );
     expect(fs.readLocalFile).toHaveBeenCalledTimes(1);
@@ -85,7 +87,7 @@ describe('modules/manager/npm/post-update/npm', () => {
       'some-dir',
       {},
       'package-lock.json',
-      { skipInstalls },
+      { skipInstalls, constraints: { npm: '^6.0.0' } },
       updates
     );
     expect(fs.readLocalFile).toHaveBeenCalledTimes(1);
@@ -103,7 +105,7 @@ describe('modules/manager/npm/post-update/npm', () => {
       'some-dir',
       {},
       'npm-shrinkwrap.json',
-      { skipInstalls }
+      { skipInstalls, constraints: { npm: '^6.0.0' } }
     );
     expect(fs.renameLocalFile).toHaveBeenCalledTimes(1);
     expect(fs.renameLocalFile).toHaveBeenCalledWith(
@@ -130,7 +132,7 @@ describe('modules/manager/npm/post-update/npm', () => {
       'some-dir',
       {},
       'npm-shrinkwrap.json',
-      { skipInstalls }
+      { skipInstalls, constraints: { npm: '^6.0.0' } }
     );
     expect(fs.renameLocalFile).toHaveBeenCalledTimes(0);
     expect(fs.readLocalFile).toHaveBeenCalledTimes(1);
@@ -153,7 +155,7 @@ describe('modules/manager/npm/post-update/npm', () => {
       'some-dir',
       {},
       'package-lock.json',
-      { skipInstalls, binarySource }
+      { skipInstalls, binarySource, constraints: { npm: '^6.0.0' } }
     );
     expect(fs.readLocalFile).toHaveBeenCalledTimes(1);
     expect(res.error).toBeUndefined();
@@ -170,7 +172,7 @@ describe('modules/manager/npm/post-update/npm', () => {
       'some-dir',
       {},
       'package-lock.json',
-      { binarySource },
+      { binarySource, constraints: { npm: '^6.0.0' } },
       [{ isRemediation: true }]
     );
     expect(fs.readLocalFile).toHaveBeenCalledTimes(1);
@@ -197,13 +199,15 @@ describe('modules/manager/npm/post-update/npm', () => {
 
   it('finds npm globally', async () => {
     const execSnapshots = mockExecAll();
+    // package.json
+    fs.readLocalFile.mockResolvedValue('{}');
     fs.readLocalFile.mockResolvedValue('package-lock-contents');
     const res = await npmHelper.generateLockFile(
       'some-dir',
       {},
       'package-lock.json'
     );
-    expect(fs.readLocalFile).toHaveBeenCalledTimes(1);
+    expect(fs.readLocalFile).toHaveBeenCalledTimes(2);
     expect(res.lockFile).toBe('package-lock-contents');
     // TODO: is that right?
     expect(execSnapshots).toEqual([]);
@@ -226,6 +230,8 @@ describe('modules/manager/npm/post-update/npm', () => {
 
   it('performs lock file maintenance', async () => {
     const execSnapshots = mockExecAll();
+    // package.json
+    fs.readLocalFile.mockResolvedValue('{}');
     fs.readLocalFile.mockResolvedValue('package-lock-contents');
     const res = await npmHelper.generateLockFile(
       'some-dir',
@@ -234,7 +240,7 @@ describe('modules/manager/npm/post-update/npm', () => {
       {},
       [{ isLockFileMaintenance: true }]
     );
-    expect(fs.readLocalFile).toHaveBeenCalledTimes(1);
+    expect(fs.readLocalFile).toHaveBeenCalledTimes(2);
     expect(fs.deleteLocalFile).toHaveBeenCalledTimes(1);
     expect(res.lockFile).toBe('package-lock-contents');
     expect(execSnapshots).toMatchSnapshot();
@@ -403,6 +409,8 @@ describe('modules/manager/npm/post-update/npm', () => {
 
     it('workspace in sub-folder', async () => {
       const execSnapshots = mockExecAll();
+      // package.json
+      fs.readLocalFile.mockResolvedValue('{}');
       fs.readLocalFile.mockResolvedValueOnce('package-lock content');
       const skipInstalls = true;
       const res = await npmHelper.generateLockFile(
@@ -412,7 +420,7 @@ describe('modules/manager/npm/post-update/npm', () => {
         { skipInstalls },
         updates
       );
-      expect(fs.readLocalFile).toHaveBeenCalledTimes(1);
+      expect(fs.readLocalFile).toHaveBeenCalledTimes(2);
       expect(res.error).toBeUndefined();
       expect(execSnapshots).toMatchObject([
         {
@@ -436,6 +444,8 @@ describe('modules/manager/npm/post-update/npm', () => {
         };
       });
       const execSnapshots = mockExecAll();
+      // package.json
+      fs.readLocalFile.mockResolvedValue('{}');
       fs.readLocalFile.mockResolvedValueOnce('package-lock content');
       const skipInstalls = true;
       const res = await npmHelper.generateLockFile(
@@ -445,7 +455,7 @@ describe('modules/manager/npm/post-update/npm', () => {
         { skipInstalls },
         modifiedUpdates
       );
-      expect(fs.readLocalFile).toHaveBeenCalledTimes(1);
+      expect(fs.readLocalFile).toHaveBeenCalledTimes(2);
       expect(res.error).toBeUndefined();
       expect(execSnapshots).toMatchObject([
         {
diff --git a/lib/modules/manager/npm/post-update/npm.ts b/lib/modules/manager/npm/post-update/npm.ts
index 499b7c6188d85b91678290a0f4212da46ef10b26..2e4175760432363f4e051c036b6cb34804afa492 100644
--- a/lib/modules/manager/npm/post-update/npm.ts
+++ b/lib/modules/manager/npm/post-update/npm.ts
@@ -25,6 +25,7 @@ import type { PostUpdateConfig, Upgrade } from '../../types';
 import { composeLockFile, parseLockFile } from '../utils';
 import { getNodeToolConstraint } from './node-version';
 import type { GenerateLockFileResult } from './types';
+import { getPackageManagerVersion, lazyLoadPackageJson } from './utils';
 
 export async function generateLockFile(
   lockFileDir: string,
@@ -41,9 +42,12 @@ export async function generateLockFile(
 
   let lockFile: string | null = null;
   try {
+    const lazyPgkJson = lazyLoadPackageJson(lockFileDir);
     const npmToolConstraint: ToolConstraint = {
       toolName: 'npm',
-      constraint: config.constraints?.npm ?? config.extractedConstraints?.npm,
+      constraint:
+        config.constraints?.npm ??
+        getPackageManagerVersion('npm', await lazyPgkJson.getValue()),
     };
     const commands: string[] = [];
     let cmdOptions = '';
@@ -67,7 +71,7 @@ export async function generateLockFile(
       cwdFile: lockFileName,
       extraEnv,
       toolConstraints: [
-        await getNodeToolConstraint(config, upgrades, lockFileDir),
+        await getNodeToolConstraint(config, upgrades, lockFileDir, lazyPgkJson),
         npmToolConstraint,
       ],
       docker: {},
diff --git a/lib/modules/manager/npm/post-update/pnpm.ts b/lib/modules/manager/npm/post-update/pnpm.ts
index f244637f0dc3a139d90ccbb71c604128e9a3d04d..15ff68359615da39924e842429ae28d24e5129e4 100644
--- a/lib/modules/manager/npm/post-update/pnpm.ts
+++ b/lib/modules/manager/npm/post-update/pnpm.ts
@@ -12,9 +12,9 @@ import type {
 } from '../../../../util/exec/types';
 import { deleteLocalFile, readLocalFile } from '../../../../util/fs';
 import type { PostUpdateConfig, Upgrade } from '../../types';
-import type { NpmPackage } from '../extract/types';
 import { getNodeToolConstraint } from './node-version';
 import type { GenerateLockFileResult, PnpmLockFile } from './types';
+import { getPackageManagerVersion, lazyLoadPackageJson } from './utils';
 
 function getPnpmConstraintFromUpgrades(upgrades: Upgrade[]): string | null {
   for (const upgrade of upgrades) {
@@ -38,12 +38,13 @@ export async function generateLockFile(
   let stderr: string | undefined;
   let cmd = 'pnpm';
   try {
+    const lazyPgkJson = lazyLoadPackageJson(lockFileDir);
     const pnpmToolConstraint: ToolConstraint = {
       toolName: 'pnpm',
       constraint:
         getPnpmConstraintFromUpgrades(upgrades) ?? // if pnpm is being upgraded, it comes first
         config.constraints?.pnpm ?? // from user config or extraction
-        (await getPnpmConstraintFromPackageFile(lockFileDir)) ?? // look in package.json > packageManager or engines
+        getPackageManagerVersion('pnpm', await lazyPgkJson.getValue()) ?? // look in package.json > packageManager or engines
         (await getConstraintFromLockFile(lockFileName)), // use lockfileVersion to find pnpm version range
     };
 
@@ -56,7 +57,7 @@ export async function generateLockFile(
       extraEnv,
       docker: {},
       toolConstraints: [
-        await getNodeToolConstraint(config, upgrades, lockFileDir),
+        await getNodeToolConstraint(config, upgrades, lockFileDir, lazyPgkJson),
         pnpmToolConstraint,
       ],
     };
@@ -117,31 +118,6 @@ export async function generateLockFile(
   return { lockFile };
 }
 
-export async function getPnpmConstraintFromPackageFile(
-  lockFileDir: string
-): Promise<string | undefined> {
-  let constraint: string | undefined;
-  const rootPackageJson = upath.join(lockFileDir, 'package.json');
-  const content = await readLocalFile(rootPackageJson, 'utf8');
-  if (content) {
-    const packageJson: NpmPackage = JSON.parse(content);
-    const packageManager = packageJson?.packageManager;
-    if (packageManager?.includes('@')) {
-      const nameAndVersion = packageManager.split('@');
-      const name = nameAndVersion[0];
-      if (name === 'pnpm') {
-        constraint = nameAndVersion[1];
-      }
-    } else {
-      const engines = packageJson?.engines;
-      if (engines) {
-        constraint = engines['pnpm'];
-      }
-    }
-  }
-  return constraint;
-}
-
 export async function getConstraintFromLockFile(
   lockFileName: string
 ): Promise<string | null> {
diff --git a/lib/modules/manager/npm/post-update/utils.ts b/lib/modules/manager/npm/post-update/utils.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a52c4658b915c0d203c33ed50e8565c71df1c540
--- /dev/null
+++ b/lib/modules/manager/npm/post-update/utils.ts
@@ -0,0 +1,39 @@
+import upath from 'upath';
+import { readLocalFile } from '../../../../util/fs';
+import { Lazy } from '../../../../util/lazy';
+import { PackageJson, PackageJsonSchema } from '../schema';
+
+export function lazyLoadPackageJson(
+  lockFileDir: string
+): Lazy<Promise<PackageJsonSchema>> {
+  return new Lazy(() => loadPackageJson(lockFileDir));
+}
+
+export type LazyPackageJson = ReturnType<typeof lazyLoadPackageJson>;
+
+export async function loadPackageJson(
+  lockFileDir: string
+): Promise<PackageJsonSchema> {
+  const json = await readLocalFile(
+    upath.join(lockFileDir, 'package.json'),
+    'utf8'
+  );
+  const res = PackageJson.safeParse(json);
+  if (res.success) {
+    return res.data;
+  }
+  return {};
+}
+
+export function getPackageManagerVersion(
+  name: string,
+  pkg: PackageJsonSchema
+): string | null {
+  if (pkg.packageManager?.name === name) {
+    return pkg.packageManager.version;
+  }
+  if (pkg.engines?.[name]) {
+    return pkg.engines[name];
+  }
+  return null;
+}
diff --git a/lib/modules/manager/npm/post-update/yarn.spec.ts b/lib/modules/manager/npm/post-update/yarn.spec.ts
index e41cb4b4ca1ff5e64c220a74b0976ddc16c6667f..b17e525e865d32f55b9d0ad426f2e2525ab784ed 100644
--- a/lib/modules/manager/npm/post-update/yarn.spec.ts
+++ b/lib/modules/manager/npm/post-update/yarn.spec.ts
@@ -350,7 +350,7 @@ describe('modules/manager/npm/post-update/yarn', () => {
     Fixtures.mock({});
     const execSnapshots = mockExecAll(new Error('some-error'));
     const res = await yarnHelper.generateLockFile('some-dir', {});
-    expect(fs.readFile).toHaveBeenCalledTimes(1);
+    expect(fs.readFile).toHaveBeenCalledTimes(2);
     expect(res.error).toBeTrue();
     expect(res.lockFile).toBeUndefined();
     expect(fixSnapshots(execSnapshots)).toMatchSnapshot();
diff --git a/lib/modules/manager/npm/post-update/yarn.ts b/lib/modules/manager/npm/post-update/yarn.ts
index 3b894e4c06e6186de27f69f8b999fdacc3fdbdd6..5652c1f5f904639c1520bf0016d7fc07b994e9b3 100644
--- a/lib/modules/manager/npm/post-update/yarn.ts
+++ b/lib/modules/manager/npm/post-update/yarn.ts
@@ -27,6 +27,7 @@ import type { PostUpdateConfig, Upgrade } from '../../types';
 import type { NpmManagerData } from '../types';
 import { getNodeToolConstraint } from './node-version';
 import type { GenerateLockFileResult } from './types';
+import { getPackageManagerVersion, lazyLoadPackageJson } from './utils';
 
 export async function checkYarnrc(
   lockFileDir: string
@@ -98,16 +99,18 @@ export async function generateLockFile(
   logger.debug(`Spawning yarn install to create ${lockFileName}`);
   let lockFile: string | null = null;
   try {
+    const lazyPgkJson = lazyLoadPackageJson(lockFileDir);
     const toolConstraints: ToolConstraint[] = [
-      await getNodeToolConstraint(config, upgrades, lockFileDir),
+      await getNodeToolConstraint(config, upgrades, lockFileDir, lazyPgkJson),
     ];
     const yarnUpdate = upgrades.find(isYarnUpdate);
     const yarnCompatibility = yarnUpdate
       ? yarnUpdate.newValue
-      : config.constraints?.yarn ?? config.extractedConstraints?.yarn;
+      : config.constraints?.yarn ??
+        getPackageManagerVersion('yarn', await lazyPgkJson.getValue());
     const minYarnVersion =
       semver.validRange(yarnCompatibility) &&
-      semver.minVersion(yarnCompatibility);
+      semver.minVersion(yarnCompatibility!);
     const isYarn1 = !minYarnVersion || minYarnVersion.major === 1;
     const isYarnDedupeAvailable =
       minYarnVersion && semver.gte(minYarnVersion, '2.2.0');
diff --git a/lib/modules/manager/npm/extract/schema.ts b/lib/modules/manager/npm/schema.ts
similarity index 59%
rename from lib/modules/manager/npm/extract/schema.ts
rename to lib/modules/manager/npm/schema.ts
index 96cde78ca64e19c68ef9b5677fc05c925fe34de4..98f50d5e96f154b413b8a8ea214462e88200dea9 100644
--- a/lib/modules/manager/npm/extract/schema.ts
+++ b/lib/modules/manager/npm/schema.ts
@@ -1,5 +1,22 @@
 import { z } from 'zod';
-import { Json, LooseRecord } from '../../../../util/schema-utils';
+import { Json, LooseRecord } from '../../../util/schema-utils';
+
+export const PackageManagerSchema = z
+  .string()
+  .transform((val) => val.split('@'))
+  .transform(([name, version]) => ({ name, version }));
+
+export const PackageJsonSchema = z.object({
+  engines: LooseRecord(z.string()).optional(),
+  dependencies: LooseRecord(z.string()).optional(),
+  devDependencies: LooseRecord(z.string()).optional(),
+  peerDependencies: LooseRecord(z.string()).optional(),
+  packageManager: PackageManagerSchema.optional(),
+});
+
+export type PackageJsonSchema = z.infer<typeof PackageJsonSchema>;
+
+export const PackageJson = Json.pipe(PackageJsonSchema);
 
 export const PackageLockV3Schema = z.object({
   lockfileVersion: z.literal(3),
diff --git a/lib/modules/manager/types.ts b/lib/modules/manager/types.ts
index de83d46ec6f272af2026d13cec6c17faf877c3f3..15a6fd974219646aea988d2a52db21c4cf4422fa 100644
--- a/lib/modules/manager/types.ts
+++ b/lib/modules/manager/types.ts
@@ -273,6 +273,8 @@ export interface ManagerApi extends ModuleApi {
 export interface PostUpdateConfig<T = Record<string, any>>
   extends Record<string, any>,
     ManagerData<T> {
+  // TODO: remove null
+  constraints?: Record<string, string> | null;
   updatedPackageFiles?: FileChange[];
   postUpdateOptions?: string[];
   skipInstalls?: boolean | null;