1'use strict'; 2 3const { 4 JSONParse, 5 ObjectPrototypeHasOwnProperty, 6 SafeMap, 7 StringPrototypeEndsWith, 8 StringPrototypeIndexOf, 9 StringPrototypeLastIndexOf, 10 StringPrototypeSlice, 11} = primordials; 12const { 13 ERR_INVALID_PACKAGE_CONFIG, 14} = require('internal/errors').codes; 15const { internalModuleReadJSON } = internalBinding('fs'); 16const { resolve, sep, toNamespacedPath } = require('path'); 17const { kEmptyObject } = require('internal/util'); 18 19const { fileURLToPath, pathToFileURL } = require('internal/url'); 20 21const cache = new SafeMap(); 22const isAIX = process.platform === 'aix'; 23 24let manifest; 25 26/** 27 * @typedef {{ 28 * exists: boolean, 29 * pjsonPath: string, 30 * exports?: string | string[] | Record<string, unknown>, 31 * imports?: string | string[] | Record<string, unknown>, 32 * name?: string, 33 * main?: string, 34 * type: 'commonjs' | 'module' | 'none', 35 * }} PackageConfig 36 */ 37 38/** 39 * @param {string} jsonPath 40 * @param {{ 41 * base?: string, 42 * specifier: string, 43 * isESM: boolean, 44 * }} options 45 * @returns {PackageConfig} 46 */ 47function read(jsonPath, { base, specifier, isESM } = kEmptyObject) { 48 if (cache.has(jsonPath)) { 49 return cache.get(jsonPath); 50 } 51 52 const { 53 0: string, 54 1: containsKeys, 55 } = internalModuleReadJSON( 56 toNamespacedPath(jsonPath), 57 ); 58 const result = { 59 __proto__: null, 60 exists: false, 61 pjsonPath: jsonPath, 62 main: undefined, 63 name: undefined, 64 type: 'none', // Ignore unknown types for forwards compatibility 65 exports: undefined, 66 imports: undefined, 67 }; 68 69 // Folder read operation succeeds in AIX. 70 // For libuv change, see https://github.com/libuv/libuv/pull/2025. 71 // https://github.com/nodejs/node/pull/48477#issuecomment-1604586650 72 // TODO(anonrig): Follow-up on this change and remove it since it is a 73 // semver-major change. 74 const isResultValid = isAIX && !isESM ? containsKeys : string !== undefined; 75 76 if (isResultValid) { 77 let parsed; 78 try { 79 parsed = JSONParse(string); 80 } catch (error) { 81 if (isESM) { 82 throw new ERR_INVALID_PACKAGE_CONFIG( 83 jsonPath, 84 (base ? `"${specifier}" from ` : '') + fileURLToPath(base || specifier), 85 error.message, 86 ); 87 } else { 88 // For backward compat, we modify the error returned by JSON.parse rather than creating a new one. 89 // TODO(aduh95): make it throw ERR_INVALID_PACKAGE_CONFIG in a semver-major with original error as cause 90 error.message = 'Error parsing ' + jsonPath + ': ' + error.message; 91 error.path = jsonPath; 92 throw error; 93 } 94 } 95 96 result.exists = true; 97 98 // ObjectPrototypeHasOwnProperty is used to avoid prototype pollution. 99 if (ObjectPrototypeHasOwnProperty(parsed, 'name') && typeof parsed.name === 'string') { 100 result.name = parsed.name; 101 } 102 103 if (ObjectPrototypeHasOwnProperty(parsed, 'main') && typeof parsed.main === 'string') { 104 result.main = parsed.main; 105 } 106 107 if (ObjectPrototypeHasOwnProperty(parsed, 'exports')) { 108 result.exports = parsed.exports; 109 } 110 111 if (ObjectPrototypeHasOwnProperty(parsed, 'imports')) { 112 result.imports = parsed.imports; 113 } 114 115 // Ignore unknown types for forwards compatibility 116 if (ObjectPrototypeHasOwnProperty(parsed, 'type') && (parsed.type === 'commonjs' || parsed.type === 'module')) { 117 result.type = parsed.type; 118 } 119 120 if (manifest === undefined) { 121 const { getOptionValue } = require('internal/options'); 122 manifest = getOptionValue('--experimental-policy') ? 123 require('internal/process/policy').manifest : 124 null; 125 } 126 if (manifest !== null) { 127 const jsonURL = pathToFileURL(jsonPath); 128 manifest.assertIntegrity(jsonURL, string); 129 } 130 } 131 cache.set(jsonPath, result); 132 return result; 133} 134 135/** 136 * @param {string} requestPath 137 * @return {PackageConfig} 138 */ 139function readPackage(requestPath) { 140 return read(resolve(requestPath, 'package.json')); 141} 142 143/** 144 * Get the nearest parent package.json file from a given path. 145 * Return the package.json data and the path to the package.json file, or false. 146 * @param {string} checkPath The path to start searching from. 147 */ 148function readPackageScope(checkPath) { 149 const rootSeparatorIndex = StringPrototypeIndexOf(checkPath, sep); 150 let separatorIndex; 151 do { 152 separatorIndex = StringPrototypeLastIndexOf(checkPath, sep); 153 checkPath = StringPrototypeSlice(checkPath, 0, separatorIndex); 154 if (StringPrototypeEndsWith(checkPath, sep + 'node_modules')) { 155 return false; 156 } 157 const pjson = readPackage(checkPath + sep); 158 if (pjson.exists) { 159 return { 160 data: pjson, 161 path: checkPath, 162 }; 163 } 164 } while (separatorIndex > rootSeparatorIndex); 165 return false; 166} 167 168module.exports = { 169 read, 170 readPackage, 171 readPackageScope, 172}; 173