Skip to content

Latest commit

 

History

History
447 lines (368 loc) · 18.9 KB

File metadata and controls

447 lines (368 loc) · 18.9 KB

Workflows

Reusable GitHub Actions job snippets for code quality checks. Each snippet is a self-contained job definition that projects compose into their own workflows.


Snippets

Tool Category File
bundler-audit security CI/security/bundler-audit.yml
gitleaks security CI/security/gitleaks.yml
pip-audit security CI/security/pip-audit.yml
semgrep security CI/security/semgrep.yml
trivy security CI/security/trivy.yml
Clippy linters CI/linters/clippy.yml
ESLint linters CI/linters/eslint.yml
golangci-lint linters CI/linters/golangci-lint.yml
Hadolint linters CI/linters/hadolint.yml
HTMLHint linters CI/linters/htmlhint.yml
ktlint linters CI/linters/ktlint.yml
markdownlint linters CI/linters/markdownlint.yml
mermaid-cli linters CI/linters/mermaid.yml
phpcs linters CI/linters/phpcs.yml
RuboCop linters CI/linters/rubocop.yml
Ruff linters CI/linters/ruff.yml
ShellCheck linters CI/linters/shellcheck.yml
SQLFluff linters CI/linters/sqlfluff.yml
Stylelint linters CI/linters/stylelint.yml
SwiftLint linters CI/linters/swiftlint.yml
tflint linters CI/linters/tflint.yml
yamllint linters CI/linters/yamllint.yml
mypy static_analysis CI/static_analysis/mypy.yml
PHPStan static_analysis CI/static_analysis/phpstan.yml
SpotBugs static_analysis CI/static_analysis/spotbugs.yml
mermaid-cli build CI/build/mermaid.yml
BATS tests CI/tests/bats.yml
cargo test tests CI/tests/cargo_test.yml
go test tests CI/tests/go_test.yml
Jest tests CI/tests/jest.yml
Laravel tests CI/tests/laravel_tests.yml
pytest tests CI/tests/pytest.yml
RSpec tests CI/tests/rspec.yml
xcodebuild test tests CI/tests/xcodebuild_test.yml (macos-latest)

How it works

The project uses a three-layer architecture:

scripts/shell/<category>/<tool>.sh   ← bash logic (tested, standalone)
        ↓  assembled by scripts/assemble-ci.sh
scripts/CI/<category>/<tool>.yml     ← source YAML (references the script)
        ↓
CI/<category>/<tool>.yml             ← assembled output (script inlined, ready to use)

Shell scripts (scripts/shell/) contain the actual linting/testing logic. They run standalone in CI without needing anything from this repo's structure.

Source YAMLs (scripts/CI/) define the GitHub Actions job: install the tool, then call the script with run: bash scripts/shell/<category>/<tool>.sh.

Assembled YAMLs (CI/) are generated by the assembler. The run: bash ... line is replaced with run: | followed by the full script body inlined. These are the files downstream projects import.


Project structure

Workflows/
├── scripts/
│   ├── assemble-ci.sh              # assembles source YAMLs → CI/
│   ├── CI/                         # source YAML templates
│   │   ├── linters/
│   │   │   ├── clippy.yml
│   │   │   ├── eslint.yml
│   │   │   ├── golangci-lint.yml
│   │   │   ├── hadolint.yml
│   │   │   ├── htmlhint.yml
│   │   │   ├── ktlint.yml
│   │   │   ├── markdownlint.yml
│   │   │   ├── mermaid.yml
│   │   │   ├── phpcs.yml
│   │   │   ├── rubocop.yml
│   │   │   ├── ruff.yml
│   │   │   ├── shellcheck.yml
│   │   │   ├── sqlfluff.yml
│   │   │   ├── stylelint.yml
│   │   │   ├── swiftlint.yml
│   │   │   ├── tflint.yml
│   │   │   └── yamllint.yml
│   │   ├── security/
│   │   │   ├── bundler-audit.yml
│   │   │   ├── gitleaks.yml
│   │   │   ├── pip-audit.yml
│   │   │   ├── semgrep.yml
│   │   │   └── trivy.yml
│   │   ├── static_analysis/
│   │   │   ├── mypy.yml
│   │   │   ├── phpstan.yml
│   │   │   └── spotbugs.yml
│   │   └── tests/
│   │       ├── bats.yml
│   │       ├── cargo_test.yml
│   │       ├── go_test.yml
│   │       ├── jest.yml
│   │       ├── laravel_tests.yml
│   │       ├── pytest.yml
│   │       ├── rspec.yml
│   │       └── xcodebuild_test.yml
│   └── shell/                      # bash scripts (one per tool)
│       ├── linters/
│       │   ├── clippy.sh
│       │   ├── eslint.sh
│       │   ├── golangci-lint.sh
│       │   ├── hadolint.sh
│       │   ├── htmlhint.sh
│       │   ├── ktlint.sh
│       │   ├── markdownlint.sh
│       │   ├── mermaid.sh
│       │   ├── phpcs.sh
│       │   ├── rubocop.sh
│       │   ├── ruff.sh
│       │   ├── shellcheck.sh
│       │   ├── sqlfluff.sh
│       │   ├── stylelint.sh
│       │   ├── swiftlint.sh
│       │   ├── tflint.sh
│       │   └── yamllint.sh
│       ├── security/
│       │   ├── bundler-audit.sh
│       │   ├── gitleaks.sh
│       │   ├── pip-audit.sh
│       │   ├── semgrep.sh
│       │   └── trivy.sh
│       └── tests/
│           ├── cargo_test.sh
│           ├── jest.sh
│           └── xcodebuild_test.sh
│
├── CI/                             # assembled output (ready to use)
│   ├── linters/
│   ├── security/
│   ├── static_analysis/
│   └── tests/
│
├── tests/
│   ├── assemble-ci.bats            # tests for the assembler
│   ├── linters/                    # unit tests for each shell script
│   │   ├── clippy.bats
│   │   ├── hadolint.bats
│   │   ├── htmlhint.bats
│   │   ├── markdownlint.bats
│   │   ├── phpcs.bats
│   │   ├── shellcheck.bats
│   │   ├── sqlfluff.bats
│   │   ├── stylelint.bats
│   │   ├── tflint.bats
│   │   └── yamllint.bats
│   ├── security/
│   │   ├── bundler-audit.bats
│   │   ├── gitleaks.bats
│   │   ├── pip-audit.bats
│   │   ├── semgrep.bats
│   │   └── trivy.bats
│   ├── tests/
│   │   ├── cargo_test.bats
│   │   ├── jest.bats
│   │   └── xcodebuild_test.bats
│   ├── dependabot/
│   │   └── dependabot.bats         # validates the Dependabot template
│   └── helpers/
│       └── common.bash             # shared test utilities (mocks, temp dirs)
│
├── dependabot/
│   └── dependabot.yml              # Dependabot config template (copy to .github/)
│
└── rules/                          # living documentation for contributors
    ├── README.md
    ├── _meta/how-to-write-rules.md
    └── process/
        ├── architecture-design.md
        └── ci-cd.md

