UNPKG

nginx-testing

Version:

Support for integration/acceptance testing of nginx configuration.

184 lines (147 loc) 5.44 kB
import * as process from 'node:process' import watch from 'node-watch' import * as parseArgs from 'minimist' import { arrify } from './internal/utils' import { log } from './logger' import { configPatch, startNginx, NginxOptions } from './nginxRunner' // XXX: This is a hack to import package.json without ending up with lib/src/. type PackageJson = { version: string, bugs: string } const { version: pkgVersion, bugs: bugsUrl } = require('../package.json') as PackageJson // TODO: Allow to set log level. const progName = 'start-nginx' // NOTE: Keep in sync with CLI section in README.adoc (until I write a script to generate it). const helpMsg = `\ Usage: ${progName} [options] <conf-file> ${progName} -h | --help Start nginx server with the given config and reload it on changes. This program is part of nginx-testing ${pkgVersion}. Arguments: <conf-file> Path of the nginx configuration file. Options: -b --bin-path <file> Name or path of the nginx binary to start. Defaults to 'nginx'. This option is ignored if --version is specified. -v --version <semver> A SemVer version range specifying the nginx version to download from nginx-binaries a and run. -A --bind-address <host> Hostname or IP address to bind the port(s) on. Defaults to 127.0.0.1. -p --port <port> Port number(s) for substituting __PORT__, __PORT_1__, ..., __PORT_9__ placeholders in the nginx config. Repeat this option for more ports. Defaults to random port numbers. -d --work-dir <dir> Path of a directory that will be passed as a prefix into nginx. If not provided, a temporary directory will be automatically created. -T --start-timeout <msec> Number of milliseconds after the start to wait for the nginx to respond to the health-check request. Defaults to 1,000 ms. -w --watch <path> Watch file or directory (recursively) and reload nginx on changes. <conf-file> is watched implicitly. Repeat this option for more paths. -D --watch-delay <msec> Delay time between reloads in milliseconds. Defaults to 200 ms. -h --help Show this message and exit. Please report bugs at <${bugsUrl}>. ` const string = (value?: any) => Array.isArray(value) ? String(value[value.length - 1]) : value != null ? String(value) : undefined const number = (value?: any) => value ? parseInt(string(value)!) : undefined type Options = Omit<NginxOptions, 'config' | 'errorLog' | 'accessLog'> & { configPath: string, watchPaths: string[], watchDelay?: number, } function parseCliArgs (argv: string[]): Options { const booleanOpts = { 'help': 'h', } as const const stringOpts = { 'bind-address': 'A', 'bin-path': 'b', 'port': 'p', 'start-timeout': 'T', 'version': 'v', 'watch': 'w', 'watch-delay': 'D', 'work-dir': 'd', } as const type ParsedArgs = Pick<parseArgs.ParsedArgs, '_' | '--'> & Partial< & Record<keyof typeof booleanOpts, boolean> & Record<keyof typeof stringOpts, string | string[]> > const args: ParsedArgs = parseArgs(argv, { boolean: Object.keys(booleanOpts), string: Object.keys(stringOpts), alias: { ...booleanOpts, ...stringOpts }, stopEarly: true, unknown: (arg: string) => { if (arg.startsWith('-')) { console.error(`Unknown option: ${arg}`) return false } else { return true } }, }) if (args.help) { console.log(helpMsg) return process.exit(0) } if (args._.length !== 1) { console.error('Invalid number of arguments\n') console.error(helpMsg) return process.exit(2) } return { bindAddress: string(args['bind-address']), binPath: string(args['bin-path']), configPath: args._[0]!, ports: arrify(args['port']).map(n => parseInt(n)), startTimeoutMsec: number(args['start-timeout']), version: string(args['version']), watchPaths: arrify(args['watch']), watchDelay: number(args['watch-delay']), workDir: string(args['work-dir']), } } async function run (opts: Options): Promise<void> { // Reload with SIGHUP doesn't work without master process. configPatch.splice(configPatch.findIndex(p => p.path === '/master_process'), 1) const nginx = await startNginx({ ...opts, accessLog: process.stdout, errorLog: 'inherit', }) log.info('Nginx has been started, press Ctrl+C to terminate it') const watcher = watch( [...opts.watchPaths, opts.configPath], { delay: opts.watchDelay || 200 }, async () => nginx.reload({ configPath: opts.configPath }), ) let stopping = false const handleSignal = async () => { log.info('Terminating...') stopping = true watcher.close() return await nginx.stop() } process.on('SIGINT', handleSignal) process.on('SIGTERM', handleSignal) const loop = () => { try { process.kill(nginx.pid, 0) // check if running setTimeout(loop, 100) } catch { if (!stopping) { log.error('Nginx process has died') watcher.close() return nginx.stop() } } return } loop() } const opts = parseCliArgs(process.argv.slice(2)) run(opts).catch(err => { log.error(err.message) log.debug(err.stack) process.exit(1) })