UNPKG

@naturalcycles/nodejs-lib

Version:
107 lines (106 loc) 4.13 kB
import os from 'node:os'; import { AsyncManager } from '@naturalcycles/js-lib'; import { setGlobalStringifyFunction } from '@naturalcycles/js-lib/string/stringify.js'; import { dimGrey } from '../colors/colors.js'; import { loadEnvFileIfExists } from '../node.util.js'; import { inspectStringifyFn } from '../string/inspect.js'; loadEnvFileIfExists(); 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, opt = {}) { 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() { 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) { return Object.entries(obj) .map(([k, v]) => `${k}: ${v}`) .join(', '); }