@@ -23,7 +23,8 @@ const { |
23 | 23 | ERR_MODULE_NOT_FOUND, |
24 | 24 | }, |
25 | 25 | } = require('internal/errors'); |
26 | | -const { kEmptyObject } = require('internal/util'); |
| 26 | +const { kEmptyObject, isWindows } = require('internal/util'); |
| 27 | +const permission = require('internal/process/permission'); |
27 | 28 | const modulesBinding = internalBinding('modules'); |
28 | 29 | const path = require('path'); |
29 | 30 | const { validateString } = require('internal/validators'); |
@@ -140,10 +141,18 @@ function read(jsonPath, { base, specifier, isESM } = kEmptyObject) { |
140 | 141 | } |
141 | 142 |
|
142 | 143 | /** |
143 | | - * A cache mapping a module's path to its parent `package.json` file's path. |
144 | | - * This is used in concert with `deserializedPackageJSONCache` to improve |
145 | | - * the performance of `getNearestParentPackageJSON` when called repeatedly |
146 | | - * on the same module paths. |
| 144 | + * A cache mapping a directory to the path of the nearest `package.json` at or |
| 145 | + * above it (`null` when there is none). The native traversal for a module |
| 146 | + * starts at the module's directory, so every module in a directory shares one |
| 147 | + * entry and one native call. Used in concert with |
| 148 | + * `deserializedPackageJSONCache`. |
| 149 | + */ |
| 150 | +const directoryToParentPackageJSONPathCache = new SafeMap(); |
| 151 | + |
| 152 | +/** |
| 153 | + * When the permission model is enabled the native traversal also depends on |
| 154 | + * the read permissions in effect at the time of the call, so results are only |
| 155 | + * remembered per exact module path, as before. |
147 | 156 | */ |
148 | 157 | const moduleToParentPackageJSONCache = new SafeMap(); |
149 | 158 |
|
@@ -158,30 +167,51 @@ const moduleToParentPackageJSONCache = new SafeMap(); |
158 | 167 | */ |
159 | 168 | const deserializedPackageJSONCache = new SafeMap(); |
160 | 169 |
|
| 170 | +/** |
| 171 | + * The directory the native nearest-parent traversal starts from for `checkPath` |
| 172 | + * (see BindingData::NormalizePath/TraverseParent): the path itself when it has |
| 173 | + * a trailing separator, its dirname otherwise. |
| 174 | + * @param {string} checkPath |
| 175 | + * @returns {string} |
| 176 | + */ |
| 177 | +function getTraversalStartDirectory(checkPath) { |
| 178 | + const last = checkPath[checkPath.length - 1]; |
| 179 | + if (last === '/' || (isWindows && last === '\\')) { |
| 180 | + return StringPrototypeSlice(checkPath, 0, -1); |
| 181 | + } |
| 182 | + return path.dirname(checkPath); |
| 183 | +} |
| 184 | + |
161 | 185 | /** |
162 | 186 | * Get the nearest parent package.json file from a given path. |
163 | 187 | * Return the package.json data and the path to the package.json file, or undefined. |
164 | 188 | * @param {string} checkPath The path to start searching from. |
165 | 189 | * @returns {undefined | DeserializedPackageConfig} |
166 | 190 | */ |
167 | 191 | function getNearestParentPackageJSON(checkPath) { |
168 | | - const parentPackageJSONPath = moduleToParentPackageJSONCache.get(checkPath); |
169 | | - if (parentPackageJSONPath !== undefined) { |
170 | | - return deserializedPackageJSONCache.get(parentPackageJSONPath); |
| 192 | + const permissionEnabled = permission.isEnabled(); |
| 193 | + const cache = permissionEnabled ? moduleToParentPackageJSONCache : directoryToParentPackageJSONPathCache; |
| 194 | + const key = permissionEnabled ? checkPath : getTraversalStartDirectory(checkPath); |
| 195 | + let parentPackageJSONPath = cache.get(key); |
| 196 | + if (parentPackageJSONPath === undefined) { |
| 197 | + const result = modulesBinding.getNearestParentPackageJSON(checkPath); |
| 198 | + if (result === undefined) { |
| 199 | + parentPackageJSONPath = null; |
| 200 | + } else { |
| 201 | + const packageConfig = deserializePackageJSON(checkPath, result); |
| 202 | + parentPackageJSONPath = packageConfig.path; |
| 203 | + if (!deserializedPackageJSONCache.has(parentPackageJSONPath)) { |
| 204 | + deserializedPackageJSONCache.set(parentPackageJSONPath, packageConfig); |
| 205 | + } |
| 206 | + } |
| 207 | + cache.set(key, parentPackageJSONPath); |
171 | 208 | } |
172 | 209 |
|
173 | | - const result = modulesBinding.getNearestParentPackageJSON(checkPath); |
174 | | - const packageConfig = deserializePackageJSON(checkPath, result); |
175 | | - |
176 | | - moduleToParentPackageJSONCache.set(checkPath, packageConfig.path); |
177 | | - |
178 | | - const maybeCachedPackageConfig = deserializedPackageJSONCache.get(packageConfig.path); |
179 | | - if (maybeCachedPackageConfig !== undefined) { |
180 | | - return maybeCachedPackageConfig; |
| 210 | + if (parentPackageJSONPath === null) { |
| 211 | + // No package.json above this path: same shape as before, carrying the queried path. |
| 212 | + return deserializePackageJSON(checkPath, undefined); |
181 | 213 | } |
182 | | - |
183 | | - deserializedPackageJSONCache.set(packageConfig.path, packageConfig); |
184 | | - return packageConfig; |
| 214 | + return deserializedPackageJSONCache.get(parentPackageJSONPath); |
185 | 215 | } |
186 | 216 |
|
187 | 217 | /** |
|