11import { Service } from './Service'
2- import serializeJavascript from 'serialize-javascript'
2+ import serializeJs from 'serialize-javascript'
33import mergeStream from 'merge-stream'
4+ import chalk from 'chalk'
45import {
56 NormalizedCompositeServiceConfig ,
67 validateAndNormalizeConfig ,
@@ -28,52 +29,41 @@ export class CompositeService {
2829
2930 this . logger = new Logger ( this . config . logLevel )
3031 outputStream . add ( this . logger . output )
31- this . logger . debug (
32- 'Config: ' +
33- serializeJavascript ( config , {
34- space : 2 ,
35- unsafe : true ,
36- } ) ,
37- )
3832
39- process . on ( 'SIGINT' , ( ) => this . die ( 130 , "Received 'SIGINT' signal" ) )
40- process . on ( 'SIGTERM' , ( ) => this . die ( 143 , "Received 'SIGTERM' signal" ) )
33+ const configText = serializeJs ( config , { space : 2 , unsafe : true } )
34+ this . logger . log ( 'debug' , `Config: ${ configText } ` )
35+
36+ process . on ( 'SIGINT' , ( ) => this . handleShutdownSignal ( 130 , 'SIGINT' ) )
37+ process . on ( 'SIGTERM' , ( ) => this . handleShutdownSignal ( 143 , 'SIGTERM' ) )
4138 if ( process . stdin . isTTY ) {
4239 process . stdin . setRawMode ( true )
4340 process . stdin . on ( 'data' , buffer => {
4441 if ( buffer . toString ( 'utf8' ) === '\u0003' ) {
45- this . die ( 130 , 'Received ctrl+c' )
42+ this . handleShutdownSignal ( 130 , 'ctrl+c' )
4643 }
4744 } )
4845 }
4946
5047 this . services = Object . entries ( this . config . services ) . map (
5148 ( [ id , config ] ) =>
52- new Service ( id , config , this . logger , message =>
53- this . handleError ( `Error in '${ id } ': ${ message } ` ) ,
54- ) ,
49+ new Service ( id , config , this . logger , this . handleFatalError . bind ( this ) ) ,
5550 )
5651 this . serviceMap = new Map (
5752 this . services . map ( service => [ service . id , service ] ) ,
5853 )
5954
60- const maxLabelLength = Math . max (
61- ...Object . keys ( this . config . services ) . map ( ( { length } ) => length ) ,
62- )
6355 outputStream . add (
64- this . services . map ( ( { output, id } ) =>
65- output . pipe (
66- mapStreamLines (
67- line => `${ `${ rightPad ( id , maxLabelLength ) } | ` } ${ line } ` ,
68- ) ,
69- ) ,
70- ) ,
56+ this . services . map ( ( { output, id } , i ) => {
57+ // luminosity of 20 because at 256 colors, luminosity from 16 to 24 yields the most colors (12 colors) while keeping high contrast with text
58+ const label = chalk . bgHsl ( ( i / this . services . length ) * 360 , 100 , 20 ) ( id )
59+ return output . pipe ( mapStreamLines ( line => `${ label } | ${ line } ` ) )
60+ } ) ,
7161 )
7262
73- this . logger . info ( 'Starting composite service...' )
63+ this . logger . log ( 'debug' , 'Starting composite service...' )
7464 Promise . all (
7565 this . services . map ( service => this . startService ( service ) ) ,
76- ) . then ( ( ) => this . logger . info ( 'Started composite service' ) )
66+ ) . then ( ( ) => this . logger . log ( 'debug' , 'Started composite service' ) )
7767 }
7868
7969 private async startService ( service : Service ) {
@@ -90,29 +80,35 @@ export class CompositeService {
9080 }
9181 }
9282
93- private handleError ( message : string ) {
94- return this . die ( 1 , message )
83+ private handleFatalError ( message : string ) : void {
84+ this . logger . log ( 'error' , `Fatal error: ${ message } ` )
85+ if ( ! this . stopping ) {
86+ this . stop ( 1 )
87+ }
9588 }
9689
97- private async die ( exitCode : number , message : string ) : Promise < never > {
90+ private handleShutdownSignal ( exitCode : number , description : string ) : void {
9891 if ( ! this . stopping ) {
99- this . stopping = true
100- const isSignalExit = exitCode > 128 // we have either a signal exit or an error exit
101- this . logger [ isSignalExit ? 'info' : 'error' ] ( message )
102- this . logger . info ( 'Stopping composite service...' )
103- if ( this . config . windowsCtrlCShutdown ) {
104- require ( 'generate-ctrl-c-event' )
105- . generateCtrlCAsync ( )
106- . catch ( ( error : Error ) => this . logger . error ( String ( error ) ) )
107- }
108- await Promise . all ( this . services . map ( service => this . stopService ( service ) ) )
109- this . logger . info ( 'Stopped composite service' )
110- await Promise . resolve ( ) // Wait one micro tick for output to flush
111- process . exit ( exitCode )
92+ this . logger . log ( 'info' , `Received shutdown signal (${ description } )` )
93+ this . stop ( exitCode )
11294 }
113- // simply return a promise that never resolves, since we can't do anything after exiting anyways
114- return never ( )
11595 }
96+
97+ private stop ( exitCode : number ) : void {
98+ if ( this . stopping ) return
99+ this . stopping = true
100+ this . logger . log ( 'debug' , 'Stopping composite service...' )
101+ if ( this . config . windowsCtrlCShutdown ) {
102+ require ( 'generate-ctrl-c-event' )
103+ . generateCtrlCAsync ( )
104+ . catch ( ( error : Error ) => this . logger . log ( 'error' , String ( error ) ) )
105+ }
106+ Promise . all ( this . services . map ( service => this . stopService ( service ) ) )
107+ . then ( ( ) => this . logger . log ( 'debug' , 'Stopped composite service' ) )
108+ // Wait one micro tick for output to flush
109+ . then ( ( ) => process . exit ( exitCode ) )
110+ }
111+
116112 private async stopService ( service : Service ) {
117113 if ( this . config . gracefulShutdown ) {
118114 const dependents = this . services . filter ( ( { config } ) =>
@@ -124,11 +120,6 @@ export class CompositeService {
124120 }
125121}
126122
127- function rightPad ( string : string , length : number ) : string {
128- // we assume length >= string.length
129- return string + ' ' . repeat ( length - string . length )
130- }
131-
132123function never ( ) : Promise < never > {
133124 return new Promise < never > ( ( ) => { } )
134125}
0 commit comments