Repository: sindresorhus/got (ts)

Issue:
`got.extend()` doesn't merge hooks
I am try to get options dict for modifing it later. But looks like hook never been called.

```JavaScript
'use strict'
const got = require('got')

const client = got
  .extend({
    baseUrl: 'https://jsonplaceholder.typicode.com/todos/1',
    json: true,
    hooks: {
      beforeRequest: [(options) => console.log(111111111111)],
    },
  })
  .get('/')
  .then(console.log)
  .catch(console.error)
```

got version: **9.2.1**
node version: **v10.10.0**



Pull request description:
Merge hooks on got.extend()
Fixes #606

Patch:
diff --git a/source/create.js b/source/create.js
index 5ff4b12..36a2511 100644
--- a/source/create.js
+++ b/source/create.js
@@ -5,10 +5,8 @@ const asPromise = require('./as-promise');
 const normalizeArguments = require('./normalize-arguments');
 const merge = require('./merge');
 const deepFreeze = require('./deep-freeze');
-const mergeInstances = require('./merge-instances');
 
 const getPromiseOrStream = options => options.stream ? asStream(options) : asPromise(options);
-const mergeOptions = (defaults, options = {}) => merge({}, defaults, options);
 
 const aliases = [
 	'get',
@@ -42,11 +40,11 @@ const create = defaults => {
 
 	got.create = create;
 	got.extend = options => create({
-		options: mergeOptions(defaults.options, options),
+		options: merge.options(defaults.options, options),
 		handler: defaults.handler
 	});
 
-	got.mergeInstances = (...args) => create(mergeInstances(args));
+	got.mergeInstances = (...args) => create(merge.instances(args));
 
 	got.stream = (url, options) => got(url, {...options, stream: true});
 
@@ -55,7 +53,7 @@ const create = defaults => {
 		got.stream[method] = (url, options) => got.stream(url, {...options, method});
 	}
 
-	Object.assign(got, {...errors, mergeOptions});
+	Object.assign(got, {...errors, mergeOptions: merge.options});
 	Object.defineProperty(got, 'defaults', {
 		value: deepFreeze(defaults),
 		writable: false,
diff --git a/source/merge-instances.js b/source/merge-instances.js
deleted file mode 100644
index 9899c95..0000000
--- a/source/merge-instances.js
+++ /dev/null
@@ -1,36 +0,0 @@
-'use strict';
-const merge = require('./merge');
-const knownHookEvents = require('./known-hook-events');
-
-module.exports = (instances, methods) => {
-	const handlers = instances.map(instance => instance.defaults.handler);
-	const size = instances.length - 1;
-
-	let options = {};
-	const hooks = {};
-	for (const instance of instances) {
-		options = merge({}, options, instance.defaults.options);
-
-		const instanceHooks = instance.defaults.options.hooks;
-		for (const name of knownHookEvents) {
-			if (hooks[name]) {
-				hooks[name] = hooks[name].concat(instanceHooks[name]);
-			} else {
-				hooks[name] = [...instanceHooks[name]];
-			}
-		}
-	}
-
-	options.hooks = hooks;
-
-	return {
-		methods,
-		options,
-		handler: (options, next) => {
-			let iteration = -1;
-			const iterate = options => handlers[++iteration](options, iteration === size ? next : iterate);
-
-			return iterate(options);
-		}
-	};
-};
diff --git a/source/merge.js b/source/merge.js
index 1402307..a61a627 100644
--- a/source/merge.js
+++ b/source/merge.js
@@ -1,6 +1,7 @@
 'use strict';
 const {URL} = require('url');
 const is = require('@sindresorhus/is');
+const knownHookEvents = require('./known-hook-events');
 
 const merge = (target, ...sources) => {
 	for (const source of sources) {
@@ -29,4 +30,48 @@ const merge = (target, ...sources) => {
 	return target;
 };
 
+const mergeOptions = (...sources) => {
+	sources = sources.map(source => source || {});
+	const merged = merge({}, ...sources);
+
+	const hooks = {};
+	for (const hook of knownHookEvents) {
+		hooks[hook] = [];
+	}
+
+	for (const source of sources) {
+		if (source.hooks) {
+			for (const hook of knownHookEvents) {
+				if (hooks[hook]) {
+					hooks[hook] = hooks[hook].concat(source.hooks[hook]);
+				}
+			}
+		}
+	}
+
+	if (!is.empty(hooks)) {
+		merged.hooks = hooks;
+	}
+
+	return merged;
+};
+
+const mergeInstances = (instances, methods) => {
+	const handlers = instances.map(instance => instance.defaults.handler);
+	const size = instances.length - 1;
+
+	return {
+		methods,
+		options: mergeOptions(...instances.map(instance => instance.defaults.options)),
+		handler: (options, next) => {
+			let iteration = -1;
+			const iterate = options => handlers[++iteration](options, iteration === size ? next : iterate);
+
+			return iterate(options);
+		}
+	};
+};
+
 module.exports = merge;
+module.exports.options = mergeOptions;
+module.exports.instances = mergeInstances;


Test patch:
diff --git a/test/create.js b/test/create.js
index d0ede0f..d28d885 100644
--- a/test/create.js
+++ b/test/create.js
@@ -115,6 +115,16 @@ test('create', async t => {
 	t.is(headers['user-agent'], undefined);
 });
 
+test('hooks are merged on got.extend()', t => {
+	const hooksA = [() => {}];
+	const hooksB = [() => {}];
+
+	const instanceA = got.create({options: {hooks: {beforeRequest: hooksA}}});
+
+	const extended = instanceA.extend({hooks: {beforeRequest: hooksB}});
+	t.deepEqual(extended.defaults.options.hooks.beforeRequest, hooksA.concat(hooksB));
+});
+
 test('custom endpoint with custom headers (extend)', async t => {
 	const instance = got.extend({headers: {unicorn: 'rainbow'}, baseUrl: s.url});
 	const headers = (await instance('/', {