66 ObjectDefineProperty,
77 RegExpPrototypeExec,
88 SafeMap,
9+ StringPrototypeEndsWith,
910 StringPrototypeIndexOf,
11+ StringPrototypeLastIndexOf,
1012 StringPrototypeSlice,
1113} = primordials ;
1214const {
@@ -26,6 +28,7 @@ const {
2628const { kEmptyObject } = require ( 'internal/util' ) ;
2729const modulesBinding = internalBinding ( 'modules' ) ;
2830const path = require ( 'path' ) ;
31+ const permission = require ( 'internal/process/permission' ) ;
2932const { validateString } = require ( 'internal/validators' ) ;
3033const internalFsBinding = internalBinding ( 'fs' ) ;
3134
@@ -127,26 +130,71 @@ function read(jsonPath, { base, specifier, isESM } = kEmptyObject) {
127130 } ;
128131}
129132
133+ /**
134+ * Given a file path, walk the filesystem upwards until we find its closest parent
135+ * `package.json` file, stopping when:
136+ * 1. we find a `package.json` file;
137+ * 2. we find a path that we do not have permission to read;
138+ * 3. we find a containing `node_modules` directory;
139+ * 4. or, we reach the filesystem root
140+ * @returns {undefined | string }
141+ */
142+ function findParentPackageJSON ( checkPath ) {
143+ const enabledPermission = permission . isEnabled ( ) ;
144+
145+ const rootSeparatorIndex = StringPrototypeIndexOf ( checkPath , path . sep ) ;
146+ let separatorIndex ;
147+
148+ do {
149+ separatorIndex = StringPrototypeLastIndexOf ( checkPath , path . sep ) ;
150+ checkPath = StringPrototypeSlice ( checkPath , 0 , separatorIndex ) ;
151+
152+ if ( enabledPermission && ! permission . has ( 'fs.read' , checkPath + path . sep ) ) {
153+ return undefined ;
154+ }
155+
156+ if ( StringPrototypeEndsWith ( checkPath , path . sep + 'node_modules' ) ) {
157+ return undefined ;
158+ }
159+
160+ const maybePackageJSONPath = checkPath + path . sep + 'package.json' ;
161+ const stat = internalFsBinding . internalModuleStat ( checkPath + path . sep + 'package.json' ) ;
162+
163+ const packageJSONExists = stat === 0 ;
164+ if ( packageJSONExists ) {
165+ return maybePackageJSONPath ;
166+ }
167+ } while ( separatorIndex > rootSeparatorIndex ) ;
168+
169+ return undefined ;
170+ }
171+
130172/**
131173 * Get the nearest parent package.json file from a given path.
132174 * Return the package.json data and the path to the package.json file, or undefined.
133175 * @param {string } checkPath The path to start searching from.
134176 * @returns {undefined | DeserializedPackageConfig }
135177 */
136178function getNearestParentPackageJSON ( checkPath ) {
137- if ( nearestParentPackageJSONCache . has ( checkPath ) ) {
138- return nearestParentPackageJSONCache . get ( checkPath ) ;
179+ const nearestParentPackageJSON = findParentPackageJSON ( checkPath ) ;
180+
181+ if ( nearestParentPackageJSON === undefined ) {
182+ return undefined ;
183+ }
184+
185+ if ( nearestParentPackageJSONCache . has ( nearestParentPackageJSON ) ) {
186+ return nearestParentPackageJSONCache . get ( nearestParentPackageJSON ) ;
139187 }
140188
141- const result = modulesBinding . getNearestParentPackageJSON ( checkPath ) ;
189+ const result = modulesBinding . readPackageJSON ( nearestParentPackageJSON ) ;
142190
143191 if ( result === undefined ) {
144192 nearestParentPackageJSONCache . set ( checkPath , undefined ) ;
145193 return undefined ;
146194 }
147195
148196 const packageConfig = deserializePackageJSON ( checkPath , result ) ;
149- nearestParentPackageJSONCache . set ( checkPath , packageConfig ) ;
197+ nearestParentPackageJSONCache . set ( nearestParentPackageJSON , packageConfig ) ;
150198
151199 return packageConfig ;
152200}
0 commit comments