Exemplos CircleCI Configuração

Exemplos de configuração CircleCI incluindo workflows, orbs, caching e estratégias de implantação avançadas

💻 CircleCI Configuração Básica yaml

🟢 simple ⭐⭐

Configuração CircleCI simples com jobs de build, teste e deploy para aplicação Node.js

⏱️ 15 min 🏷️ circleci, nodejs, workflow, testing
Prerequisites: CircleCI basics, YAML syntax, CI/CD concepts
# .circleci/config.yml - Basic CircleCI Configuration

version: 2.1

# Define reusable executor
executors:
  node-executor:
    docker:
      - image: cimg/node:18.17
      - image: cimg/postgres:15
        environment:
          POSTGRES_USER: test_user
          POSTGRES_PASSWORD: test_password
          POSTGRES_DB: test_db
    working_directory: ~/project
    resource_class: medium

# Define job templates
commands:
  setup-node:
    description: "Setup Node.js environment"
    parameters:
      cache-key:
        type: string
        default: "node-v1-{{ checksum "package-lock.json" }}"
    steps:
      - restore_cache:
          keys:
            - << parameters.cache-key >>
            - node-v1-
      - run:
          name: Install dependencies
          command: npm ci
      - save_cache:
          key: << parameters.cache-key >>
          paths:
            - ~/.npm
            - node_modules

  run-tests:
    description: "Run test suite"
    parameters:
      coverage:
        type: boolean
        default: true
    steps:
      - run:
          name: Run tests
          command: |
            <<# parameters.coverage >>
            npm run test:coverage
            << parameters.coverage >>
            npm run test
      - store_test_results:
          path: test-results
      <<# parameters.coverage >>
      - store_artifacts:
          path: coverage
          destination: coverage-report
      - store_artifacts:
          path: coverage/lcov.info
          destination: coverage-lcov
      <</ parameters.coverage >>

# Job definitions
jobs:
  # Build job
  build:
    executor: node-executor
    steps:
      - checkout
      - setup-node
      - run:
          name: Build application
          command: npm run build
      - persist_to_workspace:
          root: ~/project
          paths:
            - .
      - store_artifacts:
          path: dist
          destination: build-artifacts

  # Lint job
  lint:
    executor: node-executor
    steps:
      - checkout
      - setup-node
      - run:
          name: Run linter
          command: npm run lint
      - store_artifacts:
          path: lint-results.json
          destination: lint-report

  # Unit tests
  test:unit:
    executor: node-executor
    steps:
      - checkout
      - setup-node
      - run-tests:
          coverage: true

  # Integration tests
  test:integration:
    executor: node-executor
    environment:
      DATABASE_URL: postgresql://test_user:test_password@localhost:5432/test_db
      NODE_ENV: test
    steps:
      - checkout
      - setup-node
      - run:
          name: Wait for database
          command: dockerize -wait tcp://localhost:5432 -timeout 1m
      - run:
          name: Run integration tests
          command: npm run test:integration
      - store_test_results:
          path: integration-test-results

  # E2E tests
  test:e2e:
    executor:
      name: node-executor
      resource_class: large
    steps:
      - checkout
      - setup-node
      - run:
          name: Build for E2E tests
          command: npm run build
      - run:
          name: Install Playwright
          command: |
            npx playwright install --with-deps
            npx playwright install-deps
      - run:
          name: Run E2E tests
          command: npx playwright test
      - store_artifacts:
          path: playwright-report
          destination: playwright-report
      - store_test_results:
          path: test-results

  # Security scan
  security:
    executor: node-executor
    steps:
      - checkout
      - setup-node
      - run:
          name: Security audit
          command: |
            npm audit --audit-level=high
            npm audit --json > security-audit.json
      - store_artifacts:
          path: security-audit.json
          destination: security-report

  # Deploy to staging
  deploy:staging:
    executor: node-executor
    environment:
      name: staging
      url: https://staging.example.com
    steps:
      - attach_workspace:
          at: ~/project
      - run:
          name: Deploy to staging
          command: |
            echo "Deploying to staging environment..."
            # Add your deployment commands here
            # Example: scp -r dist/* user@staging-server:/var/www/html/
      - run:
          name: Health check
          command: |
            sleep 30
            curl -f https://staging.example.com/health || exit 1

  # Deploy to production
  deploy:production:
    executor: node-executor
    environment:
      name: production
      url: https://example.com
    steps:
      - attach_workspace:
          at: ~/project
      - run:
          name: Deploy to production
          command: |
            echo "Deploying to production environment..."
            # Add your production deployment commands here
            # Example:
            # aws s3 sync dist/ s3://production-bucket/ --delete
            # aws cloudfront create-invalidation --distribution-id $CLOUDFRONT_ID --paths "/*"
      - run:
          name: Production health check
          command: |
            sleep 60
            curl -f https://example.com/health || exit 1

