feat(ci): migrate to gitleaks and optimize docs deployments #48
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: "CI/CD" | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| job: | |
| description: "Specific job to run (leave empty for all)" | |
| type: string | |
| required: false | |
| nix_installer: | |
| description: "Nix installer strategy" | |
| type: choice | |
| options: | |
| - full | |
| - quick | |
| default: quick | |
| required: false | |
| debug_enabled: | |
| description: "Run the workflow with tmate.io debugging enabled" | |
| required: true | |
| type: boolean | |
| default: false | |
| deploy_enabled: | |
| description: "Deploy to Cloudflare Workers" | |
| required: false | |
| type: boolean | |
| default: false | |
| workflow_call: | |
| pull_request: | |
| types: [opened, labeled, reopened, synchronize] | |
| paths-ignore: | |
| - "*.md" | |
| push: | |
| branches: | |
| - "main" | |
| paths-ignore: | |
| - "*.md" | |
| defaults: | |
| run: | |
| shell: bash | |
| permissions: | |
| contents: read | |
| deployments: write | |
| actions: write | |
| id-token: write | |
| jobs: | |
| # job 0: skip-check | |
| # prevents duplicate workflow runs on same commit hash | |
| skip-check: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| should_skip: ${{ steps.skip_check.outputs.should_skip }} | |
| steps: | |
| - id: skip_check | |
| uses: fkirc/skip-duplicate-actions@f75f66ce1886f00957d99748a42c724f4330bdcf # ratchet:fkirc/skip-duplicate-actions@v5 | |
| with: | |
| skip_after_successful_duplicate: 'true' | |
| do_not_skip: '["schedule"]' | |
| concurrent_skipping: 'never' | |
| cancel_others: 'false' | |
| # job 1: secrets-scan | |
| # scans repository for hardcoded secrets using gitleaks | |
| # ALWAYS runs (ignores skip-check) - security critical | |
| secrets-scan: | |
| name: gitleaks | |
| needs: skip-check | |
| runs-on: ubuntu-latest | |
| if: | | |
| !cancelled() && | |
| (github.event_name != 'workflow_dispatch' || | |
| inputs.job == '' || | |
| inputs.job == 'secrets-scan') | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # ratchet:actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Nix | |
| uses: ./.github/actions/setup-nix | |
| env: | |
| SOPS_AGE_KEY: ${{ secrets.SOPS_AGE_KEY }} | |
| with: | |
| installer: ${{ inputs.nix_installer || 'quick' }} | |
| system: x86_64-linux | |
| enable-cachix: true | |
| - name: Scan for secrets with gitleaks | |
| run: nix develop -c just scan-secrets | |
| # job 2: set-variables | |
| # determines deployment settings and variables based on event type | |
| # ALWAYS runs (ignores skip-check) - needed for job routing | |
| set-variables: | |
| needs: [skip-check, secrets-scan] | |
| runs-on: ubuntu-latest | |
| if: | | |
| !cancelled() && | |
| (github.event_name != 'workflow_dispatch' || | |
| inputs.job == '' || | |
| inputs.job == 'set-variables') | |
| outputs: | |
| debug: ${{ steps.set-variables.outputs.debug }} | |
| skip_ci: ${{ steps.set-variables.outputs.skip_ci }} | |
| deploy_enabled: ${{ steps.set-variables.outputs.deploy_enabled }} | |
| deploy_environment: ${{ steps.set-variables.outputs.deploy_environment }} | |
| checkout_ref: ${{ steps.set-variables.outputs.checkout_ref }} | |
| checkout_rev: ${{ steps.set-variables.outputs.checkout_rev }} | |
| packages: ${{ steps.discover-packages.outputs.packages }} | |
| steps: | |
| - name: Set action variables | |
| id: set-variables | |
| run: | | |
| DEBUG="false" | |
| SKIP_CI="false" | |
| DEPLOY_ENABLED="false" | |
| DEPLOY_ENVIRONMENT="preview" | |
| if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then | |
| DEBUG="${{ inputs.debug_enabled }}" | |
| DEPLOY_ENABLED="${{ inputs.deploy_enabled }}" | |
| fi | |
| if [[ "${{ github.event_name }}" == "pull_request" ]]; then | |
| if ${{ contains(github.event.pull_request.labels.*.name, 'skip-ci') }}; then | |
| SKIP_CI="true" | |
| fi | |
| if ${{ contains(github.event.pull_request.labels.*.name, 'actions-debug') }}; then | |
| DEBUG="true" | |
| fi | |
| if ${{ contains(github.event.pull_request.labels.*.name, 'docs-preview') }}; then | |
| DEPLOY_ENABLED="true" | |
| DEPLOY_ENVIRONMENT="preview" | |
| fi | |
| CHECKOUT_REF="${{ github.event.pull_request.head.ref }}" | |
| CHECKOUT_REV="${{ github.event.pull_request.head.sha }}" | |
| else | |
| CHECKOUT_REF="${{ github.ref_name }}" | |
| CHECKOUT_REV="${{ github.sha }}" | |
| fi | |
| # Enable deployment on push to main (production) | |
| if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" == "refs/heads/main" ]]; then | |
| DEPLOY_ENABLED="true" | |
| DEPLOY_ENVIRONMENT="production" | |
| fi | |
| echo "DEBUG=$DEBUG" | |
| echo "SKIP_CI=$SKIP_CI" | |
| echo "DEPLOY_ENABLED=$DEPLOY_ENABLED" | |
| echo "DEPLOY_ENVIRONMENT=$DEPLOY_ENVIRONMENT" | |
| echo "CHECKOUT_REF=$CHECKOUT_REF" | |
| echo "CHECKOUT_REV=$CHECKOUT_REV" | |
| echo "DEBUG=$DEBUG" >> $GITHUB_OUTPUT | |
| echo "SKIP_CI=$SKIP_CI" >> $GITHUB_OUTPUT | |
| echo "DEPLOY_ENABLED=$DEPLOY_ENABLED" >> $GITHUB_OUTPUT | |
| echo "DEPLOY_ENVIRONMENT=$DEPLOY_ENVIRONMENT" >> $GITHUB_OUTPUT | |
| echo "CHECKOUT_REF=$CHECKOUT_REF" >> $GITHUB_OUTPUT | |
| echo "CHECKOUT_REV=$CHECKOUT_REV" >> $GITHUB_OUTPUT | |
| - name: Checkout for package discovery | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # ratchet:actions/checkout@v4 | |
| with: | |
| sparse-checkout: | | |
| packages | |
| justfile | |
| sparse-checkout-cone-mode: false | |
| - name: Discover packages | |
| id: discover-packages | |
| run: | | |
| # Install just for package discovery | |
| curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin | |
| PACKAGES=$(just list-packages-json) | |
| echo "packages=$PACKAGES" >> $GITHUB_OUTPUT | |
| echo "Discovered packages: $PACKAGES" | |
| nix: | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| matrix: | |
| os: [ubuntu-latest] | |
| needs: [skip-check, set-variables] | |
| if: | | |
| !cancelled() && | |
| needs.skip-check.outputs.should_skip != 'true' && | |
| needs.set-variables.outputs.skip_ci != 'true' && | |
| (github.event_name != 'workflow_dispatch' || | |
| inputs.job == '' || | |
| inputs.job == 'nix') | |
| concurrency: | |
| group: nix-${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.pull_request.number || github.ref_name }} | |
| cancel-in-progress: true | |
| steps: | |
| - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # ratchet:actions/checkout@v4 | |
| - name: Setup Nix | |
| uses: ./.github/actions/setup-nix | |
| env: | |
| SOPS_AGE_KEY: ${{ secrets.SOPS_AGE_KEY }} | |
| with: | |
| installer: ${{ inputs.nix_installer || 'quick' }} | |
| system: x86_64-linux | |
| enable-cachix: true | |
| cachix-name: ${{ vars.CACHIX_CACHE_NAME }} | |
| cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }} | |
| - name: Setup tmate debug session | |
| uses: mxschmitt/action-tmate@e5c7151931ca95bad1c6f4190c730ecf8c7dde48 # ratchet:mxschmitt/action-tmate@v3 | |
| if: ${{ needs.set-variables.outputs.debug == 'true' }} | |
| - name: Install omnix | |
| run: nix --accept-flake-config profile install "github:juspay/omnix" | |
| - name: Summarize flake | |
| run: om show . | |
| - name: Run flake CI and push to cachix | |
| env: | |
| SOPS_AGE_KEY: ${{ secrets.SOPS_AGE_KEY }} | |
| run: | | |
| nix develop -c sops exec-env vars/shared.yaml ' | |
| om ci run | tee /dev/stderr | cachix push "$CACHIX_CACHE_NAME" | |
| ' | |
| test: | |
| needs: [skip-check, set-variables] | |
| if: | | |
| !cancelled() && | |
| needs.skip-check.outputs.should_skip != 'true' && | |
| needs.set-variables.outputs.skip_ci != 'true' && | |
| (github.event_name != 'workflow_dispatch' || | |
| inputs.job == '' || | |
| inputs.job == 'test') | |
| strategy: | |
| matrix: | |
| package: ${{ fromJson(needs.set-variables.outputs.packages) }} | |
| uses: ./.github/workflows/package-test.yaml | |
| with: | |
| package-name: ${{ matrix.package.name }} | |
| package-path: ${{ matrix.package.path }} | |
| debug-enabled: ${{ needs.set-variables.outputs.debug }} | |
| nix-installer: ${{ inputs.nix_installer || 'quick' }} | |
| secrets: | |
| SOPS_AGE_KEY: ${{ secrets.SOPS_AGE_KEY }} | |
| # job 3: preview-release-version | |
| # Preview semantic-release version for each package (PR only, fast feedback) | |
| preview-release-version: | |
| needs: [set-variables] | |
| if: | | |
| !cancelled() && | |
| github.event_name == 'pull_request' | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| package: ${{ fromJson(needs.set-variables.outputs.packages) }} | |
| runs-on: ubuntu-latest | |
| # semantic-release verifyAuth requires push permission even in dry-run mode | |
| # https://github.com/semantic-release/semantic-release/blob/v25.0.1/index.js#L87-L98 | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # ratchet:actions/checkout@v4 | |
| with: | |
| ref: ${{ github.head_ref }} # Checkout actual PR branch, not merge commit | |
| fetch-depth: 0 # Full history needed for semantic-release analysis | |
| fetch-tags: true # Explicitly fetch all tags for version detection | |
| - name: Fetch target branch for preview | |
| run: | | |
| git fetch origin | |
| git branch -f main origin/main | |
| - name: Configure git identity for temporary commits | |
| run: | | |
| git config --global user.name "github-actions[bot]" | |
| git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| - name: Setup Nix | |
| uses: ./.github/actions/setup-nix | |
| env: | |
| SOPS_AGE_KEY: ${{ secrets.SOPS_AGE_KEY }} | |
| with: | |
| installer: quick | |
| system: x86_64-linux | |
| enable-cachix: true | |
| - name: Install dependencies | |
| run: nix develop -c bun install | |
| - name: Setup tmate debug session | |
| if: needs.set-variables.outputs.debug == 'true' | |
| uses: mxschmitt/action-tmate@e5c7151931ca95bad1c6f4190c730ecf8c7dde48 # ratchet:mxschmitt/action-tmate@v3 | |
| - name: Preview version for ${{ matrix.package.name }} | |
| run: | | |
| echo "::group::Preview semantic-release version" | |
| nix develop -c just preview-version main ${{ matrix.package.path }} | |
| echo "::endgroup::" | |
| # job 4: preview-docs-deploy | |
| # Deploy docs to preview environment (PR only, fast feedback) | |
| preview-docs-deploy: | |
| needs: [set-variables] | |
| if: | | |
| !cancelled() && | |
| github.event_name == 'pull_request' | |
| permissions: | |
| contents: read | |
| deployments: write | |
| uses: ./.github/workflows/deploy-docs.yaml | |
| with: | |
| branch: ${{ github.head_ref }} | |
| environment: preview | |
| debug_enabled: ${{ needs.set-variables.outputs.debug }} | |
| secrets: inherit | |
| # job 5: production-release-packages | |
| # Release packages to production on main/beta branches | |
| # IGNORES skip-check but REQUIRES test+nix success/skipped (safe for fast-forward merge) | |
| production-release-packages: | |
| needs: [set-variables, test, nix] | |
| if: | | |
| github.repository_owner == 'sciexp' && | |
| (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && | |
| (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/beta') && | |
| needs.set-variables.outputs.skip_ci != 'true' | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| package: ${{ fromJson(needs.set-variables.outputs.packages) }} | |
| permissions: | |
| contents: write | |
| id-token: write | |
| uses: ./.github/workflows/package-release.yaml | |
| with: | |
| package-path: ${{ matrix.package.path }} | |
| package-name: ${{ matrix.package.name }} | |
| release-dry-run: false | |
| debug-enabled: ${{ needs.set-variables.outputs.debug == 'true' }} | |
| checkout-ref: ${{ needs.set-variables.outputs.checkout_ref }} | |
| secrets: | |
| SOPS_AGE_KEY: ${{ secrets.SOPS_AGE_KEY }} | |
| # job 6: production-docs-deploy | |
| # Documentation deployment to production (conditional) | |
| # IGNORES skip-check - depends on production-release-packages to ensure packages released first | |
| production-docs-deploy: | |
| needs: [set-variables, test, production-release-packages] | |
| if: | | |
| !cancelled() && | |
| needs.set-variables.outputs.skip_ci != 'true' && | |
| (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && | |
| (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/beta') && | |
| needs.set-variables.outputs.deploy_enabled == 'true' && | |
| (github.event_name != 'workflow_dispatch' || | |
| inputs.job == '' || | |
| inputs.job == 'deploy') | |
| uses: ./.github/workflows/deploy-docs.yaml | |
| with: | |
| debug_enabled: ${{ needs.set-variables.outputs.debug }} | |
| branch: ${{ needs.set-variables.outputs.checkout_ref }} | |
| environment: ${{ needs.set-variables.outputs.deploy_environment }} | |
| secrets: inherit |