From 27f57cc1c90c20f86a21c12f7ac7aec72c8c7568 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Mon, 9 Oct 2017 08:35:38 +0300 Subject: [PATCH 1/2] Allow hooks to provide implementation for decorated methods Whenever a before-hook returns a function it will be used instead of the original method. When executing such a substitute function it receives a pointer to the original one as well as all its arguments. --- helpers.ts | 26 ++++++++++++++++++++++---- services/hooks-service.ts | 18 +++++++++++++----- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/helpers.ts b/helpers.ts index 74815c59..0abeaeac 100644 --- a/helpers.ts +++ b/helpers.ts @@ -281,14 +281,32 @@ export function appendZeroesToVersion(version: string, requiredVersionLength: nu return version; } -export function decorateMethod(before: (method1: any, self1: any, args1: any[]) => Promise, after: (method2: any, self2: any, result2: any, args2: any[]) => Promise) { +export function decorateMethod(before: (method1: any, self1: any, args1: any[]) => Promise, after: (method2: any, self2: any, result2: any, args2: any[]) => Promise) { return (target: Object, propertyKey: string, descriptor: TypedPropertyDescriptor) => { const sink = descriptor.value; descriptor.value = async function (...args: any[]): Promise { + let newMethods: Function[] = null; if (before) { - await before(sink, this, args); + newMethods = await before(sink, this, args); } - const result = sink.apply(this, args); + + let hasBeenReplaced = false; + let result: any; + if (newMethods && newMethods.length) { + // Find all functions + _(newMethods) + .filter(f => _.isFunction(f)) + .each(f => { + // maybe add logger.trace here + hasBeenReplaced = true; + result = f(args, sink.bind(this)); + }); + } + + if (!hasBeenReplaced) { + result = sink.apply(this, args); + } + if (after) { return await after(sink, this, result, args); } @@ -327,7 +345,7 @@ export function hook(commandName: string) { return decorateMethod( async (method: any, self: any, args: any[]) => { const hooksService = getHooksService(self); - await hooksService.executeBeforeHooks(commandName, prepareArguments(method, args, hooksService)); + return hooksService.executeBeforeHooks(commandName, prepareArguments(method, args, hooksService)); }, async (method: any, self: any, resultPromise: any, args: any[]) => { const result = await resultPromise; diff --git a/services/hooks-service.ts b/services/hooks-service.ts index 14afa480..b041edcb 100644 --- a/services/hooks-service.ts +++ b/services/hooks-service.ts @@ -63,7 +63,7 @@ export class HooksService implements IHooksService { return this.executeHooks(afterHookName, traceMessage, hookArguments); } - private async executeHooks(hookName: string, traceMessage: string, hookArguments?: IDictionary): Promise { + private async executeHooks(hookName: string, traceMessage: string, hookArguments?: IDictionary): Promise { if (this.$config.DISABLE_HOOKS || !this.$options.hooks) { return; } @@ -75,19 +75,22 @@ export class HooksService implements IHooksService { this.initialize(projectDir); this.$logger.trace(traceMessage); - + const results: any[] = []; try { for (const hooksDirectory of this.hooksDirectories) { - await this.executeHooksInDirectory(hooksDirectory, hookName, hookArguments); + results.push(await this.executeHooksInDirectory(hooksDirectory, hookName, hookArguments)); } } catch (err) { this.$logger.trace("Failed during hook execution."); this.$errors.failWithoutHelp(err.message || err); } + + return _.flatten(results); } - private async executeHooksInDirectory(directoryPath: string, hookName: string, hookArguments?: IDictionary): Promise { + private async executeHooksInDirectory(directoryPath: string, hookName: string, hookArguments?: IDictionary): Promise { hookArguments = hookArguments || {}; + const results: any[] = []; const hooks = this.getHooksByName(directoryPath, hookName); for (let i = 0; i < hooks.length; ++i) { const hook = hooks[i]; @@ -129,7 +132,8 @@ export class HooksService implements IHooksService { if (maybePromise) { this.$logger.trace('Hook promises to signal completion'); try { - await maybePromise; + const result = await maybePromise; + results.push(result); } catch (err) { if (err && _.isBoolean(err.stopExecution) && err.errorAsWarning === true) { this.$logger.warn(err.message || err); @@ -144,12 +148,16 @@ export class HooksService implements IHooksService { this.$logger.trace("Executing %s hook at location %s with environment ", hookName, hook.fullPath, environment); const output = await this.$childProcess.spawnFromEvent(command, [hook.fullPath], "close", environment, { throwError: false }); + results.push(output); + if (output.exitCode !== 0) { throw new Error(output.stdout + output.stderr); } } } } + + return results; } private getHooksByName(directoryPath: string, hookName: string): IHook[] { From b66d0281f1b1fc1786bb6cb79dc123516d6c13a5 Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Thu, 2 Nov 2017 16:18:06 +0200 Subject: [PATCH 2/2] Execute only the first replacement method --- helpers.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/helpers.ts b/helpers.ts index 0abeaeac..a4cac82e 100644 --- a/helpers.ts +++ b/helpers.ts @@ -293,14 +293,16 @@ export function decorateMethod(before: (method1: any, self1: any, args1: any[]) let hasBeenReplaced = false; let result: any; if (newMethods && newMethods.length) { - // Find all functions - _(newMethods) - .filter(f => _.isFunction(f)) - .each(f => { - // maybe add logger.trace here - hasBeenReplaced = true; - result = f(args, sink.bind(this)); - }); + const replacementMethods = _.filter(newMethods, f => _.isFunction(f)); + if (replacementMethods.length > 0) { + if (replacementMethods.length > 1) { + const $logger = $injector.resolve("logger"); + $logger.warn(`Multiple methods detected which try to replace ${sink.name}. Will execute only the first of them.`); + } + + hasBeenReplaced = true; + result = _.head(replacementMethods)(args, sink.bind(this)); + } } if (!hasBeenReplaced) {