Repository: vercel/next.js
Pull request: [release] notify release status via slack

This PR adds a step to send Slack notifications of the status of the release.

Changed files:
filename: .github/workflows/build_and_deploy.yml
patch: @@ -618,7 +618,15 @@ jobs:
         if: steps.changesets.outputs.pullRequestNumber
         run: 'gh pr edit ${{ steps.changesets.outputs.pullRequestNumber }} --add-label "created-by: CI"'
         env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }
+
+      - name: Send a Slack notification of the publish status
+        run: pnpm tsx scripts/release/slack.ts
+        env:
+          SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }}
+          RELEASE_STATUS: ${{ steps.changesets.outputs.published }}
+          WORKFLOW_LINK: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
+          WORKFLOW_ACTOR: ${{ github.actor }}
 
       - name: Upload npm log artifact
         if: steps.changesets.outputs.published == 'true'

filename: package.json
patch: @@ -118,6 +118,7 @@
     "@picocss/pico": "1.5.10",
     "@rspack/core": "1.3.9",
     "@rspack/plugin-react-refresh": "1.2.0",
+    "@slack/web-api": "7.9.1",
     "@swc/cli": "0.1.55",
     "@swc/core": "1.11.24",
     "@swc/helpers": "0.5.15",

filename: pnpm-lock.yaml
patch: @@ -136,6 +136,9 @@ importers:
       '@rspack/plugin-react-refresh':
         specifier: 1.2.0
         version: 1.2.0(react-refresh@0.12.0)(webpack-hot-middleware@2.26.1)
+      '@slack/web-api':
+        specifier: 7.9.1
+        version: 7.9.1
       '@swc/cli':
         specifier: 0.1.55
         version: 0.1.55(@swc/core@1.11.24(@swc/helpers@0.5.15))(chokidar@3.6.0)
@@ -4930,6 +4933,18 @@ packages:
     resolution: {integrity: sha512-OPwQlEdg40HAj5KNF8WW6q2KG4Z+cBCZb3m4ninfTZKaBmbIJodviQsDBoYMPHkOyJJMHnOJo5j2+LKDOhOACg==}
     deprecated: Use version 10.1.0. Version 10.2.0 has potential breaking issues
 
+  '@slack/logger@4.0.0':
+    resolution: {integrity: sha512-Wz7QYfPAlG/DR+DfABddUZeNgoeY7d1J39OCR2jR+v7VBsB8ezulDK5szTnDDPDwLH5IWhLvXIHlCFZV7MSKgA==}
+    engines: {node: '>= 18', npm: '>= 8.6.0'}
+
+  '@slack/types@2.14.0':
+    resolution: {integrity: sha512-n0EGm7ENQRxlXbgKSrQZL69grzg1gHLAVd+GlRVQJ1NSORo0FrApR7wql/gaKdu2n4TO83Sq/AmeUOqD60aXUA==}
+    engines: {node: '>= 12.13.0', npm: '>= 6.12.0'}
+
+  '@slack/web-api@7.9.1':
+    resolution: {integrity: sha512-qMcb1oWw3Y/KlUIVJhkI8+NcQXq1lNymwf+ewk93ggZsGd6iuz9ObQsOEbvlqlx1J+wd8DmIm3DORGKs0fcKdg==}
+    engines: {node: '>= 18', npm: '>= 8.6.0'}
+
   '@storybook/addon-a11y@8.6.0':
     resolution: {integrity: sha512-iIHO3GWt/PkO8BbYgzI/LYEK5JYvG31bCRka0XBk71PfZ5nrMGapVrUNH3PchO8G48HEb4ZpFiEOlV3WjCFYuw==}
     peerDependencies:
@@ -6530,6 +6545,9 @@ packages:
   axios@1.7.9:
     resolution: {integrity: sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==}
 
+  axios@1.9.0:
+    resolution: {integrity: sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==}
+
   axobject-query@4.1.0:
     resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==}
     engines: {node: '>= 0.4'}
@@ -10244,6 +10262,9 @@ packages:
     engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
     hasBin: true
 
+  is-electron@2.2.2:
+    resolution: {integrity: sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==}
+
   is-empty@1.2.0:
     resolution: {integrity: sha512-F2FnH/otLNJv0J6wc73A5Xo7oHLNnqplYqZhUu01tD54DIPvxIRSTSLkrUB/M0nHO4vo1O9PDfN4KoTxCzLh/w==}
 
