NaturalCycles/nodejs-lib

View on GitHub
src/script/runScript.ts

Summary

Maintainability
A
0 mins
Test Coverage
F
16%
import { setGlobalStringifyFunction } from '@naturalcycles/js-lib'
import type { CommonLogger } from '@naturalcycles/js-lib'
import { inspectStringifyFn } from '../string/inspect'

export interface RunScriptOptions {
  /**
   * @default false
   * Set to true to NOT call process.exit(0) after function is completed.
   * Currently it exists because of `jest --maxWorkers=1` behavior. To be investigated more..
   */
  noExit?: boolean

  /**
   * Default to `console`
   */
  logger?: CommonLogger
}

const { DEBUG_RUN_SCRIPT } = process.env

/**
 * Use it in your top-level scripts like this:
 *
 * runScript(async () => {
 *   await lalala()
 *   // my script goes on....
 * })
 *
 * Advantages:
 * - Works kind of like top-level await
 * - No need to add `void`
 * - No need to add `.then(() => process.exit()` (e.g to close DB connections)
 * - No need to add `.catch(err => { console.error(err); process.exit(1) })`
 *
 * This function is kept light, dependency-free, exported separately.
 *
 * Set env DEBUG_RUN_SCRIPT for extra debugging.
 */
export function runScript(fn: (...args: any[]) => any, opt: RunScriptOptions = {}): void {
  setGlobalStringifyFunction(inspectStringifyFn)

  const { logger = console, noExit } = opt

  process.on('uncaughtException', err => {
    logger.error('uncaughtException:', err)
  })
  process.on('unhandledRejection', err => {
    logger.error('unhandledRejection:', err)
  })

  if (DEBUG_RUN_SCRIPT) {
    process.on('exit', code => logger.log(`process.exit event, code=${code}`))
    process.on('beforeExit', code => logger.log(`process.beforeExit event, code=${code}`))
  }

  // fake timeout, to ensure node.js process won't exit until runScript main promise is resolved
  const timeout = setTimeout(() => {}, 10000000)

  void (async () => {
    try {
      await fn()

      if (DEBUG_RUN_SCRIPT) logger.log(`runScript promise resolved`)

      if (!noExit) {
        setImmediate(() => process.exit(0))
      }
    } catch (err) {
      logger.error('runScript error:', err)
      process.exitCode = 1
      if (!noExit) {
        setImmediate(() => process.exit(1))
      }
    } finally {
      clearTimeout(timeout)
    }
  })()
}