6
6
ObjectDefineProperty,
7
7
RegExpPrototypeExec,
8
8
SafeMap,
9
+ StringPrototypeEndsWith,
9
10
StringPrototypeIndexOf,
11
+ StringPrototypeLastIndexOf,
10
12
StringPrototypeSlice,
11
13
} = primordials ;
12
14
const {
@@ -26,6 +28,7 @@ const {
26
28
const { kEmptyObject } = require ( 'internal/util' ) ;
27
29
const modulesBinding = internalBinding ( 'modules' ) ;
28
30
const path = require ( 'path' ) ;
31
+ const permission = require ( 'internal/process/permission' ) ;
29
32
const { validateString } = require ( 'internal/validators' ) ;
30
33
const internalFsBinding = internalBinding ( 'fs' ) ;
31
34
@@ -127,26 +130,71 @@ function read(jsonPath, { base, specifier, isESM } = kEmptyObject) {
127
130
} ;
128
131
}
129
132
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
+
130
172
/**
131
173
* Get the nearest parent package.json file from a given path.
132
174
* Return the package.json data and the path to the package.json file, or undefined.
133
175
* @param {string } checkPath The path to start searching from.
134
176
* @returns {undefined | DeserializedPackageConfig }
135
177
*/
136
178
function 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 ) ;
139
187
}
140
188
141
- const result = modulesBinding . getNearestParentPackageJSON ( checkPath ) ;
189
+ const result = modulesBinding . readPackageJSON ( nearestParentPackageJSON ) ;
142
190
143
191
if ( result === undefined ) {
144
192
nearestParentPackageJSONCache . set ( checkPath , undefined ) ;
145
193
return undefined ;
146
194
}
147
195
148
196
const packageConfig = deserializePackageJSON ( checkPath , result ) ;
149
- nearestParentPackageJSONCache . set ( checkPath , packageConfig ) ;
197
+ nearestParentPackageJSONCache . set ( nearestParentPackageJSON , packageConfig ) ;
150
198
151
199
return packageConfig ;
152
200
}
0 commit comments