From 746648bd6ee228e7ac5de0f5e99ecc8e95b4d64b Mon Sep 17 00:00:00 2001 From: Dmytro Soroka Date: Sun, 23 Nov 2025 19:36:44 +0100 Subject: [PATCH 1/5] Refine version bump validation and improve latest tag retrieval logic --- .github/workflows/workflow-compute-next-tag.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/workflow-compute-next-tag.yml b/.github/workflows/workflow-compute-next-tag.yml index 435bf1a..01f29e5 100644 --- a/.github/workflows/workflow-compute-next-tag.yml +++ b/.github/workflows/workflow-compute-next-tag.yml @@ -53,14 +53,14 @@ jobs: case "$version_bump" in major|minor|patch) ;; *) - echo "Unsupported version bump '$version_bump'." + echo "Unsupported version bump '$version_bump'." >&2 exit 1 ;; esac git fetch --tags - latest_tag=$(git tag --list 'v*' --sort=-v:refname | head -n1) + latest_tag=$(git tag --list 'v[0-9]*.[0-9]*.[0-9]*' | sort -V | tail -n1) if [ -z "$latest_tag" ]; then latest_tag="v0.0.0" fi From 7b841d854eb964f4574f3fc0142e178486a0bbb9 Mon Sep 17 00:00:00 2001 From: Dmytro Soroka Date: Sun, 23 Nov 2025 19:39:40 +0100 Subject: [PATCH 2/5] Refactor CI workflow to ensure linting is a prerequisite for auto-merging PRs --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d39f866..1cd9efd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,8 +37,6 @@ jobs: determine_version: name: Detect version bump - needs: - - lint if: ${{ github.event_name == 'pull_request' && github.event.pull_request.draft == false }} uses: ./.github/workflows/workflow-determine-version-bump.yml with: @@ -58,8 +56,10 @@ jobs: merge_prs: name: Auto-merge PRs - needs: determine_version - if: ${{ needs.determine_version.result == 'success' && needs.determine_version.outputs.matching_pr == 'true' }} + needs: + - lint + - determine_version + if: ${{ needs.determine_version.result == 'success' && needs.determine_version.outputs.matching_pr == 'true' && needs.lint.result == 'success' }} uses: ./.github/workflows/workflow-merge-pull-requests.yml permissions: contents: write From 90b9bcdf3e68d0381662dd428a1752d3b5c6d09b Mon Sep 17 00:00:00 2001 From: Dmytro Soroka Date: Sun, 23 Nov 2025 20:44:43 +0100 Subject: [PATCH 3/5] Enhance S3 deployment workflow by adding target-branch input and updating Cloudflare API token handling --- .github/workflows/workflow-deploy-to-s3.yml | 42 +++++++++++---------- README.md | 13 ++++--- 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/.github/workflows/workflow-deploy-to-s3.yml b/.github/workflows/workflow-deploy-to-s3.yml index 8b233d2..9dc0146 100644 --- a/.github/workflows/workflow-deploy-to-s3.yml +++ b/.github/workflows/workflow-deploy-to-s3.yml @@ -3,6 +3,11 @@ name: Deploy Static Site to S3 on: workflow_call: inputs: + target-branch: + description: "Branch to check out before syncing." + required: false + default: master + type: string bucket: description: "Destination S3 bucket name (without the s3:// prefix)." required: true @@ -32,11 +37,6 @@ on: required: false default: true type: boolean - cloudflare-api-token: - description: "Optional Cloudflare API token with purge_cache permission." - required: false - default: "" - type: string email-subject: description: "Subject for the SES notification email (defaults to bucket name)." required: false @@ -47,16 +47,6 @@ on: required: false default: "" type: string - email-from: - description: "Sender address for SES notifications." - required: false - default: "" - type: string - email-to: - description: "Recipient address for SES notifications." - required: false - default: "" - type: string secrets: aws_access_key_id: description: "AWS access key for S3/SES." @@ -67,6 +57,15 @@ on: aws_session_token: description: "Optional session token for temporary credentials." required: false + cloudflare_api_token: + description: "Token with purge_cache permission." + required: false + email_from: + description: "Sender address for SES notifications." + required: false + email_to: + description: "Recipient address for SES notifications." + required: false outputs: deployed: description: "True when the sync step completed." @@ -88,6 +87,9 @@ jobs: steps: - name: Check out repository uses: actions/checkout@v4 + with: + ref: ${{ inputs.target-branch }} + fetch-depth: 0 - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4 @@ -117,10 +119,10 @@ jobs: echo "deployed=true" >> "$GITHUB_OUTPUT" - name: Purge Cloudflare cache - if: ${{ inputs.purge-cloudflare && inputs.cloudflare-zone-id != '' && inputs.cloudflare-api-token != '' }} + if: ${{ inputs.purge-cloudflare && inputs.cloudflare-zone-id != '' && env.CLOUDFLARE_API_TOKEN != '' }} env: CLOUDFLARE_ZONE_ID: ${{ inputs.cloudflare-zone-id }} - CLOUDFLARE_API_TOKEN: ${{ inputs.cloudflare-api-token }} + CLOUDFLARE_API_TOKEN: ${{ secrets.cloudflare_api_token }} run: | set -euo pipefail curl -X POST "https://api.cloudflare.com/client/v4/zones/${CLOUDFLARE_ZONE_ID}/purge_cache" \ @@ -129,10 +131,10 @@ jobs: --data '{"purge_everything":true}' - name: Send SES notification - if: ${{ inputs.email-from != '' && inputs.email-to != '' }} + if: ${{ env.EMAIL_FROM != '' && env.EMAIL_TO != '' }} env: - EMAIL_FROM: ${{ inputs.email-from }} - EMAIL_TO: ${{ inputs.email-to }} + EMAIL_FROM: ${{ secrets.email_from }} + EMAIL_TO: ${{ secrets.email_to }} CUSTOM_SUBJECT: ${{ inputs.email-subject }} CUSTOM_BODY: ${{ inputs.email-body }} run: | diff --git a/README.md b/README.md index c20d0fd..e7e791d 100644 --- a/README.md +++ b/README.md @@ -141,18 +141,19 @@ Syncs a directory to an S3 bucket with optional Cloudflare cache purge and SES n - `source` (default `public`): local directory to sync. - `aws-region` (default `us-west-2`): region for S3/SES calls. - `delete-extra-files` (default `true`): remove objects not present locally. +- `target-branch` (default `master`): branch to check out before syncing. - `cloudflare-zone-id` (optional): zone to purge after deploy. - `purge-cloudflare` (default `true`): whether to purge the zone when credentials are provided. -- `cloudflare-api-token` (optional): Cloudflare token (pass a secret from the caller). - `email-subject` (optional): SES email subject (defaults to the bucket name). - `email-body` (optional): SES email body (defaults to an auto-generated message). -- `email-from` (optional): sender address for SES notifications (pass a secret from the caller). -- `email-to` (optional): recipient address for SES notifications (pass a secret from the caller). **Secrets** - `aws_access_key_id` (required) - `aws_secret_access_key` (required) - `aws_session_token` (optional) +- `cloudflare_api_token` (optional) +- `email_from` (optional) +- `email_to` (optional) **Outputs** - `deployed`: `true` when the S3 sync completes. @@ -169,13 +170,13 @@ jobs: aws-region: us-west-2 delete-extra-files: true cloudflare-zone-id: ${{ secrets.CLOUDFLARE_ZONE_ID }} - cloudflare-api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }} email-subject: "Site deployed" - email-from: ${{ secrets.EMAIL_FROM }} - email-to: ${{ secrets.EMAIL_TO }} secrets: aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + cloudflare_api_token: ${{ secrets.CLOUDFLARE_API_TOKEN }} + email_from: ${{ secrets.EMAIL_FROM }} + email_to: ${{ secrets.EMAIL_TO }} ``` ### End-to-end usage in a caller repo From a52e7d3e5e6b7b387cfb18261e8475b16f463cbb Mon Sep 17 00:00:00 2001 From: Dmytro Soroka Date: Sun, 23 Nov 2025 20:46:44 +0100 Subject: [PATCH 4/5] Add optional 'ref' input to S3 deployment workflow to allow explicit git reference --- .github/workflows/workflow-deploy-to-s3.yml | 7 ++++++- README.md | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/workflow-deploy-to-s3.yml b/.github/workflows/workflow-deploy-to-s3.yml index 9dc0146..d5a45e9 100644 --- a/.github/workflows/workflow-deploy-to-s3.yml +++ b/.github/workflows/workflow-deploy-to-s3.yml @@ -8,6 +8,11 @@ on: required: false default: master type: string + ref: + description: "Optional explicit git ref (commit SHA/tag/branch) to deploy; overrides target-branch when set." + required: false + default: "" + type: string bucket: description: "Destination S3 bucket name (without the s3:// prefix)." required: true @@ -88,7 +93,7 @@ jobs: - name: Check out repository uses: actions/checkout@v4 with: - ref: ${{ inputs.target-branch }} + ref: ${{ inputs.ref != '' && inputs.ref || inputs.target-branch }} fetch-depth: 0 - name: Configure AWS credentials diff --git a/README.md b/README.md index e7e791d..306fee1 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,7 @@ Syncs a directory to an S3 bucket with optional Cloudflare cache purge and SES n - `aws-region` (default `us-west-2`): region for S3/SES calls. - `delete-extra-files` (default `true`): remove objects not present locally. - `target-branch` (default `master`): branch to check out before syncing. +- `ref` (optional): explicit git ref (commit SHA/tag/branch) to deploy; overrides `target-branch` when set. - `cloudflare-zone-id` (optional): zone to purge after deploy. - `purge-cloudflare` (default `true`): whether to purge the zone when credentials are provided. - `email-subject` (optional): SES email subject (defaults to the bucket name). @@ -165,6 +166,7 @@ jobs: needs: tests uses: vinitu-net/github-workflows/.github/workflows/workflow-deploy-to-s3.yml@vX.Y.Z with: + ref: ${{ github.sha }} bucket: www.example.com source: public aws-region: us-west-2 From 31d43ba879b775b4e3bdd825b2d29fa8e6098473 Mon Sep 17 00:00:00 2001 From: Dmytro Soroka Date: Fri, 28 Nov 2025 19:58:45 +0100 Subject: [PATCH 5/5] Add CI workflows for main push and PR auto-merge, including version file update --- .github/workflows/ci-main.yml | 73 ++++++++++++++++++ .github/workflows/{ci.yml => ci-pr.yml} | 4 +- .../workflow-update-version-file.yml | 74 +++++++++++++++++++ README.md | 24 ++++++ 4 files changed, 173 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/ci-main.yml rename .github/workflows/{ci.yml => ci-pr.yml} (97%) create mode 100644 .github/workflows/workflow-update-version-file.yml diff --git a/.github/workflows/ci-main.yml b/.github/workflows/ci-main.yml new file mode 100644 index 0000000..b2e663d --- /dev/null +++ b/.github/workflows/ci-main.yml @@ -0,0 +1,73 @@ +name: CI · Main Push · Patch Release + +on: + push: + branches: + - main + workflow_dispatch: + +permissions: + contents: write + +concurrency: + group: ci-main-github-workflows + cancel-in-progress: true + +jobs: + lint: + name: Run actionlint + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install actionlint + run: | + curl -sSfL https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash \ + | bash -s -- latest /usr/local/bin + + - name: Run actionlint + run: actionlint + + calculate_tag: + name: Compute next tag + needs: + - lint + if: | + github.event_name == 'push' && + github.ref == 'refs/heads/main' && + github.actor != 'github-actions[bot]' && + needs.lint.result == 'success' + uses: ./.github/workflows/workflow-compute-next-tag.yml + with: + target-branch: main + version-bump: patch + secrets: + gh_token: ${{ secrets.GITHUB_TOKEN }} + + create_tag: + name: Create git tag + needs: + - lint + - calculate_tag + if: ${{ needs.calculate_tag.result == 'success' }} + uses: ./.github/workflows/workflow-create-tag.yml + with: + target-branch: main + next-tag: ${{ needs.calculate_tag.outputs['new-tag'] }} + previous-tag: ${{ needs.calculate_tag.outputs['previous-tag'] }} + secrets: + gh_token: ${{ secrets.GITHUB_TOKEN }} + + create_release: + name: Publish release + needs: + - create_tag + if: ${{ needs.create_tag.result == 'success' }} + uses: ./.github/workflows/workflow-create-release.yml + with: + tag-name: ${{ needs.create_tag.outputs['new-tag'] }} + previous-tag: ${{ needs.create_tag.outputs['previous-tag'] }} + merged-prs: "[]" + secrets: + gh_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci-pr.yml similarity index 97% rename from .github/workflows/ci.yml rename to .github/workflows/ci-pr.yml index 1cd9efd..eb769c5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci-pr.yml @@ -1,4 +1,4 @@ -name: CI · Merge · Release +name: CI · PR Auto-merge · Release on: pull_request: @@ -16,7 +16,7 @@ permissions: pull-requests: write concurrency: - group: ci-github-workflows + group: ci-pr-github-workflows cancel-in-progress: true jobs: diff --git a/.github/workflows/workflow-update-version-file.yml b/.github/workflows/workflow-update-version-file.yml new file mode 100644 index 0000000..dc2a081 --- /dev/null +++ b/.github/workflows/workflow-update-version-file.yml @@ -0,0 +1,74 @@ +name: Update version file + +on: + workflow_call: + inputs: + target-branch: + description: Branch to check out before writing the version file + required: false + default: master + type: string + version-file: + description: Path to the version file to overwrite + required: false + default: public/version.txt + type: string + next-tag: + description: Tag value to write into the version file + required: true + type: string + secrets: + gh_token: + required: true + + outputs: + new-tag: + description: Tag that was written into the version file + value: ${{ jobs.update_version_file.outputs.new_tag }} + +jobs: + update_version_file: + name: Update version file + runs-on: ubuntu-latest + permissions: + contents: write + outputs: + new_tag: ${{ steps.write_version.outputs.new_tag }} + steps: + - name: Check out branch with tags + uses: actions/checkout@v4 + with: + ref: ${{ inputs['target-branch'] }} + fetch-depth: 0 + token: ${{ secrets.gh_token }} + + - name: Write version marker + id: write_version + env: + NEXT_TAG: ${{ inputs['next-tag'] }} + VERSION_FILE: ${{ inputs['version-file'] }} + run: | + set -euo pipefail + if [ -z "${NEXT_TAG:-}" ]; then + echo "next-tag input is required" >&2 + exit 1 + fi + mkdir -p "$(dirname "$VERSION_FILE")" + printf '%s\n' "$NEXT_TAG" > "$VERSION_FILE" + echo "new_tag=${NEXT_TAG}" >> "$GITHUB_OUTPUT" + + - name: Commit version marker + env: + VERSION_FILE: ${{ inputs['version-file'] }} + NEW_TAG: ${{ steps.write_version.outputs.new_tag }} + run: | + set -euo pipefail + git config --local user.name "github-actions[bot]" + git config --local user.email "github-actions[bot]@users.noreply.github.com" + git add "$VERSION_FILE" + if git diff --cached --quiet; then + echo "Version file already up to date." + exit 0 + fi + git commit -m "Record version ${NEW_TAG}" + git push origin HEAD:${{ inputs['target-branch'] }} diff --git a/README.md b/README.md index 306fee1..8e473c9 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,30 @@ jobs: gh_token: ${{ secrets.GITHUB_TOKEN }} ``` +### `workflow-update-version-file.yml` +Writes the provided tag into a version file on a target branch and pushes the commit. + +**Inputs** +- `target-branch` (default `master`): branch to check out before writing the version file. +- `version-file` (default `public/version.txt`): path to overwrite with the new tag. +- `next-tag` (required): tag value to write. + +**Outputs** +- `new-tag`: tag that was written. + +**Example** +```yaml +jobs: + write-version: + uses: vinitu-net/github-workflows/.github/workflows/workflow-update-version-file.yml@vX.Y.Z + with: + target-branch: master + version-file: public/version.txt + next-tag: ${{ needs.calculate-tag.outputs.new-tag }} + secrets: + gh_token: ${{ secrets.GITHUB_TOKEN }} +``` + ### `workflow-create-release.yml` Publishes a GitHub Release for a given tag. If `merged-prs` is omitted or empty, it collects merged PRs between `previous-tag` and `tag-name`.