Assembly

Run the assembler for each category:

bash scripts/assemble-ci.sh scripts/CI/linters        scripts/shell/linters        CI/linters
bash scripts/assemble-ci.sh scripts/CI/security       scripts/shell/security       CI/security
bash scripts/assemble-ci.sh scripts/CI/static_analysis scripts/shell/static_analysis CI/static_analysis
bash scripts/assemble-ci.sh scripts/CI/tests           scripts/shell/tests           CI/tests

The assembler takes three arguments: <source-yamls-dir> <scripts-dir> <output-dir>.

For each .yml in the source dir it looks for a matching .sh in the scripts dir. If found, the run: bash ... line is replaced with run: | and the script body is inlined (shebang stripped, indentation preserved). If not found, the YAML is copied as-is.

Example transformation:

Source (scripts/CI/linters/shellcheck.yml):

shellcheck:
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v4
    - name: Install ShellCheck
      run: sudo apt-get update && sudo apt-get install -y shellcheck
    - name: Run ShellCheck on scripts
      run: bash scripts/shell/linters/shellcheck.sh

Assembled (CI/linters/shellcheck.yml):

shellcheck:
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v4
    - name: Install ShellCheck
      run: sudo apt-get update && sudo apt-get install -y shellcheck
    - name: Run ShellCheck on scripts
      run: |
        ERROR_FOUND=0
        DIRS=("scripts" "docker/scripts")
        for DIR in "${DIRS[@]}"; do
          ...
        done
        ...

Available snippets

Security

Snippet Tool What it checks
CI/security/bundler-audit.yml bundler-audit CVEs in Ruby gem dependencies via the Ruby Advisory Database
CI/security/gitleaks.yml gitleaks Hardcoded secrets, tokens, and API keys
CI/security/pip-audit.yml pip-audit CVEs in Python dependencies via the PyPI Advisory Database
CI/security/semgrep.yml semgrep OWASP Top 10 patterns and insecure coding patterns across Python, JS/TS, Go, Java, Ruby, and more
CI/security/trivy.yml trivy CVEs in OS packages, container images, and dependency manifests

Linters

Snippet Tool What it checks
CI/linters/clippy.yml clippy Rust
CI/linters/eslint.yml eslint JavaScript / TypeScript
CI/linters/golangci-lint.yml golangci-lint Go
CI/linters/hadolint.yml hadolint Dockerfiles
CI/linters/htmlhint.yml htmlhint HTML files
CI/linters/ktlint.yml ktlint Kotlin
CI/linters/markdownlint.yml markdownlint Markdown files
CI/linters/mermaid.yml mermaid-cli Mermaid diagrams
CI/linters/phpcs.yml phpcs PHP (PSR-12 and custom rulesets)
CI/linters/rubocop.yml rubocop Ruby
CI/linters/ruff.yml ruff Python
CI/linters/shellcheck.yml shellcheck Shell scripts
CI/linters/sqlfluff.yml sqlfluff SQL files
CI/linters/stylelint.yml stylelint CSS / SCSS / LESS
CI/linters/swiftlint.yml swiftlint Swift
CI/linters/tflint.yml tflint Terraform
CI/linters/yamllint.yml yamllint YAML files

