/** * Posts the contents of a file to Slack as a single message. * * This exists so the workflow does not have to assemble JSON in shell. Doing * that needs jq, which the secex image is not known to ship, and it puts the * report -- backticks, newlines, whatever a theme slug happens to contain -- * through the shell on its way into a request body. Node is already installed * for the check itself and its JSON handling is not quoting-sensitive. * * No npm dependencies, for the same reason as check-dotorg-sync.mjs: the * workflow runs without `npm ci`. * * Usage: SLACK_TOKEN=… SLACK_CHANNEL=… node slack-post.mjs * * Exits 0 when posted, and also when there is nothing to post or no * credentials to post it with -- neither is a failure of the caller. Exits 1 * when Slack refused the message. */ import fs from 'fs'; const REQUEST_TIMEOUT_MS = 15_000; const messagePath = process.argv[ 2 ]; const token = process.env.SLACK_TOKEN; const channel = process.env.SLACK_CHANNEL; if ( ! messagePath ) { console.error( 'Usage: node slack-post.mjs ' ); process.exit( 1 ); } if ( ! token || ! channel ) { console.warn( '::warning::SLACK_TOKEN or SLACK_CHANNEL is not set; skipping the notification.' ); process.exit( 0 ); } const text = fs.readFileSync( messagePath, 'utf8' ).trim(); if ( ! text ) { console.warn( `::warning::${ messagePath } is empty; nothing to post.` ); process.exit( 0 ); } const response = await fetch( 'https://slack.com/api/chat.postMessage', { method: 'POST', headers: { Authorization: `Bearer ${ token }`, 'Content-Type': 'application/json; charset=utf-8', }, body: JSON.stringify( { channel, text, unfurl_links: false, unfurl_media: false, } ), signal: AbortSignal.timeout( REQUEST_TIMEOUT_MS ), } ); // chat.postMessage answers 200 with {"ok": false} on a bad token or a channel // the bot is not in, so the HTTP status proves nothing on its own. const body = await response.json().catch( () => null ); if ( ! body?.ok ) { const reason = body?.error ?? `HTTP ${ response.status }`; console.error( `::error::Slack rejected the message: ${ reason }` ); process.exit( 1 ); } console.log( `Posted the sync report to ${ channel }.` );