Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
276 changes: 262 additions & 14 deletions .github/workflows/build-test.yaml
Original file line number Diff line number Diff line change
@@ -1,38 +1,286 @@
name: Build & Test
name: 'CI: Lint, Build & Test'

on:
push:
branches: [main]
paths:
- 'src/**'
- 'package.json'
- 'package-lock.json'
- 'tsconfig.json'
- 'eslint.config.mjs'
- 'action.yaml'
- 'dist/**'
- '.github/workflows/**'
pull_request:
branches: [main]
paths:
- 'src/**'
- 'package.json'
- 'package-lock.json'
- 'tsconfig.json'
- 'eslint.config.mjs'
- 'action.yaml'
- 'dist/**'
- '.github/workflows/**'
workflow_dispatch:

# permissions:
# contents: write

jobs:
# ===========================================================================
# Build & lint — all test jobs depend on this
# ===========================================================================
build:
name: Build & Lint
runs-on: ubuntu-latest
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0
with:
egress-policy: audit

- name: Check out repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '24.x'
- run: npm ci
- run: npm run lint
- run: npm run build

- name: Build with esbuild
run: |
npm install
npm run build
# ===========================================================================
# Test: Dispatch a workflow by its display name, with inputs
# ===========================================================================
test-dispatch-by-name:
name: 'Test: Dispatch by name'
needs: build
runs-on: ubuntu-latest
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0
with:
egress-policy: audit

- name: Invoke echo 1 workflow using this action
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Dispatch echo-1 by name
id: dispatch
uses: ./
with:
workflow: Message Echo 1
inputs: '{"message": "blah blah"}'
inputs: '{"message": "hello from name test"}'
ref: ${{ github.event.pull_request.head.ref || github.ref_name }}

- name: Assert outputs are set
env:
RUN_ID: ${{ steps.dispatch.outputs.runId }}
WORKFLOW_ID: ${{ steps.dispatch.outputs.workflowId }}
RUN_URL: ${{ steps.dispatch.outputs.runUrl }}
RUN_URL_HTML: ${{ steps.dispatch.outputs.runUrlHtml }}
run: |
echo "runId=$RUN_ID workflowId=$WORKFLOW_ID"
echo "runUrl=$RUN_URL"
echo "runUrlHtml=$RUN_URL_HTML"
[[ -n "$RUN_ID" ]] || { echo "FAIL: runId empty"; exit 1; }
[[ -n "$WORKFLOW_ID" ]] || { echo "FAIL: workflowId empty"; exit 1; }
[[ -n "$RUN_URL" ]] || { echo "FAIL: runUrl empty"; exit 1; }
[[ -n "$RUN_URL_HTML" ]] || { echo "FAIL: runUrlHtml empty"; exit 1; }

- name: Invoke echo 2 workflow using this action
# ===========================================================================
# Test: Dispatch a workflow by its filename (short form)
# ===========================================================================
test-dispatch-by-filename:
name: 'Test: Dispatch by filename'
needs: build
runs-on: ubuntu-latest
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0
with:
egress-policy: audit

- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Dispatch echo-2 by filename
uses: ./
with:
workflow: echo-2.yaml
ref: ${{ github.event.pull_request.head.ref || github.ref_name }}

# ===========================================================================
# Test: Dispatch a workflow by its full path
# ===========================================================================
test-dispatch-by-path:
name: 'Test: Dispatch by full path'
needs: build
runs-on: ubuntu-latest
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0
with:
egress-policy: audit

- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Dispatch echo-2 by full path
uses: ./
with:
workflow: .github/workflows/echo-2.yaml
ref: ${{ github.event.pull_request.head.ref || github.ref_name }}

# ===========================================================================
# Test: Dispatch a workflow by its numeric ID
# ===========================================================================
test-dispatch-by-id:
name: 'Test: Dispatch by ID'
needs: build
runs-on: ubuntu-latest
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0
with:
egress-policy: audit

- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

# NOTE: This ID is specific to this repo's echo-1 workflow.
# If the workflow file is deleted & recreated, this ID will change.
- name: Dispatch echo-1 by numeric ID
uses: ./
with:
workflow: '78181717'
inputs: '{"message": "dispatched by id"}'
ref: ${{ github.event.pull_request.head.ref || github.ref_name }}

# ===========================================================================
# Test: Wait for completion on a slow workflow (success case)
# ===========================================================================
test-wait-completion:
name: 'Test: Wait for completion'
needs: build
runs-on: ubuntu-latest
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0
with:
egress-policy: audit

- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Dispatch echo-2 and wait for it to finish
uses: ./
with:
workflow: echo-2.yaml
wait-for-completion: true
ref: ${{ github.event.pull_request.head.ref || github.ref_name }}

# ===========================================================================
# Test: sync-status mirrors a successful workflow conclusion
# ===========================================================================
test-sync-status-success:
name: 'Test: Sync status (success)'
needs: build
runs-on: ubuntu-latest
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0
with:
egress-policy: audit

- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Dispatch echo-1 with wait + sync-status
uses: ./
with:
workflow: Message Echo 1
inputs: '{"message": "sync status success test"}'
wait-for-completion: true
sync-status: true
ref: ${{ github.event.pull_request.head.ref || github.ref_name }}

