Skip to main content

Implementing Code Quality Workflows

1. Running CodeQL Analysis

CodeQL is a powerful static analysis engine that helps identify security vulnerabilities and code quality issues in your GitHub Actions workflows.

Property Description Default Value
uses Specifies the CodeQL action to run github/codeql-action/analyze@v2
with.language Programming language to analyze (e.g., javascript, python) Detected from the repository
with.category Category of analysis to run (e.g., security-and-quality) security-and-quality

Example: Running CodeQL Analysis

name: "CodeQL Analysis"
on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  analyze:
    name: Analyze
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Initialize CodeQL
        uses: github/codeql-action/init@v2
        with:
          languages: javascript

      - name: Perform CodeQL Analysis
        uses: github/codeql-action/analyze@v2
Note: CodeQL analysis requires the actions/checkout step to be run before initializing the CodeQL action.
Warning: Ensure that the languages parameter matches the languages used in your project to avoid missing vulnerabilities.

Key Points

  • CodeQL is a static analysis tool that detects security vulnerabilities and code quality issues.
  • It can be integrated into GitHub Actions workflows to run automatically on push or pull request events.
  • CodeQL supports multiple programming languages including JavaScript, Python, Java, and more.

2. Using SonarCloud

Property Description Default
sonar.projectKey Unique key for the project in SonarCloud Derived from repository name
sonar.sources Path to source files .
sonar.tests Path to test files src/test
sonar.language Programming language of the project Determined automatically
sonar.coverage.exclusions Paths to exclude from coverage reports None

Example: SonarCloud Analysis

name: SonarCloud Analysis
on:
  push:
    branches:
      - main
jobs:
  analyze:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Set up JDK
        uses: actions/setup-java@v3
        with:
          java-version: "17"
          distribution: "temurin"
      - name: Cache SonarCloud packages
        uses: actions/cache@v3
        with:
          path: ~/.sonar/cache
          key: ${{ runner.os }}-sonar
          restore-keys: ${{ runner.os }}-sonar
      - name: Analyze with SonarCloud
        uses: sonarqube-community/sonarcloud-github-action@master
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
Note: Ensure the SONAR_TOKEN is set in your repository secrets.

Key Features

  • Automated code quality analysis
  • Coverage reporting
  • Bug and vulnerability detection
  • Code duplication detection
  • Supports multiple languages (Java, JavaScript, Python, etc.)

3. Using ESLint

Option Values Notes
extends "eslint:recommended" Base ESLint ruleset
rules { "no-console": "warn" } Custom rule overrides
env { "browser": true, "node": true } Environment-specific globals

Example: ESLint Configuration

{
  "extends": ["eslint:recommended"],
  "rules": {
    "no-console": "warn",
    "no-unused-vars": "error"
  },
  "env": {
    "browser": true,
    "node": true
  }
}

Example: GitHub Action Workflow

name: Lint Code
on: [push, pull_request]
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "20"
      - name: Install dependencies
        run: npm install
      - name: Run ESLint
        run: npx eslint .

4. Using Prettier

Option Values Notes
prettier.config.js { "semi": true, "singleQuote": true } Configuration file for Prettier settings
prettier.ignore node_modules/ Directories or files to ignore during formatting

Example: Format JavaScript files with Prettier

const fs = require('fs');
const prettier = require('prettier');

const fileContent = fs.readFileSync('./src/index.js', 'utf8');
const formattedContent = prettier.format(fileContent, {
  parser: 'babel',
  semi: true,
  singleQuote: true
});

fs.writeFileSync('./src/index.js', formattedContent);

Example: GitHub Actions workflow using Prettier

name: Format with Prettier
on:
  push:
    branches: [ main ]
jobs:
  format:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
      - name: Install Prettier
        run: npm install --save-dev prettier
      - name: Format code
        run: npx prettier --write .
Note: Prettier can be integrated into CI pipelines to enforce consistent code formatting across all contributions.

5. Using Dependency Scanning

Dependency scanning in GitHub Actions helps identify vulnerabilities in project dependencies. It integrates with tools like dependency-check, Snyk, and OWASP Dependency-Check.

Tool Description Integration Scan Type
Snyk Security platform for identifying vulnerabilities in dependencies snyk/monitor or snyk/snyk-action Static Analysis
OWASP Dependency-Check Open-source tool for identifying vulnerable dependencies owasp/dependency-check-action Static Analysis
GitHub Security Advisory Automatically scans for known vulnerabilities in dependencies github/codeql-action/analyze Static Analysis

Example: Snyk Dependency Scan

name: Snyk Dependency Scan
on:
  push:
    branches: [ main ]
jobs:
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run Snyk to check for vulnerabilities
        uses: snyk/actions/node@master
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}

Example: OWASP Dependency Check

name: OWASP Dependency Check
on:
  schedule:
    - cron: '0 0 * * 0'
jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run OWASP Dependency Check
        uses: owasp/dependency-check-action@v2.1.0
        with:
          project: my-project
          outdir: ./reports
Note: Ensure that the SNYK_TOKEN or other credentials are stored in GitHub Secrets for secure access.
Warning: Regular dependency scans should be scheduled to detect new vulnerabilities as they are discovered.

6. Scanning for Secrets

Option Values Notes
github.token secrets.GITHUB_TOKEN Default token with read/write permissions for the repository
scan.secrets true Enable secret scanning in the workflow
fail-on-secrets true Fail the workflow if secrets are detected

Example: Secret Scanning Workflow

