From d7d371745a05b710b2385ca3f4985501ab75eaa5 Mon Sep 17 00:00:00 2001 From: Ron Ilan Date: Fri, 25 Apr 2025 09:24:47 -0700 Subject: [PATCH 01/14] Fixed errors that were created when a three year old pull request (#652) was rebased over multiple configuration changes. --- lib/mock/http.js | 4 ++-- lib/mock/s3.js | 3 +-- package-lock.json | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/mock/http.js b/lib/mock/http.js index eb2fd09b7..b5625d8b3 100644 --- a/lib/mock/http.js +++ b/lib/mock/http.js @@ -7,8 +7,7 @@ const path = require('path'); const nock = require('nock'); const os = require('os'); -const log = require('npmlog'); -log.disableProgress(); // disable the display of a progress bar +const log = require('../util/log.js'); log.heading = 'node-pre-gyp'; // differentiate node-pre-gyp's logs from npm's function http_mock() { @@ -17,6 +16,7 @@ function http_mock() { const basePath = `${os.tmpdir()}/mock`; nock(new RegExp('([a-z0-9]+[.])*s3[.]us-east-1[.]amazonaws[.]com')) + .persist() .get(() => true) //a function that always returns true is a catch all for nock .reply( (uri) => { diff --git a/lib/mock/s3.js b/lib/mock/s3.js index 076b995be..e485e3bdb 100644 --- a/lib/mock/s3.js +++ b/lib/mock/s3.js @@ -5,8 +5,7 @@ module.exports = exports = s3_mock; const AWSMock = require('mock-aws-s3'); const os = require('os'); -const log = require('npmlog'); -log.disableProgress(); // disable the display of a progress bar +const log = require('../util/log.js'); log.heading = 'node-pre-gyp'; // differentiate node-pre-gyp's logs from npm's function s3_mock() { diff --git a/package-lock.json b/package-lock.json index 6515d46f2..43cf6bd86 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,7 +32,7 @@ "node-addon-api": "^8.1.0", "nyc": "^17.0.0", "tape": "^5.5.2", - "tar-fs": "^3.0.6" + "tar-fs": "^3.0.8" }, "engines": { "node": ">=18" From 29f00fd6f053362f14194d60417cab8eb592a85e Mon Sep 17 00:00:00 2001 From: Ron Ilan Date: Tue, 10 May 2022 12:33:54 -0700 Subject: [PATCH 02/14] Added service script to switch test apps bucket from mock to S3 and vis versa. Removed previous one. Fix CodeQL errors. --- lib/mock/http.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/mock/http.js b/lib/mock/http.js index b5625d8b3..423fe1eee 100644 --- a/lib/mock/http.js +++ b/lib/mock/http.js @@ -16,7 +16,6 @@ function http_mock() { const basePath = `${os.tmpdir()}/mock`; nock(new RegExp('([a-z0-9]+[.])*s3[.]us-east-1[.]amazonaws[.]com')) - .persist() .get(() => true) //a function that always returns true is a catch all for nock .reply( (uri) => { From 428cbae6549f5ee70baef562ade8eca99ecd4212 Mon Sep 17 00:00:00 2001 From: Ron Ilan Date: Thu, 12 May 2022 12:00:05 -0700 Subject: [PATCH 03/14] Created GitHub Actions Test Setup - Added a GitHub Actions workflow that runs whenever there is a push to the repo. - Workflow includes two jobs: - A matrix job of node versions (10, 12, 14, 16, 18) and operating systems (Linux (ubuntu), Mac and Windows (2019 Enterprise)) that runs all tests against mock and then runs s3 tests against a bucket (located at us-east-1-bucket) specified as a repo secret. - A matrix job of and NW.js versions (0.64.0, 0.50.2) and node versions (10, 12, ,14, 16) that runs the NW.js test script. --- .github/workflows/push.yml | 74 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 .github/workflows/push.yml diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml new file mode 100644 index 000000000..c0d9396bb --- /dev/null +++ b/.github/workflows/push.yml @@ -0,0 +1,74 @@ +name: Push - Matrix Tests + +on: + push: + workflow_dispatch: + +jobs: + test-on-os-node-matrix: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-2019] # due to node-gyp & node compatibility issues, windows 2022 won't work for all node versions + node: [10, 12, 14, 16, 18] + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + S3_BUCKET: ${{ secrets.S3_BUCKET }} + + name: Test Node ${{ matrix.node }} on ${{ matrix.os }} + + steps: + - name: Checkout ${{ github.ref }} + uses: actions/checkout@v2 + + - name: Setup node ${{ matrix.node }} + uses: actions/setup-node@v2 + with: + node-version: ${{ matrix.node }} + + - name: NPM Install + run: npm install + + - name: Configure Windows 2019 + run: | + echo "/c/Program Files/Microsoft Visual Studio/2019/Enterprise/MSBuild/Current/Bin/" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + npm config set msvs_version 2019 + if: ${{ matrix.os == 'windows-2019' }} + + - name: Show Environment Info + run: | + printenv + node --version + npm --version + + - name: Run All Tests (against mock) + run: npm test + env: + node_pre_gyp_mock_s3 : true + + - name: Run S3 Tests (against ${{ env.S3_BUCKET }} bucket) + run: | + npm run bucket ${{ env.S3_BUCKET }} + npm run test:s3 + if: ${{ env.S3_BUCKET != '' }} + + test-nw: + runs-on: ubuntu-18.04 # at current config the nw test requires python 2 as default. hence use older linux version + strategy: + matrix: + node: [10, 12, 14, 16] # node 18 requires glibc GLIBC_2.28 not available on older version of linux + nw: [0.64.0, 0.50.2] # current version as of may 2022 and the one tested before introduction of this action. + name: NW.js ${{ matrix.nw }} on Node ${{ matrix.node }} + + steps: + - name: Checkout ${{ github.ref }} + uses: actions/checkout@v2 + + - name: Setup node ${{ matrix.node }} + uses: actions/setup-node@v2 + with: + node-version: ${{ matrix.node }} + + - name: Run Script + run: ./scripts/test-node-webkit.sh ${{ matrix.nw }} From 4d6e9ccb43a462dd7c4822679f80bfeb72ba1d9a Mon Sep 17 00:00:00 2001 From: Ron Ilan Date: Fri, 28 Jun 2024 20:28:28 -0700 Subject: [PATCH 04/14] Updated github actions to only test node 18, 20, 22. --- .github/workflows/push.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index c0d9396bb..ad66f7e01 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -10,7 +10,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-2019] # due to node-gyp & node compatibility issues, windows 2022 won't work for all node versions - node: [10, 12, 14, 16, 18] + node: [18, 20, 22] env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} @@ -57,7 +57,7 @@ jobs: runs-on: ubuntu-18.04 # at current config the nw test requires python 2 as default. hence use older linux version strategy: matrix: - node: [10, 12, 14, 16] # node 18 requires glibc GLIBC_2.28 not available on older version of linux + node: [18, 20, 22] nw: [0.64.0, 0.50.2] # current version as of may 2022 and the one tested before introduction of this action. name: NW.js ${{ matrix.nw }} on Node ${{ matrix.node }} From 4807a7618032a427ba6695a95c9dd4c88bce191d Mon Sep 17 00:00:00 2001 From: Ron Ilan Date: Fri, 28 Jun 2024 20:31:31 -0700 Subject: [PATCH 05/14] Trying windows-latest --- .github/workflows/push.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index ad66f7e01..5870f2fcf 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -9,7 +9,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, macos-latest, windows-2019] # due to node-gyp & node compatibility issues, windows 2022 won't work for all node versions + os: [ubuntu-latest, macos-latest, windows-latest] # due to node-gyp & node compatibility issues, windows 2022 won't work for all node versions node: [18, 20, 22] env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} From d26a6a5d8183433711aa0c3269cce738c1601ddf Mon Sep 17 00:00:00 2001 From: Ron Ilan Date: Fri, 28 Jun 2024 23:31:57 -0700 Subject: [PATCH 06/14] Removed nw tests from GitHub push actions as they are not working right now. --- .github/workflows/push.yml | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 5870f2fcf..27e8e4336 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -53,22 +53,3 @@ jobs: npm run test:s3 if: ${{ env.S3_BUCKET != '' }} - test-nw: - runs-on: ubuntu-18.04 # at current config the nw test requires python 2 as default. hence use older linux version - strategy: - matrix: - node: [18, 20, 22] - nw: [0.64.0, 0.50.2] # current version as of may 2022 and the one tested before introduction of this action. - name: NW.js ${{ matrix.nw }} on Node ${{ matrix.node }} - - steps: - - name: Checkout ${{ github.ref }} - uses: actions/checkout@v2 - - - name: Setup node ${{ matrix.node }} - uses: actions/setup-node@v2 - with: - node-version: ${{ matrix.node }} - - - name: Run Script - run: ./scripts/test-node-webkit.sh ${{ matrix.nw }} From ba5efc05556833be81823396dbc7767a0b1961f9 Mon Sep 17 00:00:00 2001 From: Ron Ilan Date: Sat, 29 Jun 2024 00:35:35 -0700 Subject: [PATCH 07/14] Changed push to bucket only tests. --- .github/workflows/push.yml | 55 -------------------------------------- 1 file changed, 55 deletions(-) delete mode 100644 .github/workflows/push.yml diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml deleted file mode 100644 index 27e8e4336..000000000 --- a/.github/workflows/push.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: Push - Matrix Tests - -on: - push: - workflow_dispatch: - -jobs: - test-on-os-node-matrix: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] # due to node-gyp & node compatibility issues, windows 2022 won't work for all node versions - node: [18, 20, 22] - env: - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - S3_BUCKET: ${{ secrets.S3_BUCKET }} - - name: Test Node ${{ matrix.node }} on ${{ matrix.os }} - - steps: - - name: Checkout ${{ github.ref }} - uses: actions/checkout@v2 - - - name: Setup node ${{ matrix.node }} - uses: actions/setup-node@v2 - with: - node-version: ${{ matrix.node }} - - - name: NPM Install - run: npm install - - - name: Configure Windows 2019 - run: | - echo "/c/Program Files/Microsoft Visual Studio/2019/Enterprise/MSBuild/Current/Bin/" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - npm config set msvs_version 2019 - if: ${{ matrix.os == 'windows-2019' }} - - - name: Show Environment Info - run: | - printenv - node --version - npm --version - - - name: Run All Tests (against mock) - run: npm test - env: - node_pre_gyp_mock_s3 : true - - - name: Run S3 Tests (against ${{ env.S3_BUCKET }} bucket) - run: | - npm run bucket ${{ env.S3_BUCKET }} - npm run test:s3 - if: ${{ env.S3_BUCKET != '' }} - From c33ea879cc6b87ca8ea49c25e2dc70979d47bba2 Mon Sep 17 00:00:00 2001 From: Ron Ilan Date: Wed, 18 May 2022 12:56:38 -0700 Subject: [PATCH 08/14] Refactor PR 533 and Fix issue 653 - Moved logic regarding host selection to versioning where all user defined values from package.json are transformed into command options. - Moved testing of feature from `run.test.js` to `versioning.test.js`. - Added `development_host` option. Becomes default option for `publish` `unpublish` when present. - Changed behavior when alternate hosts are defined. Now `production_host` acts as alias to host. Defining `staging_host` or `development_host` is enough to default `publish` and `unpublish` away from production. - When a chain of commands that includes `publish` or `unpublish`, when host not specifically set via command line or environment variable, ALL commands in the chain default away from production. - An invalid `s3_host` option does not result in error and is instead silently ignored. - Change is backwards compatible with previously valid configurations. --- lib/main.js | 6 - lib/mock/http.js | 1 + lib/node-pre-gyp.js | 66 +--- lib/pre-binding.js | 1 - lib/util/versioning.js | 83 +++-- test/run.test.js | 141 +-------- test/versioning.test.js | 652 +++++++++++++++++++++++++++++++++++++++- 7 files changed, 709 insertions(+), 241 deletions(-) diff --git a/lib/main.js b/lib/main.js index f46ea3299..264e3a8b6 100644 --- a/lib/main.js +++ b/lib/main.js @@ -72,12 +72,6 @@ function run() { return; } - // set binary.host when appropriate. host determines the s3 target bucket. - const target = prog.setBinaryHostProperty(command.name); - if (target && ['install', 'publish', 'unpublish', 'info'].indexOf(command.name) >= 0) { - log.info('using binary.host: ' + prog.package_json.binary.host); - } - prog.commands[command.name](command.args, function(err) { if (err) { log.error(command.name + ' error'); diff --git a/lib/mock/http.js b/lib/mock/http.js index 423fe1eee..b5625d8b3 100644 --- a/lib/mock/http.js +++ b/lib/mock/http.js @@ -16,6 +16,7 @@ function http_mock() { const basePath = `${os.tmpdir()}/mock`; nock(new RegExp('([a-z0-9]+[.])*s3[.]us-east-1[.]amazonaws[.]com')) + .persist() .get(() => true) //a function that always returns true is a catch all for nock .reply( (uri) => { diff --git a/lib/node-pre-gyp.js b/lib/node-pre-gyp.js index 26c0e5125..aefb65ba7 100644 --- a/lib/node-pre-gyp.js +++ b/lib/node-pre-gyp.js @@ -73,11 +73,8 @@ function Run({ package_json_path = './package.json', argv }) { }); 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; @@ -201,67 +198,6 @@ proto.parseArgv = function parseOpts(argv) { 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" or "unpublish" uses "production" - * as the default without requiring any command-line options but that "publish" and "unpublish" require - * '--s3_host production_host' to be specified in order to *really* publish (or unpublish). 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' || command === 'unpublish') { - 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. */ diff --git a/lib/pre-binding.js b/lib/pre-binding.js index e110fe381..9fd4407fa 100644 --- a/lib/pre-binding.js +++ b/lib/pre-binding.js @@ -19,7 +19,6 @@ exports.find = function(package_json_path, opts) { throw new Error(package_json_path + 'does not exist'); } const prog = new npg.Run({ package_json_path, argv: process.argv }); - prog.setBinaryHostProperty(); const package_json = prog.package_json; versioning.validate_config(package_json, opts); diff --git a/lib/util/versioning.js b/lib/util/versioning.js index b65e84e7d..ad3048cee 100644 --- a/lib/util/versioning.js +++ b/lib/util/versioning.js @@ -186,12 +186,6 @@ function get_runtime_abi(runtime, target_version) { } module.exports.get_runtime_abi = get_runtime_abi; -const required_parameters = [ - 'module_name', - 'module_path', - 'host' -]; - function validate_config(package_json, opts) { const msg = package_json.name + ' package.json is not node-pre-gyp ready:\n'; const missing = []; @@ -207,25 +201,33 @@ function validate_config(package_json, opts) { if (!package_json.binary) { missing.push('binary'); } - const o = package_json.binary; - if (o) { - required_parameters.forEach((p) => { - if (!o[p] || typeof o[p] !== 'string') { - missing.push('binary.' + p); - } - }); + + if (package_json.binary) { + if (!package_json.binary.module_name) { + missing.push('binary.module_name'); + } + if (!package_json.binary.module_path) { + missing.push('binary.module_path'); + } + if (!package_json.binary.host && !package_json.binary.production_host) { + missing.push('binary.host'); + } } if (missing.length >= 1) { throw new Error(msg + 'package.json must declare these properties: \n' + missing.join('\n')); } - if (o) { - // enforce https over http - const protocol = url.parse(o.host).protocol; - if (protocol === 'http:') { - throw new Error("'host' protocol (" + protocol + ") is invalid - only 'https:' is accepted"); - } + + if (package_json.binary) { + // for all possible host definitions - verify https usage + ['host', 'production_host', 'staging_host', 'development_host'].filter((item) => package_json.binary[item]).forEach((item) => { + const protocol = url.parse(package_json.binary[item]).protocol; + if (protocol === 'http:') { + throw new Error(msg + "'" + item + "' protocol (" + protocol + ") is invalid - only 'https:' is accepted"); + } + }); } + napi.validate_package_json(package_json, opts); } @@ -309,11 +311,46 @@ module.exports.evaluate = function(package_json, options, napi_build_version) { region: package_json.binary.region, s3ForcePathStyle: package_json.binary.s3ForcePathStyle || false }; - // support host mirror with npm config `--{module_name}_binary_host_mirror` - // e.g.: https://github.com/node-inspector/v8-profiler/blob/master/package.json#L25 - // > npm install v8-profiler --profiler_binary_host_mirror=https://registry.npmmirror.com/node-inspector/ + + // user can define a target host key to use (development_host, staging_host, production_host) + // by setting the name of the host (development, staging, production) + // into an environment variable or via a command line option. + // the environment variable has priority over the the command line. + let targetHost = process.env.node_pre_gyp_s3_host || options.s3_host; + + // if value is not one of the allowed or the matching key is not found in package.json + // silently ignore the option + if (['production', 'staging', 'development'].indexOf(targetHost) === -1 || !package_json.binary[`${targetHost}_host`]) { + targetHost = ''; + } + + // the production host is as specified in 'host' key (default) + // unless there is none and alias production_host is specified (backwards compatibility) + // note: package.json is verified in validate_config to include at least one of the two. + let host = package_json.binary.host || package_json.binary.production_host; + + // when a valid target is specified by user, the host is from that target (or 'host') + if (targetHost === 'staging') { + host = package_json.binary.staging_host; + } else if (targetHost === 'development') { + host = package_json.binary.development_host; + } else if (!targetHost && (package_json.binary.development_host || package_json.binary.staging_host)) { + // when host not specifically set via command line or environment variable + // but staging and/or development host are present in package.json + // for any command (or command chain) that includes publish or unpublish + // default to lower host (development, and if not preset, staging). + if (options.argv && options.argv.remain.some((item) => (item === 'publish' || item === 'unpublish'))) { + host = package_json.binary.development_host || package_json.binary.staging_host; + } + } + + // support host mirror with npm config `--{module_name}_binary_host_mirror` + // e.g.: https://github.com/node-inspector/v8-profiler/blob/master/package.json#L25 + // > npm install v8-profiler --profiler_binary_host_mirror=https://npm.taobao.org/mirrors/node-inspector/ const validModuleName = opts.module_name.replace('-', '_'); - const host = process.env['npm_config_' + validModuleName + '_binary_host_mirror'] || package_json.binary.host; + // explicitly set mirror overrides everything set above + host = process.env['npm_config_' + validModuleName + '_binary_host_mirror'] || host; + opts.host = fix_slashes(eval_template(host, opts)); opts.module_path = eval_template(package_json.binary.module_path, opts); // now we resolve the module_path to ensure it is absolute so that binding.gyp variables work predictably diff --git a/test/run.test.js b/test/run.test.js index c45e900f8..e121df471 100644 --- a/test/run.test.js +++ b/test/run.test.js @@ -28,10 +28,6 @@ const package_json_template = { } }; - -const all_commands = ['build', 'clean', 'configure', 'info', 'install', 'package', 'publish', 'rebuild', - 'reinstall', 'reveal', 'testbinary', 'testpackage', 'unpublish']; - /** * before testing create a scratch directory to run tests in. */ @@ -67,82 +63,6 @@ test.onFinish(() => { rimraf(scratch).then(() => undefined, () => undefined); }); -test('should set staging and production hosts', (t) => { - // make sure it's good when specifying host. - const mock_package_json = makePackageJson(); - - let { prog } = setupTest(dir, mock_package_json); - t.deepEqual(prog.package_json, mock_package_json); - t.equal(prog.binaryHostSet, false, 'binary host should not be flagged as set'); - - // test with no s3_host option - all_commands.forEach((cmd) => { - const mpj = clone(mock_package_json); - mpj.binary.host = ''; - const opts = { argv: [cmd] }; - ({ prog } = setupTest(dir, mpj, opts)); - mpj.binary.host = (cmd === 'publish' || cmd === 'unpublish') ? mpj.binary.staging_host : mpj.binary.production_host; - t.deepEqual(prog.package_json, mpj, 'host should be correct for command: ' + cmd); - t.equal(prog.binaryHostSet, true, 'binary host should be flagged as set'); - }); - - // test with s3_host set to staging - all_commands.forEach((cmd) => { - const mpj = clone(mock_package_json); - mpj.binary.host = ''; - const opts = { argv: [cmd, '--s3_host=staging'] }; - ({ prog } = setupTest(dir, mpj, opts)); - mpj.binary.host = mpj.binary.staging_host; - t.deepEqual(prog.package_json, mpj, 'host should be correct for command: ' + cmd); - t.equal(prog.binaryHostSet, true, 'binary host should be flagged as set'); - }); - - // test with s3_host set to production - all_commands.forEach((cmd) => { - const mpj = clone(mock_package_json); - mpj.binary.host = ''; - const opts = { argv: [cmd, '--s3_host=production'] }; - ({ prog } = setupTest(dir, mpj, opts)); - mpj.binary.host = mpj.binary.production_host; - t.deepEqual(prog.package_json, mpj, 'host should be correct for command: ' + cmd); - t.equal(prog.binaryHostSet, true, 'binary host should be flagged as set'); - }); - - t.end(); -}); - -test('should execute setBinaryHostProperty() properly', (t) => { - // it only --s3_host only takes effect if host is falsey. - const mock_package_json = makePackageJson({ binary: { host: '' } }); - - const opts = { argv: ['publish', '--s3_host=staging'] }; - - let { prog, binaryHost } = setupTest(dir, mock_package_json, opts); - t.equal(binaryHost, mock_package_json.binary.staging_host); - - // set it again to verify that it returns the already set value - binaryHost = prog.setBinaryHostProperty('publish'); - t.equal(binaryHost, mock_package_json.binary.staging_host); - - // now do this again but expect an empty binary host value because - // staging_host is missing. - const mpj = clone(mock_package_json); - delete mpj.binary.staging_host; - ({ prog, binaryHost } = setupTest(dir, mpj, opts)); - t.equal(binaryHost, ''); - - // one more time but with an invalid value for s3_host - opts.argv = ['publish', '--s3_host=bad-news']; - try { - ({ prog, binaryHost } = setupTest(dir, mock_package_json, opts)); - t.fail('should throw with --s3_host=bad-news'); - } catch (e) { - t.equal(e.message, 'invalid s3_host bad-news'); - } - - t.end(); -}); - test('verify that the --directory option works', (t) => { const initial = process.cwd(); @@ -223,6 +143,10 @@ test('verify that a non-existent package.json fails', (t) => { // test helpers. // +// helper to clone mock package.json. +// // https://stackoverflow.com/questions/4459928/how-to-deep-clone-in-javascript +const clone = (obj) => JSON.parse(JSON.stringify(obj)); + function makePackageJson(options = {}) { const package_json = clone(package_json_template); // override binary values if supplied @@ -233,60 +157,3 @@ function makePackageJson(options = {}) { } return package_json; } - -// helper to write package.json to disk so Run() can be instantiated with it. -function setupTest(directory, package_json, opts) { - opts = opts || {}; - let argv = ['node', 'program']; - if (opts.argv) { - argv = argv.concat(opts.argv); - } - const prev_dir = process.cwd(); - if (!opts.noChdir) { - try { - fs.mkdirSync(directory); - } catch (e) { - if (e.code !== 'EEXIST') { - throw e; - } - } - process.chdir(directory); - } - - try { - fs.writeFileSync('package.json', JSON.stringify(package_json)); - const prog = new npg.Run({ package_json_path: './package.json', argv }); - const binaryHost = prog.setBinaryHostProperty(prog.todo[0] && prog.todo[0].name); - return { prog, binaryHost }; - } finally { - process.chdir(prev_dir); - } -} - -// helper to clone mock package.json. it's overkill for existing tests -// but is future-proof. -// https://stackoverflow.com/questions/4459928/how-to-deep-clone-in-javascript -function clone(obj, hash = new WeakMap()) { - if (Object(obj) !== obj) return obj; // primitives - if (hash.has(obj)) return hash.get(obj); // cyclic reference - let result; - - if (obj instanceof Set) { - result = new Set(obj); // treat set as a value - } else if (obj instanceof Map) { - result = new Map(Array.from(obj, ([key, val]) => [key, clone(val, hash)])); - } else if (obj instanceof Date) { - result = new Date(obj); - } else if (obj instanceof RegExp) { - result = new RegExp(obj.source, obj.flags); - } else if (obj.constructor) { - result = new obj.constructor(); - } else { - result = Object.create(null); - } - hash.set(obj, result); - return Object.assign(result, ...Object.keys(obj).map((key) => { - return { [key]: clone(obj[key], hash) }; - })); -} - diff --git a/test/versioning.test.js b/test/versioning.test.js index 455419998..3ea709a67 100644 --- a/test/versioning.test.js +++ b/test/versioning.test.js @@ -222,6 +222,107 @@ test('should verify that the binary property has required properties', (t) => { t.end(); }); +test('should allow production_host to act as alias to host (when host not preset)', (t) => { + const mock_package_json = { + 'name': 'test', + 'main': 'test.js', + 'version': '0.1.0', + 'binary': { + 'module_name': 'binary-module-name', + 'module_path': 'binary-module-path', + 'production_host': 's3-production-path' + } + }; + + const package_json = Object.assign({}, mock_package_json); + const opts = versioning.evaluate(package_json, { module_root: '/root' }); + t.equal(opts.host, mock_package_json.binary.production_host + '/'); + t.equal(opts.hosted_path, mock_package_json.binary.production_host + '/'); + t.equal(opts.hosted_tarball, mock_package_json.binary.production_host + '/' + opts.package_name); + + t.end(); +}); + +test('should use host over production_host (when both are preset)', (t) => { + const mock_package_json = { + 'name': 'test', + 'main': 'test.js', + 'version': '0.1.0', + 'binary': { + 'module_name': 'binary-module-name', + 'module_path': 'binary-module-path', + 'production_host': 's3-production-path', + 'host': 'binary-path' + } + }; + + const package_json = Object.assign({}, mock_package_json); + const opts = versioning.evaluate(package_json, { module_root: '/root' }); + t.equal(opts.host, mock_package_json.binary.host + '/'); + t.equal(opts.hosted_path, mock_package_json.binary.host + '/'); + t.equal(opts.hosted_tarball, mock_package_json.binary.host + '/' + opts.package_name); + + t.end(); +}); + +test('should verify that the host url protocol is https', (t) => { + const mock_package_json = { + 'name': 'test', + 'main': 'test.js', + 'version': '0.1.0', + 'binary': { + 'module_name': 'binary-module-name', + 'module_path': 'binary-module-path', + 'host': 'http://your_module.s3-us-west-1.amazonaws.com' + } + }; + + const package_json = Object.assign({}, mock_package_json); + + try { + // eslint-disable-next-line no-unused-vars + const opts = versioning.evaluate(package_json, {}); + } catch (e) { + // name won't be there if it's missing but both messages say 'undefined' + const msg = package_json.name + ' package.json is not node-pre-gyp ready:\n'; + const expectedMessage = msg + '\'host\' protocol (http:) is invalid - only \'https:\' is accepted'; + t.equal(e.message, expectedMessage); + } + + t.end(); +}); + +test('should verify that alternate hosts url protocol is https', (t) => { + const mock_package_json = { + name: 'test', + main: 'test.js', + version: '0.1.0', + binary: { + module_name: 'binary-module-name', + module_path: 'binary-module-path', + host: 'https://your_module.s3-us-west-1.amazonaws.com' + } + }; + + const hosts = ['production', 'staging', 'development']; + hosts.forEach((host) => { + const package_json = Object.assign({}, mock_package_json); + package_json[`${host}_host`] = `https://${host}_bucket.s3-us-west-1.amazonaws.com`; + + try { + // eslint-disable-next-line no-unused-vars + const opts = versioning.evaluate(package_json, {}); + } catch (e) { + // name won't be there if it's missing but both messages say 'undefined' + const msg = package_json.name + ' package.json is not node-pre-gyp ready:\n'; + const expectedMessage = msg + `'${host}_host' protocol (http:) is invalid - only 'https:' is accepted`; + t.equal(e.message, expectedMessage); + } + }); + + t.end(); +}); + test('should not add bucket name to hosted_path when s3ForcePathStyle is false', (t) => { const mock_package_json = { 'name': 'test', @@ -266,7 +367,53 @@ test('should add bucket name to hosted_path when s3ForcePathStyle is true', (t) t.end(); }); -test('should verify host overrides staging and production values', (t) => { +test('should use host key by default for install, info, publish and unpublish commands (when no other hosts specified)', (t) => { + const makeOoptions = (cmd) => { + return { + argv: { + remain: [cmd], + cooked: [cmd], + original: [cmd] + } + }; + }; + + const mock_package_json = { + name: 'test', + main: 'test.js', + version: '0.1.0', + binary: { + module_name: 'binary-module-name', + module_path: 'binary-module-path', + host: 'binary-path' + } + }; + + const cmds = ['install', 'info', 'publish', 'unpublish']; + cmds.forEach((cmd) => { + try { + const opts = versioning.evaluate(mock_package_json, makeOoptions(cmd)); + t.equal(opts.host, mock_package_json.binary.host + '/'); + t.equal(opts.hosted_path, mock_package_json.binary.host + '/'); + t.equal(opts.hosted_tarball, mock_package_json.binary.host + '/' + opts.package_name); + } catch (e) { + t.ifError(e, 'staging_host and production_host should be silently ignored'); + } + }); + t.end(); +}); + +test('should use production_host as alias for host for install and info commands (when host not preset)', (t) => { + const makeOoptions = (cmd) => { + return { + argv: { + remain: [cmd], + cooked: [cmd], + original: [cmd] + } + }; + }; + const mock_package_json = { name: 'test', main: 'test.js', @@ -274,25 +421,512 @@ test('should verify host overrides staging and production values', (t) => { binary: { module_name: 'binary-module-name', module_path: 'binary-module-path', - host: 'binary-path', - staging_host: 's3-staging-path', production_host: 's3-production-path' } }; - try { - const opts = versioning.evaluate(mock_package_json, { module_root: '/root' }); + const cmds = ['install', 'info']; + cmds.forEach((cmd) => { + + const opts = versioning.evaluate(mock_package_json, makeOoptions(cmd)); + t.equal(opts.host, mock_package_json.binary.production_host + '/'); + t.equal(opts.hosted_path, mock_package_json.binary.production_host + '/'); + t.equal(opts.hosted_tarball, mock_package_json.binary.production_host + '/' + opts.package_name); + + }); + t.end(); +}); + +test('should use host over production_host for install and info commands (when both are preset)', (t) => { + const makeOoptions = (cmd) => { + return { + argv: { + remain: [cmd], + cooked: [cmd], + original: [cmd] + } + }; + }; + + const mock_package_json = { + name: 'test', + main: 'test.js', + version: '0.1.0', + binary: { + module_name: 'binary-module-name', + module_path: 'binary-module-path', + production_host: 's3-production-path', + host: 'binary-path' + } + }; + + const cmds = ['install', 'info']; + cmds.forEach((cmd) => { + + const opts = versioning.evaluate(mock_package_json, makeOoptions(cmd)); t.equal(opts.host, mock_package_json.binary.host + '/'); t.equal(opts.hosted_path, mock_package_json.binary.host + '/'); t.equal(opts.hosted_tarball, mock_package_json.binary.host + '/' + opts.package_name); - } catch (e) { - t.ifError(e, 'staging_host and production_host should be silently ignored'); - } + }); + t.end(); +}); + +test('should use host by default for install and info commands (overriding alternate hosts, production_host not present)', (t) => { + const options = { + argv: { + remain: ['install', 'info'], + cooked: ['install', 'info'], + original: ['install', 'info'] + } + }; + + const mock_package_json = { + name: 'test', + main: 'test.js', + version: '0.1.0', + binary: { + module_name: 'binary-module-name', + module_path: 'binary-module-path', + host: 'binary-path', + development_host: 's3-development-path', + staging_host: 's3-staging-path' + } + }; + + + const opts = versioning.evaluate(mock_package_json, options); + t.equal(opts.host, mock_package_json.binary.host + '/'); + t.equal(opts.hosted_path, mock_package_json.binary.host + '/'); + t.equal(opts.hosted_tarball, mock_package_json.binary.host + '/' + opts.package_name); + + + t.end(); +}); + +test('should use host by default for install and info commands (overriding alternate hosts, host is present)', (t) => { + const options = { + argv: { + remain: ['install', 'info'], + cooked: ['install', 'info'], + original: ['install', 'info'] + } + }; + + const mock_package_json = { + name: 'test', + main: 'test.js', + version: '0.1.0', + binary: { + module_name: 'binary-module-name', + module_path: 'binary-module-path', + host: 'binary-path', + development_host: 's3-development-path', + staging_host: 's3-staging-path', + production_host: 's3-production-path' + } + }; + + + const opts = versioning.evaluate(mock_package_json, options); + t.equal(opts.host, mock_package_json.binary.host + '/'); + t.equal(opts.hosted_path, mock_package_json.binary.host + '/'); + t.equal(opts.hosted_tarball, mock_package_json.binary.host + '/' + opts.package_name); + + + t.end(); +}); + +test('should use development_host key by default for publish and unpublish commands (when it is specified)', (t) => { + const makeOoptions = (cmd) => { + return { + argv: { + remain: [cmd], + cooked: [cmd], + original: [cmd] + } + }; + }; + + const mock_package_json = { + name: 'test', + main: 'test.js', + version: '0.1.0', + binary: { + module_name: 'binary-module-name', + module_path: 'binary-module-path', + host: 'binary-path', + development_host: 's3-development-path', + staging_host: 's3-staging-path', + production_host: 's3-production-path' + } + }; + + const cmds = ['publish', 'unpublish']; + cmds.forEach((cmd) => { + + const opts = versioning.evaluate(mock_package_json, makeOoptions(cmd)); + t.equal(opts.host, mock_package_json.binary.development_host + '/'); + t.equal(opts.hosted_path, mock_package_json.binary.development_host + '/'); + t.equal(opts.hosted_tarball, mock_package_json.binary.development_host + '/' + opts.package_name); + + }); + t.end(); +}); + +test('should use staging_host key by default for publish and unpublish commands (when it is specified and no development_host)', (t) => { + const makeOoptions = (cmd) => { + return { + argv: { + remain: [cmd], + cooked: [cmd], + original: [cmd] + } + }; + }; + + const mock_package_json = { + name: 'test', + main: 'test.js', + version: '0.1.0', + binary: { + module_name: 'binary-module-name', + module_path: 'binary-module-path', + host: 'binary-path', + staging_host: 's3-staging-path', + production_host: 's3-production-path' + } + }; + + const cmds = ['publish', 'unpublish']; + cmds.forEach((cmd) => { + + const opts = versioning.evaluate(mock_package_json, makeOoptions(cmd)); + t.equal(opts.host, mock_package_json.binary.staging_host + '/'); + t.equal(opts.hosted_path, mock_package_json.binary.staging_host + '/'); + t.equal(opts.hosted_tarball, mock_package_json.binary.staging_host + '/' + opts.package_name); + + }); + t.end(); +}); + +test('should use development_host key by default for publish and unpublish commands in a chain (when it is specified)', (t) => { + const makeOoptions = (cmd) => { + return { + argv: { + remain: ['info', cmd], + cooked: ['info', cmd], + original: ['info', cmd] + } + }; + }; + + const mock_package_json = { + name: 'test', + main: 'test.js', + version: '0.1.0', + binary: { + module_name: 'binary-module-name', + module_path: 'binary-module-path', + host: 'binary-path', + development_host: 's3-development-path', + staging_host: 's3-staging-path', + production_host: 's3-production-path' + } + }; + + const cmds = ['publish', 'unpublish']; + cmds.forEach((cmd) => { + + const opts = versioning.evaluate(mock_package_json, makeOoptions(cmd)); + t.equal(opts.host, mock_package_json.binary.development_host + '/'); + t.equal(opts.hosted_path, mock_package_json.binary.development_host + '/'); + t.equal(opts.hosted_tarball, mock_package_json.binary.development_host + '/' + opts.package_name); + + }); + t.end(); +}); + +test('should use host specified by the --s3_host option', (t) => { + const makeOoptions = (cmd, host) => { + return { + s3_host: host, + argv: { + remain: [cmd], + cooked: [cmd, '--s3_host', host], + original: [cmd, `--s3_host=${host}`] + } + }; + }; + + const mock_package_json = { + name: 'test', + main: 'test.js', + version: '0.1.0', + binary: { + module_name: 'binary-module-name', + module_path: 'binary-module-path', + // host: 'binary-path', + development_host: 's3-development-path', + staging_host: 's3-staging-path', + production_host: 's3-production-path' + } + }; + + const hosts = ['production', 'staging', 'development']; + const cmds = ['install', 'info', 'publish', 'unpublish']; + + cmds.forEach((cmd) => { + hosts.forEach((host) => { + const opts = versioning.evaluate(mock_package_json, makeOoptions(cmd, host)); + t.equal(opts.host, mock_package_json.binary[`${host}_host`] + '/'); + t.equal(opts.hosted_path, mock_package_json.binary[`${host}_host`] + '/'); + t.equal(opts.hosted_tarball, mock_package_json.binary[`${host}_host`] + '/' + opts.package_name); + }); + }); + t.end(); +}); + +test('should use defaults when --s3_host option is invalid', (t) => { + const makeOoptions = (cmd) => { + return { + s3_host: 'not-valid', + argv: { + remain: [cmd], + cooked: [cmd, '--s3_host', 'not-valid'], + original: [cmd, '--s3_host=not-valid'] + } + }; + }; + + const mock_package_json = { + name: 'test', + main: 'test.js', + version: '0.1.0', + binary: { + module_name: 'binary-module-name', + module_path: 'binary-module-path', + host: 'binary-path', + development_host: 's3-development-path', + staging_host: 's3-staging-path', + production_host: 's3-production-path' + } + }; + + const cmds = ['install', 'info', 'publish', 'unpublish']; + + cmds.forEach((cmd) => { + const opts = versioning.evaluate(mock_package_json, makeOoptions(cmd)); + const host = cmd.indexOf('publish') === -1 ? 'host' : 'development_host'; + + t.equal(opts.host, mock_package_json.binary[host] + '/'); + t.equal(opts.hosted_path, mock_package_json.binary[host] + '/'); + t.equal(opts.hosted_tarball, mock_package_json.binary[host] + '/' + opts.package_name); + + }); + t.end(); +}); + +test('should use host specified by the s3_host environment variable', (t) => { + const makeOoptions = (cmd) => { + return { + argv: { + remain: [cmd], + cooked: [cmd], + original: [cmd] + } + }; + }; + + const mock_package_json = { + name: 'test', + main: 'test.js', + version: '0.1.0', + binary: { + module_name: 'binary-module-name', + module_path: 'binary-module-path', + // host: 'binary-path', + development_host: 's3-development-path', + staging_host: 's3-staging-path', + production_host: 's3-production-path' + } + }; + + const hosts = ['production', 'staging', 'development']; + const cmds = ['install', 'info', 'publish', 'unpublish']; + + cmds.forEach((cmd) => { + hosts.forEach((host) => { + process.env.node_pre_gyp_s3_host = host; + const opts = versioning.evaluate(mock_package_json, makeOoptions(cmd)); + t.equal(opts.host, mock_package_json.binary[`${host}_host`] + '/'); + t.equal(opts.hosted_path, mock_package_json.binary[`${host}_host`] + '/'); + t.equal(opts.hosted_tarball, mock_package_json.binary[`${host}_host`] + '/' + opts.package_name); + }); + }); + t.end(); +}); + +test('should use defaults when s3_host environment variable is invalid', (t) => { + const makeOoptions = (cmd) => { + return { + argv: { + remain: [cmd], + cooked: [cmd], + original: [cmd] + } + }; + }; + + const mock_package_json = { + name: 'test', + main: 'test.js', + version: '0.1.0', + binary: { + module_name: 'binary-module-name', + module_path: 'binary-module-path', + host: 'binary-path', + development_host: 's3-development-path', + staging_host: 's3-staging-path', + production_host: 's3-production-path' + } + }; + + const cmds = ['install', 'info', 'publish', 'unpublish']; + + cmds.forEach((cmd) => { + process.env.node_pre_gyp_s3_host = 'not-valid'; + const opts = versioning.evaluate(mock_package_json, makeOoptions(cmd)); + const host = cmd.indexOf('publish') === -1 ? 'host' : 'development_host'; + + t.equal(opts.host, mock_package_json.binary[host] + '/'); + t.equal(opts.hosted_path, mock_package_json.binary[host] + '/'); + t.equal(opts.hosted_tarball, mock_package_json.binary[host] + '/' + opts.package_name); + + }); + t.end(); +}); + +test('should use defaults when s3_host environment is valid but package.json does not match (production_host is default)', (t) => { + const makeOoptions = (cmd) => { + return { + argv: { + remain: [cmd], + cooked: [cmd], + original: [cmd] + } + }; + }; + + const mock_package_json = { + name: 'test', + main: 'test.js', + version: '0.1.0', + binary: { + module_name: 'binary-module-name', + module_path: 'binary-module-path', + // no development_host + staging_host: 's3-staging-path', + // production_host not host + production_host: 's3-production-path' + } + }; + + const cmds = ['install', 'info', 'publish', 'unpublish']; + + cmds.forEach((cmd) => { + process.env.node_pre_gyp_s3_host = 'development'; // specify development_host + const opts = versioning.evaluate(mock_package_json, makeOoptions(cmd)); + const host = cmd.indexOf('publish') === -1 ? 'production_host' : 'staging_host'; // defaults + + t.equal(opts.host, mock_package_json.binary[host] + '/'); + t.equal(opts.hosted_path, mock_package_json.binary[host] + '/'); + t.equal(opts.hosted_tarball, mock_package_json.binary[host] + '/' + opts.package_name); + + }); + t.end(); +}); + +test('should use defaults when s3_host environment is valid but package.json does not match (host is default)', (t) => { + const makeOoptions = (cmd) => { + return { + argv: { + remain: [cmd], + cooked: [cmd], + original: [cmd] + } + }; + }; + + const mock_package_json = { + name: 'test', + main: 'test.js', + version: '0.1.0', + binary: { + module_name: 'binary-module-name', + module_path: 'binary-module-path', + // host not production_host + host: 'binary-path', + // no development_host + staging_host: 's3-staging-path' + } + }; + + const cmds = ['install', 'info', 'publish', 'unpublish']; + + cmds.forEach((cmd) => { + process.env.node_pre_gyp_s3_host = 'development'; // specify development_host + const opts = versioning.evaluate(mock_package_json, makeOoptions(cmd)); + const host = cmd.indexOf('publish') === -1 ? 'host' : 'staging_host'; // defaults + + t.equal(opts.host, mock_package_json.binary[host] + '/'); + t.equal(opts.hosted_path, mock_package_json.binary[host] + '/'); + t.equal(opts.hosted_tarball, mock_package_json.binary[host] + '/' + opts.package_name); + + }); + t.end(); +}); + +test('should use host specified by environment variable overriding --s3_host option', (t) => { + const makeOoptions = (cmd) => { + return { + s3_host: 'staging', // from command line + argv: { + remain: [cmd], + cooked: [cmd, '--s3_host', 'staging'], + original: [cmd, '--s3_host=staging'] + } + }; + }; + + const mock_package_json = { + name: 'test', + main: 'test.js', + version: '0.1.0', + binary: { + module_name: 'binary-module-name', + module_path: 'binary-module-path', + // host: 'binary-path', + development_host: 's3-development-path', + staging_host: 's3-staging-path', + production_host: 's3-production-path' + } + }; + + + const cmds = ['install', 'info', 'publish', 'unpublish']; + cmds.forEach((cmd) => { + process.env.node_pre_gyp_s3_host = 'production'; + const opts = versioning.evaluate(mock_package_json, makeOoptions(cmd)); + t.equal(opts.host, mock_package_json.binary.production_host + '/'); + t.equal(opts.hosted_path, mock_package_json.binary.production_host + '/'); + t.equal(opts.hosted_tarball, mock_package_json.binary.production_host + '/' + opts.package_name); + + }); t.end(); }); -test('should replace "-" with "_" in custom binary host', (t) => { +test('should replace "-" with "_" in mirror binary host', (t) => { const mock_package_json = { name: 'test', main: 'test.js', From 7d55567a69f6f690f86435a0d154741ee38ae2a5 Mon Sep 17 00:00:00 2001 From: Ron Ilan Date: Thu, 24 Apr 2025 22:28:45 -0700 Subject: [PATCH 09/14] Update comment to resolve confilct. --- lib/util/versioning.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/util/versioning.js b/lib/util/versioning.js index ad3048cee..af700bc8f 100644 --- a/lib/util/versioning.js +++ b/lib/util/versioning.js @@ -346,7 +346,7 @@ module.exports.evaluate = function(package_json, options, napi_build_version) { // support host mirror with npm config `--{module_name}_binary_host_mirror` // e.g.: https://github.com/node-inspector/v8-profiler/blob/master/package.json#L25 - // > npm install v8-profiler --profiler_binary_host_mirror=https://npm.taobao.org/mirrors/node-inspector/ + // > npm install v8-profiler --profiler_binary_host_mirror=https://registry.npmmirror.com/node-inspector/ const validModuleName = opts.module_name.replace('-', '_'); // explicitly set mirror overrides everything set above host = process.env['npm_config_' + validModuleName + '_binary_host_mirror'] || host; From 0de06345c45632af820025fd5db5dacf7c8bcbd1 Mon Sep 17 00:00:00 2001 From: Ron Ilan Date: Thu, 19 May 2022 10:19:30 -0700 Subject: [PATCH 10/14] Introduced host configuration with object. - added a standarize_config function. function is called by validate_config and mutates the object holding the parsed package.json, so that hosts are defined as objects with endpoint key, rather than strings. - modified versioning.evaluate to work with object format of hosts. --- lib/util/versioning.js | 86 +++++++++++++++++++++++++++++++++++------- 1 file changed, 72 insertions(+), 14 deletions(-) diff --git a/lib/util/versioning.js b/lib/util/versioning.js index af700bc8f..c563b3cef 100644 --- a/lib/util/versioning.js +++ b/lib/util/versioning.js @@ -186,7 +186,51 @@ function get_runtime_abi(runtime, target_version) { } module.exports.get_runtime_abi = get_runtime_abi; +function standarize_config(package_json) { + // backwards compatibility via mutation of user supplied configuration + if (package_json.binary) { + // the option of setting production_host was introduced in + // https://github.com/mapbox/node-pre-gyp/pull/533 + // spec said that host should be falsey and production_host not empty. + // legacy config will thus have production_host (and staging_host) defined + // and will not have host defined. + // to support legacy configuration with new spec: + + // ** transfer the value of production_host to host + if (package_json.binary.production_host && !package_json.binary.host) { + package_json.binary.host = package_json.binary.production_host; + } + + if (package_json.binary.host) { + // hosts used to be specified as string (and user may still do so) + // to support legacy configuration with new spec: + + // map string format of host to object key + ['host', 'staging_host', 'development_host'].filter((item) => package_json.binary[item]).forEach((item) => { + if (typeof package_json.binary[item] === 'string') { + package_json.binary[item] = { endpoint: package_json.binary[item] }; + } + }); + + // the option to explicitly set buckt host properties was introduced in + // https://github.com/mapbox/node-pre-gyp/pull/576 + // spec defined options as keys of binary relating to the string value of host. + // legacy config will thus have bucket, region, s3ForcePathStyle defined under binary. + // to support legacy configuration with new spec: + + // map keys defined on binary to keys defined on host + ['bucket', 'region', 's3ForcePathStyle'].filter((item) => package_json.binary[item]).forEach((item) => { + if (typeof package_json.binary[item] !== 'object') { + package_json.binary.host[item] = package_json.binary[item]; + } + }); + } + } +} + function validate_config(package_json, opts) { + standarize_config(package_json); // the way hosts are defined changed overtime. make it standard. + const msg = package_json.name + ' package.json is not node-pre-gyp ready:\n'; const missing = []; if (!package_json.main) { @@ -209,9 +253,16 @@ function validate_config(package_json, opts) { if (!package_json.binary.module_path) { missing.push('binary.module_path'); } - if (!package_json.binary.host && !package_json.binary.production_host) { + + if (!package_json.binary.host) { missing.push('binary.host'); } + + if (package_json.binary.host) { + if (!package_json.binary.host.endpoint) { + missing.push('binary.host.endpoint'); + } + } } if (missing.length >= 1) { @@ -220,8 +271,8 @@ function validate_config(package_json, opts) { if (package_json.binary) { // for all possible host definitions - verify https usage - ['host', 'production_host', 'staging_host', 'development_host'].filter((item) => package_json.binary[item]).forEach((item) => { - const protocol = url.parse(package_json.binary[item]).protocol; + ['host', 'staging_host', 'development_host'].filter((item) => package_json.binary[item]).forEach((item) => { + const protocol = url.parse(package_json.binary[item].endpoint).protocol; if (protocol === 'http:') { throw new Error(msg + "'" + item + "' protocol (" + protocol + ") is invalid - only 'https:' is accepted"); } @@ -278,7 +329,8 @@ const default_remote_path = ''; module.exports.evaluate = function(package_json, options, napi_build_version) { options = options || {}; - validate_config(package_json, options); // options is a suitable substitute for opts in this case + standarize_config(package_json); // note: package_json is mutated + validate_config(package_json, options); const v = package_json.version; const module_version = semver.parse(v); const runtime = options.runtime || get_process_runtime(process.versions); @@ -306,10 +358,7 @@ module.exports.evaluate = function(package_json, options, napi_build_version) { target_arch: options.target_arch || process.arch, libc: options.target_libc || detect_libc.familySync() || 'unknown', module_main: package_json.main, - toolset: options.toolset || '', // address https://github.com/mapbox/node-pre-gyp/issues/119 - bucket: package_json.binary.bucket, - region: package_json.binary.region, - s3ForcePathStyle: package_json.binary.s3ForcePathStyle || false + toolset: options.toolset || '' // address https://github.com/mapbox/node-pre-gyp/issues/119 }; // user can define a target host key to use (development_host, staging_host, production_host) @@ -327,20 +376,24 @@ module.exports.evaluate = function(package_json, options, napi_build_version) { // the production host is as specified in 'host' key (default) // unless there is none and alias production_host is specified (backwards compatibility) // note: package.json is verified in validate_config to include at least one of the two. - let host = package_json.binary.host || package_json.binary.production_host; + let hostData = package_json.binary.host; // when a valid target is specified by user, the host is from that target (or 'host') if (targetHost === 'staging') { - host = package_json.binary.staging_host; + hostData = package_json.binary.staging_host; } else if (targetHost === 'development') { - host = package_json.binary.development_host; + hostData = package_json.binary.development_host; } else if (!targetHost && (package_json.binary.development_host || package_json.binary.staging_host)) { // when host not specifically set via command line or environment variable // but staging and/or development host are present in package.json // for any command (or command chain) that includes publish or unpublish // default to lower host (development, and if not preset, staging). if (options.argv && options.argv.remain.some((item) => (item === 'publish' || item === 'unpublish'))) { - host = package_json.binary.development_host || package_json.binary.staging_host; + if (package_json.binary.development_host) { + hostData = package_json.binary.development_host; + } else if (package_json.binary.staging_host) { + hostData = package_json.binary.staging_host; + } } } @@ -349,9 +402,13 @@ module.exports.evaluate = function(package_json, options, napi_build_version) { // > npm install v8-profiler --profiler_binary_host_mirror=https://registry.npmmirror.com/node-inspector/ const validModuleName = opts.module_name.replace('-', '_'); // explicitly set mirror overrides everything set above - host = process.env['npm_config_' + validModuleName + '_binary_host_mirror'] || host; + hostData.endpoint = process.env['npm_config_' + validModuleName + '_binary_host_mirror'] || hostData.endpoint; + + opts.host = fix_slashes(eval_template(hostData.endpoint, opts)); + opts.bucket = hostData.bucket; + opts.region = hostData.region; + opts.s3ForcePathStyle = hostData.s3ForcePathStyle || false; - opts.host = fix_slashes(eval_template(host, opts)); opts.module_path = eval_template(package_json.binary.module_path, opts); // now we resolve the module_path to ensure it is absolute so that binding.gyp variables work predictably if (options.module_root) { @@ -366,6 +423,7 @@ module.exports.evaluate = function(package_json, options, napi_build_version) { const package_name = package_json.binary.package_name ? package_json.binary.package_name : default_package_name; opts.package_name = eval_template(package_name, opts); opts.staged_tarball = path.join('build/stage', opts.remote_path, opts.package_name); + // when using s3ForcePathStyle the bucket is part of the http object path // add it if (opts.s3ForcePathStyle) { From d01d9a1f00ee6a2ccca36668c616eb21df096d85 Mon Sep 17 00:00:00 2001 From: Ron Ilan Date: Thu, 19 May 2022 10:21:15 -0700 Subject: [PATCH 11/14] Expanded testing to cover host configuration with an object. - added app1.3 - identical to app1 but using host, staging_host and development_host configuration with an object. - expanded and refactored versioning testing. - modified bucket switching script to work with new package.json format. --- scripts/set-bucket.js | 21 +- test/app1.1/README.md | 2 +- test/app1.2/README.md | 2 +- test/app1.3/.gitignore | 5 + test/app1.3/README.md | 4 + test/app1.3/app1.3.cc | 14 + test/app1.3/binding.gyp | 19 + test/app1.3/index.js | 6 + test/app1.3/package.json | 40 ++ test/s3.test.js | 7 + test/versioning.test.js | 884 +++++++++++++++++++++++++++------------ 11 files changed, 731 insertions(+), 273 deletions(-) create mode 100644 test/app1.3/.gitignore create mode 100644 test/app1.3/README.md create mode 100644 test/app1.3/app1.3.cc create mode 100644 test/app1.3/binding.gyp create mode 100644 test/app1.3/index.js create mode 100644 test/app1.3/package.json diff --git a/scripts/set-bucket.js b/scripts/set-bucket.js index 894d77b44..b1edd3a66 100644 --- a/scripts/set-bucket.js +++ b/scripts/set-bucket.js @@ -18,10 +18,10 @@ dirs.forEach((dir) => { const pkg = require(`${root}/${dir}/package.json`); // relative path // bucket specified as part of s3 virtual host format (auto detected by node-pre-gyp) - const keys = ['host', 'staging_host', 'production_host']; + const keys = ['host', 'development_host', 'staging_host', 'production_host']; keys.forEach((item) => { - if (pkg.binary[item]) { - + // hosts may be specified as strings (old format) + if (pkg.binary[item] && typeof pkg.binary[item] === 'string') { // match the bucket part of the url const match = pkg.binary[item].match(/^https:\/\/(.+)(?:\.s3[-.].*)$/i); if (match) { @@ -29,8 +29,21 @@ dirs.forEach((dir) => { console.log(`Success: set ${dir} ${item} to ${pkg.binary[item]}`); } } + + if (pkg.binary[item] && typeof pkg.binary[item] === 'object') { + // match the bucket part of the url + const match = pkg.binary[item].endpoint.match(/^https:\/\/(.+)(?:\.s3[-.].*)$/i); + if (match) { + pkg.binary[item].endpoint = pkg.binary[item].endpoint.replace(match[1], bucket); + console.log(`Success: set ${dir} ${item} to ${pkg.binary[item]}`); + } + if (pkg.binary[item].bucket) { + pkg.binary[item].bucket = bucket; + console.log(`Set ${dir} bucket to ${pkg.binary[item].bucket}`); + } + } }); - // bucket is specified explicitly + // bucket may be specified explicitly on binary ()old format if (pkg.binary.bucket) { pkg.binary.bucket = bucket; console.log(`Set ${dir} bucket to ${pkg.binary.bucket}`); diff --git a/test/app1.1/README.md b/test/app1.1/README.md index 1210b1527..dbfa677eb 100644 --- a/test/app1.1/README.md +++ b/test/app1.1/README.md @@ -1,4 +1,4 @@ # Test app Demonstrates a simple configuration that uses node-pre-gyp. -Identical to app1 but using production and staging binary host option. +Identical to app1 but using production and staging binary host option as string (legacy) diff --git a/test/app1.2/README.md b/test/app1.2/README.md index 5cbf76162..06a6cede5 100644 --- a/test/app1.2/README.md +++ b/test/app1.2/README.md @@ -1,4 +1,4 @@ # Test app Demonstrates a simple configuration that uses node-pre-gyp. -Identical to app1 but using explicit host, region, bucket options. +Identical to app1 but using explicit host, region, bucket options defined on the binary (legacy). diff --git a/test/app1.3/.gitignore b/test/app1.3/.gitignore new file mode 100644 index 000000000..f6a050319 --- /dev/null +++ b/test/app1.3/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +build/ +lib/binding/ +node_modules +npm-debug.log \ No newline at end of file diff --git a/test/app1.3/README.md b/test/app1.3/README.md new file mode 100644 index 000000000..e1e26982b --- /dev/null +++ b/test/app1.3/README.md @@ -0,0 +1,4 @@ +# Test app + +Demonstrates a simple configuration that uses node-pre-gyp. +Identical to app1 but using host, staging_host and development_host configuration with an object. diff --git a/test/app1.3/app1.3.cc b/test/app1.3/app1.3.cc new file mode 100644 index 000000000..4c8a8d872 --- /dev/null +++ b/test/app1.3/app1.3.cc @@ -0,0 +1,14 @@ +#include + +Napi::Value get_hello(Napi::CallbackInfo const& info) { + Napi::Env env = info.Env(); + Napi::EscapableHandleScope scope(env); + return scope.Escape(Napi::String::New(env, "hello")); +} + +Napi::Object start(Napi::Env env, Napi::Object exports) { + exports.Set("hello", Napi::Function::New(env, get_hello)); + return exports; +} + +NODE_API_MODULE(app1, start) diff --git a/test/app1.3/binding.gyp b/test/app1.3/binding.gyp new file mode 100644 index 000000000..05ef4f1c3 --- /dev/null +++ b/test/app1.3/binding.gyp @@ -0,0 +1,19 @@ +{ + "targets": [ + { + "target_name": "<(module_name)", + "sources": [ "<(module_name).cc" ], + 'product_dir': '<(module_path)', + 'include_dirs': ["../../node_modules/node-addon-api/"], + 'cflags!': [ '-fno-exceptions' ], + 'cflags_cc!': [ '-fno-exceptions' ], + "xcode_settings": { + 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES', + "CLANG_CXX_LIBRARY": "libc++" + }, + 'msvs_settings': { + 'VCCLCompilerTool': { 'ExceptionHandling': 1 }, + } + } + ] +} diff --git a/test/app1.3/index.js b/test/app1.3/index.js new file mode 100644 index 000000000..084fc583f --- /dev/null +++ b/test/app1.3/index.js @@ -0,0 +1,6 @@ +var binary = require('node-pre-gyp'); +var path = require('path') +var binding_path = binary.find(path.resolve(path.join(__dirname,'./package.json'))); +var binding = require(binding_path); + +require('assert').equal(binding.hello(),"hello"); \ No newline at end of file diff --git a/test/app1.3/package.json b/test/app1.3/package.json new file mode 100644 index 000000000..cd92843fc --- /dev/null +++ b/test/app1.3/package.json @@ -0,0 +1,40 @@ +{ + "name": "node-pre-gyp-test-app1.3", + "author": "Dane Springmeyer ", + "description": "node-pre-gyp test", + "repository": { + "type": "git", + "url": "git://github.com/mapbox/node-pre-gyp.git" + }, + "license": "BSD-3-Clause", + "version": "0.1.0", + "main": "./index.js", + "binary": { + "module_name": "app1.3", + "module_path": "./lib/binding/", + "host": { + "endpoint": "https://s3.us-east-1.amazonaws.com", + "bucket": "npg-mock-bucket", + "region": "us-east-1", + "s3ForcePathStyle": true + }, + "staging_host": { + "endpoint": "https://s3.us-east-1.amazonaws.com", + "bucket": "npg-mock-bucket", + "region": "us-east-1", + "s3ForcePathStyle": true + }, + "development_host": { + "endpoint": "https://s3.us-east-1.amazonaws.com", + "bucket": "npg-mock-bucket", + "region": "us-east-1", + "s3ForcePathStyle": true + }, + "remote_path": "./node-pre-gyp/{name}/v{version}/{configuration}/{toolset}/", + "package_name": "{node_abi}-{platform}-{arch}.tar.gz" + }, + "scripts": { + "install": "node-pre-gyp install --fallback-to-build", + "test": "node index.js" + } +} diff --git a/test/s3.test.js b/test/s3.test.js index 52123c500..6b7a555ab 100644 --- a/test/s3.test.js +++ b/test/s3.test.js @@ -36,6 +36,13 @@ const apps = [ 'base': ['binding/app1.2.node'] } }, + { + 'name': 'app1.3', + 'args': '', + 'files': { + 'base': ['binding/app1.3.node'] + } + }, { 'name': 'app2', 'args': '--custom_include_path=../include --debug', diff --git a/test/versioning.test.js b/test/versioning.test.js index 3ea709a67..f69e9e9cb 100644 --- a/test/versioning.test.js +++ b/test/versioning.test.js @@ -5,28 +5,7 @@ const versioning = require('../lib/util/versioning.js'); const test = require('tape'); const detect_libc = require('detect-libc'); -test('should normalize double slash', (t) => { - const mock_package_json = { - 'name': 'test', - 'main': 'test.js', - 'version': '0.1.0', - 'binary': { - 'module_name': 'test', - 'module_path': './lib/binding/{configuration}/{toolset}/{name}', - 'remote_path': './{name}/v{version}/{configuration}/{version}/{toolset}/', - 'package_name': '{module_name}-v{major}.{minor}.{patch}-{prerelease}+{build}-{toolset}-{node_abi}-{platform}-{arch}.tar.gz', - 'host': 'https://some-bucket.s3.us-east-1.amazonaws.com' - } - }; - const opts = versioning.evaluate(mock_package_json, {}); - t.equal(opts.remote_path, './test/v0.1.0/Release/0.1.0/'); - // Node v0.11.x on windows lowercases C:// when path.join is called - // https://github.com/joyent/node/issues/7031 - t.equal(path.normalize(opts.module_path), path.join(process.cwd(), 'lib/binding/Release/test')); - const opts_toolset = versioning.evaluate(mock_package_json, { toolset: 'custom-toolset' }); - t.equal(opts_toolset.remote_path, './test/v0.1.0/Release/0.1.0/custom-toolset/'); - t.end(); -}); +/* versioning */ test('should detect abi for node process', (t) => { const mock_process_versions = { @@ -35,6 +14,7 @@ test('should detect abi for node process', (t) => { modules: '11' }; const abi = versioning.get_node_abi('node', mock_process_versions); + t.equal(abi, 'node-v11'); t.equal(versioning.get_runtime_abi('node', undefined), versioning.get_node_abi('node', process.versions)); t.end(); @@ -46,19 +26,20 @@ test('should detect abi for odd node target', (t) => { modules: 'bogus' }; const abi = versioning.get_node_abi('node', mock_process_versions); + t.equal(abi, 'node-v0.11.1000000'); t.end(); }); test('should detect abi for custom node target', (t) => { const mock_process_versions = { - 'node': '0.10.0', - 'modules': '11' + node: '0.10.0', + modules: '11' }; t.equal(versioning.get_runtime_abi('node', '0.10.0'), versioning.get_node_abi('node', mock_process_versions)); const mock_process_versions2 = { - 'node': '0.8.0', - 'v8': '3.11' + node: '0.8.0', + v8: '3.11' }; t.equal(versioning.get_runtime_abi('node', '0.8.0'), versioning.get_node_abi('node', mock_process_versions2)); t.end(); @@ -66,11 +47,11 @@ test('should detect abi for custom node target', (t) => { test('should detect runtime for node-webkit and electron', (t) => { const mock_process_versions = { - 'electron': '0.37.3' + electron: '0.37.3' }; t.equal(versioning.get_process_runtime(mock_process_versions), 'electron'); const mock_process_versions2 = { - 'node': '0.8.0' + node: '0.8.0' }; t.equal(versioning.get_process_runtime(mock_process_versions2), 'node'); const mock_process_versions3 = { @@ -95,6 +76,7 @@ test('should throw when custom node target is not found in abi_crosswalk file', versioning.get_runtime_abi('node', '123456789.0.0'); } catch (e) { const expectedMessage = 'Unsupported target version: 123456789.0.0'; + t.equal(e.message, expectedMessage); t.end(); } @@ -105,47 +87,53 @@ test('should throw when custom node target is not semver', (t) => { versioning.get_runtime_abi('node', '1.2.3.4'); } catch (e) { const expectedMessage = 'Unknown target version: 1.2.3.4'; + t.equal(e.message, expectedMessage); t.end(); } }); test('should detect custom binary host from env', (t) => { - const mock_package_json = { - 'name': 'test', - 'main': 'test.js', - 'version': '0.1.0', - 'binary': { - 'module_name': 'test', - 'module_path': './lib/binding/{configuration}/{toolset}/{name}', - 'remote_path': './{name}/v{version}/{configuration}/{version}/{toolset}/', - 'package_name': '{module_name}-v{major}.{minor}.{patch}-{prerelease}+{build}-{toolset}-{node_abi}-{platform}-{arch}.tar.gz', - 'host': 'https://some-bucket.s3.us-east-1.amazonaws.com' + const parsed_package_json = { + name: 'test', + main: 'test.js', + version: '0.1.0', + binary: { + module_name: 'test', + module_path: './lib/binding/{configuration}/{toolset}/{name}', + remote_path: './{name}/v{version}/{configuration}/{version}/{toolset}/', + package_name: '{module_name}-v{major}.{minor}.{patch}-{prerelease}+{build}-{toolset}-{node_abi}-{platform}-{arch}.tar.gz', + host: 'https://some-bucket.s3.us-east-1.amazonaws.com' } }; // mock npm_config_test_binary_host_mirror env process.env.npm_config_test_binary_host_mirror = 'https://registry.npmmirror.com/node-inspector/'; - const opts = versioning.evaluate(mock_package_json, {}); - t.equal(opts.host, 'https://registry.npmmirror.com/node-inspector/'); + const cloned = JSON.parse(JSON.stringify(parsed_package_json)); + const opts = versioning.evaluate(cloned, {}); + + t.equal(opts.host, 'https://npm.taobao.org/mirrors/node-inspector/'); + delete process.env.npm_config_test_binary_host_mirror; t.end(); }); test('should detect libc', (t) => { - const mock_package_json = { - 'name': 'test', - 'main': 'test.js', - 'version': '0.1.0', - 'binary': { - 'module_name': 'test', - 'module_path': './lib/binding/{name}-{libc}', - 'remote_path': './{name}/{libc}/', - 'package_name': '{module_name}-{libc}.tar.gz', - 'host': 'https://some-bucket.s3-us-west-1.amazonaws.com' - } - }; - const opts = versioning.evaluate(mock_package_json, { module_root: '/root' }); + const parsed_package_json = { + name: 'test', + main: 'test.js', + version: '0.1.0', + binary: { + module_name: 'test', + module_path: './lib/binding/{name}-{libc}', + remote_path: './{name}/{libc}/', + package_name: '{module_name}-{libc}.tar.gz', + host: 'https://some-bucket.s3-us-west-1.amazonaws.com' + } + }; + const cloned = JSON.parse(JSON.stringify(parsed_package_json)); + const opts = versioning.evaluate(cloned, { module_root: '/root' }); const expected_libc_token = detect_libc.familySync() || 'unknown'; + t.comment('performing test with the following libc token: ' + expected_libc_token); t.equal(opts.module_path, path.normalize('/root/lib/binding/test-' + expected_libc_token)); t.equal(opts.module, path.normalize('/root/lib/binding/test-' + expected_libc_token + '/test.node')); @@ -155,34 +143,34 @@ test('should detect libc', (t) => { t.end(); }); -// -// validate package.json versioning configurations -// +/* package.json verification */ + test('should verify that package.json has required properties', (t) => { - const mock_package_json = { - 'name': 'test', - 'main': 'test.js', - 'version': '0.1.0', - 'binary': { - 'module_name': 'binary-module-name', - 'module_path': 'binary-module-path', - 'host': 'binary-path' + const parsed_package_json = { + name: 'test', + main: 'test.js', + version: '0.1.0', + binary: { + module_name: 'binary-module-name', + module_path: 'binary-module-path', + host: 'binary-path' } }; - const requireds = Object.keys(mock_package_json); + const requireds = Object.keys(parsed_package_json); for (let i = 0; i < requireds.length; i++) { - const package_json = Object.assign({}, mock_package_json); - delete package_json[requireds[i]]; + const cloned = JSON.parse(JSON.stringify(parsed_package_json)); + delete cloned[requireds[i]]; const missing = [requireds[i]]; try { // eslint-disable-next-line no-unused-vars - const opts = versioning.evaluate(package_json, { module_root: '/root' }); + const opts = versioning.evaluate(cloned, {}); } catch (e) { // name won't be there if it's missing but both messages say 'undefined' - const msg = package_json.name + ' package.json is not node-pre-gyp ready:\n'; + const msg = cloned.name + ' package.json is not node-pre-gyp ready:\n'; const expectedMessage = msg + 'package.json must declare these properties: \n' + missing.join('\n'); + t.equal(e.message, expectedMessage); } } @@ -190,32 +178,65 @@ test('should verify that package.json has required properties', (t) => { }); test('should verify that the binary property has required properties', (t) => { - const mock_package_json = { - 'name': 'test', - 'main': 'test.js', - 'version': '0.1.0', - 'binary': { - 'module_name': 'binary-module-name', - 'module_path': 'binary-module-path', - 'host': 'binary-path' + const parsed_package_json = { + name: 'test', + main: 'test.js', + version: '0.1.0', + binary: { + module_name: 'binary-module-name', + module_path: 'binary-module-path', + host: 'binary-path' } }; - const requireds = Object.keys(mock_package_json.binary); + const requireds = Object.keys(parsed_package_json.binary); for (let i = 0; i < requireds.length; i++) { - const package_json = Object.assign({}, mock_package_json); - package_json.binary = Object.assign({}, mock_package_json.binary); - - delete package_json.binary[requireds[i]]; + const cloned = JSON.parse(JSON.stringify(parsed_package_json)); + delete cloned.binary[requireds[i]]; const missing = ['binary.' + requireds[i]]; try { // eslint-disable-next-line no-unused-vars - const opts = versioning.evaluate(package_json, { module_root: '/root' }); + const opts = versioning.evaluate(cloned, {}); + } catch (e) { + // name won't be there if it's missing but both messages say 'undefined' + const msg = cloned.name + ' package.json is not node-pre-gyp ready:\n'; + const expectedMessage = msg + 'package.json must declare these properties: \n' + missing.join('\n'); + + t.equal(e.message, expectedMessage); + } + } + t.end(); +}); + +test('should verify that the binary.host has required properties', (t) => { + const parsed_package_json = { + name: 'test', + main: 'test.js', + version: '0.1.0', + binary: { + module_name: 'binary-module-name', + module_path: 'binary-module-path', + host: { + endpoint: 'binary-path' + } + } + }; + const requireds = Object.keys(parsed_package_json.binary.host); + + for (let i = 0; i < requireds.length; i++) { + const cloned = JSON.parse(JSON.stringify(parsed_package_json)); + delete cloned.binary.host[requireds[i]]; + const missing = ['binary.host.' + requireds[i]]; + + try { + // eslint-disable-next-line no-unused-vars + const opts = versioning.evaluate(cloned, {}); } catch (e) { // name won't be there if it's missing but both messages say 'undefined' - const msg = package_json.name + ' package.json is not node-pre-gyp ready:\n'; + const msg = cloned.name + ' package.json is not node-pre-gyp ready:\n'; const expectedMessage = msg + 'package.json must declare these properties: \n' + missing.join('\n'); + t.equal(e.message, expectedMessage); } } @@ -223,69 +244,94 @@ test('should verify that the binary property has required properties', (t) => { }); test('should allow production_host to act as alias to host (when host not preset)', (t) => { - const mock_package_json = { - 'name': 'test', - 'main': 'test.js', - 'version': '0.1.0', - 'binary': { - 'module_name': 'binary-module-name', - 'module_path': 'binary-module-path', - 'production_host': 's3-production-path' + const parsed_package_json = { + name: 'test', + main: 'test.js', + version: '0.1.0', + binary: { + module_name: 'binary-module-name', + module_path: 'binary-module-path', + production_host: 's3-production-path' } }; - const package_json = Object.assign({}, mock_package_json); - const opts = versioning.evaluate(package_json, { module_root: '/root' }); - t.equal(opts.host, mock_package_json.binary.production_host + '/'); - t.equal(opts.hosted_path, mock_package_json.binary.production_host + '/'); - t.equal(opts.hosted_tarball, mock_package_json.binary.production_host + '/' + opts.package_name); + const cloned = JSON.parse(JSON.stringify(parsed_package_json)); + const opts = versioning.evaluate(cloned, {}); + t.equal(opts.host, parsed_package_json.binary.production_host + '/'); + t.equal(opts.hosted_path, parsed_package_json.binary.production_host + '/'); + t.equal(opts.hosted_tarball, parsed_package_json.binary.production_host + '/' + opts.package_name); t.end(); }); test('should use host over production_host (when both are preset)', (t) => { - const mock_package_json = { - 'name': 'test', - 'main': 'test.js', - 'version': '0.1.0', - 'binary': { - 'module_name': 'binary-module-name', - 'module_path': 'binary-module-path', - 'production_host': 's3-production-path', - 'host': 'binary-path' + const parsed_package_json = { + name: 'test', + main: 'test.js', + version: '0.1.0', + binary: { + module_name: 'binary-module-name', + module_path: 'binary-module-path', + production_host: 's3-production-path', + host: 'binary-path' } }; - const package_json = Object.assign({}, mock_package_json); - const opts = versioning.evaluate(package_json, { module_root: '/root' }); - t.equal(opts.host, mock_package_json.binary.host + '/'); - t.equal(opts.hosted_path, mock_package_json.binary.host + '/'); - t.equal(opts.hosted_tarball, mock_package_json.binary.host + '/' + opts.package_name); + let cloned = JSON.parse(JSON.stringify(parsed_package_json)); + let opts = versioning.evaluate(cloned, {}); + + t.equal(opts.host, parsed_package_json.binary.host + '/'); + t.equal(opts.hosted_path, parsed_package_json.binary.host + '/'); + t.equal(opts.hosted_tarball, parsed_package_json.binary.host + '/' + opts.package_name); + + // change to object format + parsed_package_json.binary.host = { endpoint: 'binary-path' }; + + cloned = JSON.parse(JSON.stringify(parsed_package_json)); + opts = versioning.evaluate(cloned, {}); + + t.equal(opts.host, parsed_package_json.binary.host.endpoint + '/'); + t.equal(opts.hosted_path, parsed_package_json.binary.host.endpoint + '/'); + t.equal(opts.hosted_tarball, parsed_package_json.binary.host.endpoint + '/' + opts.package_name); t.end(); }); test('should verify that the host url protocol is https', (t) => { - const mock_package_json = { - 'name': 'test', - 'main': 'test.js', - 'version': '0.1.0', - 'binary': { - 'module_name': 'binary-module-name', - 'module_path': 'binary-module-path', - 'host': 'http://your_module.s3-us-west-1.amazonaws.com' + const parsed_package_json = { + name: 'test', + main: 'test.js', + version: '0.1.0', + binary: { + module_name: 'binary-module-name', + module_path: 'binary-module-path', + host: 'http://your_module.s3-us-west-1.amazonaws.com' } }; - const package_json = Object.assign({}, mock_package_json); + let cloned = JSON.parse(JSON.stringify(parsed_package_json)); + try { + // eslint-disable-next-line no-unused-vars + const opts = versioning.evaluate(cloned, {}); + } catch (e) { + // name won't be there if it's missing but both messages say 'undefined' + const msg = cloned.name + ' package.json is not node-pre-gyp ready:\n'; + const expectedMessage = msg + '\'host\' protocol (http:) is invalid - only \'https:\' is accepted'; + + t.equal(e.message, expectedMessage); + } + // change to object format + parsed_package_json.binary.host = { endpoint: 'binary-path' }; + cloned = JSON.parse(JSON.stringify(parsed_package_json)); try { // eslint-disable-next-line no-unused-vars - const opts = versioning.evaluate(package_json, {}); + const opts = versioning.evaluate(cloned, {}); } catch (e) { // name won't be there if it's missing but both messages say 'undefined' - const msg = package_json.name + ' package.json is not node-pre-gyp ready:\n'; + const msg = cloned.name + ' package.json is not node-pre-gyp ready:\n'; const expectedMessage = msg + '\'host\' protocol (http:) is invalid - only \'https:\' is accepted'; + t.equal(e.message, expectedMessage); } @@ -293,7 +339,7 @@ test('should verify that the host url protocol is https', (t) => { }); test('should verify that alternate hosts url protocol is https', (t) => { - const mock_package_json = { + const parsed_package_json = { name: 'test', main: 'test.js', version: '0.1.0', @@ -306,67 +352,43 @@ test('should verify that alternate hosts url protocol is https', (t) => { const hosts = ['production', 'staging', 'development']; hosts.forEach((host) => { - const package_json = Object.assign({}, mock_package_json); - package_json[`${host}_host`] = `https://${host}_bucket.s3-us-west-1.amazonaws.com`; + const cloned = JSON.parse(JSON.stringify(parsed_package_json)); + cloned[`${host}_host`] = `http://${host}_bucket.s3-us-west-1.amazonaws.com`; try { // eslint-disable-next-line no-unused-vars - const opts = versioning.evaluate(package_json, {}); + const opts = versioning.evaluate(cloned, {}); } catch (e) { // name won't be there if it's missing but both messages say 'undefined' - const msg = package_json.name + ' package.json is not node-pre-gyp ready:\n'; + const msg = cloned.name + ' package.json is not node-pre-gyp ready:\n'; const expectedMessage = msg + `'${host}_host' protocol (http:) is invalid - only 'https:' is accepted`; + t.equal(e.message, expectedMessage); } }); - t.end(); -}); - -test('should not add bucket name to hosted_path when s3ForcePathStyle is false', (t) => { - const mock_package_json = { - 'name': 'test', - 'main': 'test.js', - 'version': '0.1.0', - 'binary': { - 'module_name': 'binary-module-name', - 'module_path': 'binary-module-path', - 'host': 'binary-path', - 'bucket': 'bucket-name', - 'region': 'us-west-1', - 's3ForcePathStyle': false - } - }; - - const package_json = Object.assign({}, mock_package_json); - const opts = versioning.evaluate(package_json, { module_root: '/root' }); - t.equal(opts.hosted_path, mock_package_json.binary.host + '/'); + hosts.forEach((host) => { + // change to object format + const cloned = JSON.parse(JSON.stringify(parsed_package_json)); + cloned.binary[`${host}_host`] = { endpoint: `http://${host}_bucket.s3-us-west-1.amazonaws.com` }; - t.end(); -}); + try { + // eslint-disable-next-line no-unused-vars + const opts = versioning.evaluate(cloned, {}); + } catch (e) { + // name won't be there if it's missing but both messages say 'undefined' + const msg = cloned.name + ' package.json is not node-pre-gyp ready:\n'; + const expectedMessage = msg + `'${host}_host' protocol (http:) is invalid - only 'https:' is accepted`; -test('should add bucket name to hosted_path when s3ForcePathStyle is true', (t) => { - const mock_package_json = { - 'name': 'test', - 'main': 'test.js', - 'version': '0.1.0', - 'binary': { - 'module_name': 'binary-module-name', - 'module_path': 'binary-module-path', - 'host': 'binary-path', - 'bucket': 'bucket-name', - 'region': 'us-west-1', - 's3ForcePathStyle': true + t.equal(e.message, expectedMessage); } - }; - - const package_json = Object.assign({}, mock_package_json); - const opts = versioning.evaluate(package_json, { module_root: '/root' }); - t.equal(opts.hosted_path, mock_package_json.binary.host + '/' + mock_package_json.binary.bucket + '/'); + }); t.end(); }); +/* host options */ + test('should use host key by default for install, info, publish and unpublish commands (when no other hosts specified)', (t) => { const makeOoptions = (cmd) => { return { @@ -378,7 +400,7 @@ test('should use host key by default for install, info, publish and unpublish co }; }; - const mock_package_json = { + const parsed_package_json = { name: 'test', main: 'test.js', version: '0.1.0', @@ -391,14 +413,22 @@ test('should use host key by default for install, info, publish and unpublish co const cmds = ['install', 'info', 'publish', 'unpublish']; cmds.forEach((cmd) => { - try { - const opts = versioning.evaluate(mock_package_json, makeOoptions(cmd)); - t.equal(opts.host, mock_package_json.binary.host + '/'); - t.equal(opts.hosted_path, mock_package_json.binary.host + '/'); - t.equal(opts.hosted_tarball, mock_package_json.binary.host + '/' + opts.package_name); - } catch (e) { - t.ifError(e, 'staging_host and production_host should be silently ignored'); - } + const cloned = JSON.parse(JSON.stringify(parsed_package_json)); + const opts = versioning.evaluate(cloned, makeOoptions(cmd)); + + t.equal(opts.host, parsed_package_json.binary.host + '/'); + t.equal(opts.hosted_path, parsed_package_json.binary.host + '/'); + t.equal(opts.hosted_tarball, parsed_package_json.binary.host + '/' + opts.package_name); + }); + cmds.forEach((cmd) => { + // change to object format + parsed_package_json.binary.host = { endpoint: 'binary-path' }; + const cloned = JSON.parse(JSON.stringify(parsed_package_json)); + const opts = versioning.evaluate(cloned, makeOoptions(cmd)); + + t.equal(opts.host, parsed_package_json.binary.host.endpoint + '/'); + t.equal(opts.hosted_path, parsed_package_json.binary.host.endpoint + '/'); + t.equal(opts.hosted_tarball, parsed_package_json.binary.host.endpoint + '/' + opts.package_name); }); t.end(); }); @@ -414,7 +444,7 @@ test('should use production_host as alias for host for install and info commands }; }; - const mock_package_json = { + const parsed_package_json = { name: 'test', main: 'test.js', version: '0.1.0', @@ -427,12 +457,12 @@ test('should use production_host as alias for host for install and info commands const cmds = ['install', 'info']; cmds.forEach((cmd) => { + const cloned = JSON.parse(JSON.stringify(parsed_package_json)); + const opts = versioning.evaluate(cloned, makeOoptions(cmd)); - const opts = versioning.evaluate(mock_package_json, makeOoptions(cmd)); - t.equal(opts.host, mock_package_json.binary.production_host + '/'); - t.equal(opts.hosted_path, mock_package_json.binary.production_host + '/'); - t.equal(opts.hosted_tarball, mock_package_json.binary.production_host + '/' + opts.package_name); - + t.equal(opts.host, parsed_package_json.binary.production_host + '/'); + t.equal(opts.hosted_path, parsed_package_json.binary.production_host + '/'); + t.equal(opts.hosted_tarball, parsed_package_json.binary.production_host + '/' + opts.package_name); }); t.end(); }); @@ -448,7 +478,7 @@ test('should use host over production_host for install and info commands (when b }; }; - const mock_package_json = { + const parsed_package_json = { name: 'test', main: 'test.js', version: '0.1.0', @@ -462,26 +492,39 @@ test('should use host over production_host for install and info commands (when b const cmds = ['install', 'info']; cmds.forEach((cmd) => { + const cloned = JSON.parse(JSON.stringify(parsed_package_json)); + const opts = versioning.evaluate(cloned, makeOoptions(cmd)); - const opts = versioning.evaluate(mock_package_json, makeOoptions(cmd)); - t.equal(opts.host, mock_package_json.binary.host + '/'); - t.equal(opts.hosted_path, mock_package_json.binary.host + '/'); - t.equal(opts.hosted_tarball, mock_package_json.binary.host + '/' + opts.package_name); + t.equal(opts.host, parsed_package_json.binary.host + '/'); + t.equal(opts.hosted_path, parsed_package_json.binary.host + '/'); + t.equal(opts.hosted_tarball, parsed_package_json.binary.host + '/' + opts.package_name); + }); + cmds.forEach((cmd) => { + // change to object format + parsed_package_json.binary.host = { endpoint: 'binary-path' }; + const cloned = JSON.parse(JSON.stringify(parsed_package_json)); + const opts = versioning.evaluate(cloned, makeOoptions(cmd)); + + t.equal(opts.host, parsed_package_json.binary.host.endpoint + '/'); + t.equal(opts.hosted_path, parsed_package_json.binary.host.endpoint + '/'); + t.equal(opts.hosted_tarball, parsed_package_json.binary.host.endpoint + '/' + opts.package_name); }); t.end(); }); test('should use host by default for install and info commands (overriding alternate hosts, production_host not present)', (t) => { - const options = { - argv: { - remain: ['install', 'info'], - cooked: ['install', 'info'], - original: ['install', 'info'] - } + const makeOoptions = (cmd) => { + return { + argv: { + remain: [cmd], + cooked: [cmd], + original: [cmd] + } + }; }; - const mock_package_json = { + const parsed_package_json = { name: 'test', main: 'test.js', version: '0.1.0', @@ -494,26 +537,40 @@ test('should use host by default for install and info commands (overriding alter } }; + const cmds = ['install', 'info']; + cmds.forEach((cmd) => { + const cloned = JSON.parse(JSON.stringify(parsed_package_json)); + const opts = versioning.evaluate(cloned, makeOoptions(cmd)); - const opts = versioning.evaluate(mock_package_json, options); - t.equal(opts.host, mock_package_json.binary.host + '/'); - t.equal(opts.hosted_path, mock_package_json.binary.host + '/'); - t.equal(opts.hosted_tarball, mock_package_json.binary.host + '/' + opts.package_name); - - + t.equal(opts.host, parsed_package_json.binary.host + '/'); + t.equal(opts.hosted_path, parsed_package_json.binary.host + '/'); + t.equal(opts.hosted_tarball, parsed_package_json.binary.host + '/' + opts.package_name); + }); + cmds.forEach((cmd) => { + // change to object format + parsed_package_json.binary.host = { endpoint: 'binary-path' }; + const cloned = JSON.parse(JSON.stringify(parsed_package_json)); + const opts = versioning.evaluate(cloned, makeOoptions(cmd)); + + t.equal(opts.host, parsed_package_json.binary.host.endpoint + '/'); + t.equal(opts.hosted_path, parsed_package_json.binary.host.endpoint + '/'); + t.equal(opts.hosted_tarball, parsed_package_json.binary.host.endpoint + '/' + opts.package_name); + }); t.end(); }); test('should use host by default for install and info commands (overriding alternate hosts, host is present)', (t) => { - const options = { - argv: { - remain: ['install', 'info'], - cooked: ['install', 'info'], - original: ['install', 'info'] - } + const makeOoptions = (cmd) => { + return { + argv: { + remain: [cmd], + cooked: [cmd], + original: [cmd] + } + }; }; - const mock_package_json = { + const parsed_package_json = { name: 'test', main: 'test.js', version: '0.1.0', @@ -527,13 +584,25 @@ test('should use host by default for install and info commands (overriding alter } }; + const cmds = ['install', 'info']; + cmds.forEach((cmd) => { + const cloned = JSON.parse(JSON.stringify(parsed_package_json)); + const opts = versioning.evaluate(cloned, makeOoptions(cmd)); - const opts = versioning.evaluate(mock_package_json, options); - t.equal(opts.host, mock_package_json.binary.host + '/'); - t.equal(opts.hosted_path, mock_package_json.binary.host + '/'); - t.equal(opts.hosted_tarball, mock_package_json.binary.host + '/' + opts.package_name); - - + t.equal(opts.host, parsed_package_json.binary.host + '/'); + t.equal(opts.hosted_path, parsed_package_json.binary.host + '/'); + t.equal(opts.hosted_tarball, parsed_package_json.binary.host + '/' + opts.package_name); + }); + cmds.forEach((cmd) => { + // change to object format + parsed_package_json.binary.host = { endpoint: 'binary-path' }; + const cloned = JSON.parse(JSON.stringify(parsed_package_json)); + const opts = versioning.evaluate(cloned, makeOoptions(cmd)); + + t.equal(opts.host, parsed_package_json.binary.host.endpoint + '/'); + t.equal(opts.hosted_path, parsed_package_json.binary.host.endpoint + '/'); + t.equal(opts.hosted_tarball, parsed_package_json.binary.host.endpoint + '/' + opts.package_name); + }); t.end(); }); @@ -548,7 +617,7 @@ test('should use development_host key by default for publish and unpublish comma }; }; - const mock_package_json = { + const parsed_package_json = { name: 'test', main: 'test.js', version: '0.1.0', @@ -564,12 +633,22 @@ test('should use development_host key by default for publish and unpublish comma const cmds = ['publish', 'unpublish']; cmds.forEach((cmd) => { + const cloned = JSON.parse(JSON.stringify(parsed_package_json)); + const opts = versioning.evaluate(cloned, makeOoptions(cmd)); - const opts = versioning.evaluate(mock_package_json, makeOoptions(cmd)); - t.equal(opts.host, mock_package_json.binary.development_host + '/'); - t.equal(opts.hosted_path, mock_package_json.binary.development_host + '/'); - t.equal(opts.hosted_tarball, mock_package_json.binary.development_host + '/' + opts.package_name); - + t.equal(opts.host, parsed_package_json.binary.development_host + '/'); + t.equal(opts.hosted_path, parsed_package_json.binary.development_host + '/'); + t.equal(opts.hosted_tarball, parsed_package_json.binary.development_host + '/' + opts.package_name); + }); + cmds.forEach((cmd) => { + // change to object format + parsed_package_json.binary.development_host = { endpoint: 's3-development-path' }; + const cloned = JSON.parse(JSON.stringify(parsed_package_json)); + const opts = versioning.evaluate(cloned, makeOoptions(cmd)); + + t.equal(opts.host, parsed_package_json.binary.development_host.endpoint + '/'); + t.equal(opts.hosted_path, parsed_package_json.binary.development_host.endpoint + '/'); + t.equal(opts.hosted_tarball, parsed_package_json.binary.development_host.endpoint + '/' + opts.package_name); }); t.end(); }); @@ -585,7 +664,7 @@ test('should use staging_host key by default for publish and unpublish commands }; }; - const mock_package_json = { + const parsed_package_json = { name: 'test', main: 'test.js', version: '0.1.0', @@ -600,13 +679,24 @@ test('should use staging_host key by default for publish and unpublish commands const cmds = ['publish', 'unpublish']; cmds.forEach((cmd) => { + const cloned = JSON.parse(JSON.stringify(parsed_package_json)); + const opts = versioning.evaluate(cloned, makeOoptions(cmd)); - const opts = versioning.evaluate(mock_package_json, makeOoptions(cmd)); - t.equal(opts.host, mock_package_json.binary.staging_host + '/'); - t.equal(opts.hosted_path, mock_package_json.binary.staging_host + '/'); - t.equal(opts.hosted_tarball, mock_package_json.binary.staging_host + '/' + opts.package_name); + t.equal(opts.host, parsed_package_json.binary.staging_host + '/'); + t.equal(opts.hosted_path, parsed_package_json.binary.staging_host + '/'); + t.equal(opts.hosted_tarball, parsed_package_json.binary.staging_host + '/' + opts.package_name); }); + cmds.forEach((cmd) => { + // change to object format + parsed_package_json.binary.staging_host = { endpoint: 's3-staging-path' }; + const cloned = JSON.parse(JSON.stringify(parsed_package_json)); + const opts = versioning.evaluate(cloned, makeOoptions(cmd)); + + t.equal(opts.host, parsed_package_json.binary.staging_host.endpoint + '/'); + t.equal(opts.hosted_path, parsed_package_json.binary.staging_host.endpoint + '/'); + t.equal(opts.hosted_tarball, parsed_package_json.binary.staging_host.endpoint + '/' + opts.package_name); + }); t.end(); }); @@ -621,7 +711,7 @@ test('should use development_host key by default for publish and unpublish comma }; }; - const mock_package_json = { + const parsed_package_json = { name: 'test', main: 'test.js', version: '0.1.0', @@ -637,12 +727,22 @@ test('should use development_host key by default for publish and unpublish comma const cmds = ['publish', 'unpublish']; cmds.forEach((cmd) => { + const cloned = JSON.parse(JSON.stringify(parsed_package_json)); + const opts = versioning.evaluate(cloned, makeOoptions(cmd)); - const opts = versioning.evaluate(mock_package_json, makeOoptions(cmd)); - t.equal(opts.host, mock_package_json.binary.development_host + '/'); - t.equal(opts.hosted_path, mock_package_json.binary.development_host + '/'); - t.equal(opts.hosted_tarball, mock_package_json.binary.development_host + '/' + opts.package_name); - + t.equal(opts.host, parsed_package_json.binary.development_host + '/'); + t.equal(opts.hosted_path, parsed_package_json.binary.development_host + '/'); + t.equal(opts.hosted_tarball, parsed_package_json.binary.development_host + '/' + opts.package_name); + }); + cmds.forEach((cmd) => { + // change to object format + parsed_package_json.binary.development_host = { endpoint: 's3-development-path' }; + const cloned = JSON.parse(JSON.stringify(parsed_package_json)); + const opts = versioning.evaluate(cloned, makeOoptions(cmd)); + + t.equal(opts.host, parsed_package_json.binary.development_host.endpoint + '/'); + t.equal(opts.hosted_path, parsed_package_json.binary.development_host.endpoint + '/'); + t.equal(opts.hosted_tarball, parsed_package_json.binary.development_host.endpoint + '/' + opts.package_name); }); t.end(); }); @@ -659,7 +759,7 @@ test('should use host specified by the --s3_host option', (t) => { }; }; - const mock_package_json = { + const parsed_package_json = { name: 'test', main: 'test.js', version: '0.1.0', @@ -675,13 +775,33 @@ test('should use host specified by the --s3_host option', (t) => { const hosts = ['production', 'staging', 'development']; const cmds = ['install', 'info', 'publish', 'unpublish']; + cmds.forEach((cmd) => { + hosts.forEach((host) => { + const cloned = JSON.parse(JSON.stringify(parsed_package_json)); + const opts = versioning.evaluate(cloned, makeOoptions(cmd, host)); + t.equal(opts.host, parsed_package_json.binary[`${host}_host`] + '/'); + t.equal(opts.hosted_path, parsed_package_json.binary[`${host}_host`] + '/'); + t.equal(opts.hosted_tarball, parsed_package_json.binary[`${host}_host`] + '/' + opts.package_name); + }); + }); cmds.forEach((cmd) => { hosts.forEach((host) => { - const opts = versioning.evaluate(mock_package_json, makeOoptions(cmd, host)); - t.equal(opts.host, mock_package_json.binary[`${host}_host`] + '/'); - t.equal(opts.hosted_path, mock_package_json.binary[`${host}_host`] + '/'); - t.equal(opts.hosted_tarball, mock_package_json.binary[`${host}_host`] + '/' + opts.package_name); + parsed_package_json.binary = { + module_name: 'binary-module-name', + module_path: 'binary-module-path', + // host: { endpoint: 'binary-path' }, + development_host: { endpoint: 's3-development-path' }, + staging_host: { endpoint: 's3-staging-path' }, + production_host: { endpoint: 's3-production-path' } + }; + + const cloned = JSON.parse(JSON.stringify(parsed_package_json)); + const opts = versioning.evaluate(cloned, makeOoptions(cmd, host)); + + t.equal(opts.host, parsed_package_json.binary[`${host}_host`].endpoint + '/'); + t.equal(opts.hosted_path, parsed_package_json.binary[`${host}_host`].endpoint + '/'); + t.equal(opts.hosted_tarball, parsed_package_json.binary[`${host}_host`].endpoint + '/' + opts.package_name); }); }); t.end(); @@ -699,7 +819,7 @@ test('should use defaults when --s3_host option is invalid', (t) => { }; }; - const mock_package_json = { + const parsed_package_json = { name: 'test', main: 'test.js', version: '0.1.0', @@ -714,15 +834,31 @@ test('should use defaults when --s3_host option is invalid', (t) => { }; const cmds = ['install', 'info', 'publish', 'unpublish']; - cmds.forEach((cmd) => { - const opts = versioning.evaluate(mock_package_json, makeOoptions(cmd)); + const cloned = JSON.parse(JSON.stringify(parsed_package_json)); + const opts = versioning.evaluate(cloned, makeOoptions(cmd)); const host = cmd.indexOf('publish') === -1 ? 'host' : 'development_host'; - t.equal(opts.host, mock_package_json.binary[host] + '/'); - t.equal(opts.hosted_path, mock_package_json.binary[host] + '/'); - t.equal(opts.hosted_tarball, mock_package_json.binary[host] + '/' + opts.package_name); + t.equal(opts.host, parsed_package_json.binary[host] + '/'); + t.equal(opts.hosted_path, parsed_package_json.binary[host] + '/'); + t.equal(opts.hosted_tarball, parsed_package_json.binary[host] + '/' + opts.package_name); + }); + cmds.forEach((cmd) => { + parsed_package_json.binary = { + module_name: 'binary-module-name', + module_path: 'binary-module-path', + host: { endpoint: 'binary-path' }, + development_host: { endpoint: 's3-development-path' }, + staging_host: { endpoint: 's3-staging-path' } + }; + + const cloned = JSON.parse(JSON.stringify(parsed_package_json)); + const opts = versioning.evaluate(cloned, makeOoptions(cmd)); + const host = cmd.indexOf('publish') === -1 ? 'host' : 'development_host'; + t.equal(opts.host, parsed_package_json.binary[host].endpoint + '/'); + t.equal(opts.hosted_path, parsed_package_json.binary[host].endpoint + '/'); + t.equal(opts.hosted_tarball, parsed_package_json.binary[host].endpoint + '/' + opts.package_name); }); t.end(); }); @@ -738,7 +874,7 @@ test('should use host specified by the s3_host environment variable', (t) => { }; }; - const mock_package_json = { + const parsed_package_json = { name: 'test', main: 'test.js', version: '0.1.0', @@ -758,10 +894,31 @@ test('should use host specified by the s3_host environment variable', (t) => { cmds.forEach((cmd) => { hosts.forEach((host) => { process.env.node_pre_gyp_s3_host = host; - const opts = versioning.evaluate(mock_package_json, makeOoptions(cmd)); - t.equal(opts.host, mock_package_json.binary[`${host}_host`] + '/'); - t.equal(opts.hosted_path, mock_package_json.binary[`${host}_host`] + '/'); - t.equal(opts.hosted_tarball, mock_package_json.binary[`${host}_host`] + '/' + opts.package_name); + const cloned = JSON.parse(JSON.stringify(parsed_package_json)); + const opts = versioning.evaluate(cloned, makeOoptions(cmd)); + + t.equal(opts.host, parsed_package_json.binary[`${host}_host`] + '/'); + t.equal(opts.hosted_path, parsed_package_json.binary[`${host}_host`] + '/'); + t.equal(opts.hosted_tarball, parsed_package_json.binary[`${host}_host`] + '/' + opts.package_name); + }); + }); + cmds.forEach((cmd) => { + hosts.forEach((host) => { + process.env.node_pre_gyp_s3_host = host; + parsed_package_json.binary = { + module_name: 'binary-module-name', + module_path: 'binary-module-path', + // host: { endpoint: 'binary-path' }, + development_host: { endpoint: 's3-development-path' }, + staging_host: { endpoint: 's3-staging-path' }, + production_host: { endpoint: 's3-production-path' } + }; + const cloned = JSON.parse(JSON.stringify(parsed_package_json)); + const opts = versioning.evaluate(cloned, makeOoptions(cmd, host)); + + t.equal(opts.host, parsed_package_json.binary[`${host}_host`].endpoint + '/'); + t.equal(opts.hosted_path, parsed_package_json.binary[`${host}_host`].endpoint + '/'); + t.equal(opts.hosted_tarball, parsed_package_json.binary[`${host}_host`].endpoint + '/' + opts.package_name); }); }); t.end(); @@ -778,7 +935,7 @@ test('should use defaults when s3_host environment variable is invalid', (t) => }; }; - const mock_package_json = { + const parsed_package_json = { name: 'test', main: 'test.js', version: '0.1.0', @@ -796,13 +953,30 @@ test('should use defaults when s3_host environment variable is invalid', (t) => cmds.forEach((cmd) => { process.env.node_pre_gyp_s3_host = 'not-valid'; - const opts = versioning.evaluate(mock_package_json, makeOoptions(cmd)); + const cloned = JSON.parse(JSON.stringify(parsed_package_json)); + const opts = versioning.evaluate(cloned, makeOoptions(cmd)); const host = cmd.indexOf('publish') === -1 ? 'host' : 'development_host'; - t.equal(opts.host, mock_package_json.binary[host] + '/'); - t.equal(opts.hosted_path, mock_package_json.binary[host] + '/'); - t.equal(opts.hosted_tarball, mock_package_json.binary[host] + '/' + opts.package_name); + t.equal(opts.host, parsed_package_json.binary[host] + '/'); + t.equal(opts.hosted_path, parsed_package_json.binary[host] + '/'); + t.equal(opts.hosted_tarball, parsed_package_json.binary[host] + '/' + opts.package_name); + }); + cmds.forEach((cmd) => { + parsed_package_json.binary = { + module_name: 'binary-module-name', + module_path: 'binary-module-path', + host: { endpoint: 'binary-path' }, + development_host: { endpoint: 's3-development-path' }, + staging_host: { endpoint: 's3-staging-path' } + }; + const cloned = JSON.parse(JSON.stringify(parsed_package_json)); + const opts = versioning.evaluate(cloned, makeOoptions(cmd)); + const host = cmd.indexOf('publish') === -1 ? 'host' : 'development_host'; + + t.equal(opts.host, parsed_package_json.binary[host].endpoint + '/'); + t.equal(opts.hosted_path, parsed_package_json.binary[host].endpoint + '/'); + t.equal(opts.hosted_tarball, parsed_package_json.binary[host].endpoint + '/' + opts.package_name); }); t.end(); }); @@ -818,7 +992,7 @@ test('should use defaults when s3_host environment is valid but package.json doe }; }; - const mock_package_json = { + const parsed_package_json = { name: 'test', main: 'test.js', version: '0.1.0', @@ -836,13 +1010,32 @@ test('should use defaults when s3_host environment is valid but package.json doe cmds.forEach((cmd) => { process.env.node_pre_gyp_s3_host = 'development'; // specify development_host - const opts = versioning.evaluate(mock_package_json, makeOoptions(cmd)); + const cloned = JSON.parse(JSON.stringify(parsed_package_json)); + const opts = versioning.evaluate(cloned, makeOoptions(cmd)); const host = cmd.indexOf('publish') === -1 ? 'production_host' : 'staging_host'; // defaults - t.equal(opts.host, mock_package_json.binary[host] + '/'); - t.equal(opts.hosted_path, mock_package_json.binary[host] + '/'); - t.equal(opts.hosted_tarball, mock_package_json.binary[host] + '/' + opts.package_name); + t.equal(opts.host, parsed_package_json.binary[host] + '/'); + t.equal(opts.hosted_path, parsed_package_json.binary[host] + '/'); + t.equal(opts.hosted_tarball, parsed_package_json.binary[host] + '/' + opts.package_name); + }); + cmds.forEach((cmd) => { + process.env.node_pre_gyp_s3_host = 'development'; // specify development_host + parsed_package_json.binary = { + module_name: 'binary-module-name', + module_path: 'binary-module-path', + // no development_host + staging_host: { endpoint: 's3-staging-path' }, + // production_host not host + production_host: { endpoint: 's3-production-path' } + }; + + const cloned = JSON.parse(JSON.stringify(parsed_package_json)); + const opts = versioning.evaluate(cloned, makeOoptions(cmd)); + const host = cmd.indexOf('publish') === -1 ? 'production_host' : 'staging_host'; // defaults + t.equal(opts.host, parsed_package_json.binary[host].endpoint + '/'); + t.equal(opts.hosted_path, parsed_package_json.binary[host].endpoint + '/'); + t.equal(opts.hosted_tarball, parsed_package_json.binary[host].endpoint + '/' + opts.package_name); }); t.end(); }); @@ -858,7 +1051,7 @@ test('should use defaults when s3_host environment is valid but package.json doe }; }; - const mock_package_json = { + const parsed_package_json = { name: 'test', main: 'test.js', version: '0.1.0', @@ -876,13 +1069,32 @@ test('should use defaults when s3_host environment is valid but package.json doe cmds.forEach((cmd) => { process.env.node_pre_gyp_s3_host = 'development'; // specify development_host - const opts = versioning.evaluate(mock_package_json, makeOoptions(cmd)); + const cloned = JSON.parse(JSON.stringify(parsed_package_json)); + const opts = versioning.evaluate(cloned, makeOoptions(cmd)); const host = cmd.indexOf('publish') === -1 ? 'host' : 'staging_host'; // defaults - t.equal(opts.host, mock_package_json.binary[host] + '/'); - t.equal(opts.hosted_path, mock_package_json.binary[host] + '/'); - t.equal(opts.hosted_tarball, mock_package_json.binary[host] + '/' + opts.package_name); + t.equal(opts.host, parsed_package_json.binary[host] + '/'); + t.equal(opts.hosted_path, parsed_package_json.binary[host] + '/'); + t.equal(opts.hosted_tarball, parsed_package_json.binary[host] + '/' + opts.package_name); + }); + cmds.forEach((cmd) => { + process.env.node_pre_gyp_s3_host = 'development'; // specify development_host + parsed_package_json.binary = { + module_name: 'binary-module-name', + module_path: 'binary-module-path', + // host not production_host + host: { endpoint: 'binary-path' }, + // no development_host + staging_host: { endpoint: 's3-staging-path' } + }; + + const cloned = JSON.parse(JSON.stringify(parsed_package_json)); + const opts = versioning.evaluate(cloned, makeOoptions(cmd)); + const host = cmd.indexOf('publish') === -1 ? 'host' : 'staging_host'; // defaults + t.equal(opts.host, parsed_package_json.binary[host].endpoint + '/'); + t.equal(opts.hosted_path, parsed_package_json.binary[host].endpoint + '/'); + t.equal(opts.hosted_tarball, parsed_package_json.binary[host].endpoint + '/' + opts.package_name); }); t.end(); }); @@ -899,7 +1111,7 @@ test('should use host specified by environment variable overriding --s3_host opt }; }; - const mock_package_json = { + const parsed_package_json = { name: 'test', main: 'test.js', version: '0.1.0', @@ -917,17 +1129,129 @@ test('should use host specified by environment variable overriding --s3_host opt const cmds = ['install', 'info', 'publish', 'unpublish']; cmds.forEach((cmd) => { process.env.node_pre_gyp_s3_host = 'production'; - const opts = versioning.evaluate(mock_package_json, makeOoptions(cmd)); - t.equal(opts.host, mock_package_json.binary.production_host + '/'); - t.equal(opts.hosted_path, mock_package_json.binary.production_host + '/'); - t.equal(opts.hosted_tarball, mock_package_json.binary.production_host + '/' + opts.package_name); + const cloned = JSON.parse(JSON.stringify(parsed_package_json)); + const opts = versioning.evaluate(cloned, makeOoptions(cmd)); + t.equal(opts.host, parsed_package_json.binary.production_host + '/'); + t.equal(opts.hosted_path, parsed_package_json.binary.production_host + '/'); + t.equal(opts.hosted_tarball, parsed_package_json.binary.production_host + '/' + opts.package_name); }); + + cmds.forEach((cmd) => { + process.env.node_pre_gyp_s3_host = 'production'; + parsed_package_json.binary = { + module_name: 'binary-module-name', + module_path: 'binary-module-path', + // host: { endpoint: 'binary-path' }, + development_host: { endpoint: 's3-development-path' }, + staging_host: { endpoint: 's3-staging-path' }, + production_host: { endpoint: 's3-production-path' } + }; + const cloned = JSON.parse(JSON.stringify(parsed_package_json)); + const opts = versioning.evaluate(cloned, makeOoptions(cmd)); + + t.equal(opts.host, parsed_package_json.binary.production_host.endpoint + '/'); + t.equal(opts.hosted_path, parsed_package_json.binary.production_host.endpoint + '/'); + t.equal(opts.hosted_tarball, parsed_package_json.binary.production_host.endpoint + '/' + opts.package_name); + }); + t.end(); +}); + +/* hosted path variations */ + +test('should not add bucket name to hosted_path when s3ForcePathStyle is false', (t) => { + let parsed_package_json = { + name: 'test', + main: 'test.js', + version: '0.1.0', + binary: { + module_name: 'binary-module-name', + module_path: 'binary-module-path', + host: 'binary-path', + bucket: 'bucket-name', + region: 'us-west-1', + s3ForcePathStyle: false + } + }; + + let cloned = JSON.parse(JSON.stringify(parsed_package_json)); + let opts = versioning.evaluate(cloned, {}); + + t.equal(opts.hosted_path, parsed_package_json.binary.host + '/'); + + // change to object format + parsed_package_json = { + name: 'test', + main: 'test.js', + version: '0.1.0', + binary: { + module_name: 'binary-module-name', + module_path: 'binary-module-path', + host: { + endpoint: 'binary-path', + bucket: 'bucket-name', + region: 'us-west-1', + s3ForcePathStyle: false + } + } + }; + + cloned = JSON.parse(JSON.stringify(parsed_package_json)); + opts = versioning.evaluate(cloned, {}); + + t.equal(opts.hosted_path, parsed_package_json.binary.host.endpoint + '/'); + t.end(); }); +test('should not add bucket name to hosted_path when s3ForcePathStyle is true', (t) => { + let parsed_package_json = { + name: 'test', + main: 'test.js', + version: '0.1.0', + binary: { + module_name: 'binary-module-name', + module_path: 'binary-module-path', + host: 'binary-path', + bucket: 'bucket-name', + region: 'us-west-1', + s3ForcePathStyle: true + } + }; + + let cloned = JSON.parse(JSON.stringify(parsed_package_json)); + let opts = versioning.evaluate(cloned, {}); + + t.equal(opts.hosted_path, parsed_package_json.binary.host + '/' + parsed_package_json.binary.bucket + '/'); + + // change to object format + parsed_package_json = { + name: 'test', + main: 'test.js', + version: '0.1.0', + binary: { + module_name: 'binary-module-name', + module_path: 'binary-module-path', + host: { + endpoint: 'binary-path', + bucket: 'bucket-name', + region: 'us-west-1', + s3ForcePathStyle: true + } + } + }; + + cloned = JSON.parse(JSON.stringify(parsed_package_json)); + opts = versioning.evaluate(cloned, {}); + + t.equal(opts.hosted_path, parsed_package_json.binary.host.endpoint + '/' + parsed_package_json.binary.host.bucket + '/'); + t.end(); +}); + +/* other */ + test('should replace "-" with "_" in mirror binary host', (t) => { - const mock_package_json = { + const parsed_package_json = { name: 'test', main: 'test.js', version: '0.1.0', @@ -947,3 +1271,29 @@ test('should replace "-" with "_" in mirror binary host', (t) => { t.end(); }); +test('should normalize double slash', (t) => { + const parsed_package_json = { + name: 'test', + main: 'test.js', + version: '0.1.0', + binary: { + module_name: 'test', + module_path: './lib/binding/{configuration}/{toolset}/{name}', + remote_path: './{name}/v{version}/{configuration}/{version}/{toolset}/', + package_name: '{module_name}-v{major}.{minor}.{patch}-{prerelease}+{build}-{toolset}-{node_abi}-{platform}-{arch}.tar.gz', + host: 'https://some-bucket.s3.us-east-1.amazonaws.com' + } + }; + const cloned = JSON.parse(JSON.stringify(parsed_package_json)); + const opts = versioning.evaluate(cloned, {}); + + t.equal(opts.remote_path, './test/v0.1.0/Release/0.1.0/'); + // Node v0.11.x on windows lowercases C:// when path.join is called + // https://github.com/joyent/node/issues/7031 + t.equal(path.normalize(opts.module_path), path.join(process.cwd(), 'lib/binding/Release/test')); + + const opts_toolset = versioning.evaluate(cloned, { toolset: 'custom-toolset' }); + + t.equal(opts_toolset.remote_path, './test/v0.1.0/Release/0.1.0/custom-toolset/'); + t.end(); +}); From feb19fb04b185546efed7a90fd23101e45d56568 Mon Sep 17 00:00:00 2001 From: Ron Ilan Date: Thu, 19 May 2022 10:21:45 -0700 Subject: [PATCH 12/14] Updated Documentation to cover host configuration with an object. --- README.md | 109 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 73 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 5aef79a64..9e3fbc950 100644 --- a/README.md +++ b/README.md @@ -103,25 +103,29 @@ This is a guide to configuring your module to use node-pre-gyp. - Add `@mapbox/node-pre-gyp` to `dependencies` - Add `aws-sdk` as a `devDependency` - Add a custom `install` script - - Declare a `binary` object + - Declare a `binary` and spcify `host` object This looks like: ```js - "dependencies" : { - "@mapbox/node-pre-gyp": "1.x" - }, - "devDependencies": { - "aws-sdk": "2.x" - } - "scripts": { - "install": "node-pre-gyp install --fallback-to-build" - }, - "binary": { - "module_name": "your_module", - "module_path": "./lib/binding/", - "host": "https://your_module.s3-us-west-1.amazonaws.com" +{ + "dependencies":{ + "@mapbox/node-pre-gyp":"1.x" + }, + "devDependencies":{ + "aws-sdk":"2.x" + }, + "scripts":{ + "install":"node-pre-gyp install --fallback-to-build" + }, + "binary":{ + "module_name":"your_module", + "module_path":"./lib/binding/", + "host":{ + "endpoint":"https://your_module.s3-us-west-1.amazonaws.com" } + } +} ``` For a full example see [node-addon-examples's package.json](https://github.com/springmeyer/node-addon-example/blob/master/package.json). @@ -151,9 +155,9 @@ The location your native module is placed after a build. This should be an empty Note: This property supports variables based on [Versioning](#versioning). -###### host +###### host (and host.endpoint) -A url to the remote location where you've published tarball binaries (must be `https` not `http`). +An object with atleast a single key `endpoint` defining the remote location where you've published tarball binaries (must be `https` not `http`). It is highly recommended that you use Amazon S3. The reasons are: @@ -165,13 +169,21 @@ Why then not require S3? Because while some applications using node-pre-gyp need It should also be mentioned that there is an optional and entirely separate npm module called [node-pre-gyp-github](https://github.com/bchr02/node-pre-gyp-github) which is intended to complement node-pre-gyp and be installed along with it. It provides the ability to store and publish your binaries within your repositories GitHub Releases if you would rather not use S3 directly. Installation and usage instructions can be found [here](https://github.com/bchr02/node-pre-gyp-github), but the basic premise is that instead of using the ```node-pre-gyp publish``` command you would use ```node-pre-gyp-github publish```. -##### The `binary` object other optional S3 properties +This looks like: -If you are not using a standard s3 path like `bucket_name.s3(.-)region.amazonaws.com`, you might get an error on `publish` because node-pre-gyp extracts the region and bucket from the `host` url. For example, you may have an on-premises s3-compatible storage server, or may have configured a specific dns redirecting to an s3 endpoint. In these cases, you can explicitly set the `region` and `bucket` properties to tell node-pre-gyp to use these values instead of guessing from the `host` property. The following values can be used in the `binary` section: +```js +{ + "binary": { + "host": { + "endpoint": "https://some-bucket.s3.us-east-1.amazonaws.com", + } + } +} +``` -###### host +##### The `host` object other optional S3 properties -The url to the remote server root location (must be `https` not `http`). +If you are not using a standard s3 path like `bucket_name.s3(.-)region.amazonaws.com`, you might get an error on `publish` because node-pre-gyp extracts the region and bucket from the `host` url. For example, you may have an on-premises s3-compatible storage server, or may have configured a specific dns redirecting to an s3 endpoint. In these cases, you can explicitly set the `region` and `bucket` properties to tell node-pre-gyp to use these values instead of guessing from the `host` property. The following values can be used in the `binary` section: ###### bucket @@ -185,6 +197,21 @@ Your S3 server region. Set `s3ForcePathStyle` to true if the endpoint url should not be prefixed with the bucket name. If false (default), the server endpoint would be constructed as `bucket_name.your_server.com`. +For example using an alternate S3 compatible host: + +```js +{ + "binary": { + "host": { + "endpoint": "https://play.min.io", + "bucket": "node-pre-gyp-production", + "region": "us-east-1", + "s3ForcePathStyle": true + } + } +} +``` + ##### The `binary` object has optional properties ###### remote_path @@ -312,28 +339,38 @@ If a a binary was not available for a given platform and `--fallback-to-build` w #### 9) One more option -It may be that you want to work with two s3 buckets, one for staging and one for production; this -arrangement makes it less likely to accidentally overwrite a production binary. It also allows the production -environment to have more restrictive permissions than staging while still enabling publishing when -developing and testing. +It may be that you want to work with multiple s3 buckets, one for development, on for staging and one for production; such arrangement makes it less likely to accidentally overwrite a production binary. It also allows the production environment to have more restrictive permissions than development or staging while still enabling publishing when developing and testing. -The binary.host property can be set at execution time. In order to do so all of the following conditions -must be true. -- binary.host is falsey or not present -- binary.staging_host is not empty -- binary.production_host is not empty +To use that option set `staging_host` and/or `development_host` using settings similar to those used for `host`. -If any of these checks fail then the operation will not perform execution time determination of the s3 target. +``` +{ + "binary": { + "host": { + "endpoint": "https://dns.pointed.example.com", + "bucket": "obscured-production-bucket", + "region": "us-east-1", + "s3ForcePathStyle": true + } + "staging_host": { + "endpoint": "https://my-staging-bucket.s3.us-east-1.amazonaws.com", + }, + "development_host": { + "endpoint": "https://play.min.io", + "bucket": "node-pre-gyp-development", + "region": "us-east-1", + "s3ForcePathStyle": true + } + } +} +``` -If the command being executed is either "publish" or "unpublish" then the default is set to `binary.staging_host`. In all other cases -the default is `binary.production_host`. +Once a development and/or staging host is defined, if the command being executed is either "publish" or "unpublish" then it will default to the lower of the alternate hosts (development and if not present, staging). if the command being executed is either "install" or "info" it will default to the production host (specified by `host`). -The command-line options `--s3_host=staging` or `--s3_host=production` override the default. If `s3_host` -is present and not `staging` or `production` an exception is thrown. +To explicitly choose a host use command-line options `--s3_host=development`, `--s3_host=staging` or `--s3_host=production`, or set environment variable `node_pre_gyp_s3_host` to either `development`, `staging` or `production`. Note that the environment variable has priority over the the command line. -This allows installing from staging by specifying `--s3_host=staging`. And it requires specifying -`--s3_option=production` in order to publish to, or unpublish from, production, making accidental errors less likely. +This setup allows installing from development or staging by specifying `--s3_host=staging`. And it requires specifying `--s3_option=production` in order to publish to, or unpublish from, production, making accidental errors less likely. ## Node-API Considerations From dbe127f7aede5d192af75eaf6049acbc7ebb34fb Mon Sep 17 00:00:00 2001 From: Ron Ilan Date: Thu, 19 May 2022 13:18:46 -0700 Subject: [PATCH 13/14] Fixed bug and added test coverage for case. Now obeys production s3_host option when package.json has only host key specified. --- lib/util/versioning.js | 16 ++++++----- test/versioning.test.js | 60 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 7 deletions(-) diff --git a/lib/util/versioning.js b/lib/util/versioning.js index c563b3cef..a1c9f2b68 100644 --- a/lib/util/versioning.js +++ b/lib/util/versioning.js @@ -367,9 +367,8 @@ module.exports.evaluate = function(package_json, options, napi_build_version) { // the environment variable has priority over the the command line. let targetHost = process.env.node_pre_gyp_s3_host || options.s3_host; - // if value is not one of the allowed or the matching key is not found in package.json - // silently ignore the option - if (['production', 'staging', 'development'].indexOf(targetHost) === -1 || !package_json.binary[`${targetHost}_host`]) { + // if value is not one of the allowed silently ignore the option + if (['production', 'staging', 'development'].indexOf(targetHost) === -1) { targetHost = ''; } @@ -379,17 +378,20 @@ module.exports.evaluate = function(package_json, options, napi_build_version) { let hostData = package_json.binary.host; // when a valid target is specified by user, the host is from that target (or 'host') - if (targetHost === 'staging') { + if (targetHost === 'production') { + // all set. catch case so as to not change host based on commands. + } + else if (targetHost === 'staging' && package_json.binary.staging_host) { hostData = package_json.binary.staging_host; - } else if (targetHost === 'development') { + } else if (targetHost === 'development' && package_json.binary.development_host) { hostData = package_json.binary.development_host; - } else if (!targetHost && (package_json.binary.development_host || package_json.binary.staging_host)) { + } else if ((package_json.binary.development_host || package_json.binary.staging_host)) { // when host not specifically set via command line or environment variable // but staging and/or development host are present in package.json // for any command (or command chain) that includes publish or unpublish // default to lower host (development, and if not preset, staging). if (options.argv && options.argv.remain.some((item) => (item === 'publish' || item === 'unpublish'))) { - if (package_json.binary.development_host) { + if (!targetHost && package_json.binary.development_host) { hostData = package_json.binary.development_host; } else if (package_json.binary.staging_host) { hostData = package_json.binary.staging_host; diff --git a/test/versioning.test.js b/test/versioning.test.js index f69e9e9cb..a421575c9 100644 --- a/test/versioning.test.js +++ b/test/versioning.test.js @@ -759,6 +759,66 @@ test('should use host specified by the --s3_host option', (t) => { }; }; + const parsed_package_json = { + name: 'test', + main: 'test.js', + version: '0.1.0', + binary: { + module_name: 'binary-module-name', + module_path: 'binary-module-path', + host: 's3-production-path', + development_host: 's3-development-path', + staging_host: 's3-staging-path' + } + }; + + const hosts = ['production', 'staging', 'development']; + const cmds = ['install', 'info', 'publish', 'unpublish']; + cmds.forEach((cmd) => { + hosts.forEach((host) => { + const checkAgainst = host !== 'production' ? `${host}_host` : 'host'; + const cloned = JSON.parse(JSON.stringify(parsed_package_json)); + const opts = versioning.evaluate(cloned, makeOoptions(cmd, host)); + + t.equal(opts.host, parsed_package_json.binary[checkAgainst] + '/'); + t.equal(opts.hosted_path, parsed_package_json.binary[checkAgainst] + '/'); + t.equal(opts.hosted_tarball, parsed_package_json.binary[checkAgainst] + '/' + opts.package_name); + }); + }); + cmds.forEach((cmd) => { + hosts.forEach((host) => { + parsed_package_json.binary = { + module_name: 'binary-module-name', + module_path: 'binary-module-path', + host: { endpoint: 's3-production-path' }, + development_host: { endpoint: 's3-development-path' }, + staging_host: { endpoint: 's3-staging-path' } + }; + + const checkAgainst = host !== 'production' ? `${host}_host` : 'host'; + const cloned = JSON.parse(JSON.stringify(parsed_package_json)); + const opts = versioning.evaluate(cloned, makeOoptions(cmd, host)); + + t.equal(opts.host, parsed_package_json.binary[checkAgainst].endpoint + '/'); + t.equal(opts.hosted_path, parsed_package_json.binary[checkAgainst].endpoint + '/'); + t.equal(opts.hosted_tarball, parsed_package_json.binary[checkAgainst].endpoint + '/' + opts.package_name); + }); + }); + t.end(); +}); + +test('should use host specified by the --s3_host option (production_host used)', (t) => { + const makeOoptions = (cmd, host) => { + return { + s3_host: host, + argv: { + remain: [cmd], + cooked: [cmd, '--s3_host', host], + original: [cmd, `--s3_host=${host}`] + } + }; + }; + const parsed_package_json = { name: 'test', main: 'test.js', From dda6d5ce6de68e9473951dd7ff7628d6060e734e Mon Sep 17 00:00:00 2001 From: Ron Ilan Date: Fri, 25 Apr 2025 10:25:52 -0700 Subject: [PATCH 14/14] Fixed failimng tests. --- test/versioning.test.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/versioning.test.js b/test/versioning.test.js index a421575c9..d5285924e 100644 --- a/test/versioning.test.js +++ b/test/versioning.test.js @@ -111,7 +111,7 @@ test('should detect custom binary host from env', (t) => { const cloned = JSON.parse(JSON.stringify(parsed_package_json)); const opts = versioning.evaluate(cloned, {}); - t.equal(opts.host, 'https://npm.taobao.org/mirrors/node-inspector/'); + t.equal(opts.host, 'https://registry.npmmirror.com/node-inspector/'); delete process.env.npm_config_test_binary_host_mirror; t.end(); @@ -1325,7 +1325,8 @@ test('should replace "-" with "_" in mirror binary host', (t) => { }; process.env.npm_config_canvas_prebuilt_binary_host_mirror = 'https://registry.npmmirror.com/node-canvas-prebuilt/'; - const opts = versioning.evaluate(mock_package_json, {}); + const cloned = JSON.parse(JSON.stringify(parsed_package_json)); + const opts = versioning.evaluate(cloned, {}); t.equal(opts.host, 'https://registry.npmmirror.com/node-canvas-prebuilt/'); delete process.env.npm_config_canvas_prebuilt_binary_host_mirror; t.end();