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,70 @@ 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+ */
141+ function findParentPackageJSON ( checkPath ) {
142+ const enabledPermission = permission . isEnabled ( ) ;
143+
144+ const rootSeparatorIndex = StringPrototypeIndexOf ( checkPath , path . sep ) ;
145+ let separatorIndex ;
146+
147+ do {
148+ separatorIndex = StringPrototypeLastIndexOf ( checkPath , path . sep ) ;
149+ checkPath = StringPrototypeSlice ( checkPath , 0 , separatorIndex ) ;
150+
151+ if ( enabledPermission && ! permission . has ( 'fs.read' , checkPath + path . sep ) ) {
152+ return undefined ;
153+ }
154+
155+ if ( StringPrototypeEndsWith ( checkPath , path . sep + 'node_modules' ) ) {
156+ return undefined ;
157+ }
158+
159+ const maybePackageJSONPath = checkPath + path . sep + 'package.json' ;
160+ const stat = internalFsBinding . internalModuleStat ( checkPath + path . sep + 'package.json' ) ;
161+
162+ const packageJSONExists = stat === 0 ;
163+ if ( packageJSONExists ) {
164+ return maybePackageJSONPath ;
165+ }
166+ } while ( separatorIndex > rootSeparatorIndex ) ;
167+
168+ return undefined ;
169+ }
170+
130171/**
131172 * Get the nearest parent package.json file from a given path.
132173 * Return the package.json data and the path to the package.json file, or undefined.
133174 * @param {string } checkPath The path to start searching from.
134175 * @returns {undefined | DeserializedPackageConfig }
135176 */
136177function getNearestParentPackageJSON ( checkPath ) {
137- if ( nearestParentPackageJSONCache . has ( checkPath ) ) {
138- return nearestParentPackageJSONCache . get ( checkPath ) ;
178+ const nearestParentPackageJSON = findParentPackageJSON ( checkPath ) ;
179+
180+ if ( nearestParentPackageJSON === undefined ) {
181+ return undefined ;
182+ }
183+
184+ if ( nearestParentPackageJSONCache . has ( nearestParentPackageJSON ) ) {
185+ return nearestParentPackageJSONCache . get ( nearestParentPackageJSON ) ;
139186 }
140187
141- const result = modulesBinding . getNearestParentPackageJSON ( checkPath ) ;
188+ const result = modulesBinding . readPackageJSON ( nearestParentPackageJSON ) ;
142189
143190 if ( result === undefined ) {
144191 nearestParentPackageJSONCache . set ( checkPath , undefined ) ;
145192 return undefined ;
146193 }
147194
148195 const packageConfig = deserializePackageJSON ( checkPath , result ) ;
149- nearestParentPackageJSONCache . set ( checkPath , packageConfig ) ;
196+ nearestParentPackageJSONCache . set ( nearestParentPackageJSON , packageConfig ) ;
150197
151198 return packageConfig ;
152199}
0 commit comments