node-pre-gyp.js 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. 'use strict';
  2. /**
  3. * Module exports.
  4. */
  5. module.exports = exports;
  6. /**
  7. * Module dependencies.
  8. */
  9. // load mocking control function for accessing s3 via https. the function is a noop always returning
  10. // false if not mocking.
  11. exports.mockS3Http = require('./util/s3_setup').get_mockS3Http();
  12. exports.mockS3Http('on');
  13. const mocking = exports.mockS3Http('get');
  14. const fs = require('fs');
  15. const path = require('path');
  16. const nopt = require('nopt');
  17. const log = require('npmlog');
  18. log.disableProgress();
  19. const napi = require('./util/napi.js');
  20. const EE = require('events').EventEmitter;
  21. const inherits = require('util').inherits;
  22. const cli_commands = [
  23. 'clean',
  24. 'install',
  25. 'reinstall',
  26. 'build',
  27. 'rebuild',
  28. 'package',
  29. 'testpackage',
  30. 'publish',
  31. 'unpublish',
  32. 'info',
  33. 'testbinary',
  34. 'reveal',
  35. 'configure'
  36. ];
  37. const aliases = {};
  38. // differentiate node-pre-gyp's logs from npm's
  39. log.heading = 'node-pre-gyp';
  40. if (mocking) {
  41. log.warn(`mocking s3 to ${process.env.node_pre_gyp_mock_s3}`);
  42. }
  43. // this is a getter to avoid circular reference warnings with node v14.
  44. Object.defineProperty(exports, 'find', {
  45. get: function() {
  46. return require('./pre-binding').find;
  47. },
  48. enumerable: true
  49. });
  50. // in the following, "my_module" is using node-pre-gyp to
  51. // prebuild and install pre-built binaries. "main_module"
  52. // is using "my_module".
  53. //
  54. // "bin/node-pre-gyp" invokes Run() without a path. the
  55. // expectation is that the working directory is the package
  56. // root "my_module". this is true because in all cases npm is
  57. // executing a script in the context of "my_module".
  58. //
  59. // "pre-binding.find()" is executed by "my_module" but in the
  60. // context of "main_module". this is because "main_module" is
  61. // executing and requires "my_module" which is then executing
  62. // "pre-binding.find()" via "node-pre-gyp.find()", so the working
  63. // directory is that of "main_module".
  64. //
  65. // that's why "find()" must pass the path to package.json.
  66. //
  67. function Run({ package_json_path = './package.json', argv }) {
  68. this.package_json_path = package_json_path;
  69. this.commands = {};
  70. const self = this;
  71. cli_commands.forEach((command) => {
  72. self.commands[command] = function(argvx, callback) {
  73. log.verbose('command', command, argvx);
  74. return require('./' + command)(self, argvx, callback);
  75. };
  76. });
  77. this.parseArgv(argv);
  78. // this is set to true after the binary.host property was set to
  79. // either staging_host or production_host.
  80. this.binaryHostSet = false;
  81. }
  82. inherits(Run, EE);
  83. exports.Run = Run;
  84. const proto = Run.prototype;
  85. /**
  86. * Export the contents of the package.json.
  87. */
  88. proto.package = require('../package.json');
  89. /**
  90. * nopt configuration definitions
  91. */
  92. proto.configDefs = {
  93. help: Boolean, // everywhere
  94. arch: String, // 'configure'
  95. debug: Boolean, // 'build'
  96. directory: String, // bin
  97. proxy: String, // 'install'
  98. loglevel: String // everywhere
  99. };
  100. /**
  101. * nopt shorthands
  102. */
  103. proto.shorthands = {
  104. release: '--no-debug',
  105. C: '--directory',
  106. debug: '--debug',
  107. j: '--jobs',
  108. silent: '--loglevel=silent',
  109. silly: '--loglevel=silly',
  110. verbose: '--loglevel=verbose'
  111. };
  112. /**
  113. * expose the command aliases for the bin file to use.
  114. */
  115. proto.aliases = aliases;
  116. /**
  117. * Parses the given argv array and sets the 'opts', 'argv',
  118. * 'command', and 'package_json' properties.
  119. */
  120. proto.parseArgv = function parseOpts(argv) {
  121. this.opts = nopt(this.configDefs, this.shorthands, argv);
  122. this.argv = this.opts.argv.remain.slice();
  123. const commands = this.todo = [];
  124. // create a copy of the argv array with aliases mapped
  125. argv = this.argv.map((arg) => {
  126. // is this an alias?
  127. if (arg in this.aliases) {
  128. arg = this.aliases[arg];
  129. }
  130. return arg;
  131. });
  132. // process the mapped args into "command" objects ("name" and "args" props)
  133. argv.slice().forEach((arg) => {
  134. if (arg in this.commands) {
  135. const args = argv.splice(0, argv.indexOf(arg));
  136. argv.shift();
  137. if (commands.length > 0) {
  138. commands[commands.length - 1].args = args;
  139. }
  140. commands.push({ name: arg, args: [] });
  141. }
  142. });
  143. if (commands.length > 0) {
  144. commands[commands.length - 1].args = argv.splice(0);
  145. }
  146. // if a directory was specified package.json is assumed to be relative
  147. // to it.
  148. let package_json_path = this.package_json_path;
  149. if (this.opts.directory) {
  150. package_json_path = path.join(this.opts.directory, package_json_path);
  151. }
  152. this.package_json = JSON.parse(fs.readFileSync(package_json_path));
  153. // expand commands entries for multiple napi builds
  154. this.todo = napi.expand_commands(this.package_json, this.opts, commands);
  155. // support for inheriting config env variables from npm
  156. const npm_config_prefix = 'npm_config_';
  157. Object.keys(process.env).forEach((name) => {
  158. if (name.indexOf(npm_config_prefix) !== 0) return;
  159. const val = process.env[name];
  160. if (name === npm_config_prefix + 'loglevel') {
  161. log.level = val;
  162. } else {
  163. // add the user-defined options to the config
  164. name = name.substring(npm_config_prefix.length);
  165. // avoid npm argv clobber already present args
  166. // which avoids problem of 'npm test' calling
  167. // script that runs unique npm install commands
  168. if (name === 'argv') {
  169. if (this.opts.argv &&
  170. this.opts.argv.remain &&
  171. this.opts.argv.remain.length) {
  172. // do nothing
  173. } else {
  174. this.opts[name] = val;
  175. }
  176. } else {
  177. this.opts[name] = val;
  178. }
  179. }
  180. });
  181. if (this.opts.loglevel) {
  182. log.level = this.opts.loglevel;
  183. }
  184. log.resume();
  185. };
  186. /**
  187. * allow the binary.host property to be set at execution time.
  188. *
  189. * for this to take effect requires all the following to be true.
  190. * - binary is a property in package.json
  191. * - binary.host is falsey
  192. * - binary.staging_host is not empty
  193. * - binary.production_host is not empty
  194. *
  195. * if any of the previous checks fail then the function returns an empty string
  196. * and makes no changes to package.json's binary property.
  197. *
  198. *
  199. * if command is "publish" then the default is set to "binary.staging_host"
  200. * if command is not "publish" the the default is set to "binary.production_host"
  201. *
  202. * if the command-line option '--s3_host' is set to "staging" or "production" then
  203. * "binary.host" is set to the specified "staging_host" or "production_host". if
  204. * '--s3_host' is any other value an exception is thrown.
  205. *
  206. * if '--s3_host' is not present then "binary.host" is set to the default as above.
  207. *
  208. * this strategy was chosen so that any command other than "publish" uses "production"
  209. * as the default without requiring any command-line options but that "publish" requires
  210. * '--s3_host production_host' to be specified in order to *really* publish. publishing
  211. * to staging can be done freely without worrying about disturbing any production releases.
  212. */
  213. proto.setBinaryHostProperty = function(command) {
  214. if (this.binaryHostSet) {
  215. return this.package_json.binary.host;
  216. }
  217. const p = this.package_json;
  218. // don't set anything if host is present. it must be left blank to trigger this.
  219. if (!p || !p.binary || p.binary.host) {
  220. return '';
  221. }
  222. // and both staging and production must be present. errors will be reported later.
  223. if (!p.binary.staging_host || !p.binary.production_host) {
  224. return '';
  225. }
  226. let target = 'production_host';
  227. if (command === 'publish') {
  228. target = 'staging_host';
  229. }
  230. // the environment variable has priority over the default or the command line. if
  231. // either the env var or the command line option are invalid throw an error.
  232. const npg_s3_host = process.env.node_pre_gyp_s3_host;
  233. if (npg_s3_host === 'staging' || npg_s3_host === 'production') {
  234. target = `${npg_s3_host}_host`;
  235. } else if (this.opts['s3_host'] === 'staging' || this.opts['s3_host'] === 'production') {
  236. target = `${this.opts['s3_host']}_host`;
  237. } else if (this.opts['s3_host'] || npg_s3_host) {
  238. throw new Error(`invalid s3_host ${this.opts['s3_host'] || npg_s3_host}`);
  239. }
  240. p.binary.host = p.binary[target];
  241. this.binaryHostSet = true;
  242. return p.binary.host;
  243. };
  244. /**
  245. * Returns the usage instructions for node-pre-gyp.
  246. */
  247. proto.usage = function usage() {
  248. const str = [
  249. '',
  250. ' Usage: node-pre-gyp <command> [options]',
  251. '',
  252. ' where <command> is one of:',
  253. cli_commands.map((c) => {
  254. return ' - ' + c + ' - ' + require('./' + c).usage;
  255. }).join('\n'),
  256. '',
  257. 'node-pre-gyp@' + this.version + ' ' + path.resolve(__dirname, '..'),
  258. 'node@' + process.versions.node
  259. ].join('\n');
  260. return str;
  261. };
  262. /**
  263. * Version number getter.
  264. */
  265. Object.defineProperty(proto, 'version', {
  266. get: function() {
  267. return this.package.version;
  268. },
  269. enumerable: true
  270. });