conf.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. "use strict";
  2. const assert = require("assert");
  3. const fs = require("fs");
  4. const path = require("path");
  5. const { ConfigChain } = require("config-chain");
  6. const envReplace = require("./env-replace");
  7. const findPrefix = require("./find-prefix");
  8. const parseField = require("./parse-field");
  9. const toNerfDart = require("./nerf-dart");
  10. class Conf extends ConfigChain {
  11. // https://github.com/npm/npm/blob/latest/lib/config/core.js#L208-L222
  12. constructor(base) {
  13. super(base);
  14. this.root = base;
  15. }
  16. // https://github.com/npm/npm/blob/latest/lib/config/core.js#L332-L342
  17. add(data, marker) {
  18. try {
  19. /* eslint-disable no-param-reassign */
  20. for (const x of Object.keys(data)) {
  21. // https://github.com/npm/npm/commit/f0e998d
  22. const newKey = envReplace(x);
  23. const newField = parseField(data[x], newKey);
  24. delete data[x];
  25. data[newKey] = newField;
  26. }
  27. /* eslint-enable no-param-reassign */
  28. } catch (err) {
  29. throw err;
  30. }
  31. return super.add(data, marker);
  32. }
  33. // https://github.com/npm/npm/blob/latest/lib/config/core.js#L312-L325
  34. addFile(file, name = file) {
  35. const marker = { __source__: name };
  36. this.sources[name] = { path: file, type: "ini" };
  37. this.push(marker);
  38. this._await();
  39. try {
  40. const contents = fs.readFileSync(file, "utf8");
  41. this.addString(contents, file, "ini", marker);
  42. } catch (err) {
  43. this.add({}, marker);
  44. }
  45. return this;
  46. }
  47. // https://github.com/npm/npm/blob/latest/lib/config/core.js#L344-L360
  48. addEnv(env = process.env) {
  49. const conf = {};
  50. Object.keys(env)
  51. .filter((x) => /^npm_config_/i.test(x))
  52. .forEach((x) => {
  53. if (!env[x]) {
  54. return;
  55. }
  56. // leave first char untouched, even if it is a '_'
  57. // convert all other '_' to '-'
  58. const p = x
  59. .toLowerCase()
  60. .replace(/^npm_config_/, "")
  61. .replace(/(?!^)_/g, "-");
  62. conf[p] = env[x];
  63. });
  64. return super.addEnv("", conf, "env");
  65. }
  66. // https://github.com/npm/npm/blob/latest/lib/config/load-prefix.js
  67. loadPrefix() {
  68. const cli = this.list[0];
  69. Object.defineProperty(this, "prefix", {
  70. enumerable: true,
  71. set: (prefix) => {
  72. const g = this.get("global");
  73. this[g ? "globalPrefix" : "localPrefix"] = prefix;
  74. },
  75. get: () => {
  76. const g = this.get("global");
  77. return g ? this.globalPrefix : this.localPrefix;
  78. },
  79. });
  80. Object.defineProperty(this, "globalPrefix", {
  81. enumerable: true,
  82. set: (prefix) => {
  83. this.set("prefix", prefix);
  84. },
  85. get: () => path.resolve(this.get("prefix")),
  86. });
  87. let p;
  88. Object.defineProperty(this, "localPrefix", {
  89. enumerable: true,
  90. set: (prefix) => {
  91. p = prefix;
  92. },
  93. get: () => p,
  94. });
  95. if (Object.prototype.hasOwnProperty.call(cli, "prefix")) {
  96. p = path.resolve(cli.prefix);
  97. } else {
  98. try {
  99. p = findPrefix(process.cwd());
  100. } catch (err) {
  101. throw err;
  102. }
  103. }
  104. return p;
  105. }
  106. // https://github.com/npm/npm/blob/latest/lib/config/load-cafile.js
  107. loadCAFile(file) {
  108. if (!file) {
  109. return;
  110. }
  111. try {
  112. const contents = fs.readFileSync(file, "utf8");
  113. const delim = "-----END CERTIFICATE-----";
  114. const output = contents
  115. .split(delim)
  116. .filter((x) => Boolean(x.trim()))
  117. .map((x) => x.trimLeft() + delim);
  118. this.set("ca", output);
  119. } catch (err) {
  120. if (err.code === "ENOENT") {
  121. return;
  122. }
  123. throw err;
  124. }
  125. }
  126. // https://github.com/npm/npm/blob/latest/lib/config/set-user.js
  127. loadUser() {
  128. const defConf = this.root;
  129. if (this.get("global")) {
  130. return;
  131. }
  132. if (process.env.SUDO_UID) {
  133. defConf.user = Number(process.env.SUDO_UID);
  134. return;
  135. }
  136. const prefix = path.resolve(this.get("prefix"));
  137. try {
  138. const stats = fs.statSync(prefix);
  139. defConf.user = stats.uid;
  140. } catch (err) {
  141. if (err.code === "ENOENT") {
  142. return;
  143. }
  144. throw err;
  145. }
  146. }
  147. // https://github.com/npm/npm/blob/24ec9f2/lib/config/get-credentials-by-uri.js
  148. getCredentialsByURI(uri) {
  149. assert(uri && typeof uri === "string", "registry URL is required");
  150. const nerfed = toNerfDart(uri);
  151. const defnerf = toNerfDart(this.get("registry"));
  152. // hidden class micro-optimization
  153. const c = {
  154. scope: nerfed,
  155. token: undefined,
  156. password: undefined,
  157. username: undefined,
  158. email: undefined,
  159. auth: undefined,
  160. alwaysAuth: undefined,
  161. };
  162. // used to override scope matching for tokens as well as legacy auth
  163. if (this.get(`${nerfed}:always-auth`) !== undefined) {
  164. const val = this.get(`${nerfed}:always-auth`);
  165. c.alwaysAuth = val === "false" ? false : !!val;
  166. } else if (this.get("always-auth") !== undefined) {
  167. c.alwaysAuth = this.get("always-auth");
  168. }
  169. if (this.get(`${nerfed}:_authToken`)) {
  170. c.token = this.get(`${nerfed}:_authToken`);
  171. // the bearer token is enough, don't confuse things
  172. return c;
  173. }
  174. // Handle the old-style _auth=<base64> style for the default registry, if set.
  175. let authDef = this.get("_auth");
  176. let userDef = this.get("username");
  177. let passDef = this.get("_password");
  178. if (authDef && !(userDef && passDef)) {
  179. authDef = Buffer.from(authDef, "base64").toString();
  180. authDef = authDef.split(":");
  181. userDef = authDef.shift();
  182. passDef = authDef.join(":");
  183. }
  184. if (this.get(`${nerfed}:_password`)) {
  185. c.password = Buffer.from(this.get(`${nerfed}:_password`), "base64").toString("utf8");
  186. } else if (nerfed === defnerf && passDef) {
  187. c.password = passDef;
  188. }
  189. if (this.get(`${nerfed}:username`)) {
  190. c.username = this.get(`${nerfed}:username`);
  191. } else if (nerfed === defnerf && userDef) {
  192. c.username = userDef;
  193. }
  194. if (this.get(`${nerfed}:email`)) {
  195. c.email = this.get(`${nerfed}:email`);
  196. } else if (this.get("email")) {
  197. c.email = this.get("email");
  198. }
  199. if (c.username && c.password) {
  200. c.auth = Buffer.from(`${c.username}:${c.password}`).toString("base64");
  201. }
  202. return c;
  203. }
  204. // https://github.com/npm/npm/blob/24ec9f2/lib/config/set-credentials-by-uri.js
  205. setCredentialsByURI(uri, c) {
  206. assert(uri && typeof uri === "string", "registry URL is required");
  207. assert(c && typeof c === "object", "credentials are required");
  208. const nerfed = toNerfDart(uri);
  209. if (c.token) {
  210. this.set(`${nerfed}:_authToken`, c.token, "user");
  211. this.del(`${nerfed}:_password`, "user");
  212. this.del(`${nerfed}:username`, "user");
  213. this.del(`${nerfed}:email`, "user");
  214. this.del(`${nerfed}:always-auth`, "user");
  215. } else if (c.username || c.password || c.email) {
  216. assert(c.username, "must include username");
  217. assert(c.password, "must include password");
  218. assert(c.email, "must include email address");
  219. this.del(`${nerfed}:_authToken`, "user");
  220. const encoded = Buffer.from(c.password, "utf8").toString("base64");
  221. this.set(`${nerfed}:_password`, encoded, "user");
  222. this.set(`${nerfed}:username`, c.username, "user");
  223. this.set(`${nerfed}:email`, c.email, "user");
  224. if (c.alwaysAuth !== undefined) {
  225. this.set(`${nerfed}:always-auth`, c.alwaysAuth, "user");
  226. } else {
  227. this.del(`${nerfed}:always-auth`, "user");
  228. }
  229. } else {
  230. throw new Error("No credentials to set.");
  231. }
  232. }
  233. }
  234. module.exports = Conf;