@aimee-blue/ab-service-kit
Version:
Aimee Blue Service Template
179 lines (157 loc) • 4.7 kB
text/typescript
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<ICommandLineArgs>)
.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();
}
})
);
};
}