src/script/runScript.ts
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)
}
})()
}