name: Build & Push Docker Image on: push: branches: [main] paths-ignore: - 'docs/**' - '**/*.md' - 'wiki/**' - '.github/workflows/wiki.yml' workflow_dispatch: inputs: bump: description: 'Force bump line (auto = patch/finalize as today)' type: choice options: [auto, patch, minor, major] default: auto confirm_major: description: "Type MAJOR (all caps) to confirm a major release" type: string default: '' permissions: contents: write concurrency: group: stable-build cancel-in-progress: false jobs: version-bump: runs-on: ubuntu-latest outputs: version: ${{ steps.bump.outputs.VERSION }} steps: - uses: actions/checkout@v4 with: fetch-depth: 0 fetch-tags: true token: ${{ secrets.GITHUB_TOKEN }} - name: Determine bump type and update version id: bump run: | git fetch --tags # Derive version from git tags — no package.json dependency STABLE_TAG=$(git tag -l 'v[0-9]*.[0-9]*.[0-9]*' | grep -v '\-pre\.' | sort -V | tail -1) STABLE="${STABLE_TAG#v}" STABLE="${STABLE:-0.0.0}" PRE_TAG=$(git tag -l 'v*-pre.*' | sort -V | tail -1) BUMP_INPUT="${{ github.event.inputs.bump || 'auto' }}" IFS='.' read -r MAJOR MINOR PATCH <<< "$STABLE" if [ "$BUMP_INPUT" = "major" ]; then if [ "${{ github.event.inputs.confirm_major }}" != "MAJOR" ]; then echo "::error::confirm_major must equal 'MAJOR' to cut a major release" exit 1 fi NEW_VERSION="$((MAJOR + 1)).0.0" BUMP="major" elif [ "$BUMP_INPUT" = "minor" ]; then NEW_VERSION="${MAJOR}.$((MINOR + 1)).0" BUMP="minor" elif [ "$BUMP_INPUT" = "patch" ]; then NEW_VERSION="${MAJOR}.${MINOR}.$((PATCH + 1))" BUMP="patch" else # auto: finalize in-flight prerelease if one exists, else patch if [ -n "$PRE_TAG" ]; then PRE_BASE="${PRE_TAG#v}" PRE_BASE="${PRE_BASE%-pre.*}" PRE_MAJOR="$(echo "$PRE_BASE" | cut -d. -f1)" # Refuse to auto-finalize a major bump — it bypasses confirm_major if [ "$PRE_MAJOR" -gt "$MAJOR" ]; then echo "::error::In-flight prerelease $PRE_TAG is a major bump ($STABLE → $PRE_BASE). Use bump=major with confirm_major=MAJOR to finalize." exit 1 fi # If prerelease base is strictly greater than stable, finalize it HIGHEST=$(printf '%s\n' "$PRE_BASE" "$STABLE" | sort -V | tail -1) if [ "$HIGHEST" = "$PRE_BASE" ] && [ "$PRE_BASE" != "$STABLE" ]; then NEW_VERSION="$PRE_BASE" BUMP="finalize" else PATCH=$((PATCH + 1)) NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}" BUMP="patch" fi else PATCH=$((PATCH + 1)) NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}" BUMP="patch" fi fi echo "Bump type: $BUMP" echo "VERSION=$NEW_VERSION" >> $GITHUB_OUTPUT echo "$STABLE → $NEW_VERSION ($BUMP)" # Update package.json files and Helm chart cd server && npm version "$NEW_VERSION" --no-git-tag-version && cd .. cd client && npm version "$NEW_VERSION" --no-git-tag-version && cd .. sed -i "s/^version: .*/version: $NEW_VERSION/" charts/trek/Chart.yaml sed -i "s/^appVersion: .*/appVersion: \"$NEW_VERSION\"/" charts/trek/Chart.yaml # Commit and tag git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" git add server/package.json server/package-lock.json client/package.json client/package-lock.json charts/trek/Chart.yaml git commit -m "chore: bump version to $NEW_VERSION [skip ci]" git tag "v$NEW_VERSION" git push origin main --follow-tags build: runs-on: ${{ matrix.runner }} needs: version-bump strategy: fail-fast: false matrix: include: - platform: linux/amd64 runner: ubuntu-latest - platform: linux/arm64 runner: ubuntu-24.04-arm steps: - name: Prepare platform tag-safe name run: echo "PLATFORM_PAIR=$(echo ${{ matrix.platform }} | sed 's|/|-|g')" >> $GITHUB_ENV - uses: actions/checkout@v4 with: ref: main - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push by digest id: build uses: docker/build-push-action@v6 with: context: . platforms: ${{ matrix.platform }} outputs: type=image,name=mauriceboe/trek,push-by-digest=true,name-canonical=true,push=true no-cache: true build-args: | APP_VERSION=${{ needs.version-bump.outputs.version }} - name: Export digest run: | mkdir -p /tmp/digests digest="${{ steps.build.outputs.digest }}" touch "/tmp/digests/${digest#sha256:}" - name: Upload digest artifact uses: actions/upload-artifact@v4 with: name: digests-${{ env.PLATFORM_PAIR }} path: /tmp/digests/* if-no-files-found: error retention-days: 1 merge: runs-on: ubuntu-latest needs: [version-bump, build] steps: - uses: actions/checkout@v4 with: ref: main - name: Download build digests uses: actions/download-artifact@v4 with: path: /tmp/digests pattern: digests-* merge-multiple: true - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Create and push multi-arch manifest working-directory: /tmp/digests run: | VERSION="${{ needs.version-bump.outputs.version }}" mapfile -t digests < <(printf 'mauriceboe/trek@sha256:%s\n' *) MAJOR_TAG="$(echo "$VERSION" | cut -d. -f1)" docker buildx imagetools create \ -t "mauriceboe/trek:latest" \ -t "mauriceboe/trek:$MAJOR_TAG" \ -t "mauriceboe/trek:$VERSION" \ "${digests[@]}" - name: Inspect manifest run: docker buildx imagetools inspect mauriceboe/trek:latest release-helm: runs-on: ubuntu-latest needs: version-bump steps: - name: Checkout uses: actions/checkout@v4 with: ref: main - name: Publish Helm chart uses: stefanprodan/helm-gh-pages@v1.7.0 with: token: ${{ secrets.GITHUB_TOKEN }} charts_dir: charts