Files
supabase/scripts/actions/send-slack-pr-notification.ts
Ivan Vasilov 03660838ef chore: Refactor the script for posting PRs for review on Slack (#43022)
This pull request refactors the GitHub Actions workflow for notifying
about stale Dashboard PRs by replacing custom JavaScript scripts and the
`actions/github-script` action with new TypeScript scripts that
communicate via standard input/output. This simplifies the workflow,
improves maintainability, and adds better error handling, especially for
API rate limits. The Slack notification script is also rewritten in
TypeScript and now reads PR data from stdin, making the workflow steps
more composable.
2026-02-20 15:32:12 +01:00

141 lines
3.7 KiB
TypeScript

const SLACK_WEBHOOK_URL = process.env.SLACK_WEBHOOK_URL
if (!SLACK_WEBHOOK_URL) {
console.error('SLACK_WEBHOOK_URL environment variable is required')
process.exit(1)
}
interface StalePR {
number: number
title: string
url: string
author: string
createdAt: string
hoursOld: number
daysOld: number
fileCount: number
reviewStatus: string
reviewEmoji: string
mergeableStatus: string
mergeableEmoji: string
}
function escapeSlack(text: string) {
return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
}
async function sendSlackNotification(stalePRs: StalePR[]) {
const count = stalePRs.length
const prBlocks = stalePRs.map((pr) => {
const remainingHours = pr.hoursOld % 24
const ageText = pr.daysOld > 0 ? `${pr.daysOld}d ${remainingHours}h` : `${pr.hoursOld}h`
const maxTitleLength = 200
const safeTitle =
pr.title.length > maxTitleLength
? escapeSlack(pr.title.substring(0, maxTitleLength) + '...')
: escapeSlack(pr.title)
const reviewStatusText =
pr.reviewStatus === 'approved'
? 'Approved'
: pr.reviewStatus === 'changes-requested'
? 'Changes Requested'
: pr.reviewStatus === 'commented'
? 'Commented'
: 'Needs Review'
const mergeableStatusText =
pr.mergeableStatus === 'ready'
? 'Ready to Merge'
: pr.mergeableStatus === 'conflicts'
? 'Has Conflicts'
: pr.mergeableStatus === 'blocked'
? 'Blocked'
: pr.mergeableStatus === 'unstable'
? 'Unstable'
: pr.mergeableStatus === 'behind'
? 'Behind Base'
: pr.mergeableStatus === 'draft'
? 'Draft'
: 'Unknown'
return {
type: 'section',
text: {
type: 'mrkdwn',
text: `*<${pr.url}|#${pr.number}: ${safeTitle}>*\n:bust_in_silhouette: @${pr.author} • :clock3: ${ageText} old • :file_folder: ${pr.fileCount} Dashboard files\n${pr.reviewEmoji} ${reviewStatusText}${pr.mergeableEmoji} ${mergeableStatusText}`,
},
}
})
const MAX_PRS_TO_SHOW = 47
const prBlocksToShow = prBlocks.slice(0, MAX_PRS_TO_SHOW)
const hasMorePRs = prBlocks.length > MAX_PRS_TO_SHOW
const slackMessage = {
text: 'Dashboard PRs needing attention',
blocks: [
{
type: 'header',
text: {
type: 'plain_text',
text: 'Dashboard PRs Older Than 24 Hours',
},
},
{
type: 'section',
text: {
type: 'mrkdwn',
text: `There are *${count}* open PRs affecting /apps/studio/ that are older than 24 hours:${hasMorePRs ? ` (showing first ${MAX_PRS_TO_SHOW})` : ''}`,
},
},
{
type: 'divider',
},
...prBlocksToShow,
],
}
const response = await fetch(SLACK_WEBHOOK_URL!, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(slackMessage),
})
if (!response.ok) {
const errorText = await response.text()
throw new Error(
`Slack notification failed: ${response.status} ${response.statusText}\n${errorText}`
)
}
console.error('Slack notification sent successfully!')
}
// Read JSON from stdin
async function readStdin(): Promise<string> {
const chunks: Buffer[] = []
for await (const chunk of process.stdin) {
chunks.push(chunk)
}
return Buffer.concat(chunks).toString('utf-8')
}
readStdin()
.then(async (input) => {
const stalePRs: StalePR[] = JSON.parse(input)
if (stalePRs.length === 0) {
console.error('No stale PRs to notify about')
return
}
await sendSlackNotification(stalePRs)
})
.catch((error) => {
console.error('Error:', error.message)
process.exit(1)
})