@@ -14,6 +14,10 @@ import {
14
14
IProjectHelper ,
15
15
IStringDictionary ,
16
16
} from "../declarations" ;
17
+ import {
18
+ INsConfigHooks ,
19
+ IProjectConfigService ,
20
+ } from "../../definitions/project" ;
17
21
import { IInjector } from "../definitions/yok" ;
18
22
import { injector } from "../yok" ;
19
23
@@ -38,7 +42,8 @@ export class HooksService implements IHooksService {
38
42
private $injector : IInjector ,
39
43
private $projectHelper : IProjectHelper ,
40
44
private $options : IOptions ,
41
- private $performanceService : IPerformanceService
45
+ private $performanceService : IPerformanceService ,
46
+ private $projectConfigService : IProjectConfigService
42
47
) { }
43
48
44
49
public get hookArgsName ( ) : string {
@@ -61,6 +66,12 @@ export class HooksService implements IHooksService {
61
66
this . $logger . trace (
62
67
"Hooks directories: " + util . inspect ( this . hooksDirectories )
63
68
) ;
69
+
70
+ const customHooks = this . $projectConfigService . getValue ( "hooks" , [ ] ) ;
71
+
72
+ if ( customHooks . length ) {
73
+ this . $logger . trace ( "Custom hooks: " + util . inspect ( customHooks ) ) ;
74
+ }
64
75
}
65
76
66
77
private static formatHookName ( commandName : string ) : string {
@@ -118,6 +129,19 @@ export class HooksService implements IHooksService {
118
129
)
119
130
) ;
120
131
}
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
+ }
121
145
} catch ( err ) {
122
146
this . $logger . trace ( `Failed during hook execution ${ hookName } .` ) ;
123
147
this . $errors . fail ( err . message || err ) ;
@@ -126,142 +150,186 @@ export class HooksService implements IHooksService {
126
150
return _ . flatten ( results ) ;
127
151
}
128
152
129
- private async executeHooksInDirectory (
153
+ private async executeHook (
130
154
directoryPath : string ,
131
155
hookName : string ,
156
+ hook : IHook ,
132
157
hookArguments ?: IDictionary < any >
133
- ) : Promise < any [ ] > {
158
+ ) : Promise < any > {
134
159
hookArguments = hookArguments || { } ;
135
- const results : any [ ] = [ ] ;
136
- const hooks = this . getHooksByName ( directoryPath , hookName ) ;
137
160
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 ) ) ;
155
175
}
176
+ }
156
177
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 ) ;
165
186
166
- this . $logger . trace ( `Validating ${ hookName } arguments.` ) ;
187
+ this . $logger . trace ( `Validating ${ hookName } arguments.` ) ;
167
188
168
- const invalidArguments = this . validateHookArguments (
169
- hookEntryPoint ,
170
- hook . fullPath
171
- ) ;
189
+ const invalidArguments = this . validateHookArguments (
190
+ hookEntryPoint ,
191
+ hook . fullPath
192
+ ) ;
172
193
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
+ }
183
204
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
+ }
196
217
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 } .` ) ;
220
237
}
221
-
222
- this . $logger . trace ( "Hook completed" ) ;
223
238
}
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
- ) ;
232
239
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
+ ) ;
241
250
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 ;
245
259
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 ) ;
252
262
}
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
259
295
) ;
296
+
297
+ if ( result ) {
298
+ results . push ( result ) ;
299
+ }
260
300
}
261
301
262
302
return results ;
263
303
}
264
304
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
+
265
333
private getHooksByName ( directoryPath : string , hookName : string ) : IHook [ ] {
266
334
const allBaseHooks = this . getHooksInDirectory ( directoryPath ) ;
267
335
const baseHooks = _ . filter (
0 commit comments