diff --git a/bin/cli.ts b/bin/cli.ts index a47cad8..75a638b 100755 --- a/bin/cli.ts +++ b/bin/cli.ts @@ -7,15 +7,25 @@ const { fromName: serverFromName } = require('../lib/net/server') import Node from '../lib/node' import { Server as RPCServer } from 'jayson' import { Config } from '../lib/config' +import { Logger } from 'winston' const RPCManager = require('../lib/rpc') const level = require('level') const os = require('os') const path = require('path') const fs = require('fs-extra') +const yargs = require('yargs') + +type UserConfig = { + logger?: Logger +} const networks = Object.entries(chains.names) -const args = require('yargs') +const args = yargs .options({ + config: { + describe: 'Path to ethereumjs.config.js', + default: undefined, + }, network: { describe: `Network`, choices: networks.map((n) => n[1]), @@ -80,7 +90,16 @@ const args = require('yargs') }, }) .locale('en_EN').argv -const logger = getLogger({ loglevel: args.loglevel }) + +const config: UserConfig = {} + +if (args.config) { + const userConfig: UserConfig = require(path.resolve(process.cwd(), args.config)) + + Object.assign(config, userConfig) +} + +const logger = config.logger ?? getLogger({ loglevel: args.loglevel }) async function runNode(options: any) { logger.info('Initializing Ethereumjs client...') @@ -153,16 +172,30 @@ async function run() { minPeers: args.minPeers, maxPeers: args.maxPeers, } - const node = await runNode(options) - const server = args.rpc ? runRpcServer(node, options) : null - process.on('SIGINT', async () => { + let node: Node | null + let server: RPCServer | null + + process.once('SIGINT', async () => { + process.once('SIGINT', () => { + logger.info('Force shutdown. Exit immediately.') + process.exit(1) + }) logger.info('Caught interrupt signal. Shutting down...') - if (server) server.http().close() - await node.stop() + if (server) { + server.http().close() + server = null + } + if (node) { + await node.stop() + node = null + } logger.info('Exiting.') - process.exit() + process.exit(0) }) + + node = await runNode(options) + server = args.rpc ? runRpcServer(node, options) : null } run().catch((err) => logger.error(err)) diff --git a/test/cli/cli.spec.ts b/test/cli/cli.spec.ts new file mode 100644 index 0000000..5fd330a --- /dev/null +++ b/test/cli/cli.spec.ts @@ -0,0 +1,62 @@ +import path from 'path' +import { spawn } from 'child_process' + +import tape from 'tape' + +tape('[CLI]', (t) => { + t.test('should handle SIGINT', { timeout: 160000 }, (t) => { + t.plan(1) + const file = require.resolve('../../dist/bin/cli.js') + const child = spawn( + process.execPath, + [file, '--config', path.join(__dirname, '/fixtures/ethereumjs.config.js')], + { stdio: ['pipe', 'pipe', 'pipe', 'ipc'] } + ) + + const timeout = setTimeout(() => { + child.kill('SIGINT') + }, 120000) + + const end = () => { + clearTimeout(timeout) + child.kill('SIGINT') + t.end() + } + + function onSigintSent([level, message]: [string, string]) { + if (level === 'info') { + if (message === 'Exiting.') { + child.removeListener('message', onSigintSent) + t.pass('Client exited') + clearTimeout(timeout) + } + } + } + + function onServiceStarted([level, message]: [string, string]) { + if (level === 'info') { + if (message === 'Started eth service.') { + child.removeListener('message', onServiceStarted) + child.on('message', onSigintSent) + child.kill('SIGINT') + } + } + } + + child.on('message', onServiceStarted) + child.on('message', ([level, message]: [string, string]) => { + process.stdout.write(`${level}: ${message}\n`) + }) + + child.on('error', (error) => { + t.fail(`Error: ${error}`) + }) + + child.on('close', (code, signal) => { + if (code !== 0) { + t.fail(`child process exited with code ${code}, signal ${signal}`) + end() + } + }) + }) +}) diff --git a/test/cli/fixtures/ethereumjs.config.js b/test/cli/fixtures/ethereumjs.config.js new file mode 100644 index 0000000..8c39b6a --- /dev/null +++ b/test/cli/fixtures/ethereumjs.config.js @@ -0,0 +1,12 @@ +function send(level, message) { + process.send([level, message]) +} + +module.exports = { + logger: { + warn: send.bind(null, 'warn'), + info: send.bind(null, 'info'), + debug: send.bind(null, 'debug'), + error: send.bind(null, 'error'), + }, +}