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