'use strict'; /** * Module exports. */ module.exports = exports; /** * Module dependencies. */ // load mocking control function for accessing s3 via https. the function is a noop always returning // false if not mocking. exports.mockS3Http = require('./util/s3_setup').get_mockS3Http(); exports.mockS3Http('on'); const mocking = exports.mockS3Http('get'); const fs = require('fs'); const path = require('path'); const nopt = require('nopt'); const log = require('npmlog'); log.disableProgress(); const napi = require('./util/napi.js'); const EE = require('events').EventEmitter; const inherits = require('util').inherits; const cli_commands = [ 'clean', 'install', 'reinstall', 'build', 'rebuild', 'package', 'testpackage', 'publish', 'unpublish', 'info', 'testbinary', 'reveal', 'configure' ]; const aliases = {}; // differentiate node-pre-gyp's logs from npm's log.heading = 'node-pre-gyp'; if (mocking) { log.warn(`mocking s3 to ${process.env.node_pre_gyp_mock_s3}`); } // this is a getter to avoid circular reference warnings with node v14. Object.defineProperty(exports, 'find', { get: function() { return require('./pre-binding').find; }, enumerable: true }); // in the following, "my_module" is using node-pre-gyp to // prebuild and install pre-built binaries. "main_module" // is using "my_module". // // "bin/node-pre-gyp" invokes Run() without a path. the // expectation is that the working directory is the package // root "my_module". this is true because in all cases npm is // executing a script in the context of "my_module". // // "pre-binding.find()" is executed by "my_module" but in the // context of "main_module". this is because "main_module" is // executing and requires "my_module" which is then executing // "pre-binding.find()" via "node-pre-gyp.find()", so the working // directory is that of "main_module". // // that's why "find()" must pass the path to package.json. // function Run({ package_json_path = './package.json', argv }) { this.package_json_path = package_json_path; this.commands = {}; const self = this; cli_commands.forEach((command) => { self.commands[command] = function(argvx, callback) { log.verbose('command', command, argvx); return require('./' + command)(self, argvx, callback); }; }); this.parseArgv(argv); // this is set to true after the binary.host property was set to // either staging_host or production_host. this.binaryHostSet = false; } inherits(Run, EE); exports.Run = Run; const proto = Run.prototype; /** * Export the contents of the package.json. */ proto.package = require('../package.json'); /** * nopt configuration definitions */ proto.configDefs = { help: Boolean, // everywhere arch: String, // 'configure' debug: Boolean, // 'build' directory: String, // bin proxy: String, // 'install' loglevel: String // everywhere }; /** * nopt shorthands */ proto.shorthands = { release: '--no-debug', C: '--directory', debug: '--debug', j: '--jobs', silent: '--loglevel=silent', silly: '--loglevel=silly', verbose: '--loglevel=verbose' }; /** * expose the command aliases for the bin file to use. */ proto.aliases = aliases; /** * Parses the given argv array and sets the 'opts', 'argv', * 'command', and 'package_json' properties. */ proto.parseArgv = function parseOpts(argv) { this.opts = nopt(this.configDefs, this.shorthands, argv); this.argv = this.opts.argv.remain.slice(); const commands = this.todo = []; // create a copy of the argv array with aliases mapped argv = this.argv.map((arg) => { // is this an alias? if (arg in this.aliases) { arg = this.aliases[arg]; } return arg; }); // process the mapped args into "command" objects ("name" and "args" props) argv.slice().forEach((arg) => { if (arg in this.commands) { const args = argv.splice(0, argv.indexOf(arg)); argv.shift(); if (commands.length > 0) { commands[commands.length - 1].args = args; } commands.push({ name: arg, args: [] }); } }); if (commands.length > 0) { commands[commands.length - 1].args = argv.splice(0); } // if a directory was specified package.json is assumed to be relative // to it. let package_json_path = this.package_json_path; if (this.opts.directory) { package_json_path = path.join(this.opts.directory, package_json_path); } this.package_json = JSON.parse(fs.readFileSync(package_json_path)); // expand commands entries for multiple napi builds this.todo = napi.expand_commands(this.package_json, this.opts, commands); // support for inheriting config env variables from npm const npm_config_prefix = 'npm_config_'; Object.keys(process.env).forEach((name) => { if (name.indexOf(npm_config_prefix) !== 0) return; const val = process.env[name]; if (name === npm_config_prefix + 'loglevel') { log.level = val; } else { // add the user-defined options to the config name = name.substring(npm_config_prefix.length); // avoid npm argv clobber already present args // which avoids problem of 'npm test' calling // script that runs unique npm install commands if (name === 'argv') { if (this.opts.argv && this.opts.argv.remain && this.opts.argv.remain.length) { // do nothing } else { this.opts[name] = val; } } else { this.opts[name] = val; } } }); if (this.opts.loglevel) { log.level = this.opts.loglevel; } log.resume(); }; /** * allow the binary.host property to be set at execution time. * * for this to take effect requires all the following to be true. * - binary is a property in package.json * - binary.host is falsey * - binary.staging_host is not empty * - binary.production_host is not empty * * if any of the previous checks fail then the function returns an empty string * and makes no changes to package.json's binary property. * * * if command is "publish" then the default is set to "binary.staging_host" * if command is not "publish" the the default is set to "binary.production_host" * * if the command-line option '--s3_host' is set to "staging" or "production" then * "binary.host" is set to the specified "staging_host" or "production_host". if * '--s3_host' is any other value an exception is thrown. * * if '--s3_host' is not present then "binary.host" is set to the default as above. * * this strategy was chosen so that any command other than "publish" uses "production" * as the default without requiring any command-line options but that "publish" requires * '--s3_host production_host' to be specified in order to *really* publish. publishing * to staging can be done freely without worrying about disturbing any production releases. */ proto.setBinaryHostProperty = function(command) { if (this.binaryHostSet) { return this.package_json.binary.host; } const p = this.package_json; // don't set anything if host is present. it must be left blank to trigger this. if (!p || !p.binary || p.binary.host) { return ''; } // and both staging and production must be present. errors will be reported later. if (!p.binary.staging_host || !p.binary.production_host) { return ''; } let target = 'production_host'; if (command === 'publish') { target = 'staging_host'; } // the environment variable has priority over the default or the command line. if // either the env var or the command line option are invalid throw an error. const npg_s3_host = process.env.node_pre_gyp_s3_host; if (npg_s3_host === 'staging' || npg_s3_host === 'production') { target = `${npg_s3_host}_host`; } else if (this.opts['s3_host'] === 'staging' || this.opts['s3_host'] === 'production') { target = `${this.opts['s3_host']}_host`; } else if (this.opts['s3_host'] || npg_s3_host) { throw new Error(`invalid s3_host ${this.opts['s3_host'] || npg_s3_host}`); } p.binary.host = p.binary[target]; this.binaryHostSet = true; return p.binary.host; }; /** * Returns the usage instructions for node-pre-gyp. */ proto.usage = function usage() { const str = [ '', ' Usage: node-pre-gyp [options]', '', ' where is one of:', cli_commands.map((c) => { return ' - ' + c + ' - ' + require('./' + c).usage; }).join('\n'), '', 'node-pre-gyp@' + this.version + ' ' + path.resolve(__dirname, '..'), 'node@' + process.versions.node ].join('\n'); return str; }; /** * Version number getter. */ Object.defineProperty(proto, 'version', { get: function() { return this.package.version; }, enumerable: true });