@naturalcycles/nodejs-lib
Version:
Standard library for Node.js
135 lines (113 loc) • 3.53 kB
text/typescript
import { _substringAfterLast } from '@naturalcycles/js-lib'
import { inspectAny } from '..'
import { SlackSharedService } from '..'
import { dimGrey, yellow } from '../colors'
export interface RunScriptOptions {
/**
* Set it to a channel name (e.g 'backend', without #), so it will report to slack on runScript failure.
* Requires env.SLACK_WEBHOOK_URL to be set.
* Overrides env.SLACK_ON_FAILURE
*/
slackOnFailure?: string | false
/**
* Set it to a channel name (e.g 'backend') to report to slack on runScript success.
* It will include the value returned to runScript.
*/
slackOnSuccess?: string | false
}
const { SLACK_WEBHOOK_URL } = 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) })`
* - Supports automatic failure reporting to Slack (!)
*/
export function runScriptWithSlack(fn: (...args: any[]) => any, opt: RunScriptOptions = {}): void {
process.on('uncaughtException', err => {
console.error('uncaughtException', err)
})
process.on('unhandledRejection', err => {
console.error('unhandledRejection', err)
})
void (async () => {
try {
const res = await fn()
if (opt.slackOnSuccess) {
await onSuccess(res, opt)
}
setImmediate(() => process.exit(0))
} catch (err) {
console.error('runScript failed:', err)
onFailure(err, opt)
process.exitCode = 1
}
})()
}
async function onSuccess(res: any, opt: RunScriptOptions): Promise<void> {
const { slackOnSuccess: channel } = opt
if (!channel) return
if (!SLACK_WEBHOOK_URL) {
return console.warn(yellow(`env.SLACK_WEBHOOK_URL is missing, unable to slack on success`))
}
let text: string
if (res) {
text = inspectAny(res, {
colors: false,
})
// Wrap in markdown-text-block if it's anything but plain String
if (typeof res !== 'string') {
text = '```' + text + '```'
}
} else {
text = '```empty response```'
}
text = `\`${_substringAfterLast(__filename, '/')}\` completed successfully\n\n${text}`
await new SlackSharedService({
webhookUrl: SLACK_WEBHOOK_URL,
})
.sendMsg({
text,
channel,
noLog: true, // cause we logged the error already
throwOnError: true,
})
.then(() => {
console.log(dimGrey(`slacked the result to channel: ${channel}`))
})
.catch(_err => {
console.log(yellow(`failed to slack the result to channel: ${channel}`))
console.error(_err)
})
}
function onFailure(err: Error, opt: RunScriptOptions): void {
const channel = process.env.SLACK_ON_FAILURE || opt.slackOnFailure
if (!channel) return
if (!SLACK_WEBHOOK_URL) {
return console.warn(yellow(`env.SLACK_WEBHOOK_URL is missing, unable to slack on failure`))
}
void new SlackSharedService({
webhookUrl: SLACK_WEBHOOK_URL,
})
.sendMsg({
text: err,
channel,
noLog: true, // cause we logged the error already
throwOnError: true,
})
.then(() => {
console.log(dimGrey(`slacked the error to channel: ${channel}`))
})
.catch(_err => {
console.log(yellow(`failed to slack the error to channel: ${channel}`))
console.error(_err)
})
}