@@ -12570,6 +12591,10 @@ packages:
     resolution: {integrity: sha512-2USApvnsutq8uoxZBGbbWM0JIYLiEMJ9RlaN7fAzVNb9OZN0SHjjTTfIcb667XynS5Y1VhwDJVDa72TnPzAYWw==}
     engines: {node: '>=8'}
 
+  p-retry@4.6.2:
+    resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==}
+    engines: {node: '>=8'}
+
   p-retry@6.2.1:
     resolution: {integrity: sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==}
     engines: {node: '>=16.17'}
@@ -13815,6 +13840,7 @@ packages:
     engines: {node: '>=0.6.0', teleport: '>=0.2.0'}
     deprecated: |-
       You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.
+
       (For a CapTP with native promises, see @endo/eventual-send and @endo/captp)
 
   qs@6.11.0:
@@ -20549,6 +20575,29 @@ snapshots:
     dependencies:
       '@sinonjs/commons': 3.0.0
 
+  '@slack/logger@4.0.0':
+    dependencies:
+      '@types/node': 20.17.6
+
+  '@slack/types@2.14.0': {}
+
+  '@slack/web-api@7.9.1':
+    dependencies:
+      '@slack/logger': 4.0.0
+      '@slack/types': 2.14.0
+      '@types/node': 20.17.6
+      '@types/retry': 0.12.0
+      axios: 1.9.0
+      eventemitter3: 5.0.1
+      form-data: 4.0.0
+      is-electron: 2.2.2
+      is-stream: 2.0.0
+      p-queue: 6.6.2
+      p-retry: 4.6.2
+      retry: 0.13.1
+    transitivePeerDependencies:
+      - debug
+
   '@storybook/addon-a11y@8.6.0(storybook@8.6.0(prettier@3.3.3))':
     dependencies:
       '@storybook/addon-highlight': 8.6.0(storybook@8.6.0(prettier@3.3.3))
@@ -22498,6 +22547,14 @@ snapshots:
     transitivePeerDependencies:
       - debug
 
+  axios@1.9.0:
+    dependencies:
+      follow-redirects: 1.15.9(debug@4.1.1)
+      form-data: 4.0.0
+      proxy-from-env: 1.1.0
+    transitivePeerDependencies:
+      - debug
+
   axobject-query@4.1.0: {}
 
   babel-jest@29.7.0(@babel/core@7.26.10):
@@ -27318,6 +27375,8 @@ snapshots:
 
   is-docker@3.0.0: {}
 
+  is-electron@2.2.2: {}
+
   is-empty@1.2.0: {}
 
   is-extendable@0.1.1: {}
@@ -30550,6 +30609,11 @@ snapshots:
 
   p-reduce@2.1.0: {}
 
+  p-retry@4.6.2:
+    dependencies:
+      '@types/retry': 0.12.0
+      retry: 0.13.1
+
   p-retry@6.2.1:
     dependencies:
       '@types/retry': 0.12.0

filename: scripts/release/slack.ts
patch: @@ -0,0 +1,40 @@
+import { WebClient } from '@slack/web-api'
+
+async function slack() {
+  if (!process.env.SLACK_TOKEN) {
+    throw new Error('SLACK_TOKEN not set')
+  }
+
+  const workflowLink = process.env.WORKFLOW_LINK
+  if (!workflowLink) {
+    throw new Error('WORKFLOW_LINK not set')
+  }
+
+  const channel = process.env.SLACK_CHANNEL_ID ?? 'C0668R2391V' // #next-releases
+  // TODO: Configure SLACK_ envs for dry run testing to not ping the oncall.
+  // TODO: It'll become `@oncall-nextjs`
+  const ping = process.env.SLACK_PING ?? '@nextjs-oncall'
+
+  const message =
+    process.env.SLACK_MESSAGE ?? process.env.RELEASE_STATUS === 'true'
+      ? `Successfully published a new release!\n<https://github.com/vercel/next.js/releases|Releases Link>`
+      : `Failed to publish a new release triggered by "${process.env.WORKFLOW_ACTOR}". ${ping}\n<${workflowLink}|Workflow Link>`
+
+  const slackClient = new WebClient(process.env.SLACK_TOKEN)
+  const res = await slackClient.chat.postMessage({
+    channel,
+    text: message,
+    username: 'Next.js Bot',
+    icon_emoji: ':nextjs:',
+  })
+
+  if (!res.ok) {
+    throw new Error(
+      `Failed to send message "${message}" to Slack channel #next-releases: ${res.error}`
+    )
+  }
+
+  console.log('Successfully sent message to Slack!')
+}
+
+slack()