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' ;
@@ -107,6 +108,7 @@ export default class AddCommandModule
107108 private readonly schematicName = 'ng-add' ;
108109 private rootRequire = createRequire ( this . context . root + '/' ) ;
109110 #projectVersionCache = new Map < string , string | null > ( ) ;
111+ #rootManifestCache: PackageManifest | null = null ;
110112
111113 override async builder ( argv : Argv ) : Promise < Argv < AddCommandArgs > > {
112114 const localYargs = ( await super . builder ( argv ) )
@@ -156,6 +158,7 @@ export default class AddCommandModule
156158
157159 async run ( options : Options < AddCommandArgs > & OtherOptions ) : Promise < number | void > {
158160 this . #projectVersionCache. clear ( ) ;
161+ this . #rootManifestCache = null ;
159162 const { logger } = this . context ;
160163 const { collection, skipConfirmation } = options ;
161164
@@ -657,18 +660,7 @@ export default class AddCommandModule
657660 }
658661
659662 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 ;
663+ return ! ! this . resolvePackageJson ( name ) ;
672664 }
673665
674666 private executeSchematic (
@@ -707,12 +699,7 @@ export default class AddCommandModule
707699 return cachedVersion ;
708700 }
709701
710- const { root } = this . context ;
711- let installedPackagePath ;
712- try {
713- installedPackagePath = this . rootRequire . resolve ( join ( name , 'package.json' ) ) ;
714- } catch { }
715-
702+ const installedPackagePath = this . resolvePackageJson ( name ) ;
716703 if ( installedPackagePath ) {
717704 try {
718705 const installedPackage = JSON . parse (
@@ -724,13 +711,7 @@ export default class AddCommandModule
724711 } catch { }
725712 }
726713
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-
714+ const projectManifest = await this . getProjectManifest ( ) ;
734715 if ( projectManifest ) {
735716 const version =
736717 projectManifest . dependencies ?. [ name ] || projectManifest . devDependencies ?. [ name ] ;
@@ -746,6 +727,53 @@ export default class AddCommandModule
746727 return null ;
747728 }
748729
730+ private async getProjectManifest ( ) : Promise < PackageManifest | null > {
731+ if ( this . #rootManifestCache) {
732+ return this . #rootManifestCache;
733+ }
734+
735+ const { root } = this . context ;
736+ try {
737+ this . #rootManifestCache = JSON . parse (
738+ await fs . readFile ( join ( root , 'package.json' ) , 'utf-8' ) ,
739+ ) as PackageManifest ;
740+
741+ return this . #rootManifestCache;
742+ } catch {
743+ return null ;
744+ }
745+ }
746+
747+ private resolvePackageJson ( name : string ) : string | undefined {
748+ try {
749+ return this . rootRequire . resolve ( join ( name , 'package.json' ) ) ;
750+ } catch ( e ) {
751+ assertIsError ( e ) ;
752+ if ( e . code === 'ERR_PACKAGE_PATH_NOT_EXPORTED' ) {
753+ try {
754+ const mainPath = this . rootRequire . resolve ( name ) ;
755+ let directory = dirname ( mainPath ) ;
756+
757+ // Stop at the node_modules boundary or the root of the file system
758+ while ( directory && basename ( directory ) !== 'node_modules' ) {
759+ const packageJsonPath = join ( directory , 'package.json' ) ;
760+ if ( existsSync ( packageJsonPath ) ) {
761+ return packageJsonPath ;
762+ }
763+
764+ const parent = dirname ( directory ) ;
765+ if ( parent === directory ) {
766+ break ;
767+ }
768+ directory = parent ;
769+ }
770+ } catch { }
771+ }
772+ }
773+
774+ return undefined ;
775+ }
776+
749777 private async getPeerDependencyConflicts ( manifest : PackageManifest ) : Promise < string [ ] | false > {
750778 if ( ! manifest . peerDependencies ) {
751779 return false ;
0 commit comments