|
22 | 22 |
|
23 | 23 | import * as fs from 'node:fs'; |
24 | 24 | import * as path from 'node:path'; |
25 | | -import {createRequire} from 'node:module'; |
26 | 25 |
|
27 | 26 | import {MIN_NODE_VERSION, isNodeVersionSupported} from '../../foundation/env/node-version.mjs'; |
28 | 27 | import {CLI_ROOT, findCoreDir} from '../../foundation/fs/paths.mjs'; |
29 | 28 | import {detectPackageManager, getCliInvocation} from '../../foundation/env/package-manager.mjs'; |
30 | 29 | import {findConfigPath, Project} from '../../foundation/config/project.mjs'; |
31 | 30 | import {semverCompare, isValidSemver, satisfiesRange} from '../../foundation/env/semver.mjs'; |
32 | 31 |
|
33 | | -const _require = createRequire(import.meta.url); |
34 | | - |
35 | 32 | /** |
36 | 33 | * @typedef {'pass'|'warn'|'fail'|'info'} DoctorStatus |
37 | 34 | * |
@@ -99,6 +96,31 @@ function findNodeModules(startDir) { |
99 | 96 | return null; |
100 | 97 | } |
101 | 98 |
|
| 99 | +/** |
| 100 | + * Locate a package inside the project's own node_modules chain. |
| 101 | + * |
| 102 | + * Deliberately not `require.resolve`: Node folds NODE_PATH and the global |
| 103 | + * folders into resolution even when `paths` is given, so a package merely |
| 104 | + * reachable from the ambient environment would read as installed in the |
| 105 | + * user's project. Reading package.json off disk also sidesteps packages that |
| 106 | + * don't export it, so the version is always available to range-check. |
| 107 | + * |
| 108 | + * @param {string} startDir |
| 109 | + * @param {string} name |
| 110 | + * @returns {string|null} the package directory, or null when not installed |
| 111 | + */ |
| 112 | +function findInstalledPackage(startDir, name) { |
| 113 | + let dir = startDir; |
| 114 | + for (let i = 0; i < 6; i++) { |
| 115 | + const candidate = path.join(dir, 'node_modules', ...name.split('/')); |
| 116 | + if (fs.existsSync(path.join(candidate, 'package.json'))) return candidate; |
| 117 | + const parent = path.dirname(dir); |
| 118 | + if (parent === dir) break; |
| 119 | + dir = parent; |
| 120 | + } |
| 121 | + return null; |
| 122 | +} |
| 123 | + |
102 | 124 | /** |
103 | 125 | * Find every installed @astryxdesign/theme-* package under node_modules. |
104 | 126 | * @param {string} cwd |
@@ -440,23 +462,15 @@ export function checkPeerDeps(ctx) { |
440 | 462 | const mismatched = []; |
441 | 463 | for (const name of peerNames) { |
442 | 464 | const want = peers[name]; |
443 | | - let pkgJsonPath; |
444 | | - try { |
445 | | - pkgJsonPath = _require.resolve(`${name}/package.json`, {paths: [ctx.cwd]}); |
446 | | - } catch { |
447 | | - // package.json isn't exported — fall back to entry resolution for |
448 | | - // presence only (we then can't read the version to range-check it). |
449 | | - try { |
450 | | - _require.resolve(name, {paths: [ctx.cwd]}); |
451 | | - } catch { |
452 | | - missing.push(`${name}@${want}`); |
453 | | - } |
| 465 | + const installedDir = findInstalledPackage(ctx.cwd, name); |
| 466 | + if (!installedDir) { |
| 467 | + missing.push(`${name}@${want}`); |
454 | 468 | continue; |
455 | 469 | } |
456 | 470 | // Present and version-readable: verify it actually satisfies the range, |
457 | 471 | // not just that the package exists (a bare `npm install` can resolve an |
458 | 472 | // out-of-range version from a stale consumer range and still "look" fine). |
459 | | - const have = pkgVersion(path.dirname(pkgJsonPath)); |
| 473 | + const have = pkgVersion(installedDir); |
460 | 474 | if (have && !satisfiesRange(have, want)) { |
461 | 475 | mismatched.push({name, want, have}); |
462 | 476 | } |
|
0 commit comments