# Define workflows
workflows:
  version: 2
  # Main workflow
  build-and-deploy:
    jobs:
      - build
      - lint:
          requires:
            - build
      - test:unit:
          requires:
            - build
      - test:integration:
          requires:
            - build
      - test:e2e:
          requires:
            - build
      - security:
          requires:
            - build
      - hold-for-approval:
          type: approval
          requires:
            - lint
            - test:unit
            - test:integration
            - test:e2e
            - security
          filters:
            branches:
              only:
                - main
      - deploy:staging:
          requires:
            - hold-for-approval
          filters:
            branches:
              only:
                - main
      - deploy:production:
          requires:
            - deploy:staging
          filters:
            branches:
              only:
                - main

  # Nightly workflow
  nightly:
    triggers:
      - schedule:
          cron: "0 2 * * *"  # Run at 2 AM UTC
          filters:
            branches:
              only:
                - main
    jobs:
      - build
      - test:unit:
          requires:
            - build
      - test:integration:
          requires:
            - build
      - security:
          requires:
            - build

  # Feature branch workflow
  feature-branch:
    jobs:
      - build
      - lint:
          requires:
            - build
      - test:unit:
          requires:
            - build
      - test:integration:
          requires:
            - build
      filters:
        branches:
          ignore:
            - main
            - staging
            - production

💻 CircleCI Workflows Avançados com Orbs yaml

🟡 intermediate ⭐⭐⭐⭐

Configuração CircleCI avançada usando orbs, configuração dinâmica e estratégias de implantação complexas

⏱️ 30 min 🏷️ circleci, orbs, kubernetes, blue-green, deployment
Prerequisites: CircleCI advanced, Docker, Kubernetes, Helm, AWS EKS
# .circleci/config.yml - Advanced Configuration with Orbs

version: 2.1

# Import official orbs
orbs:
  # Node.js orb for Node.js specific commands
  node: circleci/[email protected]

  # Docker orb for Docker operations
  docker: circleci/[email protected]

  # AWS orb for AWS deployments
  aws: circleci/[email protected]

  # Slack orb for notifications
  slack: circleci/[email protected]

  # Helm orb for Kubernetes deployments
  helm: circleci/[email protected]

# Custom commands
commands:
  setup-environment:
    description: "Setup environment variables and configuration"
    parameters:
      environment:
        type: string
        default: "development"
    steps:
      - run:
          name: Setup environment
          command: |
            echo "Setting up environment: << parameters.environment >>"
            echo "NODE_ENV=<< parameters.environment >>" >> $BASH_ENV
            echo "BUILD_NUMBER=$CIRCLE_BUILD_NUM" >> $BASH_ENV
            echo "BRANCH_NAME=$CIRCLE_BRANCH" >> $BASH_ENV
            echo "COMMIT_SHA=$CIRCLE_SHA1" >> $BASH_ENV
            echo "CI=true" >> $BASH_ENV

  deploy-notification:
    description: "Send deployment notification"
    parameters:
      environment:
        type: string
      status:
        type: string
        default: "started"
    steps:
      - slack/notify:
          event: always
          template: basic_success_1
          channel: '#deployments'
          custom: |
            {
              "text": "🚀 Deployment << parameters.status >> for << parameters.environment >>",
              "attachments": [
                {
                  "color": "<< parameters.status >>" == "completed" ? "good" : "warning",
                  "fields": [
                    {
                      "title": "Branch",
                      "value": "$CIRCLE_BRANCH",
                      "short": true
                    },
                    {
                      "title": "Commit",
                      "value": "$CIRCLE_SHA1",
                      "short": true
                    },
                    {
                      "title": "Build URL",
                      "value": "<$CIRCLE_BUILD_URL|View Build>",
                      "short": false
                    }
                  ]
                }
              ]
            }

# Custom executors
executors:
  node-executor:
    docker:
      - image: cimg/node:18.17
    working_directory: ~/project
    resource_class: medium

  docker-executor:
    machine:
      image: ubuntu-2204:2023.04.2
    resource_class: medium

  large-executor:
    docker:
      - image: cimg/node:18.17
    resource_class: large

