-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
32 changed files
with
765 additions
and
816 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
module.exports = { | ||
globals: { | ||
'ts-jest': { | ||
babelConfig: '.babelrc', | ||
"ts-jest": { | ||
babelConfig: ".babelrc", | ||
}, | ||
}, | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,22 +1,22 @@ | ||
module.exports = { | ||
debug: true, | ||
plugins: [ | ||
'@semantic-release/commit-analyzer', | ||
'@semantic-release/release-notes-generator', | ||
"@semantic-release/commit-analyzer", | ||
"@semantic-release/release-notes-generator", | ||
[ | ||
'@semantic-release/changelog', | ||
"@semantic-release/changelog", | ||
{ | ||
changelogFile: 'CHANGELOG.md', | ||
changelogTitle: '# Changelog', | ||
changelogFile: "CHANGELOG.md", | ||
changelogTitle: "# Changelog", | ||
}, | ||
], | ||
'@semantic-release/npm', | ||
"@semantic-release/npm", | ||
[ | ||
'@semantic-release/git', | ||
"@semantic-release/git", | ||
{ | ||
assets: ['package.json', 'CHANGELOG.md'], | ||
assets: ["package.json", "CHANGELOG.md"], | ||
}, | ||
], | ||
'@semantic-release/github', | ||
"@semantic-release/github", | ||
], | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,120 +1,112 @@ | ||
import { formatWithOptions } from 'util' | ||
import mergeStream from 'merge-stream' | ||
import { Service } from './Service' | ||
import { NormalizedCompositeServiceConfig } from './validateAndNormalizeConfig' | ||
import { Logger } from './Logger' | ||
import { mapStreamLines } from './util/stream' | ||
import { formatWithOptions } from "util"; | ||
import mergeStream from "merge-stream"; | ||
import { Service } from "./Service"; | ||
import { NormalizedCompositeServiceConfig } from "./validateAndNormalizeConfig"; | ||
import { Logger } from "./Logger"; | ||
import { mapStreamLines } from "./util/stream"; | ||
|
||
export class CompositeService { | ||
private config: NormalizedCompositeServiceConfig | ||
private services: Service[] | ||
private serviceMap: Map<string, Service> | ||
private stopping = false | ||
private logger: Logger | ||
private config: NormalizedCompositeServiceConfig; | ||
private services: Service[]; | ||
private serviceMap: Map<string, Service>; | ||
private stopping = false; | ||
private logger: Logger; | ||
|
||
constructor(config: NormalizedCompositeServiceConfig) { | ||
this.config = config | ||
this.config = config; | ||
|
||
if (this.config.windowsCtrlCShutdown) { | ||
require('generate-ctrl-c-event') // make sure this module loads before we even start | ||
require("generate-ctrl-c-event"); // make sure this module loads before we even start | ||
} | ||
|
||
const outputStream = mergeStream() | ||
outputStream.pipe(process.stdout) | ||
const outputStream = mergeStream(); | ||
outputStream.pipe(process.stdout); | ||
|
||
this.logger = new Logger(this.config.logLevel) | ||
outputStream.add(this.logger.output) | ||
this.logger = new Logger(this.config.logLevel); | ||
outputStream.add(this.logger.output); | ||
|
||
this.logger.log( | ||
'debug', | ||
formatWithOptions({ depth: null }, 'Config: %O', config), | ||
) | ||
this.logger.log("debug", formatWithOptions({ depth: null }, "Config: %O", config)); | ||
|
||
process.on('SIGINT', () => this.handleShutdownSignal(130, 'SIGINT')) | ||
process.on('SIGTERM', () => this.handleShutdownSignal(143, 'SIGTERM')) | ||
process.on("SIGINT", () => this.handleShutdownSignal(130, "SIGINT")); | ||
process.on("SIGTERM", () => this.handleShutdownSignal(143, "SIGTERM")); | ||
if (process.stdin.isTTY) { | ||
process.stdin.setRawMode(true) | ||
process.stdin.on('data', buffer => { | ||
if (buffer.toString('utf8') === '\u0003') { | ||
this.handleShutdownSignal(130, 'ctrl+c') | ||
process.stdin.setRawMode(true); | ||
process.stdin.on("data", buffer => { | ||
if (buffer.toString("utf8") === "\u0003") { | ||
this.handleShutdownSignal(130, "ctrl+c"); | ||
} | ||
}) | ||
}); | ||
} | ||
|
||
this.services = Object.entries(this.config.services).map( | ||
([id, config]) => | ||
new Service(id, config, this.logger, this.handleFatalError.bind(this)), | ||
) | ||
this.serviceMap = new Map( | ||
this.services.map(service => [service.id, service]), | ||
) | ||
([id, config]) => new Service(id, config, this.logger, this.handleFatalError.bind(this)), | ||
); | ||
this.serviceMap = new Map(this.services.map(service => [service.id, service])); | ||
|
||
outputStream.add( | ||
this.services.map(({ output, id }) => | ||
output.pipe(mapStreamLines(line => `${id} | ${line}\n`)), | ||
), | ||
) | ||
); | ||
|
||
this.logger.log('debug', 'Starting composite service...') | ||
Promise.all( | ||
this.services.map(service => this.startService(service)), | ||
).then(() => this.logger.log('debug', 'Started composite service')) | ||
this.logger.log("debug", "Starting composite service..."); | ||
Promise.all(this.services.map(service => this.startService(service))).then(() => | ||
this.logger.log("debug", "Started composite service"), | ||
); | ||
} | ||
|
||
private async startService(service: Service) { | ||
const dependencies = service.config.dependencies.map( | ||
id => this.serviceMap.get(id)!, | ||
) | ||
await Promise.all(dependencies.map(service => this.startService(service))) | ||
const dependencies = service.config.dependencies.map(id => this.serviceMap.get(id)!); | ||
await Promise.all(dependencies.map(service => this.startService(service))); | ||
if (this.stopping) { | ||
await never() | ||
await never(); | ||
} | ||
await service.start() | ||
await service.start(); | ||
if (this.stopping) { | ||
await never() | ||
await never(); | ||
} | ||
} | ||
|
||
private handleFatalError(message: string): void { | ||
this.logger.log('error', `Fatal error: ${message}`) | ||
this.logger.log("error", `Fatal error: ${message}`); | ||
if (!this.stopping) { | ||
this.stop(1) | ||
this.stop(1); | ||
} | ||
} | ||
|
||
private handleShutdownSignal(exitCode: number, description: string): void { | ||
if (!this.stopping) { | ||
this.logger.log('info', `Received shutdown signal (${description})`) | ||
this.stop(exitCode) | ||
this.logger.log("info", `Received shutdown signal (${description})`); | ||
this.stop(exitCode); | ||
} | ||
} | ||
|
||
private stop(exitCode: number): void { | ||
if (this.stopping) return | ||
this.stopping = true | ||
this.logger.log('debug', 'Stopping composite service...') | ||
if (this.stopping) return; | ||
this.stopping = true; | ||
this.logger.log("debug", "Stopping composite service..."); | ||
if (this.config.windowsCtrlCShutdown) { | ||
require('generate-ctrl-c-event') | ||
require("generate-ctrl-c-event") | ||
.generateCtrlCAsync() | ||
.catch((error: Error) => this.logger.log('error', String(error))) | ||
.catch((error: Error) => this.logger.log("error", String(error))); | ||
} | ||
Promise.all(this.services.map(service => this.stopService(service))) | ||
.then(() => this.logger.log('debug', 'Stopped composite service')) | ||
.then(() => this.logger.log("debug", "Stopped composite service")) | ||
// Wait one micro tick for output to flush | ||
.then(() => process.exit(exitCode)) | ||
.then(() => process.exit(exitCode)); | ||
} | ||
|
||
private async stopService(service: Service) { | ||
if (this.config.gracefulShutdown) { | ||
const dependents = this.services.filter(({ config }) => | ||
config.dependencies.includes(service.id), | ||
) | ||
await Promise.all(dependents.map(service => this.stopService(service))) | ||
); | ||
await Promise.all(dependents.map(service => this.stopService(service))); | ||
} | ||
await service.stop(this.config.windowsCtrlCShutdown) | ||
await service.stop(this.config.windowsCtrlCShutdown); | ||
} | ||
} | ||
|
||
function never(): Promise<never> { | ||
return new Promise<never>(() => {}) | ||
return new Promise<never>(() => {}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,10 @@ | ||
const genericMessage = | ||
'This is a bug in composite-service. Please file an issue in https://github.com/zenflow/composite-service/issues' | ||
"This is a bug in composite-service. Please file an issue in https://github.com/zenflow/composite-service/issues"; | ||
|
||
export class InternalError extends Error { | ||
constructor(message: string) { | ||
super(`${message}. ${genericMessage}`) | ||
Object.setPrototypeOf(this, InternalError.prototype) | ||
super(`${message}. ${genericMessage}`); | ||
Object.setPrototypeOf(this, InternalError.prototype); | ||
} | ||
} | ||
InternalError.prototype.name = InternalError.name | ||
InternalError.prototype.name = InternalError.name; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,25 +1,23 @@ | ||
import { PassThrough } from 'stream' | ||
import { PassThrough } from "stream"; | ||
|
||
export type LogLevel = 'debug' | 'info' | 'error' | ||
export type LogLevel = "debug" | "info" | "error"; | ||
|
||
const orderedLogLevels: LogLevel[] = ['error', 'info', 'debug'] | ||
const orderedLogLevels: LogLevel[] = ["error", "info", "debug"]; | ||
|
||
export class Logger { | ||
private level: LogLevel | ||
public readonly output = new PassThrough({ objectMode: true }) | ||
private level: LogLevel; | ||
public readonly output = new PassThrough({ objectMode: true }); | ||
constructor(level: LogLevel) { | ||
this.level = level | ||
this.level = level; | ||
} | ||
public log(level: LogLevel, text: string) { | ||
if (this.shouldLog(level)) { | ||
for (const line of text.split('\n')) { | ||
this.output.write(` (${level}) ${line}\n`) | ||
for (const line of text.split("\n")) { | ||
this.output.write(` (${level}) ${line}\n`); | ||
} | ||
} | ||
} | ||
private shouldLog(level: LogLevel) { | ||
return ( | ||
orderedLogLevels.indexOf(level) <= orderedLogLevels.indexOf(this.level) | ||
) | ||
return orderedLogLevels.indexOf(level) <= orderedLogLevels.indexOf(this.level); | ||
} | ||
} |
Oops, something went wrong.