# ===========================================================================
# Test: sync-status propagates a failing workflow as a failure
# ===========================================================================
test-sync-status-failure:
name: 'Test: Sync status (failure)'
needs: build
runs-on: ubuntu-latest
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0
with:
egress-policy: audit

- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Dispatch echo-3 (always fails) with sync-status
id: dispatch-fail
continue-on-error: true
uses: ./
with:
workflow: echo-3.yaml
wait-for-completion: true
sync-status: true
ref: ${{ github.event.pull_request.head.ref || github.ref_name }}

- name: Assert action reported failure
run: |
if [[ "${{ steps.dispatch-fail.outcome }}" != "failure" ]]; then
echo "FAIL: Expected action to fail when sync-status mirrors a failing workflow"
exit 1
fi
echo "PASS: Action correctly propagated failure"

# ===========================================================================
# Test: Invalid workflow reference produces an error
# ===========================================================================
test-invalid-workflow:
name: 'Test: Invalid workflow name'
needs: build
runs-on: ubuntu-latest
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0
with:
egress-policy: audit

- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Dispatch a workflow that does not exist
id: bad-workflow
continue-on-error: true
uses: ./
with:
workflow: this-workflow-does-not-exist.yaml
ref: ${{ github.event.pull_request.head.ref || github.ref_name }}

- name: Assert action failed
run: |
if [[ "${{ steps.bad-workflow.outcome }}" != "failure" ]]; then
echo "FAIL: Expected action to fail for non-existent workflow"
exit 1
fi
echo "PASS: Action correctly failed for invalid workflow reference"

# ===========================================================================
# Test: Wait timeout fires before a slow workflow finishes
# ===========================================================================
test-wait-timeout:
name: 'Test: Wait timeout'
needs: build
runs-on: ubuntu-latest
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0
with:
egress-policy: audit

- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

# echo-2 sleeps for 11s — a 1-second timeout will trigger after the
# first poll cycle (~5s). The action warns but does NOT fail on timeout.
- name: Dispatch echo-2 with 1-second timeout
uses: ./
with:
workflow: echo-2.yaml
wait-for-completion: true
wait-timeout-seconds: 1
ref: ${{ github.event.pull_request.head.ref || github.ref_name }}
4 changes: 2 additions & 2 deletions .github/workflows/echo-2.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
with:
egress-policy: audit

- name: Echo message slowly with a wait
- name: Echo message with a wait
run: |
sleep 125
sleep 11
echo '${{ inputs.message }}'
16 changes: 6 additions & 10 deletions .github/workflows/echo-3.yaml
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
name: Message Echo 3

# A version using workflow_call for investigation purposes

on:
workflow_call:
on:
workflow_dispatch:
inputs:
message:
required: false
default: "this is echo 3"
type: string
description: "Message to echo"

default: 'this is echo 3'
description: 'Message to echo'
permissions:
contents: read

Expand All @@ -23,5 +19,5 @@ jobs:
with:
egress-policy: audit

- name: Echo message
run: echo '${{ inputs.message }}'
- name: Intentional failure
run: exit 1
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@ The workflow must be configured for this event type e.g. `on: [workflow_dispatch

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.

**2026 Update**: We finally have a way to get the details of the triggered workflow run, including the run ID and URL, which means we can now poll for the run status and wait for it to complete if required. This is a common ask and I'm glad to have added this feature after nearly 6 years!

![Screenshot the logs of a workflow run](./etc/screen.png)

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/)

_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.
Note, 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.

The GitHub UI will report flows triggered by this action as "manually triggered" this might seem confusing at first, but it makes sense when you remember that the workflow_dispatch event is designed to be triggered manually by a user, and this action is simulating that manual trigger.

_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.
## 2026 Update

_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}}"`
We finally have a way to get the details of the triggered workflow run, including the run ID and URL, which means we can now poll for the run status and wait for it to complete if required. This is a common ask and I'm glad to have added this feature after nearly 6 years!

## Action Inputs

Expand All @@ -33,6 +33,8 @@ workflow: my-workflow.yaml
workflow: 1218419
```

> _Note:_ If you want to reference the target workflow by ID, you will need to list the IDs with a REST API call `curl https://api.github.com/repos/{{owner}}/{{repo}}/actions/workflows -H "Authorization: token {{pat-token}}"`

### `inputs`

**Optional.** The inputs to pass to the workflow (if any are configured), this must be a JSON encoded string, e.g. `{ "myInput": "foobar" }`
Expand Down
5 changes: 5 additions & 0 deletions action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ inputs:
ref:
description: 'The reference can be a branch, tag, or a commit SHA'
required: false
default: ${{ github.head_ref || github.ref }}
repo:
description: 'Repo owner & name, slash separated, only set if invoking a workflow in a different repo'
required: false
Expand All @@ -26,6 +27,10 @@ inputs:
description: 'Maximum time in seconds to wait for the workflow run to complete before timing out (only applies if wait-for-completion is true)'
required: false
default: 900
sync-status:
description: 'Whether to set the status of this action to failed if the triggered workflow run fails, or is cancelled. Only applies if wait-for-completion is true.'
required: false
default: false

outputs:
runId:
Expand Down
Loading
Loading