# Job definitions
jobs:
  # Build job with caching and optimization
  build:
    executor: node-executor
    steps:
      - checkout
      - setup-environment:
          environment: production
      - node/install-packages:
          cache-version: v1
          pkg-manager: npm
      - run:
          name: Build application
          command: |
            npm run build
            npm run analyze
      - persist_to_workspace:
          root: ~/project
          paths:
            - dist/
            - node_modules/
            - package.json
            - package-lock.json
      - store_artifacts:
          path: dist/
          destination: build-artifacts
      - store_artifacts:
          path: bundle-analysis.html
          destination: bundle-analysis

  # Multi-environment testing
  test-matrix:
    parameters:
      node-version:
        type: string
      test-type:
        type: enum
        enum: ["unit", "integration", "e2e"]
    executor:
      name: node-executor
      docker:
        - image: cimg/node:<< parameters.node-version >>
    steps:
      - checkout
      - setup-environment:
          environment: test
      - node/install-packages:
          cache-version: "v1-<< parameters.node-version >>"
      - when:
          condition:
            equal: [ unit, << parameters.test-type >> ]
          steps:
            - run:
                name: Run unit tests
                command: npm run test:unit
            - store_test_results:
                path: test-results
            - store_artifacts:
                path: coverage/
                destination: coverage-report
      - when:
          condition:
            equal: [ integration, << parameters.test-type >> ]
          steps:
            - run:
                name: Run integration tests
                command: npm run test:integration
            - store_test_results:
                path: integration-test-results
      - when:
          condition:
            equal: [ e2e, << parameters.test-type >> ]
          steps:
            - run:
                name: Install Playwright
                command: |
                  npx playwright install --with-deps
                  npx playwright install-deps
            - run:
                name: Build application
                command: npm run build
            - run:
                name: Run E2E tests
                command: npx playwright test
            - store_artifacts:
                path: playwright-report
                destination: playwright-report

  # Docker build and push
  docker-build:
    executor: docker-executor
    parameters:
      image-name:
        type: string
      tag:
        type: string
        default: "$CIRCLE_SHA1"
    steps:
      - checkout
      - setup-environment
      - docker/check:
          docker-username: DOCKER_USERNAME
          docker-password: DOCKER_PASSWORD
      - docker/build:
          image: << parameters.image-name >>
          tag: << parameters.tag >>
          path: .
          docker-context: .
          extra-build-args: |
            --build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
            --build-arg VCS_REF=$CIRCLE_SHA1
            --build-arg VERSION=<< parameters.tag >>
      - docker/push:
          image: << parameters.image-name >>
          tag: << parameters.tag >>

  # Security scanning
  security-scan:
    executor: node-executor
    parallelism: 2
    steps:
      - checkout
      - setup-environment
      - node/install-packages
      - run:
          name: Security audit
          command: |
            npm audit --audit-level=high --json > npm-audit.json || true
            npm audit --audit-level=high
      - run:
          name: SAST scan
          command: |
            npm install -g semgrep
            semgrep --config=auto --json --output=semgrep-report.json .
      - store_artifacts:
          path: npm-audit.json
          destination: npm-security-audit
      - store_artifacts:
          path: semgrep-report.json
          destination: sast-report

  # Kubernetes deployment
  deploy:k8s:
    executor: docker-executor
    parameters:
      environment:
        type: string
      namespace:
        type: string
      helm-chart:
        type: string
        default: "./helm/app"
    steps:
      - checkout
      - setup-environment:
          environment: << parameters.environment >>
      - aws-eks/update-kubeconfig:
          aws-region: AWS_REGION
          cluster-name: EKS_CLUSTER_NAME
          install-kubectl: true
      - helm/install-helm-client:
          version: v3.12.0
      - run:
          name: Deploy with Helm
          command: |
            helm upgrade --install app-<< parameters.namespace >> \
              << parameters.helm-chart >> \
              --namespace << parameters.namespace >> \
              --create-namespace \
              --set image.tag=$CIRCLE_SHA1 \
              --set environment=<< parameters.environment >> \
              --set ingress.hosts[0].host=<< parameters.namespace >>.example.com \
              --wait --timeout=10m
      - run:
          name: Health check
          command: |
            sleep 60
            kubectl wait --for=condition=ready pod -l app=app-<< parameters.namespace >> -n << parameters.namespace >> --timeout=300s

  # Blue-green deployment
  deploy:blue-green:
    executor: docker-executor
    parameters:
      environment:
        type: string
      namespace:
        type: string
    steps:
      - checkout
      - setup-environment:
          environment: << parameters.environment >>
      - aws-eks/update-kubeconfig:
          aws-region: AWS_REGION
          cluster-name: EKS_CLUSTER_NAME
          install-kubectl: true
      - run:
          name: Blue-green deployment
          command: |
            # Determine current active color
            CURRENT_COLOR=$(kubectl get service app-<< parameters.namespace >> -n << parameters.namespace >> -o jsonpath='{.spec.selector.color}' || echo "blue")
            NEW_COLOR="blue"
            if [ "$CURRENT_COLOR" = "blue" ]; then
              NEW_COLOR="green"
            fi

            echo "Current color: $CURRENT_COLOR"
            echo "New color: $NEW_COLOR"

            # Deploy new version with new color
            helm upgrade --install app-<< parameters.namespace >>-$NEW_COLOR ./helm/app \
              --namespace << parameters.namespace >> \
              --set image.tag=$CIRCLE_SHA1 \
              --set color=$NEW_COLOR \
              --set environment=<< parameters.environment >>

            # Wait for new deployment to be ready
            kubectl rollout status deployment/app-<< parameters.namespace >>-$NEW_COLOR -n << parameters.namespace >> --timeout=600s

            # Health check
            sleep 30
            kubectl port-forward -n << parameters.namespace >> service/app-<< parameters.namespace >>-$NEW_COLOR 8080:80 &
            PORT_FORWARD_PID=$!
            sleep 10

            # Perform health check
            if curl -f http://localhost:8080/health; then
              echo "Health check passed"
              kill $PORT_FORWARD_PID

              # Switch traffic to new version
              kubectl patch service app-<< parameters.namespace >> -n << parameters.namespace >> -p '{"spec":{"selector":{"color":"'$NEW_COLOR'"}}}'

              echo "Traffic switched to $NEW_COLOR version"

              # Wait and verify
              sleep 30
              kubectl port-forward -n << parameters.namespace >> service/app-<< parameters.namespace >> 8080:80 &
              PORT_FORWARD_PID=$!
              sleep 10

              if curl -f http://localhost:8080/health; then
                echo "Deployment successful"
                kill $PORT_FORWARD_PID

                # Clean up old version
                helm uninstall app-<< parameters.namespace >>-$CURRENT_COLOR -n << parameters.namespace >> || true
              else
                echo "Health check failed after switch, rolling back"
                kill $PORT_FORWARD_PID
                kubectl patch service app-<< parameters.namespace >> -n << parameters.namespace >> -p '{"spec":{"selector":{"color":"'$CURRENT_COLOR'"}}}'
                helm uninstall app-<< parameters.namespace >>-$NEW_COLOR -n << parameters.namespace >>
                exit 1
              fi
            else
              echo "Health check failed, rolling back"
              kill $PORT_FORWARD_PID
              helm uninstall app-<< parameters.namespace >>-$NEW_COLOR -n << parameters.namespace >>
              exit 1
            fi

  # Performance testing
  performance-test:
    executor: large-executor
    parameters:
      environment:
        type: string
        default: "staging"
    steps:
      - checkout
      - setup-environment:
          environment: << parameters.environment >>
      - node/install-packages
      - run:
          name: Install Artillery
          command: npm install -g artillery
      - run:
          name: Run performance test
          command: |
            artillery run tests/performance/load-test.yml --output performance-results.json
            artillery report performance-results.json --output performance-report.html
      - store_artifacts:
          path: performance-results.json
          destination: performance-results
      - store_artifacts:
          path: performance-report.html
          destination: performance-report

