[READ-ONLY] Mirror of https://github.com/bombshell-dev/automation. GitHub Actions for the Bombshell organization
ci
0

Configure Feed

Select the types of activity you want to include in your feed.

initial commit

Nate Moore (Nov 1, 2024, 8:28 PM -0500) 17f1808b

+362
+21
LICENSE
··· 1 + MIT License 2 + 3 + Copyright (c) 2023 Bombshell 4 + 5 + Permission is hereby granted, free of charge, to any person obtaining a copy 6 + of this software and associated documentation files (the "Software"), to deal 7 + in the Software without restriction, including without limitation the rights 8 + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 + copies of the Software, and to permit persons to whom the Software is 10 + furnished to do so, subject to the following conditions: 11 + 12 + The above copyright notice and this permission notice shall be included in all 13 + copies or substantial portions of the Software. 14 + 15 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 + SOFTWARE.
+30
README.md
··· 1 + # Automation 2 + 3 + Shared GitHub Actions for the `bombshell-dev` organization. 4 + 5 + ## mergebot 6 + 7 + This workflow posts a celebratory message in a Discord channel of your choice for each commit. For example: 8 + 9 + > 🥳 Merged! user-a: commit (#001) 10 + > With essential contributions from user-b and user-c! 💣 11 + 12 + ### Usage 13 + ```yml 14 + name: mergebot 15 + 16 + on: 17 + push: 18 + branches: [main] 19 + 20 + jobs: 21 + mergebot: 22 + if: ${{ github.repository_owner == 'bombshell-dev' }} 23 + uses: bombshell-dev/automation/mergebot.yml@main 24 + secrets: 25 + DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_MERGEBOT }} 26 + ``` 27 + 28 + ## Acknowledgements 29 + 30 + This repository borrows heavily from [`withastro/automation`](https://github.com/withastro/automation), published under the MIT License—Copyright (c) 2023 Astro.
+44
format.yml
··· 1 + name: Format 2 + 3 + on: 4 + workflow_call: 5 + inputs: 6 + command: 7 + description: 'The package.json script name which will be called via "pnpm run <command>"' 8 + default: "format" 9 + required: false 10 + type: string 11 + 12 + jobs: 13 + format: 14 + if: github.repository_owner == 'bombshell-dev' 15 + runs-on: ubuntu-latest 16 + steps: 17 + - uses: actions/checkout@v4 18 + with: 19 + ref: ${{ github.head_ref }} 20 + # Needs access to push to main 21 + token: ${{ secrets.BOT_ACCESS_TOKEN }} 22 + 23 + - name: Setup PNPM 24 + uses: pnpm/action-setup@v4 25 + 26 + - name: Setup Node 27 + uses: actions/setup-node@v4 28 + with: 29 + node-version: 22 30 + cache: "pnpm" 31 + 32 + - name: Install dependencies 33 + run: pnpm install 34 + 35 + - name: Format code 36 + run: pnpm run ${{ inputs.command }} 37 + 38 + - name: Commit changes 39 + uses: stefanzweifel/git-auto-commit-action@v5 40 + with: 41 + commit_message: "[ci] format" 42 + branch: ${{ github.head_ref }} 43 + commit_user_name: bombshell-bot 44 + commit_author: ${{ github.event.commits[0].author.name }} <${{ github.actor }}@users.noreply.github.com>
+74
mergebot.yml
··· 1 + name: mergebot 2 + 3 + on: 4 + workflow_call: 5 + secrets: 6 + DISCORD_WEBHOOK: 7 + description: > 8 + URL of a Discord webhook. To create one: 9 + 1. Find the channel you want to post a message in. 10 + 2. Right-click and select “Edit Channel”. 11 + 3. Navigate to “Integrations” > “View Webhooks”. 12 + 4. Click “New Webhook” and copy the URL for your newly created webhook. 13 + required: true 14 + inputs: 15 + EMOJIS: 16 + description: > 17 + Comma-delimited set of emojis. 18 + Each mergebot message will pick one at random for the start of the message. 19 + default: 🎉,🥳,💣,💥,🫡,🙌,👏,🙏,🏆 20 + type: string 21 + required: false 22 + COAUTHOR_TEMPLATES: 23 + description: > 24 + A JSON array of co-author recognition templates. 25 + Each template should contain the `<names>` placeholder. 26 + This will be replaced by the names of one or more co-authors for this commit. 27 + (Ignored for commits without any co-authors.) 28 + 29 + When designing templates, bear in mind that `<names>` could be one, two, or more names. 30 + default: > 31 + [ 32 + "Thanks for the assist, <names>! 🙏", 33 + "<names> coming in clutch—thank you! 🙌", 34 + "<names> made it happen! 💪", 35 + "We couldn't have done it without <names>! 👏", 36 + "With essential contributions from <names>! 💣", 37 + "Teamwork makes the dream work… right <names>?! 🏆", 38 + "feat. <names>! 🫡" 39 + ] 40 + type: string 41 + required: false 42 + 43 + jobs: 44 + post-message: 45 + runs-on: ubuntu-latest 46 + steps: 47 + - name: Checkout 48 + uses: actions/checkout@v4 49 + with: 50 + repository: "bombshell-dev/automation" 51 + ref: "main" 52 + path: "automation" 53 + 54 + - name: Setup Node 55 + uses: actions/setup-node@v4 56 + with: 57 + node-version: 22 58 + 59 + - id: message 60 + name: Format Discord message 61 + env: 62 + GITHUB_REPO: ${{ github.event.repository.full_name }} 63 + COMMIT_AUTHOR: ${{ github.event.commits[0].author.name }} 64 + COMMIT_MESSAGE: ${{ github.event.commits[0].message }} 65 + COMMIT_ID: ${{ github.event.commits[0].id }} 66 + EMOJIS: ${{ inputs.EMOJIS }} 67 + COAUTHOR_TEMPLATES: ${{ inputs.COAUTHOR_TEMPLATES }} 68 + run: node automation/scripts/mergebot.js 69 + 70 + - name: Send message on Discord 71 + env: 72 + DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} 73 + DISCORD_MESSAGE: "${{ steps.message.outputs.DISCORD_MESSAGE }}" 74 + run: node automation/scripts/discord.js
+3
package.json
··· 1 + { 2 + "type": "module" 3 + }
+35
scripts/discord.js
··· 1 + const { 2 + GITHUB_ACTION, 3 + GITHUB_EVENT_NAME, 4 + GITHUB_EVENT_PATH, 5 + DISCORD_WEBHOOK, 6 + DISCORD_MESSAGE, 7 + DISCORD_USERNAME, 8 + DISCORD_AVATAR, 9 + } = process.env; 10 + if (!GITHUB_ACTION || !DISCORD_WEBHOOK) { 11 + throw new Error( 12 + `Missing input.\nRequired environment variables: GITHUB_ACTION, GITHUB_EVENT_NAME, DISCORD_WEBHOOK\n\nAvailable environment variables: ${Object.keys( 13 + process.env, 14 + ).join(", ")}\n`, 15 + ); 16 + } 17 + 18 + const body = { 19 + content: DISCORD_MESSAGE, 20 + }; 21 + if (DISCORD_USERNAME) { 22 + body.username = DISCORD_USERNAME; 23 + } 24 + if (DISCORD_AVATAR) { 25 + body.avatar_url = DISCORD_AVATAR; 26 + } 27 + const headers = { 28 + "Content-Type": "application/json", 29 + "X-GitHub-Event": GITHUB_EVENT_NAME, 30 + }; 31 + 32 + await fetch(`${DISCORD_WEBHOOK}?wait=true`, { 33 + body: JSON.stringify(body), 34 + headers, 35 + });
+95
scripts/mergebot.js
··· 1 + import { setOutput } from "./utils.js"; 2 + 3 + const { COMMIT_AUTHOR, COMMIT_ID, COMMIT_MESSAGE, GITHUB_REPO } = process.env; 4 + if (!COMMIT_AUTHOR || !COMMIT_ID || !COMMIT_MESSAGE || !GITHUB_REPO) { 5 + throw new Error( 6 + `Missing input.\nRequired environment variables: COMMIT_AUTHOR, COMMIT_ID, COMMIT_MESSAGE, GITHUB_REPO\n\nAvailable environment variables: ${Object.keys(process.env).join(", ")}\n`, 7 + ); 8 + } 9 + setDiscordMessage(COMMIT_AUTHOR, COMMIT_ID, COMMIT_MESSAGE, GITHUB_REPO); 10 + 11 + /** 12 + * @param {string} author The name of the commit author 13 + * @param {string} id The commit ID 14 + * @param {string} commitMsg A full commit message 15 + * @param {string} repo The full GitHub repo name to link to, e.g. `'withastro/starlight'` 16 + */ 17 + function setDiscordMessage(author, id, commitMsg, repo) { 18 + const commitMessage = commitMsg 19 + .split("\n")[0] 20 + .replaceAll("`", "") 21 + .replaceAll("-", "–"); 22 + 23 + const coAuthors = commitMsg 24 + .split("\n") 25 + .slice(2) 26 + .filter((line) => line.match(/Co-authored-by: (.+) <.+>/i)) 27 + .map((line) => line.match(/Co-authored-by: (.+) <.+>/i)[1]) 28 + .filter((name) => name !== "github-actions[bot]"); 29 + 30 + let coAuthorThanks = ""; 31 + if (coAuthors.length > 0) { 32 + const uniqueCoAuthors = [...new Set(coAuthors)]; 33 + const names = formatAsCommaSeparatedList(uniqueCoAuthors); 34 + coAuthorThanks = `\n${getCoAuthorsMessage(names)}`; 35 + } 36 + 37 + const defaultEmoji = ["🎉", "🎊", "🧑‍🚀", "🥳", "🙌", "🚀"]; 38 + const userEmoji = process.env.EMOJIS?.split(","); 39 + const emoji = pick( 40 + userEmoji && userEmoji.length > 0 ? userEmoji : defaultEmoji, 41 + ); 42 + 43 + setOutput( 44 + "DISCORD_MESSAGE", 45 + `${emoji} **Merged!** ${author}: [\`${commitMessage}\`](<https://github.com/${repo}/commit/${id}>)${coAuthorThanks}`, 46 + ); 47 + } 48 + 49 + /** 50 + * Generate a list like `'foo, bar and baz'` from an array 51 + * like `['foo', 'bar', 'baz']`. 52 + * @param {string[]} list List of words to format 53 + */ 54 + function formatAsCommaSeparatedList(list) { 55 + if (list.length === 1) return list[0]; 56 + return `${list.slice(0, -1).join(", ")} & ${list.at(-1)}`; 57 + } 58 + 59 + /** 60 + * Pick a random item from an array of items. 61 + * @param {string[]} items Items to pick from 62 + */ 63 + function pick(items) { 64 + return items[Math.floor(Math.random() * items.length)]; 65 + } 66 + 67 + /** 68 + * Get a randomised fun thank you message for co-authors. 69 + * @param {string} names Names of co-authors to be thanked 70 + */ 71 + function getCoAuthorsMessage(names) { 72 + /** @type {string[]} */ 73 + let messages = []; 74 + try { 75 + messages = JSON.parse(process.env.COAUTHOR_TEMPLATES || "[]"); 76 + } catch (err) { 77 + console.error( 78 + "Failed to parse `COAUTHOR_TEMPLATES` as JSON. Falling back to default templates.\n ", 79 + err, 80 + ); 81 + } 82 + if (!messages || messages.length === 0) { 83 + messages = [ 84 + "Thanks <names> for helping! ✨", 85 + "<names> stepped up to lend a hand — thank you! 🙌", 86 + "<names> with the assist! 💪", 87 + "Couldn’t have done this without <names>! 💜", 88 + "Made even better by <names>! 🚀", 89 + "And the team effort award goes to… <names>! 🏆", 90 + "Featuring contributions by <names>! 🌟", 91 + ]; 92 + } 93 + const chosenMessage = pick(messages); 94 + return `_${chosenMessage.replace("<names>", names).trim()}_`; 95 + }
+60
scripts/utils.js
··· 1 + import * as crypto from 'node:crypto' 2 + import * as fs from 'node:fs' 3 + import * as os from 'node:os' 4 + 5 + /** Based on https://github.com/actions/toolkit/blob/4e3b068ce116d28cb840033c02f912100b4592b0/packages/core/src/file-command.ts */ 6 + export function setOutput(key, value) { 7 + const filePath = process.env.GITHUB_OUTPUT || '' 8 + if (filePath) { 9 + return issueFileCommand('OUTPUT', prepareKeyValueMessage(key, value)) 10 + } 11 + process.stdout.write(os.EOL) 12 + } 13 + 14 + function issueFileCommand(command, message) { 15 + const filePath = process.env[`GITHUB_${command}`] 16 + if (!filePath) { 17 + throw new Error( 18 + `Unable to find environment variable for file command ${command}` 19 + ) 20 + } 21 + if (!fs.existsSync(filePath)) { 22 + throw new Error(`Missing file at path: ${filePath}`) 23 + } 24 + 25 + fs.appendFileSync(filePath, `${toCommandValue(message)}${os.EOL}`, { 26 + encoding: 'utf8' 27 + }) 28 + } 29 + 30 + function prepareKeyValueMessage(key, value) { 31 + const delimiter = `gh-delimiter-${crypto.randomUUID()}` 32 + const convertedValue = toCommandValue(value) 33 + 34 + // These should realistically never happen, but just in case someone finds a 35 + // way to exploit uuid generation let's not allow keys or values that contain 36 + // the delimiter. 37 + if (key.includes(delimiter)) { 38 + throw new Error( 39 + `Unexpected input: name should not contain the delimiter "${delimiter}"` 40 + ) 41 + } 42 + 43 + if (convertedValue.includes(delimiter)) { 44 + throw new Error( 45 + `Unexpected input: value should not contain the delimiter "${delimiter}"` 46 + ) 47 + } 48 + 49 + return `${key}<<${delimiter}${os.EOL}${convertedValue}${os.EOL}${delimiter}` 50 + } 51 + 52 + function toCommandValue(input) { 53 + if (input === null || input === undefined) { 54 + return '' 55 + } 56 + if (typeof input === 'string' || input instanceof String) { 57 + return input 58 + } 59 + return JSON.stringify(input) 60 + }