88
99import { Listr , ListrRenderer , ListrTaskWrapper , color , figures } from 'listr2' ;
1010import assert from 'node:assert' ;
11+ import { existsSync } from 'node:fs' ;
1112import fs from 'node:fs/promises' ;
1213import { createRequire } from 'node:module' ;
13- import { dirname , join } from 'node:path' ;
14+ import { basename , dirname , join } from 'node:path' ;
1415import npa from 'npm-package-arg' ;
1516import semver , { Range , compare , intersects , prerelease , satisfies , valid } from 'semver' ;
1617import { Argv } from 'yargs' ;
@@ -30,6 +31,7 @@ import {
3031 PackageMetadata ,
3132} from '../../package-managers' ;
3233import { assertIsError } from '../../utilities/error' ;
34+ import { findUpSync } from '../../utilities/find-up' ;
3335import { isTTY } from '../../utilities/tty' ;
3436import { VERSION } from '../../utilities/version' ;
3537
@@ -107,6 +109,7 @@ export default class AddCommandModule
107109 private readonly schematicName = 'ng-add' ;
108110 private rootRequire = createRequire ( this . context . root + '/' ) ;
109111 #projectVersionCache = new Map < string , string | null > ( ) ;
112+ #rootManifestCache: PackageManifest | null = null ;
110113
111114 override async builder ( argv : Argv ) : Promise < Argv < AddCommandArgs > > {
112115 const localYargs = ( await super . builder ( argv ) )
@@ -156,6 +159,7 @@ export default class AddCommandModule
156159
157160 async run ( options : Options < AddCommandArgs > & OtherOptions ) : Promise < number | void > {
158161 this . #projectVersionCache. clear ( ) ;
162+ this . #rootManifestCache = null ;
159163 const { logger } = this . context ;
160164 const { collection, skipConfirmation } = options ;
161165
@@ -657,18 +661,7 @@ export default class AddCommandModule
657661 }
658662
659663 private isPackageInstalled ( name : string ) : boolean {
660- try {
661- this . rootRequire . resolve ( join ( name , 'package.json' ) ) ;
662-
663- return true ;
664- } catch ( e ) {
665- assertIsError ( e ) ;
666- if ( e . code !== 'MODULE_NOT_FOUND' ) {
667- throw e ;
668- }
669- }
670-
671- return false ;
664+ return ! ! this . resolvePackageJson ( name ) ;
672665 }
673666
674667 private executeSchematic (
@@ -707,12 +700,7 @@ export default class AddCommandModule
707700 return cachedVersion ;
708701 }
709702
710- const { root } = this . context ;
711- let installedPackagePath ;
712- try {
713- installedPackagePath = this . rootRequire . resolve ( join ( name , 'package.json' ) ) ;
714- } catch { }
715-
703+ const installedPackagePath = this . resolvePackageJson ( name ) ;
716704 if ( installedPackagePath ) {
717705 try {
718706 const installedPackage = JSON . parse (
@@ -724,13 +712,7 @@ export default class AddCommandModule
724712 } catch { }
725713 }
726714
727- let projectManifest ;
728- try {
729- projectManifest = JSON . parse (
730- await fs . readFile ( join ( root , 'package.json' ) , 'utf-8' ) ,
731- ) as PackageManifest ;
732- } catch { }
733-
715+ const projectManifest = await this . getProjectManifest ( ) ;
734716 if ( projectManifest ) {
735717 const version =
736718 projectManifest . dependencies ?. [ name ] || projectManifest . devDependencies ?. [ name ] ;
@@ -746,6 +728,53 @@ export default class AddCommandModule
746728 return null ;
747729 }
748730
731+ private async getProjectManifest ( ) : Promise < PackageManifest | null > {
732+ if ( this . #rootManifestCache) {
733+ return this . #rootManifestCache;
734+ }
735+
736+ const { root } = this . context ;
737+ try {
738+ this . #rootManifestCache = JSON . parse (
739+ await fs . readFile ( join ( root , 'package.json' ) , 'utf-8' ) ,
740+ ) as PackageManifest ;
741+
742+ return this . #rootManifestCache;
743+ } catch {
744+ return null ;
745+ }
746+ }
747+
748+ private resolvePackageJson ( name : string ) : string | undefined {
749+ try {
750+ return this . rootRequire . resolve ( join ( name , 'package.json' ) ) ;
751+ } catch ( e ) {
752+ assertIsError ( e ) ;
753+ if ( e . code === 'ERR_PACKAGE_PATH_NOT_EXPORTED' ) {
754+ try {
755+ const mainPath = this . rootRequire . resolve ( name ) ;
756+ let directory = dirname ( mainPath ) ;
757+
758+ // Stop at the node_modules boundary or the root of the file system
759+ while ( directory && basename ( directory ) !== 'node_modules' ) {
760+ const packageJsonPath = join ( directory , 'package.json' ) ;
761+ if ( existsSync ( packageJsonPath ) ) {
762+ return packageJsonPath ;
763+ }
764+
765+ const parent = dirname ( directory ) ;
766+ if ( parent === directory ) {
767+ break ;
768+ }
769+ directory = parent ;
770+ }
771+ } catch { }
772+ }
773+ }
774+
775+ return undefined ;
776+ }
777+
749778 private async getPeerDependencyConflicts ( manifest : PackageManifest ) : Promise < string [ ] | false > {
750779 if ( ! manifest . peerDependencies ) {
751780 return false ;
0 commit comments