├── .eslintignore ├── .eslintrc.json ├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── automerge-dependabot.yml │ ├── ci.yml │ ├── on-repository-dispatch.yml │ ├── slash-command-dispatch.yml │ ├── test-dispatch.yml │ └── update-major-version.yml ├── .gitignore ├── .prettierignore ├── .prettierrc.json ├── LICENSE ├── README.md ├── action.yml ├── dist └── index.js ├── jest.config.js ├── package-lock.json ├── package.json ├── src └── main.ts └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | lib/ 3 | node_modules/ 4 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { "node": true, "jest": true }, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { "ecmaVersion": 9, "sourceType": "module" }, 5 | "extends": [ 6 | "eslint:recommended", 7 | "plugin:@typescript-eslint/eslint-recommended", 8 | "plugin:@typescript-eslint/recommended", 9 | "plugin:import/errors", 10 | "plugin:import/warnings", 11 | "plugin:import/typescript", 12 | "plugin:prettier/recommended" 13 | ], 14 | "plugins": ["@typescript-eslint"], 15 | "rules": { 16 | "@typescript-eslint/camelcase": "off" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: peter-evans -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | day: "friday" 8 | labels: 9 | - "dependencies" 10 | 11 | - package-ecosystem: "npm" 12 | directory: "/" 13 | schedule: 14 | interval: "weekly" 15 | day: "friday" 16 | ignore: 17 | - dependency-name: "*" 18 | update-types: ["version-update:semver-major"] 19 | labels: 20 | - "dependencies" 21 | 22 | -------------------------------------------------------------------------------- /.github/workflows/automerge-dependabot.yml: -------------------------------------------------------------------------------- 1 | name: Auto-merge Dependabot 2 | on: pull_request 3 | 4 | jobs: 5 | automerge: 6 | runs-on: ubuntu-latest 7 | if: github.actor == 'dependabot[bot]' 8 | steps: 9 | - uses: peter-evans/enable-pull-request-automerge@v3 10 | with: 11 | token: ${{ secrets.ACTIONS_BOT_TOKEN }} 12 | pull-request-number: ${{ github.event.pull_request.number }} 13 | merge-method: squash 14 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: [main] 5 | paths-ignore: 6 | - 'README.md' 7 | - 'docs/**' 8 | pull_request: 9 | branches: [main] 10 | paths-ignore: 11 | - 'README.md' 12 | - 'docs/**' 13 | 14 | permissions: 15 | pull-requests: write 16 | contents: write 17 | 18 | jobs: 19 | build: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v4 23 | - uses: actions/setup-node@v4 24 | with: 25 | node-version: 20.x 26 | - run: npm ci 27 | - run: npm run build 28 | - run: npm run format-check 29 | - run: npm run lint 30 | - run: npm run test 31 | - uses: actions/upload-artifact@v4 32 | with: 33 | name: dist 34 | path: dist 35 | - uses: actions/upload-artifact@v4 36 | with: 37 | name: action.yml 38 | path: action.yml 39 | 40 | test: 41 | if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository 42 | needs: [build] 43 | runs-on: ubuntu-latest 44 | strategy: 45 | matrix: 46 | target: [built, committed] 47 | steps: 48 | - uses: actions/checkout@v4 49 | - if: matrix.target == 'built' || github.event_name == 'pull_request' 50 | uses: actions/download-artifact@v4 51 | with: 52 | name: dist 53 | path: dist 54 | - if: matrix.target == 'built' || github.event_name == 'pull_request' 55 | uses: actions/download-artifact@v4 56 | with: 57 | name: action.yml 58 | path: . 59 | 60 | - name: Test repository dispatch 61 | uses: ./ 62 | with: 63 | event-type: tests 64 | client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}"}' 65 | 66 | - name: Test repository dispatch (default payload) 67 | uses: ./ 68 | with: 69 | event-type: tests 70 | 71 | package: 72 | if: github.event_name == 'push' && github.ref == 'refs/heads/main' 73 | needs: [test] 74 | runs-on: ubuntu-latest 75 | steps: 76 | - uses: actions/checkout@v4 77 | - uses: actions/download-artifact@v4 78 | with: 79 | name: dist 80 | path: dist 81 | - name: Create Pull Request 82 | uses: peter-evans/create-pull-request@v7 83 | with: 84 | token: ${{ secrets.ACTIONS_BOT_TOKEN }} 85 | commit-message: Update distribution 86 | title: Update distribution 87 | body: | 88 | - Updates the distribution for changes on `main` 89 | 90 | Auto-generated by [create-pull-request][1] 91 | 92 | [1]: https://github.com/peter-evans/create-pull-request 93 | branch: update-distribution 94 | -------------------------------------------------------------------------------- /.github/workflows/on-repository-dispatch.yml: -------------------------------------------------------------------------------- 1 | name: Repository Dispatch 2 | on: 3 | repository_dispatch: 4 | types: [tests] 5 | jobs: 6 | tests: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Dump the client payload context 10 | env: 11 | PAYLOAD_CONTEXT: ${{ toJson(github.event.client_payload) }} 12 | run: echo "$PAYLOAD_CONTEXT" 13 | 14 | - uses: actions/checkout@v4 15 | if: github.event.client_payload.ref != '' 16 | with: 17 | ref: ${{ github.event.client_payload.ref }} 18 | 19 | - run: echo ${{ github.event.client_payload.sha }} 20 | -------------------------------------------------------------------------------- /.github/workflows/slash-command-dispatch.yml: -------------------------------------------------------------------------------- 1 | name: Slash Command Dispatch 2 | on: 3 | issue_comment: 4 | types: [created] 5 | jobs: 6 | slashCommandDispatch: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Slash Command Dispatch 10 | uses: peter-evans/slash-command-dispatch@v4 11 | with: 12 | token: ${{ secrets.ACTIONS_BOT_TOKEN }} 13 | config: > 14 | [ 15 | { 16 | "command": "rebase", 17 | "permission": "admin", 18 | "repository": "peter-evans/slash-command-dispatch-processor", 19 | "issue_type": "pull-request" 20 | } 21 | ] 22 | -------------------------------------------------------------------------------- /.github/workflows/test-dispatch.yml: -------------------------------------------------------------------------------- 1 | name: Test Dispatch 2 | on: workflow_dispatch 3 | 4 | permissions: 5 | pull-requests: write 6 | contents: write 7 | 8 | jobs: 9 | test: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Test repository dispatch 13 | uses: peter-evans/repository-dispatch@v3 14 | with: 15 | # token: ${{ secrets.PUBLIC_REPO_SCOPED_TOKEN }} 16 | event-type: tests 17 | client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}"}' 18 | -------------------------------------------------------------------------------- /.github/workflows/update-major-version.yml: -------------------------------------------------------------------------------- 1 | name: Update Major Version 2 | run-name: Update ${{ github.event.inputs.main_version }} to ${{ github.event.inputs.target }} 3 | 4 | on: 5 | workflow_dispatch: 6 | inputs: 7 | target: 8 | description: The target tag or reference 9 | required: true 10 | main_version: 11 | type: choice 12 | description: The major version tag to update 13 | options: 14 | - v3 15 | 16 | jobs: 17 | tag: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v4 21 | with: 22 | token: ${{ secrets.ACTIONS_BOT_TOKEN }} 23 | fetch-depth: 0 24 | - name: Git config 25 | run: | 26 | git config user.name actions-bot 27 | git config user.email actions-bot@users.noreply.github.com 28 | - name: Tag new target 29 | run: git tag -f ${{ github.event.inputs.main_version }} ${{ github.event.inputs.target }} 30 | - name: Push new tag 31 | run: git push origin ${{ github.event.inputs.main_version }} --force 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib/ 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | lib/ 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": false, 9 | "arrowParens": "avoid", 10 | "parser": "typescript" 11 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Peter Evans 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 | # Repository Dispatch 2 | [![CI](https://github.com/peter-evans/repository-dispatch/workflows/CI/badge.svg)](https://github.com/peter-evans/repository-dispatch/actions?query=workflow%3ACI) 3 | [![GitHub Marketplace](https://img.shields.io/badge/Marketplace-Repository%20Dispatch-blue.svg?colorA=24292e&colorB=0366d6&style=flat&longCache=true&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAM6wAADOsB5dZE0gAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAERSURBVCiRhZG/SsMxFEZPfsVJ61jbxaF0cRQRcRJ9hlYn30IHN/+9iquDCOIsblIrOjqKgy5aKoJQj4O3EEtbPwhJbr6Te28CmdSKeqzeqr0YbfVIrTBKakvtOl5dtTkK+v4HfA9PEyBFCY9AGVgCBLaBp1jPAyfAJ/AAdIEG0dNAiyP7+K1qIfMdonZic6+WJoBJvQlvuwDqcXadUuqPA1NKAlexbRTAIMvMOCjTbMwl1LtI/6KWJ5Q6rT6Ht1MA58AX8Apcqqt5r2qhrgAXQC3CZ6i1+KMd9TRu3MvA3aH/fFPnBodb6oe6HM8+lYHrGdRXW8M9bMZtPXUji69lmf5Cmamq7quNLFZXD9Rq7v0Bpc1o/tp0fisAAAAASUVORK5CYII=)](https://github.com/marketplace/actions/repository-dispatch) 4 | 5 | A GitHub action to create a repository dispatch event. 6 | 7 | ## Usage 8 | 9 | Dispatch an event to the current repository. 10 | ```yml 11 | - name: Repository Dispatch 12 | uses: peter-evans/repository-dispatch@v3 13 | with: 14 | event-type: my-event 15 | ``` 16 | 17 | Dispatch an event to a remote repository using a `repo` scoped [Personal Access Token (PAT)](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token). 18 | ```yml 19 | - name: Repository Dispatch 20 | uses: peter-evans/repository-dispatch@v3 21 | with: 22 | token: ${{ secrets.PAT }} 23 | repository: username/my-repo 24 | event-type: my-event 25 | ``` 26 | 27 | ### Action inputs 28 | 29 | | Name | Description | Default | 30 | | --- | --- | --- | 31 | | `token` | `GITHUB_TOKEN` (permissions `contents: write`) or a `repo` scoped [Personal Access Token (PAT)](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token). See [token](#token) for further details. | `GITHUB_TOKEN` | 32 | | `repository` | The full name of the repository to send the dispatch. | `github.repository` (current repository) | 33 | | `event-type` | (**required**) A custom webhook event name. | | 34 | | `client-payload` | JSON payload with extra information about the webhook event that your action or workflow may use. | `{}` | 35 | 36 | #### Token 37 | 38 | This action creates [`repository_dispatch`](https://docs.github.com/en/rest/repos/repos#create-a-repository-dispatch-event) events. 39 | The default `GITHUB_TOKEN` token can only be used if you are dispatching the same repository that the workflow is executing in. 40 | 41 | To dispatch to a remote repository you must create a [Personal Access Token (PAT)](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) with the `repo` scope and store it as a secret. 42 | If you will be dispatching to a public repository then you can use the more limited `public_repo` scope. 43 | 44 | You can also use a [fine-grained personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-fine-grained-personal-access-token) (beta). It needs the following permissions on the target repositories: 45 | - `contents: read & write` 46 | - `metadata: read only` (automatically selected when selecting the contents permission) 47 | 48 | ## Example 49 | 50 | Here is an example setting all of the input parameters. 51 | 52 | ```yml 53 | - name: Repository Dispatch 54 | uses: peter-evans/repository-dispatch@v3 55 | with: 56 | token: ${{ secrets.PAT }} 57 | repository: username/my-repo 58 | event-type: my-event 59 | client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}"}' 60 | ``` 61 | 62 | Here is an example `on: repository_dispatch` workflow to receive the event. 63 | Note that repository dispatch events will only trigger a workflow run if the workflow is committed to the default branch. 64 | 65 | ```yml 66 | name: Repository Dispatch 67 | on: 68 | repository_dispatch: 69 | types: [my-event] 70 | jobs: 71 | myEvent: 72 | runs-on: ubuntu-latest 73 | steps: 74 | - uses: actions/checkout@v3 75 | with: 76 | ref: ${{ github.event.client_payload.ref }} 77 | - run: echo ${{ github.event.client_payload.sha }} 78 | ``` 79 | 80 | ### Dispatch to multiple repositories 81 | 82 | You can dispatch to multiple repositories by using a [matrix strategy](https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstrategymatrix). In the following example, after the `build` job succeeds, an event is dispatched to three different repositories. 83 | 84 | ```yml 85 | jobs: 86 | build: 87 | # Main workflow job that builds, tests, etc. 88 | 89 | dispatch: 90 | needs: build 91 | strategy: 92 | matrix: 93 | repo: ['my-org/repo1', 'my-org/repo2', 'my-org/repo3'] 94 | runs-on: ubuntu-latest 95 | steps: 96 | - name: Repository Dispatch 97 | uses: peter-evans/repository-dispatch@v3 98 | with: 99 | token: ${{ secrets.PAT }} 100 | repository: ${{ matrix.repo }} 101 | event-type: my-event 102 | ``` 103 | 104 | ## Client payload 105 | 106 | The GitHub API allows a maximum of 10 top-level properties in the `client-payload` JSON. 107 | If you use more than that you will see an error message like the following. 108 | 109 | ``` 110 | No more than 10 properties are allowed; 14 were supplied. 111 | ``` 112 | 113 | For example, this payload will fail because it has more than 10 top-level properties. 114 | 115 | ```yml 116 | client-payload: ${{ toJson(github) }} 117 | ``` 118 | 119 | To solve this you can simply wrap the payload in a single top-level property. 120 | The following payload will succeed. 121 | 122 | ```yml 123 | client-payload: '{"github": ${{ toJson(github) }}}' 124 | ``` 125 | 126 | Additionally, there is a limitation on the total data size of the `client-payload`. A very large payload may result in a `client_payload is too large` error. 127 | 128 | ### Multiline 129 | 130 | A multiline `client-payload` can be set directly in YAML, as in the following example. 131 | 132 | ```yml 133 | - name: Repository Dispatch 134 | uses: peter-evans/repository-dispatch@v3 135 | with: 136 | token: ${{ secrets.PAT }} 137 | repository: username/my-repo 138 | event-type: my-event 139 | client-payload: |- 140 | { 141 | "repo": { 142 | "name": "${{ github.repository }}", 143 | "branch": "${{ needs.build_cfg.outputs.REPO_BRANCH }}", 144 | "tag": "${{ needs.build_cfg.outputs.REPO_TAG }}" 145 | }, 146 | "deployment": { 147 | "project": "${{ env.MY_PROJECT }}", 148 | "container": "${{ env.MY_CONTAINER }}", 149 | "deploy_msg": "${{ env.SLACK_DEPLOY_MSG }}", 150 | } 151 | } 152 | ``` 153 | 154 | ## License 155 | 156 | [MIT](LICENSE) 157 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: 'Repository Dispatch' 2 | description: 'Create a repository dispatch event' 3 | inputs: 4 | token: 5 | description: 'GITHUB_TOKEN or a `repo` scoped Personal Access Token (PAT)' 6 | default: ${{ github.token }} 7 | repository: 8 | description: 'The full name of the repository to send the dispatch.' 9 | default: ${{ github.repository }} 10 | event-type: 11 | description: 'A custom webhook event name.' 12 | required: true 13 | client-payload: 14 | description: 'JSON payload with extra information about the webhook event that your action or worklow may use.' 15 | default: '{}' 16 | runs: 17 | using: 'node20' 18 | main: 'dist/index.js' 19 | branding: 20 | icon: 'target' 21 | color: 'gray-dark' 22 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | clearMocks: true, 3 | moduleFileExtensions: ['js', 'ts'], 4 | testEnvironment: 'node', 5 | testMatch: ['**/*.test.ts'], 6 | testRunner: 'jest-circus/runner', 7 | transform: { 8 | '^.+\\.ts$': 'ts-jest' 9 | }, 10 | verbose: true 11 | } 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "repository-dispatch", 3 | "version": "3.0.0", 4 | "private": true, 5 | "description": "Create a repository dispatch event", 6 | "main": "lib/main.js", 7 | "scripts": { 8 | "build": "tsc && ncc build", 9 | "format": "prettier --write '**/*.ts'", 10 | "format-check": "prettier --check '**/*.ts'", 11 | "lint": "eslint src/**/*.ts", 12 | "test": "jest --passWithNoTests" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/peter-evans/repository-dispatch.git" 17 | }, 18 | "keywords": [ 19 | "actions", 20 | "repository", 21 | "dispatch" 22 | ], 23 | "author": "Peter Evans", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/peter-evans/repository-dispatch/issues" 27 | }, 28 | "homepage": "https://github.com/peter-evans/repository-dispatch#readme", 29 | "dependencies": { 30 | "@actions/core": "^1.11.1", 31 | "@actions/github": "^6.0.1" 32 | }, 33 | "devDependencies": { 34 | "@types/jest": "^27.0.3", 35 | "@types/node": "^18.19.108", 36 | "@typescript-eslint/parser": "^5.62.0", 37 | "@vercel/ncc": "^0.38.3", 38 | "eslint": "^8.57.1", 39 | "eslint-plugin-github": "^4.10.2", 40 | "eslint-plugin-jest": "^25.7.0", 41 | "eslint-plugin-prettier": "^5.4.1", 42 | "jest": "^27.5.1", 43 | "jest-circus": "^29.7.0", 44 | "js-yaml": "^4.1.0", 45 | "prettier": "^3.5.3", 46 | "ts-jest": "^27.1.5", 47 | "typescript": "^4.9.5" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import * as core from '@actions/core' 2 | import * as github from '@actions/github' 3 | import {inspect} from 'util' 4 | 5 | /* eslint-disable @typescript-eslint/no-explicit-any */ 6 | function hasErrorStatus(error: any): error is {status: number} { 7 | return typeof error.status === 'number' 8 | } 9 | 10 | function getErrorMessage(error: unknown) { 11 | if (error instanceof Error) return error.message 12 | return String(error) 13 | } 14 | 15 | async function run(): Promise { 16 | try { 17 | const inputs = { 18 | token: core.getInput('token'), 19 | repository: core.getInput('repository'), 20 | eventType: core.getInput('event-type'), 21 | clientPayload: core.getInput('client-payload') 22 | } 23 | core.debug(`Inputs: ${inspect(inputs)}`) 24 | 25 | const [owner, repo] = inputs.repository.split('/') 26 | 27 | const octokit = github.getOctokit(inputs.token) 28 | 29 | await octokit.rest.repos.createDispatchEvent({ 30 | owner: owner, 31 | repo: repo, 32 | event_type: inputs.eventType, 33 | client_payload: JSON.parse(inputs.clientPayload) 34 | }) 35 | } catch (error) { 36 | core.debug(inspect(error)) 37 | if (hasErrorStatus(error) && error.status == 404) { 38 | core.setFailed( 39 | 'Repository not found, OR token has insufficient permissions.' 40 | ) 41 | } else { 42 | core.setFailed(getErrorMessage(error)) 43 | } 44 | } 45 | } 46 | 47 | run() 48 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "lib": [ 6 | "es6" 7 | ], 8 | "outDir": "./lib", 9 | "rootDir": "./src", 10 | "declaration": true, 11 | "strict": true, 12 | "noImplicitAny": false, 13 | "esModuleInterop": true 14 | }, 15 | "exclude": ["__test__", "lib", "node_modules"] 16 | } 17 | --------------------------------------------------------------------------------