├── .gitignore ├── .prettierrc ├── .vscode └── settings.json ├── eslint.config.mjs ├── .github └── workflows │ ├── echo-1.yaml │ ├── echo-2.yaml │ ├── echo-3.yaml │ ├── test.yaml │ └── build-test.yaml ├── etc └── test.http ├── package.json ├── action.yaml ├── tsconfig.json ├── LICENSE ├── src └── main.ts └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .env -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "printWidth": 120 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": { 3 | "source.fixAll.eslint": true 4 | }, 5 | "eslint.format.enable": true, 6 | "[typescript]": { 7 | "editor.defaultFormatter": "dbaeumer.vscode-eslint" 8 | } 9 | } -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import eslint from '@eslint/js' 4 | import tseslint from 'typescript-eslint' 5 | 6 | export default tseslint.config(eslint.configs.recommended, ...tseslint.configs.recommended, ...tseslint.configs.strict) 7 | -------------------------------------------------------------------------------- /.github/workflows/echo-1.yaml: -------------------------------------------------------------------------------- 1 | name: Message Echo 1 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | message: 7 | description: "Message to echo" 8 | required: true 9 | # No default 10 | 11 | jobs: 12 | echo: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Echo message 16 | run: echo '${{ inputs.message }}' 17 | -------------------------------------------------------------------------------- /.github/workflows/echo-2.yaml: -------------------------------------------------------------------------------- 1 | name: Message Echo 2 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | message: 7 | description: "Message to echo" 8 | required: false 9 | default: "this is echo 2" 10 | 11 | jobs: 12 | echo: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Echo message 16 | run: echo '${{ inputs.message }}' -------------------------------------------------------------------------------- /etc/test.http: -------------------------------------------------------------------------------- 1 | POST https://api.github.com/repos/benc-uk/workflow-dispatch/actions/workflows/1854248/dispatches 2 | Authorization: Basic benc-uk:{{$dotenv GITHUB_PAT}} 3 | Content-Type: application/json 4 | 5 | { 6 | "ref": "refs/heads/master" 7 | } 8 | 9 | ### 10 | 11 | GET https://api.github.com/repos/benc-uk/workflow-dispatch/actions/workflows 12 | Authorization: Basic benc-uk:{{$dotenv GITHUB_PAT}} 13 | -------------------------------------------------------------------------------- /.github/workflows/echo-3.yaml: -------------------------------------------------------------------------------- 1 | name: Message Echo 3 2 | 3 | # A version using workflow_call for investigation purposes 4 | 5 | on: 6 | workflow_call: 7 | inputs: 8 | message: 9 | required: false 10 | default: "this is echo 3" 11 | type: string 12 | description: "Message to echo" 13 | 14 | jobs: 15 | echo: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Echo message 19 | run: echo '${{ inputs.message }}' -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Workflow Tester 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | testAction: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Invoke echo 1 12 | uses: ./ 13 | with: 14 | workflow: echo-1.yaml 15 | inputs: '{"message": "blah blah this is a test"}' 16 | - name: Invoke echo 2 17 | uses: ./ 18 | with: 19 | workflow: Message Echo 2 20 | inputs: '{"message": "mushrooms in the morning"}' 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "workflow-dispatch", 3 | "version": "1.2.4", 4 | "description": "Trigger running GitHub Actions workflows", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "build": "ncc build src/main.ts -o dist", 8 | "lint": "eslint src/", 9 | "lint-fix": "eslint src/ --fix", 10 | "format": "prettier --write src/" 11 | }, 12 | "keywords": [ 13 | "github", 14 | "actions" 15 | ], 16 | "author": "Ben Coleman", 17 | "license": "MIT", 18 | "devDependencies": { 19 | "@actions/core": "^1.10.1", 20 | "@actions/github": "^6.0.0", 21 | "@vercel/ncc": "^0.38.1", 22 | "typescript-eslint": "^8.0.0", 23 | "eslint": "^9.8.0", 24 | "typescript": "^5.5.4", 25 | "prettier": "^3.3.3" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /action.yaml: -------------------------------------------------------------------------------- 1 | name: "Workflow Dispatch" 2 | description: "Trigger and chain GitHub Actions workflows with workflow_dispatch events" 3 | 4 | inputs: 5 | workflow: 6 | description: "Name, filename or ID of workflow to run" 7 | required: true 8 | token: 9 | description: "GitHub token with repo write access, only required if the workflow is in a different repository" 10 | required: false 11 | default: ${{ github.token }} 12 | inputs: 13 | description: "Inputs to pass to the workflow, must be a JSON string" 14 | required: false 15 | ref: 16 | description: "The reference can be a branch, tag, or a commit SHA" 17 | required: false 18 | repo: 19 | description: "Repo owner & name, slash separated, only set if invoking a workflow in a different repo" 20 | required: false 21 | 22 | runs: 23 | using: "node20" 24 | main: "dist/index.js" 25 | 26 | branding: 27 | color: purple 28 | icon: send 29 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ 4 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 5 | "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 6 | "strict": true, /* Enable all strict type-checking options. */ 7 | "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 8 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 9 | "resolveJsonModule": true 10 | }, 11 | "exclude": ["node_modules", "**/*.test.ts"] 12 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020 Ben Coleman 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /.github/workflows/build-test.yaml: -------------------------------------------------------------------------------- 1 | name: Build & Test 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | workflow_dispatch: 7 | 8 | # permissions: 9 | # contents: write 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Check out repository 16 | uses: actions/checkout@v4 17 | 18 | - name: Build with ncc 19 | run: | 20 | npm install 21 | npm run build 22 | 23 | - name: Invoke echo 1 workflow using this action 24 | uses: ./ 25 | with: 26 | workflow: Message Echo 1 27 | inputs: '{"message": "blah blah"}' 28 | 29 | - name: Invoke echo 2 workflow using this action 30 | uses: ./ 31 | with: 32 | workflow: echo-2.yaml 33 | 34 | - name: Invoke echo 1 workflow by id 35 | uses: ./ 36 | with: 37 | workflow: "1854247" 38 | inputs: '{"message": "Mango jam"}' 39 | 40 | # - name: Push dist back to GitHub 41 | # uses: ad-m/github-push-action@master 42 | # with: 43 | # github_token: ${{ secrets.GITHUB_TOKEN }} 44 | # branch: ${{ github.ref }} 45 | 46 | # - name: Invoke external workflow using this action 47 | # uses: ./ 48 | # with: 49 | # workflow: Deploy To Kubernetes 50 | # repo: benc-uk/dapr-store 51 | # token: ${{ secrets.PERSONAL_TOKEN }} 52 | # ref: master 53 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Copyright (c) Ben Coleman, 2020 3 | // Licensed under the MIT License. 4 | // 5 | // Workflow Dispatch Action - Main task code 6 | // ---------------------------------------------------------------------------- 7 | 8 | import * as core from '@actions/core' 9 | import * as github from '@actions/github' 10 | import * as PackageJSON from '../package.json' 11 | 12 | type Workflow = { 13 | id: number 14 | name: string 15 | path: string 16 | } 17 | 18 | // 19 | // Main task function (async wrapper) 20 | // 21 | async function run(): Promise { 22 | core.info(`🏃 Workflow Dispatch Action v${PackageJSON.version}`) 23 | try { 24 | // Required inputs 25 | const workflowRef = core.getInput('workflow') 26 | 27 | // Optional inputs, with defaults 28 | const token = core.getInput('token') 29 | const ref = core.getInput('ref') || github.context.ref 30 | const [owner, repo] = core.getInput('repo') 31 | ? core.getInput('repo').split('/') 32 | : [github.context.repo.owner, github.context.repo.repo] 33 | 34 | // Decode inputs, this MUST be a valid JSON string 35 | let inputs = {} 36 | const inputsJson = core.getInput('inputs') 37 | if (inputsJson) { 38 | inputs = JSON.parse(inputsJson) 39 | } 40 | 41 | // Get octokit client for making API calls 42 | const octokit = github.getOctokit(token) 43 | 44 | // List workflows via API, and handle paginated results 45 | const workflows: Workflow[] = await octokit.paginate( 46 | octokit.rest.actions.listRepoWorkflows.endpoint.merge({ 47 | owner, 48 | repo, 49 | ref, 50 | inputs, 51 | }), 52 | ) 53 | 54 | // Debug response if ACTIONS_STEP_DEBUG is enabled 55 | core.debug('### START List Workflows response data') 56 | core.debug(JSON.stringify(workflows, null, 3)) 57 | core.debug('### END: List Workflows response data') 58 | 59 | // Locate workflow either by name, id or filename 60 | const foundWorkflow = workflows.find((workflow) => { 61 | return ( 62 | workflow.name === workflowRef || 63 | workflow.id.toString() === workflowRef || 64 | workflow.path.endsWith(`/${workflowRef}`) || // Add a leading / to avoid matching workflow with same suffix 65 | workflow.path == workflowRef 66 | ) // Or it stays in top level directory 67 | }) 68 | 69 | if (!foundWorkflow) throw new Error(`Unable to find workflow '${workflowRef}' in ${owner}/${repo} 😥`) 70 | 71 | console.log(`🔎 Found workflow, id: ${foundWorkflow.id}, name: ${foundWorkflow.name}, path: ${foundWorkflow.path}`) 72 | 73 | // Call workflow_dispatch API 74 | console.log('🚀 Calling GitHub API to dispatch workflow...') 75 | const dispatchResp = await octokit.request( 76 | `POST /repos/${owner}/${repo}/actions/workflows/${foundWorkflow.id}/dispatches`, 77 | { 78 | ref: ref, 79 | inputs: inputs, 80 | }, 81 | ) 82 | 83 | core.info(`🏆 API response status: ${dispatchResp.status}`) 84 | core.setOutput('workflowId', foundWorkflow.id) 85 | } catch (error) { 86 | const e = error as Error 87 | 88 | if (e.message.endsWith('a disabled workflow')) { 89 | core.warning('Workflow is disabled, no action was taken') 90 | return 91 | } 92 | 93 | core.setFailed(e.message) 94 | } 95 | } 96 | 97 | // 98 | // Call the main task run function 99 | // 100 | run() 101 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GitHub Action for Dispatching Workflows 2 | 3 | This action triggers another GitHub Actions workflow, using the `workflow_dispatch` event. 4 | The workflow must be configured for this event type e.g. `on: [workflow_dispatch]` 5 | 6 | This allows you to chain workflows, the classic use case is have a CI build workflow, trigger a CD release/deploy workflow when it completes. Allowing you to maintain separate workflows for CI and CD, and pass data between them as required. 7 | 8 | For details of the `workflow_dispatch` even see [this blog post introducing this type of trigger](https://github.blog/changelog/2020-07-06-github-actions-manual-triggers-with-workflow_dispatch/) 9 | 10 | _Note 1._ GitHub now has a native way to chain workflows called "reusable workflows". See the docs on [reusing workflows](https://docs.github.com/en/actions/using-workflows/reusing-workflows). This approach is somewhat different from workflow_dispatch but it's worth keeping in mind. 11 | 12 | _Note 2._ The GitHub UI will report flows triggered by this action as "manually triggered" even though they have been run programmatically via another workflow and the API. 13 | 14 | _Note 3._ If you want to reference the target workflow by ID, you will need to list them with the following REST API call `curl https://api.github.com/repos/{{owner}}/{{repo}}/actions/workflows -H "Authorization: token {{pat-token}}"` 15 | 16 | ## Action Inputs 17 | 18 | ### `workflow` 19 | 20 | **Required.** The name, filename or ID of the workflow to be triggered and run. All three possibilities are used when looking for the workflow. e.g. 21 | 22 | ```yaml 23 | workflow: My Workflow 24 | # or 25 | workflow: my-workflow.yaml 26 | # or 27 | workflow: 1218419 28 | ``` 29 | 30 | ### `inputs` 31 | 32 | **Optional.** The inputs to pass to the workflow (if any are configured), this must be a JSON encoded string, e.g. `{ "myInput": "foobar" }` 33 | 34 | ### `ref` 35 | 36 | **Optional.** The Git reference used with the triggered workflow run. The reference can be a branch, tag, or a commit SHA. If omitted the context ref of the triggering workflow is used. If you want to trigger on pull requests and run the target workflow in the context of the pull request branch, set the ref to `${{ github.event.pull_request.head.ref }}`. 37 | 38 | ### `repo` 39 | 40 | **Optional.** The default behavior is to trigger workflows in the same repo as the triggering workflow, if you wish to trigger in another GitHub repo "externally", then provide the owner + repo name with slash between them e.g. `microsoft/vscode`. 41 | 42 | - When triggering across repos like this, you **must** provide a `token` (see below), or you will get an _"Resource not accessible by integration"_ error. 43 | - If the default branch in the other repo is different from the calling repo, you must provide `ref` input also, or you will get a _"No ref found"_ error. 44 | 45 | ### `token` 46 | 47 | **Optional.** By default the standard `github.token`/`GITHUB_TOKEN` will be used and you no longer need to provide your own token here. 48 | 49 | **⚠️ IMPORTANT:** When using the `repo` option to call across repos, you **must** provide the token. In order to do so, create a PAT token with repo rights, and pass it here via a secret, e.g. `${{ secrets.MY_TOKEN }}`. 50 | 51 | This option is also left for backwards compatibility with older versions where this field was mandatory. 52 | 53 | ## Action Outputs 54 | 55 | This Action emits a single output named `workflowId`. 56 | 57 | ## Example usage 58 | 59 | ```yaml 60 | - name: Invoke workflow without inputs 61 | uses: benc-uk/workflow-dispatch@v1 62 | with: 63 | workflow: My Workflow 64 | ``` 65 | 66 | ```yaml 67 | - name: Invoke workflow with inputs 68 | uses: benc-uk/workflow-dispatch@v1 69 | with: 70 | workflow: Another Workflow 71 | inputs: '{ "message": "blah blah", "something": true }' 72 | ``` 73 | 74 | ```yaml 75 | - name: Invoke workflow in another repo with inputs 76 | uses: benc-uk/workflow-dispatch@v1 77 | with: 78 | workflow: my-workflow.yaml 79 | repo: benc-uk/example 80 | inputs: '{ "message": "blah blah", "something": false }' 81 | # Required when using the `repo` option. Either a PAT or a token generated from the GitHub app or CLI 82 | token: "${{ secrets.MY_TOKEN }}" 83 | ``` 84 | --------------------------------------------------------------------------------