diff --git a/.github/actions/bump-manifest-image/README.md b/.github/actions/bump-manifest-image/README.md
new file mode 100644
index 0000000..85f346d
--- /dev/null
+++ b/.github/actions/bump-manifest-image/README.md
@@ -0,0 +1,99 @@
+# Bump manifest image
+
+Opens a pull request that bumps a container's image tag in a Kubernetes
+deployment manifest, for a single environment. Domain-agnostic — the caller
+supplies the manifest path, container name, image repo, and tag. It is
+semver-aware (never downgrades), skips pre-releases for `prod`, and can enable
+auto-merge on the PR it opens. Call it from a matrix over environments to fan a
+single release out to staging and prod.
+
+## Inputs
+
+
+
+| INPUT | TYPE | REQUIRED | DEFAULT | DESCRIPTION |
+|----------------|--------|----------|-----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| auto-merge | string | false | `"false"` | Enable GitHub auto-merge (squash) on the
opened PR. |
+| base-branch | string | false | `"main"` | Base branch for the PR. |
+| container-name | string | true | | Name of the container whose image
tag is tracked. |
+| environment | string | true | | Target environment. "prod" skips pre-release tags. |
+| image-repo | string | true | | Image repo without tag (e.g. ghcr.io/loft-sh/revops-events-api). |
+| manifest-path | string | true | | Path to the deployment manifest to
edit. |
+| tag | string | true | | Release tag to roll out (e.g. v0.2.0 or 0.2.0-rc1). |
+| token | string | true | | PAT used to open the PR
and (if enabled) enable auto-merge. Must be
a PAT or App token, not
GITHUB_TOKEN: a GITHUB_TOKEN merge emits no
events, so downstream merge-triggered workflows would
never run. |
+
+
+
+## Outputs
+
+
+
+| OUTPUT | TYPE | DESCRIPTION |
+|---------------------|--------|----------------------------------------------------------------------|
+| pull-request-number | string | Number of the opened PR (empty when no update). |
+| updated | string | true when a PR was opened
(a newer applicable version existed). |
+
+
+
+## Usage
+
+```yaml
+on:
+ repository_dispatch:
+ types: [update-my-app-version]
+ workflow_dispatch:
+ inputs:
+ tag:
+ description: 'Image tag to roll out (e.g. v0.2.0)'
+ required: true
+
+jobs:
+ bump:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ environment: [staging, prod]
+ permissions:
+ contents: write
+ pull-requests: write
+ steps:
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ persist-credentials: false
+ - uses: loft-sh/github-actions/.github/actions/bump-manifest-image@bump-manifest-image/v1
+ with:
+ tag: ${{ github.event.client_payload.tag || inputs.tag }}
+ environment: ${{ matrix.environment }}
+ manifest-path: kubernetes/manifests/${{ matrix.environment }}/my-app/deployment.yaml
+ container-name: my-app
+ image-repo: ghcr.io/loft-sh/my-app
+ auto-merge: ${{ matrix.environment == 'staging' }}
+ token: ${{ secrets.GH_ACCESS_TOKEN }}
+```
+
+### Auth
+
+`token` must be a Personal Access Token or GitHub App token (not
+`GITHUB_TOKEN`). It opens the PR and, when `auto-merge` is enabled, performs
+the merge. A merge performed with `GITHUB_TOKEN` emits no events, so any
+downstream merge-triggered workflow (for example a deploy notification) would
+never run.
+
+### Behaviour
+
+- **Semver-aware** — the PR is only opened when `tag` is strictly newer than
+ the tag currently in the manifest. Equal or older versions are a no-op.
+- **Prod safety** — pre-release tags (anything that is not a bare `X.Y.Z`) are
+ skipped when `environment` is `prod`.
+- **PR shape** — title and branch are derived from the image repo's last path
+ segment, e.g. `chore(my-app): bump staging to v0.2.0` on branch
+ `update-staging-my-app-0.2.0`.
+
+## Testing
+
+```bash
+make test-bump-manifest-image
+```
+
+Runs the bats suites in `test/` against the `src/` scripts. `yq` must be on
+`PATH` (pre-installed on GitHub hosted runners).
diff --git a/.github/actions/bump-manifest-image/action.yml b/.github/actions/bump-manifest-image/action.yml
new file mode 100644
index 0000000..032793a
--- /dev/null
+++ b/.github/actions/bump-manifest-image/action.yml
@@ -0,0 +1,112 @@
+name: Bump manifest image
+description: |
+ Opens a pull request that bumps a container's image tag in a Kubernetes
+ deployment manifest, for a single environment. Domain-agnostic: the caller
+ supplies the manifest path, container name, image repo, and tag. Skips
+ pre-releases for prod, never downgrades (semver-aware), and optionally
+ enables auto-merge. Designed to be called from a matrix over environments.
+inputs:
+ tag:
+ description: 'Release tag to roll out (e.g. v0.2.0 or 0.2.0-rc1).'
+ required: true
+ environment:
+ description: 'Target environment. "prod" skips pre-release tags.'
+ required: true
+ manifest-path:
+ description: 'Path to the deployment manifest to edit.'
+ required: true
+ container-name:
+ description: 'Name of the container whose image tag is tracked.'
+ required: true
+ image-repo:
+ description: 'Image repo without tag (e.g. ghcr.io/loft-sh/revops-events-api).'
+ required: true
+ auto-merge:
+ description: 'Enable GitHub auto-merge (squash) on the opened PR.'
+ required: false
+ default: 'false'
+ base-branch:
+ description: 'Base branch for the PR.'
+ required: false
+ default: 'main'
+ token:
+ description: |
+ PAT used to open the PR and (if enabled) enable auto-merge. Must be a PAT
+ or App token, not GITHUB_TOKEN: a GITHUB_TOKEN merge emits no events, so
+ downstream merge-triggered workflows would never run.
+ required: true
+outputs:
+ updated:
+ description: 'true when a PR was opened (a newer applicable version existed).'
+ value: ${{ steps.decide.outputs.should_update }}
+ pull-request-number:
+ description: 'Number of the opened PR (empty when no update).'
+ value: ${{ steps.pr.outputs.pull-request-number }}
+runs:
+ using: composite
+ steps:
+ - name: Resolve version
+ id: resolve
+ shell: bash
+ env:
+ RAW_TAG: ${{ inputs.tag }}
+ IMAGE_REPO: ${{ inputs.image-repo }}
+ run: ${{ github.action_path }}/src/resolve-version.sh
+
+ - name: Decide whether to update
+ id: decide
+ shell: bash
+ env:
+ MANIFEST_PATH: ${{ inputs.manifest-path }}
+ CONTAINER_NAME: ${{ inputs.container-name }}
+ NEW_VERSION: ${{ steps.resolve.outputs.new_version }}
+ IS_STABLE: ${{ steps.resolve.outputs.is_stable }}
+ ENVIRONMENT: ${{ inputs.environment }}
+ run: ${{ github.action_path }}/src/should-update.sh
+
+ - name: Apply image bump
+ if: steps.decide.outputs.should_update == 'true'
+ shell: bash
+ env:
+ MANIFEST_PATH: ${{ inputs.manifest-path }}
+ CONTAINER_NAME: ${{ inputs.container-name }}
+ IMAGE_REPO: ${{ inputs.image-repo }}
+ NEW_TAG: ${{ steps.resolve.outputs.new_tag }}
+ run: ${{ github.action_path }}/src/apply-bump.sh
+
+ - name: Create pull request
+ id: pr
+ if: steps.decide.outputs.should_update == 'true'
+ uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
+ with:
+ token: ${{ inputs.token }}
+ base: ${{ inputs.base-branch }}
+ committer: Loft Bot
+ commit-message: "chore(${{ steps.resolve.outputs.app_name }}): bump ${{ inputs.environment }} to ${{ steps.resolve.outputs.new_tag }}"
+ title: "chore(${{ steps.resolve.outputs.app_name }}): bump ${{ inputs.environment }} to ${{ steps.resolve.outputs.new_tag }}"
+ body: |
+ Automated image bump.
+
+ - Environment: `${{ inputs.environment }}`
+ - Image: `${{ inputs.image-repo }}:${{ steps.resolve.outputs.new_tag }}`
+ - Manifest: `${{ inputs.manifest-path }}`
+
+ Opened by the `bump-manifest-image` action.
+ branch: "update-${{ inputs.environment }}-${{ steps.resolve.outputs.app_name }}-${{ steps.resolve.outputs.new_version }}"
+ delete-branch: true
+
+ - name: Enable auto-merge
+ if: steps.decide.outputs.should_update == 'true' && inputs.auto-merge == 'true' && steps.pr.outputs.pull-request-number
+ shell: bash
+ env:
+ # PAT, not GITHUB_TOKEN: the merge event must trigger downstream workflows.
+ GH_TOKEN: ${{ inputs.token }}
+ PR_NUMBER: ${{ steps.pr.outputs.pull-request-number }}
+ run: gh pr merge "$PR_NUMBER" --squash --auto
+
+ - name: Summary
+ shell: bash
+ env:
+ ENVIRONMENT: ${{ inputs.environment }}
+ REASON: ${{ steps.decide.outputs.reason }}
+ run: echo "$ENVIRONMENT - $REASON"
diff --git a/.github/actions/bump-manifest-image/src/apply-bump.sh b/.github/actions/bump-manifest-image/src/apply-bump.sh
new file mode 100755
index 0000000..b5aafa8
--- /dev/null
+++ b/.github/actions/bump-manifest-image/src/apply-bump.sh
@@ -0,0 +1,23 @@
+#!/usr/bin/env bash
+# Rewrites the tracked container's image tag in the manifest, in place.
+#
+# Inputs (env):
+# MANIFEST_PATH Path to the deployment manifest.
+# CONTAINER_NAME Container whose image is rewritten.
+# IMAGE_REPO Image repo without tag (e.g. ghcr.io/loft-sh/app).
+# NEW_TAG Tag to pin (leading v preserved).
+set -euo pipefail
+
+: "${MANIFEST_PATH:?MANIFEST_PATH is required}"
+: "${CONTAINER_NAME:?CONTAINER_NAME is required}"
+: "${IMAGE_REPO:?IMAGE_REPO is required}"
+: "${NEW_TAG:?NEW_TAG is required}"
+
+export NEW_IMAGE="${IMAGE_REPO}:${NEW_TAG}"
+export CN="$CONTAINER_NAME"
+
+yq eval \
+ '(.spec.template.spec.containers[] | select(.name == env(CN)) | .image) = env(NEW_IMAGE)' \
+ -i "$MANIFEST_PATH"
+
+echo "set ${CONTAINER_NAME} image to ${NEW_IMAGE} in ${MANIFEST_PATH}"
diff --git a/.github/actions/bump-manifest-image/src/resolve-version.sh b/.github/actions/bump-manifest-image/src/resolve-version.sh
new file mode 100755
index 0000000..b8deb4d
--- /dev/null
+++ b/.github/actions/bump-manifest-image/src/resolve-version.sh
@@ -0,0 +1,33 @@
+#!/usr/bin/env bash
+# Normalises a release tag and derives the bits the downstream steps need.
+#
+# Inputs (env):
+# RAW_TAG Release tag as received (e.g. v0.2.0 or 0.2.0-rc1).
+# IMAGE_REPO Full image repo (e.g. ghcr.io/loft-sh/revops-events-api).
+# Outputs ($GITHUB_OUTPUT):
+# new_tag The tag verbatim, leading v preserved (image tags keep it).
+# new_version Tag with any leading v stripped (for semver comparison).
+# is_stable true when new_version is a bare X.Y.Z (no pre-release suffix).
+# app_name Last path segment of IMAGE_REPO (used in PR title/branch).
+set -euo pipefail
+
+: "${RAW_TAG:?RAW_TAG is required}"
+: "${IMAGE_REPO:?IMAGE_REPO is required}"
+
+new_tag="$RAW_TAG"
+new_version="${new_tag#v}"
+
+if [[ "$new_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
+ is_stable=true
+else
+ is_stable=false
+fi
+
+app_name="${IMAGE_REPO##*/}"
+
+{
+ echo "new_tag=${new_tag}"
+ echo "new_version=${new_version}"
+ echo "is_stable=${is_stable}"
+ echo "app_name=${app_name}"
+} >> "$GITHUB_OUTPUT"
diff --git a/.github/actions/bump-manifest-image/src/should-update.sh b/.github/actions/bump-manifest-image/src/should-update.sh
new file mode 100755
index 0000000..724328e
--- /dev/null
+++ b/.github/actions/bump-manifest-image/src/should-update.sh
@@ -0,0 +1,64 @@
+#!/usr/bin/env bash
+# Decides whether the target manifest should be bumped to NEW_VERSION.
+#
+# Inputs (env):
+# MANIFEST_PATH Path to the deployment manifest.
+# CONTAINER_NAME Container whose image tag is tracked.
+# NEW_VERSION Incoming version, v-stripped (e.g. 0.2.0).
+# IS_STABLE true|false — whether NEW_VERSION is a stable release.
+# ENVIRONMENT Target environment (prod skips pre-releases).
+# Outputs ($GITHUB_OUTPUT):
+# should_update true when a newer, applicable version warrants a PR.
+# current_tag Existing image tag found in the manifest (empty if none).
+# reason Human-readable explanation for the decision.
+set -euo pipefail
+
+: "${MANIFEST_PATH:?MANIFEST_PATH is required}"
+: "${CONTAINER_NAME:?CONTAINER_NAME is required}"
+: "${NEW_VERSION:?NEW_VERSION is required}"
+: "${IS_STABLE:?IS_STABLE is required}"
+: "${ENVIRONMENT:?ENVIRONMENT is required}"
+
+emit() {
+ {
+ echo "should_update=$1"
+ echo "current_tag=${2:-}"
+ echo "reason=$3"
+ } >> "$GITHUB_OUTPUT"
+ echo "$3"
+}
+
+if [ ! -f "$MANIFEST_PATH" ]; then
+ emit false "" "manifest not found at ${MANIFEST_PATH}"
+ exit 0
+fi
+
+# Prod never tracks pre-releases.
+if [ "$ENVIRONMENT" = "prod" ] && [ "$IS_STABLE" != "true" ]; then
+ emit false "" "pre-release skipped for prod"
+ exit 0
+fi
+
+current_image=$(CN="$CONTAINER_NAME" yq eval \
+ '.spec.template.spec.containers[] | select(.name == env(CN)) | .image' \
+ "$MANIFEST_PATH")
+if [ -z "$current_image" ] || [ "$current_image" = "null" ]; then
+ emit false "" "container ${CONTAINER_NAME} not found in ${MANIFEST_PATH}"
+ exit 0
+fi
+
+current_tag="${current_image##*:}"
+current_version="${current_tag#v}"
+
+# Greater-than test via version sort. Equal returns false (no churn).
+verlt() {
+ [ "$1" != "$2" ] && [ "$1" = "$(printf '%s\n%s' "$1" "$2" | sort -V | head -n1)" ]
+}
+
+if [ "$NEW_VERSION" = "$current_version" ]; then
+ emit false "$current_tag" "already at ${current_tag}"
+elif verlt "$current_version" "$NEW_VERSION"; then
+ emit true "$current_tag" "newer than ${current_tag}, will bump"
+else
+ emit false "$current_tag" "${NEW_VERSION} is not newer than ${current_tag}"
+fi
diff --git a/.github/actions/bump-manifest-image/test/apply-bump.bats b/.github/actions/bump-manifest-image/test/apply-bump.bats
new file mode 100644
index 0000000..fafdf3f
--- /dev/null
+++ b/.github/actions/bump-manifest-image/test/apply-bump.bats
@@ -0,0 +1,35 @@
+#!/usr/bin/env bats
+# Coverage for apply-bump.sh: rewrites only the target container's tag.
+
+SCRIPT="$BATS_TEST_DIRNAME/../src/apply-bump.sh"
+FIXTURE="$BATS_TEST_DIRNAME/fixtures/deployment.yaml"
+
+setup() { MANIFEST="$(mktemp)"; cp "$FIXTURE" "$MANIFEST"; }
+teardown() { rm -f "$MANIFEST"; }
+
+image_of() {
+ CN="$1" yq eval '.spec.template.spec.containers[] | select(.name == env(CN)) | .image' "$MANIFEST"
+}
+
+@test "rewrites the tracked container image" {
+ run env \
+ MANIFEST_PATH="$MANIFEST" CONTAINER_NAME=revops-events-api \
+ IMAGE_REPO=ghcr.io/loft-sh/revops-events-api NEW_TAG=v0.2.0 "$SCRIPT"
+ [ "$status" -eq 0 ]
+ [ "$(image_of revops-events-api)" = "ghcr.io/loft-sh/revops-events-api:v0.2.0" ]
+}
+
+@test "leaves other containers untouched" {
+ run env \
+ MANIFEST_PATH="$MANIFEST" CONTAINER_NAME=revops-events-api \
+ IMAGE_REPO=ghcr.io/loft-sh/revops-events-api NEW_TAG=v0.2.0 "$SCRIPT"
+ [ "$status" -eq 0 ]
+ [ "$(image_of sidecar)" = "ghcr.io/loft-sh/sidecar:v9.9.9" ]
+}
+
+@test "missing NEW_TAG fails" {
+ run env \
+ MANIFEST_PATH="$MANIFEST" CONTAINER_NAME=revops-events-api \
+ IMAGE_REPO=ghcr.io/loft-sh/revops-events-api "$SCRIPT"
+ [ "$status" -ne 0 ]
+}
diff --git a/.github/actions/bump-manifest-image/test/fixtures/deployment.yaml b/.github/actions/bump-manifest-image/test/fixtures/deployment.yaml
new file mode 100644
index 0000000..2b836f5
--- /dev/null
+++ b/.github/actions/bump-manifest-image/test/fixtures/deployment.yaml
@@ -0,0 +1,12 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: revops-events-api
+spec:
+ template:
+ spec:
+ containers:
+ - name: revops-events-api
+ image: ghcr.io/loft-sh/revops-events-api:v0.1.0
+ - name: sidecar
+ image: ghcr.io/loft-sh/sidecar:v9.9.9
diff --git a/.github/actions/bump-manifest-image/test/resolve-version.bats b/.github/actions/bump-manifest-image/test/resolve-version.bats
new file mode 100644
index 0000000..a9e0ace
--- /dev/null
+++ b/.github/actions/bump-manifest-image/test/resolve-version.bats
@@ -0,0 +1,46 @@
+#!/usr/bin/env bats
+# Coverage for resolve-version.sh: tag normalisation, stability, app name.
+
+SCRIPT="$BATS_TEST_DIRNAME/../src/resolve-version.sh"
+
+setup() { export GITHUB_OUTPUT; GITHUB_OUTPUT="$(mktemp)"; }
+teardown() { rm -f "$GITHUB_OUTPUT"; }
+
+run_script() {
+ run env RAW_TAG="$1" IMAGE_REPO="$2" GITHUB_OUTPUT="$GITHUB_OUTPUT" "$SCRIPT"
+}
+
+assert_kv() {
+ local want="$1=$2" actual
+ actual=$(grep "^$1=" "$GITHUB_OUTPUT" | tail -n1)
+ [ "$actual" = "$want" ] || { echo "want: $want"; echo "got: $actual"; cat "$GITHUB_OUTPUT"; return 1; }
+}
+
+@test "v-prefixed stable tag" {
+ run_script 'v0.2.0' 'ghcr.io/loft-sh/revops-events-api'
+ [ "$status" -eq 0 ]
+ assert_kv new_tag v0.2.0
+ assert_kv new_version 0.2.0
+ assert_kv is_stable true
+ assert_kv app_name revops-events-api
+}
+
+@test "bare stable tag (no v)" {
+ run_script '1.4.2' 'ghcr.io/loft-sh/app'
+ [ "$status" -eq 0 ]
+ assert_kv new_tag 1.4.2
+ assert_kv new_version 1.4.2
+ assert_kv is_stable true
+}
+
+@test "pre-release tag is not stable" {
+ run_script 'v1.0.0-rc1' 'ghcr.io/loft-sh/app'
+ [ "$status" -eq 0 ]
+ assert_kv is_stable false
+ assert_kv new_version 1.0.0-rc1
+}
+
+@test "missing RAW_TAG fails" {
+ run env IMAGE_REPO='ghcr.io/loft-sh/app' GITHUB_OUTPUT="$GITHUB_OUTPUT" "$SCRIPT"
+ [ "$status" -ne 0 ]
+}
diff --git a/.github/actions/bump-manifest-image/test/should-update.bats b/.github/actions/bump-manifest-image/test/should-update.bats
new file mode 100644
index 0000000..3be5226
--- /dev/null
+++ b/.github/actions/bump-manifest-image/test/should-update.bats
@@ -0,0 +1,62 @@
+#!/usr/bin/env bats
+# Decision-table coverage for should-update.sh.
+
+SCRIPT="$BATS_TEST_DIRNAME/../src/should-update.sh"
+FIXTURE="$BATS_TEST_DIRNAME/fixtures/deployment.yaml"
+
+setup() {
+ export GITHUB_OUTPUT; GITHUB_OUTPUT="$(mktemp)"
+ # Work on a copy so the fixture is never mutated.
+ MANIFEST="$(mktemp)"; cp "$FIXTURE" "$MANIFEST"
+}
+teardown() { rm -f "$GITHUB_OUTPUT" "$MANIFEST"; }
+
+run_script() {
+ # args: new_version is_stable environment [manifest] [container]
+ run env \
+ MANIFEST_PATH="${4:-$MANIFEST}" CONTAINER_NAME="${5:-revops-events-api}" \
+ NEW_VERSION="$1" IS_STABLE="$2" ENVIRONMENT="$3" \
+ GITHUB_OUTPUT="$GITHUB_OUTPUT" "$SCRIPT"
+}
+
+assert_kv() {
+ local want="$1=$2" actual
+ actual=$(grep "^$1=" "$GITHUB_OUTPUT" | tail -n1)
+ [ "$actual" = "$want" ] || { echo "want: $want"; echo "got: $actual"; cat "$GITHUB_OUTPUT"; return 1; }
+}
+
+@test "newer stable version → should_update true" {
+ run_script 0.2.0 true staging
+ [ "$status" -eq 0 ]; assert_kv should_update true; assert_kv current_tag v0.1.0
+}
+
+@test "same version → should_update false" {
+ run_script 0.1.0 true staging
+ [ "$status" -eq 0 ]; assert_kv should_update false
+}
+
+@test "older version → should_update false (no downgrade)" {
+ run_script 0.0.9 true staging
+ [ "$status" -eq 0 ]; assert_kv should_update false
+}
+
+@test "pre-release to prod → skipped" {
+ run_script 0.2.0-rc1 false prod
+ [ "$status" -eq 0 ]; assert_kv should_update false
+ assert_kv reason "pre-release skipped for prod"
+}
+
+@test "pre-release to staging → allowed when newer" {
+ run_script 0.2.0-rc1 false staging
+ [ "$status" -eq 0 ]; assert_kv should_update true
+}
+
+@test "missing manifest → false with reason" {
+ run_script 0.2.0 true staging /no/such/file.yaml
+ [ "$status" -eq 0 ]; assert_kv should_update false
+}
+
+@test "unknown container → false" {
+ run_script 0.2.0 true staging "$MANIFEST" not-a-container
+ [ "$status" -eq 0 ]; assert_kv should_update false
+}
diff --git a/.github/workflows/test-bump-manifest-image.yaml b/.github/workflows/test-bump-manifest-image.yaml
new file mode 100644
index 0000000..5412680
--- /dev/null
+++ b/.github/workflows/test-bump-manifest-image.yaml
@@ -0,0 +1,22 @@
+name: Test bump-manifest-image
+
+on:
+ pull_request:
+ paths:
+ - '.github/workflows/test-bump-manifest-image.yaml'
+ - '.github/actions/bump-manifest-image/**'
+
+permissions:
+ contents: read
+
+jobs:
+ bats:
+ name: Run bats tests
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ persist-credentials: false
+ - uses: bats-core/bats-action@77d6fb60505b4d0d1d73e48bd035b55074bbfb43 # 4.0.0
+ with:
+ tests: .github/actions/bump-manifest-image/test
diff --git a/Makefile b/Makefile
index 9717582..d7b2e23 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-.PHONY: test test-semver-validation test-linear-pr-commenter test-release-notification test-linear-release-sync test-aws-test-infra test-cleanup-head-charts test-ci-test-notify test-auto-approve-bot-prs test-ai-pr-review test-ai-step test-publish-helm-chart test-govulncheck test-go-licenses test-run-ginkgo test-sticky-pr-comment test-repository-dispatch build-linear-release-sync lint install-auto-doc generate-docs check-docs help
+.PHONY: test test-semver-validation test-linear-pr-commenter test-release-notification test-linear-release-sync test-aws-test-infra test-cleanup-head-charts test-ci-test-notify test-auto-approve-bot-prs test-ai-pr-review test-ai-step test-publish-helm-chart test-govulncheck test-go-licenses test-run-ginkgo test-sticky-pr-comment test-repository-dispatch test-bump-manifest-image build-linear-release-sync lint install-auto-doc generate-docs check-docs help
ACTIONS_DIR := .github/actions
WORKFLOWS_DIR := .github/workflows
@@ -93,7 +93,7 @@ check-docs: generate-docs ## verify docs are up to date (fails if drift detected
help: ## show this help
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " %-30s %s\n", $$1, $$2}'
-test: test-semver-validation test-linear-pr-commenter test-release-notification test-linear-release-sync test-aws-test-infra test-cleanup-head-charts test-auto-approve-bot-prs test-ai-pr-review test-ai-step test-ci-test-notify test-go-licenses test-publish-helm-chart test-govulncheck test-run-ginkgo test-sticky-pr-comment test-repository-dispatch ## run all action tests
+test: test-semver-validation test-linear-pr-commenter test-release-notification test-linear-release-sync test-aws-test-infra test-cleanup-head-charts test-auto-approve-bot-prs test-ai-pr-review test-ai-step test-ci-test-notify test-go-licenses test-publish-helm-chart test-govulncheck test-run-ginkgo test-sticky-pr-comment test-repository-dispatch test-bump-manifest-image ## run all action tests
test-semver-validation: ## run semver-validation unit tests
cd $(ACTIONS_DIR)/semver-validation && npm ci --silent && NODE_OPTIONS=--experimental-vm-modules npx jest --ci --coverage --watchAll=false
@@ -143,6 +143,9 @@ test-sticky-pr-comment: ## run sticky-pr-comment bats tests
test-repository-dispatch: ## run repository-dispatch bats tests
bats $(ACTIONS_DIR)/repository-dispatch/test/*.bats
+test-bump-manifest-image: ## run bump-manifest-image bats tests
+ bats $(ACTIONS_DIR)/bump-manifest-image/test/*.bats
+
build-linear-release-sync: ## build linear-release-sync binary (linux/amd64)
cd $(ACTIONS_DIR)/linear-release-sync/src && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags="-s -w" -o ../linear-release-sync-linux-amd64 .