import 'source-map-support/register'; import * as http from 'http'; import * as https from 'https'; import fs from 'fs-extra'; import yargs from 'yargs'; import { resolve } from 'path'; import { loadEnv } from './shared/env'; import { serviceSetup } from './setup'; import { ICommandLineArgs, IServiceConfig, BasicLogger } from './shared'; import { initializeLoggerOrFallback } from './setup/initializeLogger'; const buildArgumentsParser = (config: IServiceConfig) => (yargs as yargs.Argv) .option('http', { boolean: true, description: 'Disable encryption', default: false, }) .option('watch', { boolean: true, description: 'Watch code for changes', default: false, }) .option('port', { number: true, description: 'port to listen on', default: config.defaultPort, }) .option('host', { string: true, description: 'address to listen on', }) .option('cert', { string: true, description: 'Path to an HTTPS certificate (must be full chain certificate)', }) .option('key', { string: true, description: 'Path to an HTTPS private-key', }); export async function startCore( config: IServiceConfig, args?: ICommandLineArgs, optLogger?: BasicLogger ) { const parser = buildArgumentsParser(config); const effectiveParser = config.argsBuilder ? config.argsBuilder(parser) : parser; const params = args || effectiveParser.parse(); const logger = optLogger ?? (await initializeLoggerOrFallback(config)); const shouldLoadEnvFiles = config.shouldLoadEnvFiles ?? true; if (shouldLoadEnvFiles) { await loadEnv({ envFile: params.envFile, logger, }); } const certPath = params.cert || process.env.HTTPS_CERT_PATH; const keyPath = params.cert || process.env.HTTPS_KEY_PATH; const host = params.host || process.env.SERVER_HOSTNAME; const port = process.env.PORT || params.port; if (!params.http) { if (!certPath) { throw new Error( 'No certificate path provided. Please generate HTTPS certificate and key, or use --http option' ); } if (!keyPath) { throw new Error( 'No private key path provided. Please generate HTTPS certificate and key, or use --http option' ); } const certExist = await fs.pathExists(certPath); if (!certExist) { throw new Error(`Cannot find file at path '${certPath}'`); } const keyExist = await fs.pathExists(keyPath); if (!keyExist) { throw new Error(`Cannot find file at path '${keyPath}'`); } } const server = params.http ? http.createServer() : https.createServer({ cert: certPath && fs.readFileSync(certPath, 'utf8'), key: keyPath && fs.readFileSync(keyPath, 'utf8'), passphrase: process.env.HTTPS_PASSPHRASE, }); const handleServerRequestsWithDevTools = async () => { if (params.watch) { const { serviceSetupInWatchMode } = await import( './setup/watchServerCode' ); const configFile = config.serviceConfigModuleId || './lib/config.js'; const configFilePath = resolve(configFile); const exists = await fs.pathExists(configFilePath); if (!exists) { throw new Error( `Cannot resolve service configuration module (${configFilePath}), setup is required for watch mode to work` ); } return await serviceSetupInWatchMode( configFilePath, async (newConfig) => { return await serviceSetup(server, newConfig, params, logger); } ); } else { return await serviceSetup(server, config, params, logger); } }; const teardown = await handleServerRequestsWithDevTools(); await new Promise((res, rej) => { let handled = false; server.on('error', (err) => { if (handled) { return; } handled = true; rej(err); }); server.listen( { port, host, }, () => { if (handled) { return; } handled = true; const mode = params.http ? '(un-encrypted http/ws)' : "(https/wss) - Pass '--http' argument to disable encryption"; logger.log( `👍 PID ${process.pid}; Currently listening on ${[host, port] .filter(Boolean) .join(':')} ${mode}` ); res(); } ); }); return async () => { await teardown('destroy'); await new Promise((res, rej) => server.close((err) => { if (err) { rej(err); } else { res(); } }) ); }; }