name: Scan for Secrets
on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  scan-secrets:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Scan for secrets
        uses: github/super-linter@v5
        env:
          VALIDATE_ALL_CODEBASE: true
          DEFAULT_BRANCH: main
Note: The github/super-linter action includes secret scanning capabilities by default. Ensure that VALIDATE_ALL_CODEBASE is set to true for comprehensive scanning.
Warning: Never commit secrets to your repository. Use GitHub Actions secrets or environment variables for sensitive data.

7. Running Security Audits

Step Description Action Notes
1 Set up a workflow file workflow_dispatch trigger Initiates the workflow manually or on schedule
2 Install security tools actions/checkout and github/codeql-action/init Clones repo and initializes CodeQL for scanning
3 Run security scan github/codeql-action/analyze Scans code for vulnerabilities and security issues
4 Upload results github/codeql-action/upload Uploads scan results to GitHub for review

Example: Security Audit Workflow

name: Security Audit
on:
  workflow_dispatch:
  schedule:
    - cron: '0 0 * * 0'
jobs:
  security-scan:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
      - name: Initialize CodeQL
        uses: github/codeql-action/init@v2
        with:
          languages: javascript
      - name: Perform scan
        uses: github/codeql-action/analyze@v2
Note: Ensure that the workflow runs with appropriate permissions to access and upload scan results.

8. Using License Checkers

Checker Description Usage Notes
licensee License checker for Ruby gems, npm packages, and more licensee check Supports multiple ecosystems; can be integrated into CI
fossa Enterprise-grade license compliance tool fossa analyze Requires API key; provides detailed reports
scanoss Open source license scanner with GitHub integration scanoss scan Supports multiple languages and platforms

Example: Using licensee in GitHub Actions

name: License Check
on:
  push:
    branches: [ main ]
jobs:
  license-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install licensee
        run: gem install licensee
      - name: Run license check
        run: licensee check
Note: licensee checks for license compatibility and ensures no prohibited licenses are used in dependencies.

9. Generating Code Metrics

Metric Description Tool Configuration
Lines of Code (LOC) Total lines in the codebase codecov coverage: 100%
Code Coverage Percentage of code executed by tests cobertura threshold: 80%
Cyclomatic Complexity Measures the number of linearly independent paths through code sonarqube max: 10
Maintainability Index Quantifies how maintainable the code is sonarqube score: > 50
Code Duplication Percentage of duplicated code blocks simian threshold: 5%

Example: Setting up Code Coverage with Jest

{
  "scripts": {
    "test": "jest --coverage"
  },
  "jest": {
    "collectCoverageFrom": [
      "src/**/*.{js,ts}"
    ]
  }
}

Example: Using Codecov to Upload Coverage Reports

name: Code Coverage
on:
  push:
    branches: [ main ]
jobs:
  coverage:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Install dependencies
        run: npm install
      - name: Run tests with coverage
        run: npm test
      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v3
        with:
          file: ./coverage/lcov.info
Note: Ensure that your CI pipeline includes a step to generate and upload coverage reports for accurate metric tracking.

10. Creating Quality Gates

Example: Basic Quality Gate Workflow

name: Code Quality Gates
on:
  pull_request:
    branches: [ main ]
jobs:
  quality-gate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run linter
        run: |
          npm install
          npm run lint
      - name: Run tests
        run: npm test
Step Description Configuration
on.pull_request Triggers workflow on pull request to main branch branches: [ main ]
jobs.quality-gate.runs-on Runner environment for quality checks ubuntu-latest
steps.name Step name for clarity in logs Any descriptive string
Note: Quality gates ensure code meets standards before merging. This example includes linting and testing steps.

Quality Gate vs. Continuous Integration

  • Quality Gate: Enforces strict criteria before merging
  • CI: Runs tests on every commit, but doesn't block merges

11. Commenting on PRs

Option Values Notes
github.event.pull_request.number 123 PR number from event
github.event.pull_request.head.sha abc123def456... Head commit SHA
github.token ${{ secrets.GITHUB_TOKEN }} Token with write permissions

Example: Comment on PR with linting results

name: Comment on PR
on:
  pull_request:
    types: [opened, synchronize]
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run linter
        run: |
          npm install
          npx eslint src/
      - name: Comment on PR
        uses: actions/github-script@v7
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            const fs = require('fs');
            const results = fs.readFileSync('lint-results.txt', 'utf8');
            await github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: `Linting issues found:\n\`\`\`\n${results}\n\`\`\``
            });
Note: Requires pull_request or push trigger to access PR context.
Warning: Avoid excessive comments that can spam the PR conversation.

12. Using Pre-commit Hooks

Hook Type Description Configuration Example
pre-commit Runs before committing code .pre-commit-config.yaml
- repo: https://github.com/psf/black
  rev: 22.3.0
  hooks:
    - id: black
pre-push Runs before pushing to remote .pre-commit-config.yaml
- repo: https://github.com/pre-commit/mirrors-eslint
  rev: v0.44.0
  hooks:
    - id: eslint

Example: Setting up pre-commit hooks in GitHub Actions

name: Pre-commit Checks
on:
  pull_request:
    branches: [ main ]

jobs:
  pre-commit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.10'
      - name: Install pre-commit
        run: pip install pre-commit
      - name: Run pre-commit hooks
        run: pre-commit run --all-files
Note: Ensure .pre-commit-config.yaml is committed to the repository for hooks to be enforced.
Warning: If pre-commit hooks fail, the GitHub Actions job will also fail and prevent the commit/push.

Was this article helpful?