Move complex scripts out of workflow YAML files into separate, dedicated script files in your repository. This practice significantly improves workflow maintainability, enables local testing of CI logic, and makes code reviews more effective.

Benefits:

Example:

Instead of:

# .github/workflows/example.yml
steps:
  - name: Upload external data for benchmark
    run: |
      mkdir -p /tmp/benchmark-data
      curl -L https://example.com/data.zip -o /tmp/benchmark-data/data.zip
      unzip /tmp/benchmark-data/data.zip -d /tmp/benchmark-data
      # many more lines of complex logic...

Prefer:

# .github/workflows/example.yml
steps:
  - name: Upload external data for benchmark
    run: ./scripts/ci/upload-benchmark-data.sh

With script file:

# scripts/ci/upload-benchmark-data.sh
#!/bin/bash
set -euo pipefail

mkdir -p /tmp/benchmark-data
curl -L https://example.com/data.zip -o /tmp/benchmark-data/data.zip
unzip /tmp/benchmark-data/data.zip -d /tmp/benchmark-data
# many more lines of complex logic...

When scripts become more complex, consider also adding comments about their purpose and relationship to the build process to help other developers understand the workflow.