335 lines
6.8 KiB
YAML
335 lines
6.8 KiB
YAML
# GitLab CI/CD Pipeline for Node.js
|
|
# Optimized with caching, parallel execution, and deployment
|
|
|
|
stages:
|
|
- security
|
|
- validate
|
|
- test
|
|
- build
|
|
- deploy
|
|
|
|
# Global variables
|
|
variables:
|
|
NODE_VERSION: "20"
|
|
npm_config_cache: "$CI_PROJECT_DIR/.npm"
|
|
CYPRESS_CACHE_FOLDER: "$CI_PROJECT_DIR/.cache/Cypress"
|
|
|
|
# Global cache configuration
|
|
cache:
|
|
key:
|
|
files:
|
|
- package-lock.json
|
|
paths:
|
|
- node_modules/
|
|
- .npm/
|
|
- .cache/Cypress/
|
|
policy: pull
|
|
|
|
# Reusable configuration
|
|
.node_template:
|
|
image: node:${NODE_VERSION}
|
|
before_script:
|
|
- node --version
|
|
- npm --version
|
|
- npm ci
|
|
|
|
# Validation stage
|
|
lint:
|
|
extends: .node_template
|
|
stage: validate
|
|
cache:
|
|
key:
|
|
files:
|
|
- package-lock.json
|
|
paths:
|
|
- node_modules/
|
|
- .npm/
|
|
policy: pull-push
|
|
script:
|
|
- npm run lint
|
|
- npm run format:check
|
|
only:
|
|
- merge_requests
|
|
- main
|
|
- develop
|
|
|
|
# Test stage
|
|
unit-test:
|
|
extends: .node_template
|
|
stage: test
|
|
parallel:
|
|
matrix:
|
|
- NODE_VERSION: ["18", "20", "22"]
|
|
script:
|
|
- npm run test:unit -- --coverage
|
|
coverage: '/All files[^|]*\|[^|]*\s+([\d\.]+)/'
|
|
artifacts:
|
|
reports:
|
|
coverage_report:
|
|
coverage_format: cobertura
|
|
path: coverage/cobertura-coverage.xml
|
|
paths:
|
|
- coverage/
|
|
expire_in: 30 days
|
|
|
|
integration-test:
|
|
extends: .node_template
|
|
stage: test
|
|
services:
|
|
- postgres:15
|
|
- redis:7-alpine
|
|
variables:
|
|
POSTGRES_DB: testdb
|
|
POSTGRES_USER: testuser
|
|
POSTGRES_PASSWORD: testpass
|
|
DATABASE_URL: "postgresql://testuser:testpass@postgres:5432/testdb"
|
|
REDIS_URL: "redis://redis:6379"
|
|
script:
|
|
- npm run test:integration
|
|
artifacts:
|
|
when: always
|
|
reports:
|
|
junit: test-results/junit.xml
|
|
paths:
|
|
- test-results/
|
|
expire_in: 7 days
|
|
|
|
# Build stage
|
|
build:
|
|
extends: .node_template
|
|
stage: build
|
|
cache:
|
|
key:
|
|
files:
|
|
- package-lock.json
|
|
paths:
|
|
- node_modules/
|
|
- .npm/
|
|
policy: pull
|
|
script:
|
|
- npm run build
|
|
- echo "BUILD_VERSION=$(node -p "require('./package.json').version")" >> build.env
|
|
artifacts:
|
|
paths:
|
|
- dist/
|
|
reports:
|
|
dotenv: build.env
|
|
expire_in: 7 days
|
|
only:
|
|
- main
|
|
- develop
|
|
- tags
|
|
|
|
# Security: Secret Scanning
|
|
secret-scan:trufflehog:
|
|
stage: security
|
|
image: trufflesecurity/trufflehog:latest
|
|
script:
|
|
- trufflehog filesystem . --json --fail > trufflehog-report.json || true
|
|
- |
|
|
if [ -s trufflehog-report.json ]; then
|
|
echo "❌ Secrets detected!"
|
|
cat trufflehog-report.json
|
|
exit 1
|
|
fi
|
|
artifacts:
|
|
when: always
|
|
paths:
|
|
- trufflehog-report.json
|
|
expire_in: 30 days
|
|
allow_failure: false
|
|
only:
|
|
- merge_requests
|
|
- main
|
|
- develop
|
|
|
|
secret-scan:gitleaks:
|
|
stage: security
|
|
image: zricethezav/gitleaks:latest
|
|
script:
|
|
- gitleaks detect --source . --report-format json --report-path gitleaks-report.json
|
|
artifacts:
|
|
when: always
|
|
paths:
|
|
- gitleaks-report.json
|
|
expire_in: 30 days
|
|
allow_failure: true
|
|
only:
|
|
- merge_requests
|
|
- main
|
|
- develop
|
|
|
|
# Security: SAST
|
|
sast:semgrep:
|
|
stage: security
|
|
image: returntocorp/semgrep
|
|
script:
|
|
- semgrep scan --config=auto --sarif --output=semgrep.sarif .
|
|
- semgrep scan --config=p/owasp-top-ten --json --output=semgrep-owasp.json .
|
|
artifacts:
|
|
reports:
|
|
sast: semgrep.sarif
|
|
paths:
|
|
- semgrep.sarif
|
|
- semgrep-owasp.json
|
|
expire_in: 30 days
|
|
allow_failure: false
|
|
only:
|
|
- merge_requests
|
|
- main
|
|
- develop
|
|
|
|
sast:nodejs:
|
|
stage: security
|
|
image: node:20-alpine
|
|
script:
|
|
- npm install -g eslint eslint-plugin-security
|
|
- eslint . --plugin=security --format=json --output-file=eslint-security.json || true
|
|
artifacts:
|
|
paths:
|
|
- eslint-security.json
|
|
expire_in: 30 days
|
|
only:
|
|
exists:
|
|
- package.json
|
|
allow_failure: true
|
|
|
|
# Security: Dependency Scanning
|
|
dependency-scan:npm:
|
|
extends: .node_template
|
|
stage: security
|
|
cache:
|
|
key:
|
|
files:
|
|
- package-lock.json
|
|
paths:
|
|
- node_modules/
|
|
- .npm/
|
|
policy: pull-push
|
|
script:
|
|
- npm audit --audit-level=moderate --json > npm-audit.json || true
|
|
- npm audit --audit-level=high # Fail on high severity
|
|
artifacts:
|
|
paths:
|
|
- npm-audit.json
|
|
expire_in: 30 days
|
|
allow_failure: false
|
|
only:
|
|
- merge_requests
|
|
- main
|
|
- develop
|
|
|
|
# GitLab built-in security templates
|
|
include:
|
|
- template: Security/SAST.gitlab-ci.yml
|
|
- template: Security/Dependency-Scanning.gitlab-ci.yml
|
|
|
|
# E2E tests (only on main)
|
|
e2e-test:
|
|
extends: .node_template
|
|
stage: test
|
|
needs: [build]
|
|
dependencies:
|
|
- build
|
|
script:
|
|
- npm run test:e2e
|
|
artifacts:
|
|
when: always
|
|
paths:
|
|
- cypress/videos/
|
|
- cypress/screenshots/
|
|
expire_in: 7 days
|
|
only:
|
|
- main
|
|
|
|
# Deploy to staging
|
|
deploy:staging:
|
|
stage: deploy
|
|
image: node:${NODE_VERSION}
|
|
needs: [build]
|
|
dependencies:
|
|
- build
|
|
environment:
|
|
name: staging
|
|
url: https://staging.example.com
|
|
on_stop: stop:staging
|
|
script:
|
|
- echo "Deploying to staging..."
|
|
- npm install -g aws-cli
|
|
- aws s3 sync dist/ s3://${STAGING_BUCKET}
|
|
- aws cloudfront create-invalidation --distribution-id ${STAGING_CF_DIST} --paths "/*"
|
|
only:
|
|
- develop
|
|
when: manual
|
|
|
|
stop:staging:
|
|
stage: deploy
|
|
image: node:${NODE_VERSION}
|
|
environment:
|
|
name: staging
|
|
action: stop
|
|
script:
|
|
- echo "Stopping staging environment..."
|
|
when: manual
|
|
only:
|
|
- develop
|
|
|
|
# Deploy to production
|
|
deploy:production:
|
|
stage: deploy
|
|
image: node:${NODE_VERSION}
|
|
needs: [build, e2e-test]
|
|
dependencies:
|
|
- build
|
|
environment:
|
|
name: production
|
|
url: https://example.com
|
|
before_script:
|
|
- echo "Deploying version ${BUILD_VERSION} to production"
|
|
script:
|
|
- npm install -g aws-cli
|
|
- aws s3 sync dist/ s3://${PRODUCTION_BUCKET}
|
|
- aws cloudfront create-invalidation --distribution-id ${PRODUCTION_CF_DIST} --paths "/*"
|
|
# Health check
|
|
- sleep 10
|
|
- curl -f https://example.com/health || exit 1
|
|
after_script:
|
|
- echo "Deployed successfully"
|
|
only:
|
|
- main
|
|
when: manual
|
|
|
|
# Create release
|
|
release:
|
|
stage: deploy
|
|
image: registry.gitlab.com/gitlab-org/release-cli:latest
|
|
needs: [deploy:production]
|
|
script:
|
|
- echo "Creating release for version ${BUILD_VERSION}"
|
|
release:
|
|
tag_name: 'v${BUILD_VERSION}'
|
|
description: 'Release v${BUILD_VERSION}'
|
|
only:
|
|
- main
|
|
|
|
# Workflow rules
|
|
workflow:
|
|
rules:
|
|
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
|
- if: '$CI_COMMIT_BRANCH == "main"'
|
|
- if: '$CI_COMMIT_BRANCH == "develop"'
|
|
- if: '$CI_COMMIT_TAG'
|
|
|
|
# Additional optimizations
|
|
.interruptible_template:
|
|
interruptible: true
|
|
|
|
lint:
|
|
extends: [.node_template, .interruptible_template]
|
|
|
|
unit-test:
|
|
extends: [.node_template, .interruptible_template]
|
|
|
|
integration-test:
|
|
extends: [.node_template, .interruptible_template]
|