'use strict'; /** * opsNotify (Session 56) — pipeline alerting via ntfy.sh. * * Push a one-line operational alert (snapshot success / failure / stale / missed * cron) to an ntfy topic so a silent pipeline never goes unnoticed. Fire-and- * forget: NEVER throws, NEVER blocks the pipeline on a notify failure. * * Config: * NTFY_URL (default https://ntfy.sh) * NTFY_TOPIC (default vyndr-pipeline-kev2026) * PIPELINE_ALERTS=0 → disable entirely * Disabled automatically under NODE_ENV==='test' unless a fetchImpl is injected * (so the unit tests can assert the call without hitting the network). */ const NTFY_URL = () => process.env.NTFY_URL || 'https://ntfy.sh'; const NTFY_TOPIC = () => process.env.NTFY_TOPIC || 'vyndr-pipeline-kev2026'; function enabled(opts = {}) { if (opts.fetchImpl) return true; // tests inject → always "enabled" if (process.env.PIPELINE_ALERTS === '0') return false; if (process.env.NODE_ENV === 'test') return false; return true; } /** * Send an ops alert. `opts`: { title, priority ('min'|'low'|'default'|'high'| * 'urgent'), tags (string[]), fetchImpl }. Resolves { sent: boolean } — never rejects. */ async function notify(message, opts = {}) { if (!enabled(opts)) return { sent: false, reason: 'disabled' }; const doFetch = opts.fetchImpl || fetch; const headers = {}; if (opts.title) headers.Title = opts.title; if (opts.priority) headers.Priority = opts.priority; if (Array.isArray(opts.tags) && opts.tags.length) headers.Tags = opts.tags.join(','); try { await doFetch(`${NTFY_URL()}/${NTFY_TOPIC()}`, { method: 'POST', headers, body: String(message == null ? '' : message), signal: typeof AbortSignal !== 'undefined' && AbortSignal.timeout ? AbortSignal.timeout(6000) : undefined, }); return { sent: true }; } catch (err) { // Alerting must never break the pipeline. if (process.env.NODE_ENV !== 'test') console.warn('[opsNotify] failed:', err.message); return { sent: false, reason: err.message }; } } module.exports = { notify, __internals: { enabled, NTFY_URL, NTFY_TOPIC } };