Skip to content

Commit b9538e0

Browse files
committed
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.
1 parent 3eb8a1c commit b9538e0

File tree

6 files changed

+708
-241
lines changed

6 files changed

+708
-241
lines changed

lib/main.js

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,6 @@ function run() {
7272
return;
7373
}
7474

75-
// set binary.host when appropriate. host determines the s3 target bucket.
76-
const target = prog.setBinaryHostProperty(command.name);
77-
if (target && ['install', 'publish', 'unpublish', 'info'].indexOf(command.name) >= 0) {
78-
log.info('using binary.host: ' + prog.package_json.binary.host);
79-
}
80-
8175
prog.commands[command.name](command.args, function(err) {
8276
if (err) {
8377
log.error(command.name + ' error');

lib/node-pre-gyp.js

Lines changed: 1 addition & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,8 @@ function Run({ package_json_path = './package.json', argv }) {
7373
});
7474

7575
this.parseArgv(argv);
76-
77-
// this is set to true after the binary.host property was set to
78-
// either staging_host or production_host.
79-
this.binaryHostSet = false;
8076
}
77+
8178
inherits(Run, EE);
8279
exports.Run = Run;
8380
const proto = Run.prototype;
@@ -201,67 +198,6 @@ proto.parseArgv = function parseOpts(argv) {
201198
log.resume();
202199
};
203200

204-
/**
205-
* allow the binary.host property to be set at execution time.
206-
*
207-
* for this to take effect requires all the following to be true.
208-
* - binary is a property in package.json
209-
* - binary.host is falsey
210-
* - binary.staging_host is not empty
211-
* - binary.production_host is not empty
212-
*
213-
* if any of the previous checks fail then the function returns an empty string
214-
* and makes no changes to package.json's binary property.
215-
*
216-
*
217-
* if command is "publish" then the default is set to "binary.staging_host"
218-
* if command is not "publish" the the default is set to "binary.production_host"
219-
*
220-
* if the command-line option '--s3_host' is set to "staging" or "production" then
221-
* "binary.host" is set to the specified "staging_host" or "production_host". if
222-
* '--s3_host' is any other value an exception is thrown.
223-
*
224-
* if '--s3_host' is not present then "binary.host" is set to the default as above.
225-
*
226-
* this strategy was chosen so that any command other than "publish" or "unpublish" uses "production"
227-
* as the default without requiring any command-line options but that "publish" and "unpublish" require
228-
* '--s3_host production_host' to be specified in order to *really* publish (or unpublish). publishing
229-
* to staging can be done freely without worrying about disturbing any production releases.
230-
*/
231-
proto.setBinaryHostProperty = function(command) {
232-
if (this.binaryHostSet) {
233-
return this.package_json.binary.host;
234-
}
235-
const p = this.package_json;
236-
// don't set anything if host is present. it must be left blank to trigger this.
237-
if (!p || !p.binary || p.binary.host) {
238-
return '';
239-
}
240-
// and both staging and production must be present. errors will be reported later.
241-
if (!p.binary.staging_host || !p.binary.production_host) {
242-
return '';
243-
}
244-
let target = 'production_host';
245-
if (command === 'publish' || command === 'unpublish') {
246-
target = 'staging_host';
247-
}
248-
// the environment variable has priority over the default or the command line. if
249-
// either the env var or the command line option are invalid throw an error.
250-
const npg_s3_host = process.env.node_pre_gyp_s3_host;
251-
if (npg_s3_host === 'staging' || npg_s3_host === 'production') {
252-
target = `${npg_s3_host}_host`;
253-
} else if (this.opts['s3_host'] === 'staging' || this.opts['s3_host'] === 'production') {
254-
target = `${this.opts['s3_host']}_host`;
255-
} else if (this.opts['s3_host'] || npg_s3_host) {
256-
throw new Error(`invalid s3_host ${this.opts['s3_host'] || npg_s3_host}`);
257-
}
258-
259-
p.binary.host = p.binary[target];
260-
this.binaryHostSet = true;
261-
262-
return p.binary.host;
263-
};
264-
265201
/**
266202
* Returns the usage instructions for node-pre-gyp.
267203
*/

lib/pre-binding.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ exports.find = function(package_json_path, opts) {
1919
throw new Error(package_json_path + 'does not exist');
2020
}
2121
const prog = new npg.Run({ package_json_path, argv: process.argv });
22-
prog.setBinaryHostProperty();
2322
const package_json = prog.package_json;
2423

2524
versioning.validate_config(package_json, opts);

lib/util/versioning.js

Lines changed: 60 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -186,12 +186,6 @@ function get_runtime_abi(runtime, target_version) {
186186
}
187187
module.exports.get_runtime_abi = get_runtime_abi;
188188

189-
const required_parameters = [
190-
'module_name',
191-
'module_path',
192-
'host'
193-
];
194-
195189
function validate_config(package_json, opts) {
196190
const msg = package_json.name + ' package.json is not node-pre-gyp ready:\n';
197191
const missing = [];
@@ -207,25 +201,33 @@ function validate_config(package_json, opts) {
207201
if (!package_json.binary) {
208202
missing.push('binary');
209203
}
210-
const o = package_json.binary;
211-
if (o) {
212-
required_parameters.forEach((p) => {
213-
if (!o[p] || typeof o[p] !== 'string') {
214-
missing.push('binary.' + p);
215-
}
216-
});
204+
205+
if (package_json.binary) {
206+
if (!package_json.binary.module_name) {
207+
missing.push('binary.module_name');
208+
}
209+
if (!package_json.binary.module_path) {
210+
missing.push('binary.module_path');
211+
}
212+
if (!package_json.binary.host && !package_json.binary.production_host) {
213+
missing.push('binary.host');
214+
}
217215
}
218216

219217
if (missing.length >= 1) {
220218
throw new Error(msg + 'package.json must declare these properties: \n' + missing.join('\n'));
221219
}
222-
if (o) {
223-
// enforce https over http
224-
const protocol = url.parse(o.host).protocol;
225-
if (protocol === 'http:') {
226-
throw new Error("'host' protocol (" + protocol + ") is invalid - only 'https:' is accepted");
227-
}
220+
221+
if (package_json.binary) {
222+
// for all possible host definitions - verify https usage
223+
['host', 'production_host', 'staging_host', 'development_host'].filter((item) => package_json.binary[item]).forEach((item) => {
224+
const protocol = url.parse(package_json.binary[item]).protocol;
225+
if (protocol === 'http:') {
226+
throw new Error(msg + "'" + item + "' protocol (" + protocol + ") is invalid - only 'https:' is accepted");
227+
}
228+
});
228229
}
230+
229231
napi.validate_package_json(package_json, opts);
230232
}
231233

@@ -309,11 +311,46 @@ module.exports.evaluate = function(package_json, options, napi_build_version) {
309311
region: package_json.binary.region,
310312
s3ForcePathStyle: package_json.binary.s3ForcePathStyle || false
311313
};
312-
// support host mirror with npm config `--{module_name}_binary_host_mirror`
313-
// e.g.: https://github.com/node-inspector/v8-profiler/blob/master/package.json#L25
314-
// > npm install v8-profiler --profiler_binary_host_mirror=https://npm.taobao.org/mirrors/node-inspector/
314+
315+
// user can define a target host key to use (development_host, staging_host, production_host)
316+
// by setting the name of the host (development, staging, production)
317+
// into an environment variable or via a command line option.
318+
// the environment variable has priority over the the command line.
319+
let targetHost = process.env.node_pre_gyp_s3_host || options.s3_host;
320+
321+
// if value is not one of the allowed or the matching key is not found in package.json
322+
// silently ignore the option
323+
if (['production', 'staging', 'development'].indexOf(targetHost) === -1 || !package_json.binary[`${targetHost}_host`]) {
324+
targetHost = '';
325+
}
326+
327+
// the production host is as specified in 'host' key (default)
328+
// unless there is none and alias production_host is specified (backwards compatibility)
329+
// note: package.json is verified in validate_config to include at least one of the two.
330+
let host = package_json.binary.host || package_json.binary.production_host;
331+
332+
// when a valid target is specified by user, the host is from that target (or 'host')
333+
if (targetHost === 'staging') {
334+
host = package_json.binary.staging_host;
335+
} else if (targetHost === 'development') {
336+
host = package_json.binary.development_host;
337+
} else if (!targetHost && (package_json.binary.development_host || package_json.binary.staging_host)) {
338+
// when host not specifically set via command line or environment variable
339+
// but staging and/or development host are present in package.json
340+
// for any command (or command chain) that includes publish or unpublish
341+
// default to lower host (development, and if not preset, staging).
342+
if (options.argv && options.argv.remain.some((item) => (item === 'publish' || item === 'unpublish'))) {
343+
host = package_json.binary.development_host || package_json.binary.staging_host;
344+
}
345+
}
346+
347+
// support host mirror with npm config `--{module_name}_binary_host_mirror`
348+
// e.g.: https://github.com/node-inspector/v8-profiler/blob/master/package.json#L25
349+
// > npm install v8-profiler --profiler_binary_host_mirror=https://npm.taobao.org/mirrors/node-inspector/
315350
const validModuleName = opts.module_name.replace('-', '_');
316-
const host = process.env['npm_config_' + validModuleName + '_binary_host_mirror'] || package_json.binary.host;
351+
// explicitly set mirror overrides everything set above
352+
host = process.env['npm_config_' + validModuleName + '_binary_host_mirror'] || host;
353+
317354
opts.host = fix_slashes(eval_template(host, opts));
318355
opts.module_path = eval_template(package_json.binary.module_path, opts);
319356
// now we resolve the module_path to ensure it is absolute so that binding.gyp variables work predictably

test/run.test.js

Lines changed: 4 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,6 @@ const package_json_template = {
2828
}
2929
};
3030

31-
32-
const all_commands = ['build', 'clean', 'configure', 'info', 'install', 'package', 'publish', 'rebuild',
33-
'reinstall', 'reveal', 'testbinary', 'testpackage', 'unpublish'];
34-
3531
/**
3632
* before testing create a scratch directory to run tests in.
3733
*/
@@ -67,82 +63,6 @@ test.onFinish(() => {
6763
rimraf(scratch).then(() => undefined, () => undefined);
6864
});
6965

70-
test('should set staging and production hosts', (t) => {
71-
// make sure it's good when specifying host.
72-
const mock_package_json = makePackageJson();
73-
74-
let { prog } = setupTest(dir, mock_package_json);
75-
t.deepEqual(prog.package_json, mock_package_json);
76-
t.equal(prog.binaryHostSet, false, 'binary host should not be flagged as set');
77-
78-
// test with no s3_host option
79-
all_commands.forEach((cmd) => {
80-
const mpj = clone(mock_package_json);
81-
mpj.binary.host = '';
82-
const opts = { argv: [cmd] };
83-
({ prog } = setupTest(dir, mpj, opts));
84-
mpj.binary.host = (cmd === 'publish' || cmd === 'unpublish') ? mpj.binary.staging_host : mpj.binary.production_host;
85-
t.deepEqual(prog.package_json, mpj, 'host should be correct for command: ' + cmd);
86-
t.equal(prog.binaryHostSet, true, 'binary host should be flagged as set');
87-
});
88-
89-
// test with s3_host set to staging
90-
all_commands.forEach((cmd) => {
91-
const mpj = clone(mock_package_json);
92-
mpj.binary.host = '';
93-
const opts = { argv: [cmd, '--s3_host=staging'] };
94-
({ prog } = setupTest(dir, mpj, opts));
95-
mpj.binary.host = mpj.binary.staging_host;
96-
t.deepEqual(prog.package_json, mpj, 'host should be correct for command: ' + cmd);
97-
t.equal(prog.binaryHostSet, true, 'binary host should be flagged as set');
98-
});
99-
100-
// test with s3_host set to production
101-
all_commands.forEach((cmd) => {
102-
const mpj = clone(mock_package_json);
103-
mpj.binary.host = '';
104-
const opts = { argv: [cmd, '--s3_host=production'] };
105-
({ prog } = setupTest(dir, mpj, opts));
106-
mpj.binary.host = mpj.binary.production_host;
107-
t.deepEqual(prog.package_json, mpj, 'host should be correct for command: ' + cmd);
108-
t.equal(prog.binaryHostSet, true, 'binary host should be flagged as set');
109-
});
110-
111-
t.end();
112-
});
113-
114-
test('should execute setBinaryHostProperty() properly', (t) => {
115-
// it only --s3_host only takes effect if host is falsey.
116-
const mock_package_json = makePackageJson({ binary: { host: '' } });
117-
118-
const opts = { argv: ['publish', '--s3_host=staging'] };
119-
120-
let { prog, binaryHost } = setupTest(dir, mock_package_json, opts);
121-
t.equal(binaryHost, mock_package_json.binary.staging_host);
122-
123-
// set it again to verify that it returns the already set value
124-
binaryHost = prog.setBinaryHostProperty('publish');
125-
t.equal(binaryHost, mock_package_json.binary.staging_host);
126-
127-
// now do this again but expect an empty binary host value because
128-
// staging_host is missing.
129-
const mpj = clone(mock_package_json);
130-
delete mpj.binary.staging_host;
131-
({ prog, binaryHost } = setupTest(dir, mpj, opts));
132-
t.equal(binaryHost, '');
133-
134-
// one more time but with an invalid value for s3_host
135-
opts.argv = ['publish', '--s3_host=bad-news'];
136-
try {
137-
({ prog, binaryHost } = setupTest(dir, mock_package_json, opts));
138-
t.fail('should throw with --s3_host=bad-news');
139-
} catch (e) {
140-
t.equal(e.message, 'invalid s3_host bad-news');
141-
}
142-
143-
t.end();
144-
});
145-
14666
test('verify that the --directory option works', (t) => {
14767
const initial = process.cwd();
14868

@@ -223,6 +143,10 @@ test('verify that a non-existent package.json fails', (t) => {
223143
// test helpers.
224144
//
225145

146+
// helper to clone mock package.json.
147+
// // https://stackoverflow.com/questions/4459928/how-to-deep-clone-in-javascript
148+
const clone = (obj) => JSON.parse(JSON.stringify(obj));
149+
226150
function makePackageJson(options = {}) {
227151
const package_json = clone(package_json_template);
228152
// override binary values if supplied
@@ -233,60 +157,3 @@ function makePackageJson(options = {}) {
233157
}
234158
return package_json;
235159
}
236-
237-
// helper to write package.json to disk so Run() can be instantiated with it.
238-
function setupTest(directory, package_json, opts) {
239-
opts = opts || {};
240-
let argv = ['node', 'program'];
241-
if (opts.argv) {
242-
argv = argv.concat(opts.argv);
243-
}
244-
const prev_dir = process.cwd();
245-
if (!opts.noChdir) {
246-
try {
247-
fs.mkdirSync(directory);
248-
} catch (e) {
249-
if (e.code !== 'EEXIST') {
250-
throw e;
251-
}
252-
}
253-
process.chdir(directory);
254-
}
255-
256-
try {
257-
fs.writeFileSync('package.json', JSON.stringify(package_json));
258-
const prog = new npg.Run({ package_json_path: './package.json', argv });
259-
const binaryHost = prog.setBinaryHostProperty(prog.todo[0] && prog.todo[0].name);
260-
return { prog, binaryHost };
261-
} finally {
262-
process.chdir(prev_dir);
263-
}
264-
}
265-
266-
// helper to clone mock package.json. it's overkill for existing tests
267-
// but is future-proof.
268-
// https://stackoverflow.com/questions/4459928/how-to-deep-clone-in-javascript
269-
function clone(obj, hash = new WeakMap()) {
270-
if (Object(obj) !== obj) return obj; // primitives
271-
if (hash.has(obj)) return hash.get(obj); // cyclic reference
272-
let result;
273-
274-
if (obj instanceof Set) {
275-
result = new Set(obj); // treat set as a value
276-
} else if (obj instanceof Map) {
277-
result = new Map(Array.from(obj, ([key, val]) => [key, clone(val, hash)]));
278-
} else if (obj instanceof Date) {
279-
result = new Date(obj);
280-
} else if (obj instanceof RegExp) {
281-
result = new RegExp(obj.source, obj.flags);
282-
} else if (obj.constructor) {
283-
result = new obj.constructor();
284-
} else {
285-
result = Object.create(null);
286-
}
287-
hash.set(obj, result);
288-
return Object.assign(result, ...Object.keys(obj).map((key) => {
289-
return { [key]: clone(obj[key], hash) };
290-
}));
291-
}
292-

0 commit comments

Comments
 (0)