Merge pull request #26 from hfcRed/dev #87
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| pull_request: | |
| branches: [ main ] | |
| push: | |
| branches: [ main ] | |
| concurrency: | |
| group: ci-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| version-check: | |
| name: Verify .version is ahead of latest release | |
| runs-on: ubuntu-latest | |
| outputs: | |
| release_version: ${{ steps.validate.outputs.version }} | |
| steps: | |
| - name: Checkout (with tags) | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| fetch-tags: true | |
| - name: Validate .version bump uses file-as-truth | |
| id: validate | |
| run: | | |
| set -euo pipefail | |
| parse_semver() { | |
| local input="${1#v}" | |
| if [[ "$input" =~ ^([0-9]+)\.([0-9]+)(\.[0-9]+)?$ ]]; then | |
| local major="${BASH_REMATCH[1]}" | |
| local minor="${BASH_REMATCH[2]}" | |
| local patch="${BASH_REMATCH[3]#.}" | |
| if [[ -z "$patch" ]]; then patch=0; fi | |
| echo "${major} ${minor} ${patch}" | |
| else | |
| return 1 | |
| fi | |
| } | |
| if [ ! -f .version ]; then | |
| echo ".version file is missing." | |
| exit 1 | |
| fi | |
| RAW=$(tr -d '[:space:]' < .version) | |
| if [ -z "$RAW" ]; then | |
| echo ".version file is empty." | |
| exit 1 | |
| fi | |
| if ! desired_parts="$(parse_semver "$RAW")"; then | |
| echo ".version must be a valid semver string (e.g. 1.2, 1.2.3, v1.2.3)." | |
| exit 1 | |
| fi | |
| read -r desired_major desired_minor desired_patch <<<"$desired_parts" | |
| version="${desired_major}.${desired_minor}.${desired_patch}" | |
| tag=$(git tag --list 'v[0-9]*.[0-9]*.[0-9]*' --sort=-v:refname | head -n1) | |
| if [ -z "$tag" ]; then | |
| echo "No previous release tags found; accepting .version=$version." | |
| echo "version=${version}" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| if ! current_parts="$(parse_semver "$tag")"; then | |
| echo "Latest tag \"$tag\" is not a valid semver tag." | |
| exit 1 | |
| fi | |
| read -r current_major current_minor current_patch <<<"$current_parts" | |
| echo "Latest release tag: $tag" | |
| echo "Proposed .version: $version" | |
| if (( desired_major > current_major )) || (( desired_major == current_major && desired_minor > current_minor )) || (( desired_major == current_major && desired_minor == current_minor && desired_patch > current_patch )); then | |
| echo ".version (${version}) is greater than the latest release tag (${tag})." | |
| echo "version=${version}" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| echo ".version must be greater than the latest release tag (${tag})." | |
| exit 1 | |
| test: | |
| name: Run Tests and Checks | |
| runs-on: ubuntu-latest | |
| needs: version-check | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@v4 | |
| with: | |
| version: 10.12.4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| cache: pnpm | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Run Biome check | |
| run: pnpm biome check | |
| - name: TypeScript type check | |
| run: pnpm tsc --noEmit | |
| - name: Run tests | |
| run: pnpm test | |
| build-and-push: | |
| name: Build and Push Image | |
| needs: | |
| - test | |
| - version-check | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' && github.actor != 'github-actions[bot]' | |
| runs-on: ubuntu-latest | |
| environment: production | |
| # Expose next version to the release job | |
| outputs: | |
| release_version: ${{ steps.version.outputs.version }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| fetch-tags: true | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| - name: Use release version from .version (propagated) | |
| id: version | |
| shell: bash | |
| run: | | |
| VERSION="${{ needs.version-check.outputs.release_version }}" | |
| if [ -z "$VERSION" ]; then | |
| echo "Version not provided by version-check job." | |
| exit 1 | |
| fi | |
| echo "Release version: ${VERSION} (from .version)" | |
| echo "version=${VERSION}" >> "$GITHUB_OUTPUT" | |
| - name: Install doctl | |
| uses: digitalocean/action-doctl@v2 | |
| with: | |
| token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} | |
| - name: Log in to DigitalOcean Container Registry | |
| run: doctl registry login --expiry-seconds 1800 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Build and push image | |
| id: build-image | |
| uses: docker/build-push-action@v5 | |
| with: | |
| context: . | |
| file: ./Dockerfile | |
| push: true | |
| build-args: | | |
| APP_VERSION=${{ steps.version.outputs.version }} | |
| tags: | | |
| ${{ secrets.DO_REGISTRY_REPOSITORY }}/agent8s:latest | |
| ${{ secrets.DO_REGISTRY_REPOSITORY }}/agent8s:${{ github.sha }} | |
| ${{ secrets.DO_REGISTRY_REPOSITORY }}/agent8s:v${{ steps.version.outputs.version }} | |
| release: | |
| name: Tag repo and create GitHub Release | |
| needs: build-and-push | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' && github.actor != 'github-actions[bot]' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| fetch-tags: true | |
| - name: Configure git (for annotated tag) | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| - name: Create and push tag (idempotent) | |
| env: | |
| REL: ${{ needs.build-and-push.outputs.release_version }} | |
| run: | | |
| TAG="v${REL}" | |
| if git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then | |
| echo "Tag $TAG already exists; skipping push." | |
| else | |
| git tag -a "$TAG" -m "release: $TAG" | |
| git push origin "$TAG" | |
| fi | |
| - name: Create GitHub release (idempotent) | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| REL: ${{ needs.build-and-push.outputs.release_version }} | |
| run: | | |
| TAG="v${REL}" | |
| if gh release view "$TAG" >/dev/null 2>&1; then | |
| echo "Release $TAG already exists; nothing to do." | |
| else | |
| gh release create "$TAG" \ | |
| --repo="$GITHUB_REPOSITORY" \ | |
| --title="Agent 8s v${REL}" \ | |
| --generate-notes | |
| fi |