UNPKG

@naturalcycles/nodejs-lib

Version:
154 lines (134 loc) 4.62 kB
import os from 'node:os' import { AsyncManager } from '@naturalcycles/js-lib' import type { CommonLogger } from '@naturalcycles/js-lib/log' import { setGlobalStringifyFunction } from '@naturalcycles/js-lib/string/stringify.js' import type { AnyObject } from '@naturalcycles/js-lib/types' import { dimGrey } from '../colors/colors.js' import { loadEnvFileIfExists } from '../node.util.js' import { inspectStringifyFn } from '../string/inspect.js' loadEnvFileIfExists() 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 /** * Defaults to true. * Set to false if you already have your handlers elsewhere and don't need them here. */ registerUncaughtExceptionHandlers?: boolean } 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 { checkAndlogEnvironment() setGlobalStringifyFunction(inspectStringifyFn) const { logger = console, noExit, registerUncaughtExceptionHandlers = true } = opt if (registerUncaughtExceptionHandlers || DEBUG_RUN_SCRIPT) { process.on('uncaughtException', err => { logger.error('runScript uncaughtException:', err) }) process.on('unhandledRejection', err => { logger.error('runScript 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`) // to ensure all async operations are completed (with a timeout) await AsyncManager.allDone(600_000) 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) } })() } function checkAndlogEnvironment(): void { const { platform, arch, versions: { node }, env: { CPU_LIMIT, NODE_OPTIONS, TZ }, } = process const cpuLimit = Number(CPU_LIMIT) || undefined const availableParallelism = os.availableParallelism?.() const cpus = os.cpus().length console.log( dimGrey( formatObject({ node: `${node} ${platform} ${arch}`, cpus, availableParallelism, cpuLimit, }) + '\n' + formatObject({ NODE_OPTIONS: NODE_OPTIONS || 'not defined', TZ: TZ || 'not defined', }), ), ) if (!NODE_OPTIONS) { console.warn( `NODE_OPTIONS env variable is not defined. You may run into out-of-memory issues when running memory-intensive scripts. It's recommended to set it to:\n--max-old-space-size=12000`, ) } else if (NODE_OPTIONS.includes('max_old')) { console.warn( `It looks like you're using "max_old_space_size" syntax with underscores instead of dashes - it's WRONG and doesn't work in environment variables. Strongly advised to rename it to "max-old-space-size"`, ) } // if (!TZ) { // console.error( // [ // '!!! TZ environment variable is required to be set, but was not set.', // 'The runScript will exit and not continue further because of that,', // 'please ensure the TZ variable and try again.', // 'If you are running locally, you can add TZ=UTC to the local .env file.', // ].join('\n'), // ) // process.exit(1) // } } function formatObject(obj: AnyObject): string { return Object.entries(obj) .map(([k, v]) => `${k}: ${v}`) .join(', ') }