src: reduce the nearest parent package JSON cache size · nodejs/node@ac78491

@@ -6,7 +6,9 @@ const {

66

ObjectDefineProperty,

77

RegExpPrototypeExec,

88

SafeMap,

9+

StringPrototypeEndsWith,

910

StringPrototypeIndexOf,

11+

StringPrototypeLastIndexOf,

1012

StringPrototypeSlice,

1113

} = primordials;

1214

const {

@@ -26,6 +28,7 @@ const {

2628

const { kEmptyObject } = require('internal/util');

2729

const modulesBinding = internalBinding('modules');

2830

const path = require('path');

31+

const permission = require('internal/process/permission');

2932

const { validateString } = require('internal/validators');

3033

const internalFsBinding = internalBinding('fs');

3134

@@ -127,26 +130,71 @@ function read(jsonPath, { base, specifier, isESM } = kEmptyObject) {

127130

};

128131

}

129132133+

/**

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

*/

136178

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);

139187

}

140188141-

const result = modulesBinding.getNearestParentPackageJSON(checkPath);

189+

const result = modulesBinding.readPackageJSON(nearestParentPackageJSON);

142190143191

if (result === undefined) {

144192

nearestParentPackageJSONCache.set(checkPath, undefined);

145193

return undefined;

146194

}

147195148196

const packageConfig = deserializePackageJSON(checkPath, result);

149-

nearestParentPackageJSONCache.set(checkPath, packageConfig);

197+

nearestParentPackageJSONCache.set(nearestParentPackageJSON, packageConfig);

150198151199

return packageConfig;

152200

}