├── .gitattributes ├── .github ├── renovate.json └── workflows │ ├── build_test.yml │ └── release.yml ├── .gitignore ├── .prettierignore ├── .prettierrc.json ├── LICENSE ├── README.md ├── action.yml ├── dist ├── client.js ├── index.js ├── index.js.map └── notification.js ├── eslint.config.js ├── jest.config.js ├── package-lock.json ├── package.json ├── rollup.config.ts ├── src ├── client.test.ts ├── client.ts ├── index.ts └── notification.ts ├── tsconfig.json ├── tsconfig.lint.json └── tsconfig.release.json /.gitattributes: -------------------------------------------------------------------------------- 1 | dist/** -diff linguist-generated=true -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "github>harryzcy/renovate-config" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.github/workflows/build_test.yml: -------------------------------------------------------------------------------- 1 | name: Build & Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - '**' 7 | tags-ignore: 8 | - 'v*' 9 | pull_request: 10 | branches: 11 | - main 12 | 13 | jobs: 14 | test: 15 | name: Build & Test 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 20 | 21 | - name: Setup Node 22 | uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 23 | with: 24 | node-version: latest 25 | 26 | - name: Install 27 | run: npm install 28 | 29 | - name: Lint 30 | run: | 31 | npm run lint 32 | npm run format-check 33 | 34 | - name: Test 35 | run: npm run all 36 | 37 | - name: Notification 38 | uses: ./ 39 | if: ${{ github.actor != 'dependabot[bot]' && github.event_name != 'pull_request' }} 40 | with: 41 | status: ${{ job.status }} 42 | title: ${{ secrets.BARK_TITLE }} 43 | device_key: ${{ secrets.BARK_DEVICE_KEY }} 44 | level: passive 45 | bark_server_url: ${{ secrets.BARK_SERVER_URL }} 46 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Releases 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: write 13 | steps: 14 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 15 | - uses: ncipollo/release-action@440c8c1cb0ed28b9f43e4d1d670870f059653174 # v1.16.0 16 | with: 17 | tag: ${{ github.ref }} 18 | name: Release ${{ github.ref_name }} 19 | token: ${{ secrets.GITHUB_TOKEN }} 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependency directory 2 | node_modules 3 | 4 | # Rest pulled from https://github.com/github/gitignore/blob/master/Node.gitignore 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | 13 | # Diagnostic reports (https://nodejs.org/api/report.html) 14 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 15 | 16 | # Runtime data 17 | pids 18 | *.pid 19 | *.seed 20 | *.pid.lock 21 | 22 | # Directory for instrumented libs generated by jscoverage/JSCover 23 | lib-cov 24 | 25 | # Coverage directory used by tools like istanbul 26 | coverage 27 | *.lcov 28 | 29 | # nyc test coverage 30 | .nyc_output 31 | 32 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 33 | .grunt 34 | 35 | # Bower dependency directory (https://bower.io/) 36 | bower_components 37 | 38 | # node-waf configuration 39 | .lock-wscript 40 | 41 | # Compiled binary addons (https://nodejs.org/api/addons.html) 42 | build/Release 43 | 44 | # Dependency directories 45 | jspm_packages/ 46 | 47 | # TypeScript v1 declaration files 48 | typings/ 49 | 50 | # TypeScript cache 51 | *.tsbuildinfo 52 | 53 | # Optional npm cache directory 54 | .npm 55 | 56 | # Optional eslint cache 57 | .eslintcache 58 | 59 | # Optional REPL history 60 | .node_repl_history 61 | 62 | # Output of 'npm pack' 63 | *.tgz 64 | 65 | # Yarn Integrity file 66 | .yarn-integrity 67 | 68 | # dotenv environment variables file 69 | .env 70 | .env.test 71 | 72 | # parcel-bundler cache (https://parceljs.org/) 73 | .cache 74 | 75 | # next.js build output 76 | .next 77 | 78 | # nuxt.js build output 79 | .nuxt 80 | 81 | # vuepress build output 82 | .vuepress/dist 83 | 84 | # Serverless directories 85 | .serverless/ 86 | 87 | # FuseBox cache 88 | .fusebox/ 89 | 90 | # DynamoDB Local files 91 | .dynamodb/ 92 | 93 | # OS metadata 94 | .DS_Store 95 | Thumbs.db 96 | 97 | # Ignore built ts files 98 | __tests__/runner/* 99 | lib/ 100 | bin/ 101 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | bin/ 3 | node_modules/ 4 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": false, 6 | "singleQuote": true, 7 | "trailingComma": "none", 8 | "bracketSpacing": true, 9 | "arrowParens": "avoid" 10 | } 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Chongyi Zheng 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # action-bark 2 | 3 | Allow GitHub Actions to push iOS notifications via Bark 4 | 5 | ## Quick Start 6 | 7 | ```yaml 8 | steps: 9 | - uses: harryzcy/action-bark@v1 10 | with: 11 | status: ${{ job.status }} 12 | device_key: ${{ secrets.BARK_DEVICE_KEY }} 13 | bark_server_url: ${{ secrets.BARK_SERVER_URL }} 14 | if: always() # Pick up events even if the job fails or is canceled. 15 | ``` 16 | 17 | ## Advanced Usage 18 | 19 | More controls over notifications: 20 | 21 | ```yaml 22 | steps: 23 | - uses: harryzcy/action-bark@v1 24 | with: 25 | status: ${{ job.status }} 26 | on_status: failure, cancelled # only run on these status 27 | device_key: ${{ secrets.BARK_DEVICE_KEY }} 28 | level: passive # iOS notification level 'active', 'timeSensitive' or 'passive', default to 'active' 29 | bark_server_url: ${{ secrets.BARK_SERVER_URL }} 30 | if: always() # Pick up events even if the job fails or is canceled. 31 | ``` 32 | 33 | Custom title and body: 34 | 35 | ```yaml 36 | steps: 37 | - uses: harryzcy/action-bark@v1 38 | with: 39 | status: ${{ job.status }} 40 | title: custom title 41 | body: custom body 42 | device_key: ${{ secrets.BARK_DEVICE_KEY }} 43 | level: passive # iOS notification level 'active', 'timeSensitive' or 'passive', default to 'active' 44 | bark_server_url: ${{ secrets.BARK_SERVER_URL }} 45 | if: always() # Pick up events even if the job fails or is canceled. 46 | ``` 47 | 48 | ## Github Enterprise Users 49 | 50 | ```yaml 51 | steps: 52 | - uses: harryzcy/action-bark@v1 53 | with: 54 | status: ${{ job.status }} 55 | device_key: ${{ secrets.BARK_DEVICE_KEY }} 56 | bark_server_url: ${{ secrets.BARK_SERVER_URL }} 57 | github_server_url: https://your.ghe.com # Specify your GHE 58 | if: always() # Pick up events even if the job fails or is canceled. 59 | ``` 60 | 61 | ## Inputs 62 | 63 | | Input | Description | Required | 64 | | ----- | ----------- | -------- | 65 | | `title` | Specify a custom title of the notification, required if status is set to custom | false | 66 | | `body` | Specify a custom body of the notification, required if status is set to custom | false | 67 | | `device_key` | Specify Bark device key of your device | true | 68 | | `level` | Specify iOS notification level (default: active) | false | 69 | | `badge` | The number displayed next to App icon | false | 70 | | `automatically_copy` | Automatically copy the content to the clipboard (must be "1" if set) | false | 71 | |`copy` | The content to be copied to the clipboard | false | 72 | |`sound` | Sound to be played, value from | false | 73 | |`icon` | An url to the icon, available only on iOS 15 or later | false | 74 | |`group` | The group of the notification | false | 75 | |`is_archive` | Value must be "1". Whether or not should be archived by the app | false | 76 | |`url` | The url to be opened when the notification is clicked (defaults to GitHub run URL) | false | 77 | |`bark_server_url` | Specify your Bark server URL | true | 78 | |`github_server_url` | Specify your GitHub Enterprise URL | false | 79 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: "action-bark" 2 | description: "You can send push notifications to iOS via Bark" 3 | author: "harryzcy" 4 | inputs: 5 | status: 6 | description: Specify success or failure or cancelled. 7 | required: true 8 | on_status: 9 | description: | 10 | The status on which the notification would be sent. 11 | If you specify more than one, separate them by commas. 12 | e.g. failure, cancelled 13 | required: false 14 | default: "all" 15 | title: 16 | description: | 17 | Specify a custom title of the notification, 18 | required if status is set to custom 19 | required: false 20 | default: "" 21 | body: 22 | description: | 23 | Specify a custom body of the notification, 24 | required if status is set to custom 25 | required: false 26 | default: "" 27 | device_key: 28 | description: Specify Bark device key of your device 29 | required: true 30 | level: 31 | description: | 32 | Specify iOS notification level (default: active) 33 | required: false 34 | default: active 35 | badge: 36 | description: | 37 | The number displayed next to App icon 38 | required: false 39 | automatically_copy: 40 | description: | 41 | Automatically copy the content to the clipboard (must be "1" if set) 42 | required: false 43 | copy: 44 | description: | 45 | The content to be copied to the clipboard 46 | required: false 47 | sound: 48 | description: | 49 | Sound to be played, value from https://github.com/Finb/Bark/tree/master/Sounds 50 | required: false 51 | icon: 52 | description: | 53 | An url to the icon, available only on iOS 15 or later 54 | required: false 55 | group: 56 | description: | 57 | The group of the notification 58 | required: false 59 | is_archive: 60 | description: | 61 | Value must be "1". Whether or not should be archived by the app 62 | required: false 63 | url: 64 | description: | 65 | The url to be opened when the notification is clicked (defaults to GitHub run URL) 66 | required: false 67 | bark_server_url: 68 | description: Specify your Bark server URL 69 | required: true 70 | github_server_url: 71 | description: Specify your GitHub Enterprise URL 72 | required: false 73 | default: https://github.com 74 | runs: 75 | using: "node20" 76 | main: "dist/index.js" 77 | branding: 78 | icon: alert-octagon 79 | color: orange 80 | -------------------------------------------------------------------------------- /dist/client.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | export async function request(input) { 3 | let url = input.server_url; 4 | if (!url.endsWith('/')) { 5 | url += '/'; 6 | } 7 | url += 'push'; 8 | const res = await axios.post(url, { 9 | title: input.title, 10 | body: input.body, 11 | device_key: input.device_key, 12 | category: 'category', 13 | level: input.level, 14 | badge: input.badge ?? null, 15 | automatically_copy: input.automatically_copy ?? null, 16 | copy: input.copy ?? null, 17 | sound: input.sound ?? null, 18 | icon: input.icon ?? null, 19 | group: input.group ?? null, 20 | is_archive: input.is_archive ?? null, 21 | url: input.url ?? input.github_runs_url 22 | }); 23 | return res.data; 24 | } 25 | -------------------------------------------------------------------------------- /dist/notification.js: -------------------------------------------------------------------------------- 1 | import * as github from '@actions/github'; 2 | export function generateNotification(input) { 3 | const { repo, owner } = github.context.repo; 4 | const { runId } = github.context; 5 | const url = `${input.github_server_url}/${owner}/${repo}/actions/runs/${runId.toString()}`; 6 | let status_word; 7 | if (input.status === 'success') 8 | status_word = 'succeeded'; 9 | else if (input.status === 'failure') 10 | status_word = 'failed'; 11 | else 12 | status_word = 'is cancelled'; 13 | const title = input.title ?? 'Github Actions'; 14 | const body = input.body ?? 15 | `Actions #${github.context.runNumber.toString()} on ${repo} ${status_word}`; 16 | return { 17 | title, 18 | body, 19 | github_runs_url: url 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import eslint from '@eslint/js' 4 | import tseslint from 'typescript-eslint' 5 | 6 | export default tseslint.config( 7 | { 8 | ignores: ['dist/', 'bin/', 'node_modules/', '*.config.js'] 9 | }, 10 | eslint.configs.recommended, 11 | ...tseslint.configs.strictTypeChecked, 12 | ...tseslint.configs.stylisticTypeChecked, 13 | { 14 | languageOptions: { 15 | parserOptions: { 16 | project: ['./tsconfig.lint.json'], 17 | tsconfigRootDir: import.meta.dirname, 18 | ecmaVersion: 9 19 | } 20 | }, 21 | rules: { 22 | semi: [2, 'never'] 23 | } 24 | }, 25 | { 26 | files: ['**/*.js'], 27 | ...tseslint.configs.disableTypeChecked 28 | } 29 | ) 30 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | clearMocks: true, 3 | moduleFileExtensions: ['js', 'ts'], 4 | testMatch: ['**/*.test.ts'], 5 | transform: { 6 | '^.+\\.ts$': 'ts-jest' 7 | }, 8 | verbose: true 9 | } 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "action-bark", 3 | "version": "2.3.0", 4 | "type": "module", 5 | "private": true, 6 | "description": "Push iOS notifications via Bark", 7 | "main": "bin/index.js", 8 | "scripts": { 9 | "build": "tsc -p tsconfig.release.json", 10 | "format": "prettier --write '**/*.ts'", 11 | "format-check": "prettier --check '**/*.ts'", 12 | "lint": "eslint", 13 | "package": "npx rollup --config rollup.config.ts --configPlugin @rollup/plugin-typescript", 14 | "test": "jest", 15 | "all": "npm run format && npm run lint && npm run build && npm run package && npm test" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/harryzcy/action-bark.git" 20 | }, 21 | "keywords": [ 22 | "actions", 23 | "node", 24 | "setup" 25 | ], 26 | "author": "", 27 | "license": "MIT", 28 | "dependencies": { 29 | "@actions/core": "1.11.1", 30 | "@actions/github": "6.0.1", 31 | "axios": "1.9.0" 32 | }, 33 | "devDependencies": { 34 | "@eslint/js": "9.28.0", 35 | "@rollup/plugin-commonjs": "28.0.3", 36 | "@rollup/plugin-json": "6.1.0", 37 | "@rollup/plugin-node-resolve": "16.0.1", 38 | "@rollup/plugin-typescript": "12.1.2", 39 | "@tsconfig/node-lts": "22.0.1", 40 | "@types/node": "22.15.29", 41 | "@typescript-eslint/eslint-plugin": "8.33.1", 42 | "@typescript-eslint/parser": "8.33.1", 43 | "eslint": "9.28.0", 44 | "jest": "29.7.0", 45 | "prettier": "3.5.3", 46 | "rollup": "4.41.1", 47 | "ts-jest": "29.3.4", 48 | "tslib": "2.8.1", 49 | "typescript": "5.8.3", 50 | "typescript-eslint": "8.33.1" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /rollup.config.ts: -------------------------------------------------------------------------------- 1 | import commonjs from '@rollup/plugin-commonjs' 2 | import nodeResolve from '@rollup/plugin-node-resolve' 3 | import typescript from '@rollup/plugin-typescript' 4 | import json from '@rollup/plugin-json' 5 | 6 | const config = { 7 | input: 'src/index.ts', 8 | output: { 9 | esModule: true, 10 | file: 'dist/index.js', 11 | format: 'es', 12 | sourcemap: true 13 | }, 14 | plugins: [ 15 | typescript(), 16 | json(), 17 | nodeResolve({ preferBuiltins: true }), 18 | commonjs() 19 | ] 20 | } 21 | 22 | export default config 23 | -------------------------------------------------------------------------------- /src/client.test.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import * as client from '../src/client' 3 | import { expect, test, jest } from '@jest/globals' 4 | 5 | test('throws invalid number', async () => { 6 | const resp_data = { code: 200, message: 'success', timestamp: 1647679056 } 7 | const spy = jest.spyOn(axios, 'post').mockResolvedValue({ 8 | data: resp_data 9 | }) 10 | 11 | const data = await client.request({ 12 | server_url: 'https://example.org', 13 | title: 'title', 14 | body: 'body', 15 | device_key: 'device_key', 16 | level: 'passive', 17 | github_runs_url: 'https://example.org' 18 | }) 19 | expect(spy).toBeCalledTimes(1) 20 | expect(data).toBe(resp_data) 21 | 22 | expect(spy.mock.calls[0][0]).toBe('https://example.org/push') 23 | expect(spy.mock.calls[0][1]).toStrictEqual({ 24 | title: 'title', 25 | body: 'body', 26 | device_key: 'device_key', 27 | category: 'category', 28 | level: 'passive', 29 | badge: null, 30 | automatically_copy: null, 31 | copy: null, 32 | sound: null, 33 | icon: null, 34 | group: null, 35 | is_archive: null, 36 | url: 'https://example.org' 37 | }) 38 | }) 39 | -------------------------------------------------------------------------------- /src/client.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | export interface RequestInput { 4 | server_url: string 5 | title: string 6 | body: string 7 | device_key: string 8 | level: string 9 | badge?: string 10 | automatically_copy?: string 11 | copy?: string 12 | sound?: string 13 | icon?: string 14 | group?: string 15 | is_archive?: string 16 | url?: string 17 | github_runs_url: string 18 | } 19 | 20 | export interface RequestResponse { 21 | code: number 22 | message: string 23 | timestamp: number 24 | } 25 | 26 | export async function request(input: RequestInput): Promise { 27 | let url = input.server_url 28 | if (!url.endsWith('/')) { 29 | url += '/' 30 | } 31 | url += 'push' 32 | 33 | const res = await axios.post(url, { 34 | title: input.title, 35 | body: input.body, 36 | device_key: input.device_key, 37 | category: 'category', 38 | level: input.level, 39 | badge: input.badge ?? null, 40 | automatically_copy: input.automatically_copy ?? null, 41 | copy: input.copy ?? null, 42 | sound: input.sound ?? null, 43 | icon: input.icon ?? null, 44 | group: input.group ?? null, 45 | is_archive: input.is_archive ?? null, 46 | url: input.url ?? input.github_runs_url 47 | }) 48 | return res.data as RequestResponse 49 | } 50 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as client from './client' 2 | import * as core from '@actions/core' 3 | import { generateNotification } from './notification' 4 | 5 | async function run(): Promise { 6 | try { 7 | // status: success | failure | cancelled 8 | const status = core.getInput('status', { required: true }).toLowerCase() 9 | const on_status = core.getInput('on_status').toLowerCase() 10 | const title = core.getInput('title') 11 | const body = core.getInput('body') 12 | const device_key = core.getInput('device_key', { required: true }) 13 | const level = core.getInput('level') 14 | const badge = core.getInput('badge') 15 | const automatically_copy = core.getInput('automatically_copy') 16 | const copy = core.getInput('copy') 17 | const sound = core.getInput('sound') 18 | const icon = core.getInput('icon') 19 | const group = core.getInput('group') 20 | const is_archive = core.getInput('is_archive') 21 | const url = core.getInput('url') 22 | const bark_server_url = core.getInput('bark_server_url', { required: true }) 23 | const github_server_url = core.getInput('github_server_url') 24 | 25 | core.debug(`status: ${status}`) 26 | core.debug(`on_status: ${on_status}`) 27 | core.debug(`title: ${title}`) 28 | core.debug(`body: ${body}`) 29 | core.debug(`device_key: ${device_key}`) 30 | core.debug(`level: ${level}`) 31 | core.debug(`badge: ${badge}`) 32 | core.debug(`automatically_copy: ${automatically_copy}`) 33 | core.debug(`copy: ${copy}`) 34 | core.debug(`sound: ${sound}`) 35 | core.debug(`icon: ${icon}`) 36 | core.debug(`group: ${group}`) 37 | core.debug(`is_archive: ${is_archive}`) 38 | core.debug(`url: ${url}`) 39 | core.debug(`bark_server_url: ${bark_server_url}`) 40 | core.debug(`github_server_url: ${github_server_url}`) 41 | 42 | const on_status_all = on_status.split(',').map(e => e.trim()) 43 | if (!on_status_all.includes(status) && on_status !== 'all') return 44 | 45 | const notification = generateNotification({ 46 | status, 47 | title, 48 | body, 49 | github_server_url 50 | }) 51 | 52 | await client.request({ 53 | server_url: bark_server_url, 54 | title: notification.title, 55 | body: notification.body, 56 | device_key, 57 | level, 58 | badge, 59 | automatically_copy, 60 | copy, 61 | sound, 62 | icon, 63 | group, 64 | is_archive, 65 | url, 66 | github_runs_url: notification.github_runs_url 67 | }) 68 | } catch (error) { 69 | if (error instanceof Error) core.setFailed(error.message) 70 | } 71 | } 72 | 73 | void run() 74 | -------------------------------------------------------------------------------- /src/notification.ts: -------------------------------------------------------------------------------- 1 | import * as github from '@actions/github' 2 | 3 | interface NotificationInput { 4 | status: string 5 | title?: string 6 | body?: string 7 | github_server_url: string 8 | } 9 | 10 | interface Notification { 11 | title: string 12 | body: string 13 | github_runs_url: string 14 | } 15 | 16 | export function generateNotification(input: NotificationInput): Notification { 17 | const { repo, owner } = github.context.repo 18 | const { runId } = github.context 19 | const url = `${input.github_server_url}/${owner}/${repo}/actions/runs/${runId.toString()}` 20 | 21 | let status_word: string 22 | if (input.status === 'success') status_word = 'succeeded' 23 | else if (input.status === 'failure') status_word = 'failed' 24 | else status_word = 'is cancelled' 25 | 26 | const title = input.title ?? 'Github Actions' 27 | const body = 28 | input.body ?? 29 | `Actions #${github.context.runNumber.toString()} on ${repo} ${status_word}` 30 | 31 | return { 32 | title, 33 | body, 34 | github_runs_url: url 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node-lts/tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "module": "ESNext", 6 | "moduleResolution": "Bundler" 7 | }, 8 | "include": ["src"], 9 | "exclude": ["node_modules"] 10 | } 11 | -------------------------------------------------------------------------------- /tsconfig.lint.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "allowJs": true, 6 | "noEmit": true 7 | }, 8 | "exclude": ["dist", "node_modules"], 9 | "include": ["src", "eslint.config.js", "jest.config.js", "rollup.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /tsconfig.release.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "sourceMap": false, 5 | "removeComments": true 6 | }, 7 | "exclude": ["**/*.test.ts"] 8 | } 9 | --------------------------------------------------------------------------------