# GitLab CI/CD Pipeline Template

```yaml
# .gitlab-ci.yml

stages:
  - lint
  - test
  - security
  - build
  - docker
  - deploy

variables:
  NODE_VERSION: "20"
  DOCKER_REGISTRY: registry.gitlab.com
  IMAGE_NAME: $CI_REGISTRY_IMAGE

# ============================================
# Cache Configuration
# ============================================
.node_cache: &node_cache
  cache:
    key:
      files:
        - package-lock.json
    paths:
      - node_modules/
    policy: pull-push

.node_setup: &node_setup
  image: node:${NODE_VERSION}
  before_script:
    - npm ci --cache .npm --prefer-offline

# ============================================
# Stage 1: Lint
# ============================================
lint:
  stage: lint
  <<: *node_setup
  <<: *node_cache
  script:
    - npm run lint
    - npm run format:check
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

# ============================================
# Stage 2: Test
# ============================================
test:
  stage: test
  <<: *node_setup
  <<: *node_cache
  script:
    - npm test -- --coverage
  coverage: '/All files[^|]*\|[^|]*\s+([\d\.]+)/'
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage/cobertura-coverage.xml
      junit: junit.xml
    paths:
      - coverage/
    expire_in: 1 week
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

test:e2e:
  stage: test
  <<: *node_setup
  services:
    - postgres:15
    - redis:7
  variables:
    DATABASE_URL: postgres://postgres:postgres@postgres:5432/test
    REDIS_URL: redis://redis:6379
  script:
    - npm run test:e2e
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

# ============================================
# Stage 3: Security
# ============================================
security:audit:
  stage: security
  <<: *node_setup
  script:
    - npm audit --audit-level=high
  allow_failure: true
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

security:sast:
  stage: security
  include:
    - template: Security/SAST.gitlab-ci.yml
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

security:dependency:
  stage: security
  include:
    - template: Security/Dependency-Scanning.gitlab-ci.yml
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

# ============================================
# Stage 4: Build
# ============================================
build:
  stage: build
  <<: *node_setup
  <<: *node_cache
  script:
    - npm run build
  artifacts:
    paths:
      - dist/
    expire_in: 1 week
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
    - if: $CI_COMMIT_TAG

# ============================================
# Stage 5: Docker
# ============================================
docker:build:
  stage: docker
  image: docker:24
  services:
    - docker:24-dind
  variables:
    DOCKER_TLS_CERTDIR: "/certs"
  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
  script:
    - docker build -t $IMAGE_NAME:$CI_COMMIT_SHA .
    - docker tag $IMAGE_NAME:$CI_COMMIT_SHA $IMAGE_NAME:latest
    - docker push $IMAGE_NAME:$CI_COMMIT_SHA
    - docker push $IMAGE_NAME:latest
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
    - if: $CI_COMMIT_TAG

# ============================================
# Stage 6: Deploy
# ============================================
deploy:staging:
  stage: deploy
  image: alpine:latest
  environment:
    name: staging
    url: https://staging.example.com
  before_script:
    - apk add --no-cache curl
  script:
    - echo "Deploying to staging..."
    # Add deployment commands
  rules:
    - if: $CI_COMMIT_BRANCH == "develop"

deploy:production:
  stage: deploy
  image: alpine:latest
  environment:
    name: production
    url: https://example.com
  before_script:
    - apk add --no-cache curl
  script:
    - echo "Deploying to production..."
    # Add deployment commands
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
  when: manual

# ============================================
# Release
# ============================================
release:
  stage: deploy
  image: registry.gitlab.com/gitlab-org/release-cli:latest
  script:
    - echo "Creating release for $CI_COMMIT_TAG"
  release:
    name: 'Release $CI_COMMIT_TAG'
    description: 'Release $CI_COMMIT_TAG'
    tag_name: '$CI_COMMIT_TAG'
    ref: '$CI_COMMIT_TAG'
  rules:
    - if: $CI_COMMIT_TAG
```

---

## Merge Request Pipeline

```yaml
# For MR-specific jobs
.only_mr: &only_mr
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"

mr:lint:
  <<: *only_mr
  stage: lint
  <<: *node_setup
  script:
    - npm run lint

mr:test:
  <<: *only_mr
  stage: test
  <<: *node_setup
  script:
    - npm test

mr:build:
  <<: *only_mr
  stage: build
  <<: *node_setup
  script:
    - npm run build
```

---

## Child Pipeline (Monorepo)

```yaml
# .gitlab-ci.yml (root)
include:
  - local: 'packages/frontend/.gitlab-ci.yml'
  - local: 'packages/backend/.gitlab-ci.yml'
  - local: 'packages/shared/.gitlab-ci.yml'
```

```yaml
# packages/frontend/.gitlab-ci.yml
frontend:test:
  stage: test
  script:
    - cd packages/frontend && npm test
  rules:
    - changes:
        - packages/frontend/**/*
```
