name: CI/CD Pipeline on: workflow_dispatch: push: branches: [ "development", "main", "staging" ] tags: [ "v*.*.*" ] env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} jobs: set-tag: name: Set Tag Name runs-on: ubuntu-latest outputs: tag_name: ${{ steps.set_tag.outputs.tag_name }} is_main_branch: ${{ steps.branch_check.outputs.is_main }} steps: - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 0 # Fetch all history for tags - name: Check if main branch id: branch_check run: | if [[ "${GITHUB_REF}" == "refs/heads/main" || "${GITHUB_REF}" == "refs/heads/master" ]]; then echo "is_main_branch=true" >> $GITHUB_OUTPUT else echo "is_main_branch=false" >> $GITHUB_OUTPUT fi - name: Determine next semantic version tag id: set_tag run: | git fetch --tags # Find latest tag matching vX.Y.Z latest_tag=$(git tag --list 'v*.*.*' --sort=-v:refname | head -n 1) if [[ -z "$latest_tag" ]]; then major=0 minor=0 patch=0 else version="${latest_tag#v}" IFS='.' read -r major minor patch <<< "$version" fi if [[ "${GITHUB_REF}" == "refs/heads/main" || "${GITHUB_REF}" == "refs/heads/master" ]]; then major=$((major + 1)) minor=0 patch=0 elif [[ "${GITHUB_REF}" == "refs/heads/development" ]]; then minor=$((minor + 1)) patch=0 else patch=$((patch + 1)) fi new_tag="v${major}.${minor}.${patch}" echo "tag_name=${new_tag}" >> $GITHUB_OUTPUT echo "Next version tag: ${new_tag}" test: runs-on: ubuntu-latest needs: set-tag steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' cache-dependency-path: backend/package-lock.json - name: Install backend dependencies working-directory: ./backend run: | # Try npm ci first, if it fails use npm install npm ci || (echo "package-lock.json out of sync, using npm install..." && npm install) - name: Run TypeScript check working-directory: ./backend run: npx tsc --noEmit - name: Run backend tests working-directory: ./backend run: | # Skip tests if jest is not installed if [ -f "node_modules/.bin/jest" ]; then npm test else echo "⚠️ Jest not installed. Skipping tests." fi - name: Setup Python uses: actions/setup-python@v4 with: python-version: '3.11' - name: Install Python dependencies run: pip install ortools - name: Test Python integration run: | python -c "from ortools.sat.python import cp_model; print('OR-Tools available')" - name: Display next version run: | echo "Next version will be: ${{ needs.set-tag.outputs.tag_name }}" build-and-push: needs: [set-tag, test] runs-on: ubuntu-latest if: github.event_name == 'push' permissions: contents: write packages: write id-token: write steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Log in to GitHub Container Registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Extract metadata id: meta uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | type=ref,event=branch type=semver,pattern={{version}} type=sha # Add the dynamically generated semantic version ${{ needs.set-tag.outputs.tag_name }} # Add latest tag for main branch ${{ needs.set-tag.outputs.is_main_branch == 'true' && 'latest' }} - name: Build and push Docker image uses: docker/build-push-action@v5 with: context: . file: ./Dockerfile platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max - name: Create Git Tag if: success() run: | git config --local user.email "action@github.com" git config --local user.name "GitHub Action" git tag ${{ needs.set-tag.outputs.tag_name }} git push origin ${{ needs.set-tag.outputs.tag_name }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Display pushed images run: | echo "Docker images pushed successfully!" echo "- Image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}" echo "- Tags: ${{ steps.meta.outputs.tags }}" echo "- New version: ${{ needs.set-tag.outputs.tag_name }}" echo "- Is main branch: ${{ needs.set-tag.outputs.is_main_branch }}"