# Workflows
workflows:
  version: 2

  # Main development workflow
  build-and-deploy:
    jobs:
      # Build application
      - build

      # Parallel test matrix
      - test-matrix:
          matrix:
            parameters:
              node-version: ["16", "18", "20"]
              test-type: ["unit"]
          requires:
            - build

      - test-matrix:
          matrix:
            parameters:
              node-version: ["18"]
              test-type: ["integration", "e2e"]
          requires:
            - build

      # Security scanning
      - security-scan:
          requires:
            - build

      # Docker build
      - docker-build:
          matrix:
            parameters:
              image-name: ["$DOCKER_REGISTRY/app", "$DOCKER_REGISTRY/nginx"]
          requires:
            - build
            - test-matrix
            - security-scan

      # Performance testing for staging
      - performance-test:
          environment: staging
          requires:
            - docker-build
          filters:
            branches:
              only:
                - main

      # Deployment approval and deployment
      - hold-for-staging-approval:
          type: approval
          requires:
            - docker-build
            - security-scan
          filters:
            branches:
              only:
                - main

      - deploy:k8s:
          name: deploy-staging
          environment: staging
          namespace: staging
          requires:
            - hold-for-staging-approval

      - staging-health-check:
          executor: node-executor
          steps:
            - run:
                name: Staging health check
                command: |
                  sleep 120
                  curl -f https://staging.example.com/health || exit 1
          requires:
            - deploy-staging

      - hold-for-production-approval:
          type: approval
          requires:
            - deploy-staging
            - staging-health-check
            - performance-test
          filters:
            branches:
              only:
                - main

      - deploy:blue-green:
          name: deploy-production
          environment: production
          namespace: production
          requires:
            - hold-for-production-approval

      - production-health-check:
          executor: node-executor
          steps:
            - run:
                name: Production health check
                command: |
                  sleep 300
                  curl -f https://example.com/health || exit 1
          requires:
            - deploy-production

  # Release workflow
  release:
    jobs:
      - build
      - docker-build:
          matrix:
            parameters:
              image-name: ["$DOCKER_REGISTRY/app"]
              tag: ["$CIRCLE_TAG"]
          requires:
            - build
          filters:
            branches:
              ignore: /.*/
            tags:
              only: /^v[0-9]+(\.[0-9]+)*$/

  # Scheduled maintenance workflow
  scheduled-maintenance:
    triggers:
      - schedule:
          cron: "0 3 * * 6"  # Every Saturday at 3 AM UTC
          filters:
            branches:
              only:
                - main
    jobs:
      - build
      - security-scan:
          requires:
            - build
      - performance-test:
          environment: production
          requires:
            - build
      - slack/notify:
          channel: '#maintenance'
          event: pass
          template: basic_success_1
          custom: |
            {
              "text": "🔧 Scheduled maintenance completed",
              "attachments": [
                {
                  "color": "good",
                  "fields": [
                    {
                      "title": "Build Number",
                      "value": "$CIRCLE_BUILD_NUM",
                      "short": true
                    },
                    {
                      "title": "Results",
                      "value": "Security scan and performance tests passed",
                      "short": true
                    }
                  ]
                }
              ]
            }

  # Emergency rollback workflow
  emergency-rollback:
    jobs:
      - hold-for-rollback-approval:
          type: approval
          filters:
            branches:
              only:
                - main
      - deploy:k8s:
          name: rollback-production
          environment: production
          namespace: production
          helm-chart: "./helm/app"
          requires:
            - hold-for-rollback-approval
          steps:
            - checkout
            - setup-environment:
                environment: production
            - aws-eks/update-kubeconfig:
                aws-region: AWS_REGION
                cluster-name: EKS_CLUSTER_NAME
                install-kubectl: true
            - helm/install-helm-client:
                version: v3.12.0
            - run:
                name: Rollback to previous version
                command: |
                  helm rollback app-production -n production --wait --timeout=300s
                  kubectl rollout status deployment/app-production -n production --timeout=300s
            - run:
                name: Verify rollback
                command: |
                  sleep 60
                  kubectl port-forward -n production service/app-production 8080:80 &
                  PORT_FORWARD_PID=$!
                  sleep 10
                  curl -f http://localhost:8080/health || exit 1
                  kill $PORT_FORWARD_PID
                  echo "Rollback verified successfully"

