@@ -14,6 +14,10 @@ import {
1414 IProjectHelper ,
1515 IStringDictionary ,
1616} from "../declarations" ;
17+ import {
18+ INsConfigHooks ,
19+ IProjectConfigService ,
20+ } from "../../definitions/project" ;
1721import { IInjector } from "../definitions/yok" ;
1822import { injector } from "../yok" ;
1923
@@ -38,7 +42,8 @@ export class HooksService implements IHooksService {
3842 private $injector : IInjector ,
3943 private $projectHelper : IProjectHelper ,
4044 private $options : IOptions ,
41- private $performanceService : IPerformanceService
45+ private $performanceService : IPerformanceService ,
46+ private $projectConfigService : IProjectConfigService
4247 ) { }
4348
4449 public get hookArgsName ( ) : string {
@@ -61,6 +66,12 @@ export class HooksService implements IHooksService {
6166 this . $logger . trace (
6267 "Hooks directories: " + util . inspect ( this . hooksDirectories )
6368 ) ;
69+
70+ const customHooks = this . $projectConfigService . getValue ( "hooks" , [ ] ) ;
71+
72+ if ( customHooks . length ) {
73+ this . $logger . trace ( "Custom hooks: " + util . inspect ( customHooks ) ) ;
74+ }
6475 }
6576
6677 private static formatHookName ( commandName : string ) : string {
@@ -118,6 +129,19 @@ export class HooksService implements IHooksService {
118129 )
119130 ) ;
120131 }
132+
133+ const customHooks = this . getCustomHooksByName ( hookName ) ;
134+
135+ for ( const hook of customHooks ) {
136+ results . push (
137+ await this . executeHook (
138+ this . $projectHelper . projectDir ,
139+ hookName ,
140+ hook ,
141+ hookArguments
142+ )
143+ ) ;
144+ }
121145 } catch ( err ) {
122146 this . $logger . trace ( `Failed during hook execution ${ hookName } .` ) ;
123147 this . $errors . fail ( err . message || err ) ;
@@ -126,142 +150,186 @@ export class HooksService implements IHooksService {
126150 return _ . flatten ( results ) ;
127151 }
128152
129- private async executeHooksInDirectory (
153+ private async executeHook (
130154 directoryPath : string ,
131155 hookName : string ,
156+ hook : IHook ,
132157 hookArguments ?: IDictionary < any >
133- ) : Promise < any [ ] > {
158+ ) : Promise < any > {
134159 hookArguments = hookArguments || { } ;
135- const results : any [ ] = [ ] ;
136- const hooks = this . getHooksByName ( directoryPath , hookName ) ;
137160
138- for ( let i = 0 ; i < hooks . length ; ++ i ) {
139- const hook = hooks [ i ] ;
140- const relativePath = path . relative ( directoryPath , hook . fullPath ) ;
141- const trackId = relativePath . replace (
142- new RegExp ( "\\" + path . sep , "g" ) ,
143- AnalyticsEventLabelDelimiter
144- ) ;
145- let command = this . getSheBangInterpreter ( hook ) ;
146- let inProc = false ;
147- if ( ! command ) {
148- command = hook . fullPath ;
149- if ( path . extname ( hook . fullPath ) . toLowerCase ( ) === ".js" ) {
150- command = process . argv [ 0 ] ;
151- inProc = this . shouldExecuteInProcess (
152- this . $fs . readText ( hook . fullPath )
153- ) ;
154- }
161+ let result ;
162+
163+ const relativePath = path . relative ( directoryPath , hook . fullPath ) ;
164+ const trackId = relativePath . replace (
165+ new RegExp ( "\\" + path . sep , "g" ) ,
166+ AnalyticsEventLabelDelimiter
167+ ) ;
168+ let command = this . getSheBangInterpreter ( hook ) ;
169+ let inProc = false ;
170+ if ( ! command ) {
171+ command = hook . fullPath ;
172+ if ( path . extname ( hook . fullPath ) . toLowerCase ( ) === ".js" ) {
173+ command = process . argv [ 0 ] ;
174+ inProc = this . shouldExecuteInProcess ( this . $fs . readText ( hook . fullPath ) ) ;
155175 }
176+ }
156177
157- const startTime = this . $performanceService . now ( ) ;
158- if ( inProc ) {
159- this . $logger . trace (
160- "Executing %s hook at location %s in-process" ,
161- hookName ,
162- hook . fullPath
163- ) ;
164- const hookEntryPoint = require ( hook . fullPath ) ;
178+ const startTime = this . $performanceService . now ( ) ;
179+ if ( inProc ) {
180+ this . $logger . trace (
181+ "Executing %s hook at location %s in-process" ,
182+ hookName ,
183+ hook . fullPath
184+ ) ;
185+ const hookEntryPoint = require ( hook . fullPath ) ;
165186
166- this . $logger . trace ( `Validating ${ hookName } arguments.` ) ;
187+ this . $logger . trace ( `Validating ${ hookName } arguments.` ) ;
167188
168- const invalidArguments = this . validateHookArguments (
169- hookEntryPoint ,
170- hook . fullPath
171- ) ;
189+ const invalidArguments = this . validateHookArguments (
190+ hookEntryPoint ,
191+ hook . fullPath
192+ ) ;
172193
173- if ( invalidArguments . length ) {
174- this . $logger . warn (
175- `${
176- hook . fullPath
177- } will NOT be executed because it has invalid arguments - ${
178- invalidArguments . join ( ", " ) . grey
179- } .`
180- ) ;
181- continue ;
182- }
194+ if ( invalidArguments . length ) {
195+ this . $logger . warn (
196+ `${
197+ hook . fullPath
198+ } will NOT be executed because it has invalid arguments - ${
199+ invalidArguments . join ( ", " ) . grey
200+ } .`
201+ ) ;
202+ return ;
203+ }
183204
184- // HACK for backwards compatibility:
185- // In case $projectData wasn't resolved by the time we got here (most likely we got here without running a command but through a service directly)
186- // then it is probably passed as a hookArg
187- // if that is the case then pass it directly to the hook instead of trying to resolve $projectData via injector
188- // This helps make hooks stateless
189- const projectDataHookArg =
190- hookArguments [ "hookArgs" ] && hookArguments [ "hookArgs" ] [ "projectData" ] ;
191- if ( projectDataHookArg ) {
192- hookArguments [ "projectData" ] = hookArguments [
193- "$projectData"
194- ] = projectDataHookArg ;
195- }
205+ // HACK for backwards compatibility:
206+ // In case $projectData wasn't resolved by the time we got here (most likely we got here without running a command but through a service directly)
207+ // then it is probably passed as a hookArg
208+ // if that is the case then pass it directly to the hook instead of trying to resolve $projectData via injector
209+ // This helps make hooks stateless
210+ const projectDataHookArg =
211+ hookArguments [ "hookArgs" ] && hookArguments [ "hookArgs" ] [ "projectData" ] ;
212+ if ( projectDataHookArg ) {
213+ hookArguments [ "projectData" ] = hookArguments [
214+ "$projectData"
215+ ] = projectDataHookArg ;
216+ }
196217
197- const maybePromise = this . $injector . resolve (
198- hookEntryPoint ,
199- hookArguments
200- ) ;
201- if ( maybePromise ) {
202- this . $logger . trace ( "Hook promises to signal completion" ) ;
203- try {
204- const result = await maybePromise ;
205- results . push ( result ) ;
206- } catch ( err ) {
207- if (
208- err &&
209- _ . isBoolean ( err . stopExecution ) &&
210- err . errorAsWarning === true
211- ) {
212- this . $logger . warn ( err . message || err ) ;
213- } else {
214- // Print the actual error with its callstack, so it is easy to find out which hooks is causing troubles.
215- this . $logger . error ( err ) ;
216- throw (
217- err || new Error ( `Failed to execute hook: ${ hook . fullPath } .` )
218- ) ;
219- }
218+ const maybePromise = this . $injector . resolve (
219+ hookEntryPoint ,
220+ hookArguments
221+ ) ;
222+ if ( maybePromise ) {
223+ this . $logger . trace ( "Hook promises to signal completion" ) ;
224+ try {
225+ result = await maybePromise ;
226+ } catch ( err ) {
227+ if (
228+ err &&
229+ _ . isBoolean ( err . stopExecution ) &&
230+ err . errorAsWarning === true
231+ ) {
232+ this . $logger . warn ( err . message || err ) ;
233+ } else {
234+ // Print the actual error with its callstack, so it is easy to find out which hooks is causing troubles.
235+ this . $logger . error ( err ) ;
236+ throw err || new Error ( `Failed to execute hook: ${ hook . fullPath } .` ) ;
220237 }
221-
222- this . $logger . trace ( "Hook completed" ) ;
223238 }
224- } else {
225- const environment = this . prepareEnvironment ( hook . fullPath ) ;
226- this . $logger . trace (
227- "Executing %s hook at location %s with environment " ,
228- hookName ,
229- hook . fullPath ,
230- environment
231- ) ;
232239
233- const output = await this . $childProcess . spawnFromEvent (
234- command ,
235- [ hook . fullPath ] ,
236- "close" ,
237- environment ,
238- { throwError : false }
239- ) ;
240- results . push ( output ) ;
240+ this . $logger . trace ( "Hook completed" ) ;
241+ }
242+ } else {
243+ const environment = this . prepareEnvironment ( hook . fullPath ) ;
244+ this . $logger . trace (
245+ "Executing %s hook at location %s with environment " ,
246+ hookName ,
247+ hook . fullPath ,
248+ environment
249+ ) ;
241250
242- if ( output . exitCode !== 0 ) {
243- throw new Error ( output . stdout + output . stderr ) ;
244- }
251+ const output = await this . $childProcess . spawnFromEvent (
252+ command ,
253+ [ hook . fullPath ] ,
254+ "close" ,
255+ environment ,
256+ { throwError : false }
257+ ) ;
258+ result = output ;
245259
246- this . $logger . trace (
247- "Finished executing %s hook at location %s with environment " ,
248- hookName ,
249- hook . fullPath ,
250- environment
251- ) ;
260+ if ( output . exitCode !== 0 ) {
261+ throw new Error ( output . stdout + output . stderr ) ;
252262 }
253- const endTime = this . $performanceService . now ( ) ;
254- this . $performanceService . processExecutionData (
255- trackId ,
256- startTime ,
257- endTime ,
258- [ hookArguments ]
263+
264+ this . $logger . trace (
265+ "Finished executing %s hook at location %s with environment " ,
266+ hookName ,
267+ hook . fullPath ,
268+ environment
269+ ) ;
270+ }
271+ const endTime = this . $performanceService . now ( ) ;
272+ this . $performanceService . processExecutionData ( trackId , startTime , endTime , [
273+ hookArguments ,
274+ ] ) ;
275+
276+ return result ;
277+ }
278+
279+ private async executeHooksInDirectory (
280+ directoryPath : string ,
281+ hookName : string ,
282+ hookArguments ?: IDictionary < any >
283+ ) : Promise < any [ ] > {
284+ hookArguments = hookArguments || { } ;
285+ const results : any [ ] = [ ] ;
286+ const hooks = this . getHooksByName ( directoryPath , hookName ) ;
287+
288+ for ( let i = 0 ; i < hooks . length ; ++ i ) {
289+ const hook = hooks [ i ] ;
290+ const result = await this . executeHook (
291+ directoryPath ,
292+ hookName ,
293+ hook ,
294+ hookArguments
259295 ) ;
296+
297+ if ( result ) {
298+ results . push ( result ) ;
299+ }
260300 }
261301
262302 return results ;
263303 }
264304
305+ private getCustomHooksByName ( hookName : string ) : IHook [ ] {
306+ const hooks : IHook [ ] = [ ] ;
307+ const customHooks : INsConfigHooks [ ] =
308+ this . $projectConfigService . getValue ( "hooks" , [ ] ) ;
309+
310+ for ( const cHook of customHooks ) {
311+ if ( cHook . type === hookName ) {
312+ const fullPath = path . join (
313+ this . $projectHelper . projectDir ,
314+ cHook . script
315+ ) ;
316+ const isFile = this . $fs . getFsStats ( fullPath ) . isFile ( ) ;
317+
318+ if ( isFile ) {
319+ const fileNameParts = cHook . script . split ( "/" ) ;
320+ hooks . push (
321+ new Hook (
322+ this . getBaseFilename ( fileNameParts [ fileNameParts . length - 1 ] ) ,
323+ fullPath
324+ )
325+ ) ;
326+ }
327+ }
328+ }
329+
330+ return hooks ;
331+ }
332+
265333 private getHooksByName ( directoryPath : string , hookName : string ) : IHook [ ] {
266334 const allBaseHooks = this . getHooksInDirectory ( directoryPath ) ;
267335 const baseHooks = _ . filter (
0 commit comments