Static analysis

Snippet Tool What it checks
CI/static_analysis/mypy.yml mypy Python (type checking)
CI/static_analysis/phpstan.yml PHPStan PHP (level from phpstan.neon)
CI/static_analysis/spotbugs.yml SpotBugs Java (via Maven)

Tests

Snippet What it runs
CI/tests/bats.yml BATS tests (tests/ directory)
CI/tests/cargo_test.yml Rust test suite (cargo test --all-features --verbose)
CI/tests/go_test.yml Go test suite (go test ./...)
CI/tests/jest.yml JavaScript/TypeScript test suite (Jest)
CI/tests/laravel_tests.yml Laravel test suite (PHP 8.2, SQLite, parallel)
CI/tests/pytest.yml Python test suite (pytest)
CI/tests/rspec.yml Ruby test suite (RSpec via Bundler)
CI/tests/xcodebuild_test.yml Swift/iOS XCTest suite (xcodebuild test via xcpretty) — macOS only

Using snippets in a project

Each assembled YAML defines a single top-level job key. Copy the content into your workflow under jobs::

# .github/workflows/ci.yml
name: CI

on: [push, pull_request]

jobs:
  shellcheck:        # paste content of CI/linters/shellcheck.yml here
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install ShellCheck
        run: sudo apt-get update && sudo apt-get install -y shellcheck
      - name: Run ShellCheck on scripts
        run: |
          ERROR_FOUND=0
          ...

  phpstan:           # paste content of CI/static_analysis/phpstan.yml here
    ...

Dependabot template

dependabot/dependabot.yml is a ready-to-copy Dependabot configuration template. Unlike the CI snippets in CI/, this file is not a GitHub Actions job — it is a repository-level configuration that GitHub reads natively from .github/dependabot.yml.

This artefact does not follow the three-layer pattern (shell → source YAML → assembled YAML). There is no shell script or assembler step. Copy the file directly into your project's .github/ directory.

Usage

cp dependabot/dependabot.yml <your-project>/.github/dependabot.yml

The template enables weekly automated dependency-update PRs for seven ecosystems: github-actions, npm, pip, bundler, composer, cargo, and gomod. Each entry uses open-pull-requests-limit: 5 and a stable commit-message prefix so the resulting PRs are easy to filter and review.

Adjust the directory field per entry if your dependency manifests live in a subdirectory rather than the repository root.


Shell script conventions

Every script in scripts/shell/ follows the same patterns defined in rules/process/ci-cd.md:

Error accumulator — check all files before exiting, so one failure doesn't hide others:

ERROR_FOUND=0
for file in "${files[@]}"; do
  some-tool "$file" || ERROR_FOUND=1
done
if [[ $ERROR_FOUND -eq 0 ]]; then
  echo "✅ All files passed!"
else
  echo "❌ Issues found!"
  exit 1
fi

Config validation — hard fail early if a required config is missing:

if [ ! -f ".yamllint.yml" ]; then
  echo "::error::Config file .yamllint.yml not found"
  exit 1
fi

Graceful skip — exit 0 when there are no files to check:

if [ ${#files[@]} -eq 0 ]; then
  echo "⚠️ No .yml files found. Skipping."
  exit 0
fi

Output conventions:

  • — all checks passed
  • — issues found
  • ⚠️ — skipped or non-critical warning
  • ℹ️ — currently processing a file
  • ::error:: — GitHub Actions error annotation (hard failures)

Running tests locally

Tests use BATS. Each shell script has a corresponding .bats file that mocks external tools and tests success, failure, and edge cases in isolation.

# Install (macOS)
brew install bats-core

# Run all tests
bats --recursive tests/

Test helpers (tests/helpers/common.bash) provide:

  • setup_test_dir / teardown_test_dir — create and clean up a temp working directory
  • mock_tool <name> <exit_code> — stub an external binary to always exit with a given code
  • mock_tool_conditional <name> — stub that exits 1 only when its argument matches $MOCK_FAIL_PATTERN

Adding a new snippet

  1. Write the bash script in scripts/shell/<category>/<tool>.sh Follow the error accumulator pattern and output conventions above.

  2. Write tests in tests/<category>/<tool>.bats Cover: missing config, no files found, all pass, one fails, multiple files with one failing.

  3. Create the source YAML in scripts/CI/<category>/<tool>.yml Install the tool, then call the script: run: bash scripts/shell/<category>/<tool>.sh

  4. Run the assembler to generate the output file in CI/.

  5. Run tests to verify everything works.


Rules and documentation

The rules/ directory is the authoritative guide for contributors:

  • rules/process/architecture-design.md — directory layout, file naming, snippet scope
  • rules/process/ci-cd.md — mandatory patterns for every job (error accumulator, config validation, action versions, etc.)
  • rules/_meta/how-to-write-rules.md — how to write and format rules files