💻 CircleCI Workflows Monorepo yaml

🔴 complex ⭐⭐⭐⭐⭐

Configuração monorepo complexa com filtragem de caminho, geração dinâmica de jobs e workflows condicionais

⏱️ 45 min 🏷️ circleci, monorepo, dynamic, path filtering, conditional
Prerequisites: Advanced CircleCI, Monorepo architecture, Docker, Kubernetes, Git workflows
# .circleci/config.yml - Monorepo Configuration with Dynamic Workflows

version: 2.1

# Import orbs
orbs:
  node: circleci/[email protected]
  docker: circleci/[email protected]
  aws: circleci/[email protected]
  gh: circleci/[email protected]

# Global parameters and commands
parameters:
  app_name:
    type: string
    default: "my-monorepo-app"

  docker_registry:
    type: string
    default: "docker.io/myorg"

commands:
  detect-changed-packages:
    description: "Detect which packages have changed"
    parameters:
      base-branch:
        type: string
        default: "main"
    steps:
      - run:
          name: Detect changed packages
          command: |
            # Get list of changed files
            if [ "$CIRCLE_BRANCH" = "main" ]; then
              # For main branch, compare with previous commit
              BASE_COMMIT=$(git rev-parse HEAD^1)
            else
              # For feature branches, compare with main
              BASE_COMMIT=$(git merge-base origin/<< parameters.base-branch >> HEAD)
            fi

            echo "Base commit: $BASE_COMMIT"

            # Get changed files
            CHANGED_FILES=$(git diff --name-only $BASE_COMMIT HEAD)
            echo "Changed files:"
            echo "$CHANGED_FILES"

            # Detect affected packages
            AFFECTED_PACKAGES=""
            for file in $CHANGED_FILES; do
              if [[ $file == packages/* ]]; then
                PACKAGE=$(echo $file | cut -d'/' -f1-2)
                if [[ $AFFECTED_PACKAGES != *$PACKAGE* ]]; then
                  AFFECTED_PACKAGES="$AFFECTED_PACKAGES $PACKAGE"
                fi
              fi
            done

            echo "Affected packages: $AFFECTED_PACKAGES"

            # Generate matrix parameters
            MATRIX_CONFIG="{"
            FIRST=true
            for package in $AFFECTED_PACKAGES; do
              if [ "$FIRST" = true ]; then
                FIRST=false
              else
                MATRIX_CONFIG="$MATRIX_CONFIG,"
              fi
              PACKAGE_NAME=$(basename $package)
              MATRIX_CONFIG="$MATRIX_CONFIG"$PACKAGE_NAME":{"path":"$package","name":"$PACKAGE_NAME"}"
            done
            MATRIX_CONFIG="$MATRIX_CONFIG}"

            echo "Matrix config: $MATRIX_CONFIG"

            # Save to file for persistence
            echo "$MATRIX_CONFIG" > /tmp/matrix-config.json

            # Create environment file
            echo "export AFFECTED_PACKAGES='$AFFECTED_PACKAGES'" >> $BASH_ENV
            echo "export MATRIX_CONFIG_FILE='/tmp/matrix-config.json'" >> $BASH_ENV

  build-package:
    description: "Build a specific package"
    parameters:
      package-path:
        type: string
      package-name:
        type: string
      node-version:
        type: string
        default: "18"
    steps:
      - checkout
      - run:
          name: Build << parameters.package-name >>
          command: |
            cd << parameters.package-path >>
            echo "Building package: << parameters.package-name >>"
            echo "Node version: << parameters.node-version >>"
            echo "Package path: << parameters.package-path >>"

            # Use nvm to switch Node versions if needed
            if command -v nvm &> /dev/null; then
              nvm use << parameters.node-version >> || nvm install << parameters.node-version >>
            fi

            npm ci
            npm run build

            echo "Build completed for << parameters.package-name >>"
      - persist_to_workspace:
          root: ~/project
          paths:
            - << parameters.package-path >>/dist
            - << parameters.package-path >>/build
            - << parameters.package-path >>/lib

  test-package:
    description: "Test a specific package"
    parameters:
      package-path:
        type: string
      package-name:
        type: string
      test-type:
        type: enum
        enum: ["unit", "integration", "e2e"]
    steps:
      - attach_workspace:
          at: ~/project
      - run:
          name: Run << parameters.test-type >> tests for << parameters.package-name >>
          command: |
            cd << parameters.package-path >>
            echo "Running << parameters.test-type >> tests for << parameters.package-name >>"

            case "<< parameters.test-type >>" in
              "unit")
                npm run test:unit || npm test
                ;;
              "integration")
                npm run test:integration
                ;;
              "e2e")
                npm run build
                npm run test:e2e
                ;;
            esac
      - store_test_results:
          path: << parameters.package-path >>/test-results
      - when:
          condition:
            equal: [ unit, << parameters.test-type >> ]
          steps:
            - store_artifacts:
                path: << parameters.package-path >>/coverage
                destination: << parameters.package-name >>-coverage

# Executors
executors:
  node-executor:
    docker:
      - image: cimg/node:18.17
    working_directory: ~/project
    resource_class: medium

  large-executor:
    docker:
      - image: cimg/node:18.17
    working_directory: ~/project
    resource_class: large

  docker-executor:
    machine:
      image: ubuntu-2204:2023.04.2
    resource_class: medium

# Job templates
jobs:
  # Setup and detection job
  setup-and-detect:
    executor: node-executor
    steps:
      - checkout
      - detect-changed-packages
      - persist_to_workspace:
          root: ~/project
          paths:
            - /tmp/matrix-config.json

  # Dynamic build job
  build-packages:
    executor: node-executor
    parameters:
      package-path:
        type: string
      package-name:
        type: string
      node-version:
        type: string
        default: "18"
    steps:
      - build-package:
          package-path: << parameters.package-path >>
          package-name: << parameters.package-name >>
          node-version: << parameters.node-version >>

  # Dynamic test job
  test-packages:
    executor: node-executor
    parameters:
      package-path:
        type: string
      package-name:
        type: string
      test-type:
        type: enum
        enum: ["unit", "integration", "e2e"]
    steps:
      - test-package:
          package-path: << parameters.package-path >>
          package-name: << parameters.package-name >>
          test-type: << parameters.test-type >>

  # Build Docker images for packages
  build-docker-images:
    executor: docker-executor
    parameters:
      package-name:
        type: string
      dockerfile-path:
        type: string
        default: "Dockerfile"
    steps:
      - checkout
      - docker/check:
          docker-username: DOCKER_USERNAME
          docker-password: DOCKER_PASSWORD
      - run:
          name: Build Docker image for << parameters.package-name >>
          command: |
            echo "Building Docker image for << parameters.package-name >>"

            # Check if Dockerfile exists
            DOCKERFILE_PATH="<< parameters.package-name >>/<< parameters.dockerfile-path >>"
            if [ ! -f "$DOCKERFILE_PATH" ]; then
              echo "Dockerfile not found at $DOCKERFILE_PATH, skipping"
              exit 0
            fi

            # Build and push image
            IMAGE_NAME="<< parameters.docker_registry >>/<< parameters.package-name >>"
            IMAGE_TAG="$CIRCLE_SHA1"

            docker build \
              -t "$IMAGE_NAME:$IMAGE_TAG" \
              -t "$IMAGE_NAME:latest" \
              -f "$DOCKERFILE_PATH" \
              "<< parameters.package-name >>/"

            echo "Image built: $IMAGE_NAME:$IMAGE_TAG"

            # Push to registry
            docker push "$IMAGE_NAME:$IMAGE_TAG"
            docker push "$IMAGE_NAME:latest"

            echo "Image pushed: $IMAGE_NAME:$IMAGE_TAG"

  # Deploy specific package to Kubernetes
  deploy-package:
    executor: docker-executor
    parameters:
      package-name:
        type: string
      environment:
        type: string
        default: "staging"
      namespace:
        type: string
        default: "staging"
    steps:
      - checkout
      - aws-eks/update-kubeconfig:
          aws-region: AWS_REGION
          cluster-name: EKS_CLUSTER_NAME
          install-kubectl: true
      - run:
          name: Deploy << parameters.package-name >> to << parameters.environment >>
          command: |
            echo "Deploying << parameters.package-name >> to << parameters.environment >>"

            # Check if Helm chart exists
            HELM_CHART_PATH="<< parameters.package-name >>/helm"
            if [ ! -d "$HELM_CHART_PATH" ]; then
              echo "Helm chart not found at $HELM_CHART_PATH, using generic deployment"

              # Generic deployment
              cat > << parameters.package-name >>-deployment.yaml << EOF
            apiVersion: apps/v1
            kind: Deployment
            metadata:
              name: << parameters.package-name >>
              namespace: << parameters.namespace >>
            spec:
              replicas: 2
              selector:
                matchLabels:
                  app: << parameters.package-name >>
              template:
                metadata:
                  labels:
                    app: << parameters.package-name >>
                spec:
                  containers:
                  - name: << parameters.package-name >>
                    image: << parameters.docker_registry >>/<< parameters.package-name >>:$CIRCLE_SHA1
                    ports:
                    - containerPort: 3000
                    env:
                    - name: NODE_ENV
                      value: "<< parameters.environment >>"
            EOF

              kubectl apply -f << parameters.package-name >>-deployment.yaml
              kubectl rollout status deployment/<< parameters.package-name >> -n << parameters.namespace >> --timeout=300s
            else
              # Deploy with Helm
              helm upgrade --install << parameters.package-name >> \
                "$HELM_CHART_PATH" \
                --namespace << parameters.namespace >> \
                --create-namespace \
                --set image.tag=$CIRCLE_SHA1 \
                --set environment=<< parameters.environment >> \
                --set service.name=<< parameters.package-name >> \
                --wait --timeout=600s
            fi

            echo "Deployment completed for << parameters.package-name >>"

  # Integration test across packages
  integration-test-cross-package:
    executor: large-executor
    steps:
      - checkout
      - run:
          name: Setup test environment
          command: |
            echo "Setting up cross-package integration test environment"

            # Install dependencies
            npm ci

            # Start all services
            docker-compose -f docker-compose.test.yml up -d

            # Wait for services to be ready
            sleep 60

            # Check service health
            for service in frontend backend api-gateway; do
              echo "Checking $service health..."
              timeout 60 bash -c 'until curl -f http://localhost:$(docker port $service 3000 | cut -d: -f2)/health; do sleep 2; done'
            done
      - run:
          name: Run cross-package integration tests
            command: |
              echo "Running cross-package integration tests"

              # Run integration tests that span multiple packages
              npm run test:integration:cross-package

              echo "Cross-package integration tests completed"
      - store_test_results:
          path: cross-package-test-results
      - store_artifacts:
          path: integration-test-logs
          destination: integration-logs
      - run:
          name: Cleanup test environment
          command: |
            docker-compose -f docker-compose.test.yml down -v
            echo "Test environment cleaned up"

  # Performance test specific package
  performance-test-package:
    executor: large-executor
    parameters:
      package-name:
        type: string
      environment:
        type: string
        default: "staging"
    steps:
      - checkout
      - run:
          name: Install Artillery
          command: npm install -g artillery
      - run:
          name: Performance test for << parameters.package-name >>
          command: |
            echo "Running performance test for << parameters.package-name >>"

            # Determine target URL based on environment
            case "<< parameters.environment >>" in
              "staging")
                TARGET_URL="https://staging.example.com/<< parameters.package-name >>"
                ;;
              "production")
                TARGET_URL="https://example.com/<< parameters.package-name >>"
                ;;
              *)
                TARGET_URL="http://localhost:3000"
                ;;
            esac

            echo "Target URL: $TARGET_URL"

            # Create performance test config
            cat > performance-test.yml << EOF
            config:
              target: '$TARGET_URL'
              phases:
                - duration: 60
                  arrivalRate: 10
                - duration: 120
                  arrivalRate: 50
                - duration: 60
                  arrivalRate: 100
              payloads:
                path: "payloads.csv"
                fields:
                  - "endpoint"
              processor: "function (params) { return params.target + params.endpoint; }"
            scenarios:
              - name: "Load test for << parameters.package-name >>"
                weight: 100
                flow:
                  - get:
                      url: "/health"
                  - think: 1
                  - get:
                      url: "/api/data"
            EOF

            # Run performance test
            artillery run performance-test.yml --output << parameters.package-name >>-performance-results.json

            # Generate report
            artillery report << parameters.package-name >>-performance-results.json --output << parameters.package-name >>-performance-report.html
      - store_artifacts:
          path: << parameters.package-name >>-performance-results.json
          destination: << parameters.package-name >>-performance-results
      - store_artifacts:
          path: << parameters.package-name >>-performance-report.html
          destination: << parameters.package-name >>-performance-report

# Workflows
workflows:
  version: 2

  # Main monorepo workflow
  monorepo-pipeline:
    jobs:
      # Setup and detect changes
      - setup-and-detect

      # Dynamic build jobs based on changed packages
      - build-packages:
          matrix:
            parameters:
              package-path: ["packages/frontend", "packages/backend", "packages/api-gateway", "packages/shared"]
              package-name: ["frontend", "backend", "api-gateway", "shared"]
              node-version: ["18"]
          requires:
            - setup-and-detect

      # Dynamic test jobs
      - test-packages:
          matrix:
            parameters:
              package-path: ["packages/frontend", "packages/backend", "packages/api-gateway", "packages/shared"]
              package-name: ["frontend", "backend", "api-gateway", "shared"]
              test-type: ["unit"]
          requires:
            - build-packages

      - test-packages:
          matrix:
            parameters:
              package-path: ["packages/backend", "packages/api-gateway"]
              package-name: ["backend", "api-gateway"]
              test-type: ["integration"]
          requires:
            - build-packages

      - test-packages:
          matrix:
            parameters:
              package-path: ["packages/frontend"]
              package-name: ["frontend"]
              test-type: ["e2e"]
          requires:
            - build-packages

      # Build Docker images
      - build-docker-images:
          matrix:
            parameters:
              package-name: ["frontend", "backend", "api-gateway"]
          requires:
            - test-packages
          filters:
            branches:
              only:
                - main
                - develop
                - /^release\/.*/

      # Cross-package integration tests
      - integration-test-cross-package:
          requires:
            - build-packages
          filters:
            branches:
              only:
                - main
                - develop

      # Deploy to staging
      - deploy-package:
          matrix:
            parameters:
              package-name: ["frontend", "backend", "api-gateway"]
              environment: ["staging"]
              namespace: ["staging"]
          requires:
            - build-docker-images
          filters:
            branches:
              only:
                - main
                - develop

      # Performance tests for staging
      - performance-test-package:
          matrix:
            parameters:
              package-name: ["frontend", "backend", "api-gateway"]
              environment: ["staging"]
          requires:
            - deploy-package
          filters:
            branches:
              only:
                - main

      # Hold for production approval
      - hold-for-production-approval:
          type: approval
          requires:
            - integration-test-cross-package
            - deploy-package
            - performance-test-package
          filters:
            branches:
              only:
                - main

      # Deploy to production
      - deploy-package:
          matrix:
            parameters:
              package-name: ["frontend", "backend", "api-gateway"]
              environment: ["production"]
              namespace: ["production"]
          requires:
            - hold-for-production-approval
          filters:
            branches:
              only:
                - main

  # Package-specific workflows
  package-workflows:
    jobs:
      # Frontend specific workflow
      - build-packages:
          name: build-frontend-specific
          package-path: "packages/frontend"
          package-name: "frontend"
          node-version: "18"
          filters:
            branches:
              only:
                - /frontend\/.*/

      - test-packages:
          name: test-frontend-specific
          package-path: "packages/frontend"
          package-name: "frontend"
          test-type: "e2e"
          requires:
            - build-frontend-specific
          filters:
            branches:
              only:
                - /frontend\/.*/

      # Backend specific workflow
      - build-packages:
          name: build-backend-specific
          package-path: "packages/backend"
          package-name: "backend"
          node-version: "18"
          filters:
            branches:
              only:
                - /backend\/.*/

      - test-packages:
          name: test-backend-specific
          package-path: "packages/backend"
          package-name: "backend"
          test-type: "integration"
          requires:
            - build-backend-specific
          filters:
            branches:
              only:
                - /backend\/.*/

  # Nightly monorepo workflow
  nightly-monorepo:
    triggers:
      - schedule:
          cron: "0 2 * * *"  # 2 AM UTC
          filters:
            branches:
              only:
                - main
    jobs:
      - setup-and-detect
      - build-packages:
          matrix:
            parameters:
              package-path: ["packages/frontend", "packages/backend", "packages/api-gateway", "packages/shared"]
              package-name: ["frontend", "backend", "api-gateway", "shared"]
          requires:
            - setup-and-detect
      - test-packages:
          matrix:
            parameters:
              package-path: ["packages/frontend", "packages/backend", "packages/api-gateway", "packages/shared"]
              package-name: ["frontend", "backend", "api-gateway", "shared"]
              test-type: ["unit"]
          requires:
            - build-packages
      - integration-test-cross-package:
          requires:
            - build-packages

  # Release workflow
  release-packages:
    jobs:
      - setup-and-detect
      - build-packages:
          matrix:
            parameters:
              package-path: ["packages/frontend", "packages/backend", "packages/api-gateway", "packages/shared"]
              package-name: ["frontend", "backend", "api-gateway", "shared"]
          requires:
            - setup-and-detect
          filters:
            tags:
              only: /^v[0-9]+(\.[0-9]+)*$/
            branches:
              ignore: /.*/
      - test-packages:
          matrix:
            parameters:
              package-path: ["packages/frontend", "packages/backend", "packages/api-gateway", "packages/shared"]
              package-name: ["frontend", "backend", "api-gateway", "shared"]
              test-type: ["unit"]
          requires:
            - build-packages
          filters:
            tags:
              only: /^v[0-9]+(\.[0-9]+)*$/
            branches:
              ignore: /.*/
      - build-docker-images:
          matrix:
            parameters:
              package-name: ["frontend", "backend", "api-gateway"]
              dockerfile-path: ["Dockerfile"]
          requires:
            - test-packages
          filters:
            tags:
              only: /^v[0-9]+(\.[0-9]+)*$/
            branches:
              ignore: /.*/
      - deploy-package:
          matrix:
            parameters:
              package-name: ["frontend", "backend", "api-gateway"]
              environment: ["production"]
              namespace: ["production"]
          requires:
            - build-docker-images
          filters:
            tags:
              only: /^v[0-9]+(\.[0-9]+)*$/
            branches:
              ignore: /.*/