Repository navigation
main #627
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: main | |
| on: | |
| workflow_dispatch: | |
| schedule: | |
| - cron: "23 6 * * *" | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| # One lock covers checkout, snapshot restore, refresh, archive and deployment. | |
| # PRs never hold the production lock. Do not cancel an in-flight publication. | |
| concurrency: | |
| group: ${{ github.event_name == 'pull_request' && format('pr-{0}', github.ref) || 'osl-production-pages' }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| permissions: | |
| contents: read | |
| jobs: | |
| check-branch: | |
| if: ${{ github.event_name == 'pull_request' }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| concurrency: | |
| group: check-pr-${{ github.ref }} | |
| cancel-in-progress: true | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Check if the PR's branch is updated | |
| uses: osl-incubator/gh-check-pr-is-updated@1.0.0 | |
| with: | |
| remote_branch: origin/main | |
| pr_sha: ${{ github.event.pull_request.head.sha }} | |
| build: | |
| if: >- | |
| github.event_name == 'pull_request' || | |
| (github.repository == 'OpenScienceLabs/opensciencelabs.github.io' && | |
| github.ref == 'refs/heads/main') | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 45 | |
| permissions: | |
| contents: write | |
| id-token: write | |
| outputs: | |
| refresh_failed: ${{ (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && steps.refresh.outcome != 'success' }} | |
| env: | |
| # Non-secret identifiers; credentials are generated only in the auth step. | |
| GA4_PROPERTY_ID: "365530978" | |
| GA4_HOSTNAMES: "opensciencelabs.org" | |
| GA4_SERVICE_ACCOUNT: "osl-analytics-exporter@osl-general.iam.gserviceaccount.com" | |
| GA4_WIF_PROVIDER: "projects/11701823742/locations/global/workloadIdentityPools/osl-analytics/providers/github" | |
| defaults: | |
| run: | |
| # bash -el required so conda activation persists (README: IMPORTANT) | |
| shell: bash -el {0} | |
| steps: | |
| # Pending runs check out current main, not a superseded content commit. | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ github.event_name == 'pull_request' && github.sha || 'main' }} | |
| - uses: conda-incubator/setup-miniconda@v3 | |
| with: | |
| miniconda-version: "latest" | |
| environment-file: conda/dev.yaml | |
| channels: conda-forge,nodefaults | |
| activate-environment: osl-web | |
| auto-update-conda: true | |
| conda-solver: libmamba | |
| conda-remove-defaults: true | |
| - name: Install dependencies | |
| run: | | |
| poetry check | |
| poetry install | |
| python -m pip install -r requirements-analytics.txt | |
| python -m nltk.downloader punkt | |
| - name: Analytics tests (no Google credentials) | |
| run: | | |
| python -m unittest discover -s tests -v | |
| python tests/render_analytics_dom.py | |
| node tests/analytics-js.test.cjs | |
| - name: Restore last successful analytics snapshot | |
| if: ${{ github.event_name != 'pull_request' }} | |
| run: python -m scripts.analytics.restore | |
| # Render blog .qmd → .md so Build uses correct index.md (with YAML) | |
| - name: Pre-build blog (quarto + inject) | |
| run: makim pages.pre-build | |
| # Skip mkdocs-build in pre-commit so we don't fail when repo's md != pre-build output | |
| - name: Linter | |
| if: ${{ github.event_name == 'pull_request' }} | |
| env: | |
| SKIP: mkdocs-build | |
| run: | | |
| pre-commit install | |
| pre-commit run --all-files --verbose | |
| # Every main deployment, including a merged PR, attempts a fresh export. | |
| # WIF must allow push, schedule and workflow_dispatch on this workflow. | |
| # Authenticate immediately before the API calls, after slow pre-builds. | |
| - name: Check refresh configuration | |
| id: analytics_config | |
| if: ${{ github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }} | |
| run: | | |
| if [[ -n "$GA4_PROPERTY_ID" && -n "$GA4_HOSTNAMES" && -n "$GA4_WIF_PROVIDER" && -n "$GA4_SERVICE_ACCOUNT" ]]; then | |
| echo "ready=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "::warning::GA4 configuration missing; preserving the previous report. See docs/analytics.md." | |
| fi | |
| - name: Authenticate to Google (OIDC, no credential file) | |
| id: google_auth | |
| if: ${{ steps.analytics_config.outputs.ready == 'true' }} | |
| continue-on-error: true | |
| uses: google-github-actions/auth@v3 | |
| with: | |
| workload_identity_provider: ${{ env.GA4_WIF_PROVIDER }} | |
| service_account: ${{ env.GA4_SERVICE_ACCOUNT }} | |
| token_format: access_token | |
| access_token_scopes: https://www.googleapis.com/auth/analytics.readonly | |
| access_token_lifetime: 900s | |
| create_credentials_file: false | |
| export_environment_variables: false | |
| - name: Refresh analytics aggregates | |
| id: refresh | |
| if: ${{ steps.google_auth.outcome == 'success' }} | |
| continue-on-error: true | |
| env: | |
| GA4_ACCESS_TOKEN: ${{ steps.google_auth.outputs.access_token }} | |
| run: python -m scripts.analytics.export | |
| - name: Analytics refresh summary | |
| if: ${{ github.event_name != 'pull_request' }} | |
| env: | |
| REFRESH_OUTCOME: ${{ steps.refresh.outcome }} | |
| run: | | |
| echo "### Public analytics" >> "$GITHUB_STEP_SUMMARY" | |
| echo "Refresh: $REFRESH_OUTCOME. Failed/skipped refreshes never change the previous report's timestamp." >> "$GITHUB_STEP_SUMMARY" | |
| echo "See docs/analytics.md for configuration and troubleshooting." >> "$GITHUB_STEP_SUMMARY" | |
| - name: Build the book | |
| run: | | |
| makim pages.build | |
| echo "opensciencelabs.org" > build/CNAME | |
| - name: Validate endpoint and audit published files | |
| run: python -m scripts.analytics.audit | |
| # Durable storage, not a trigger for another workflow. Archive the whole | |
| # validated site as before; analytics/data.json survives all content builds. | |
| - name: Archive validated site and analytics snapshot | |
| uses: peaceiris/actions-gh-pages@v4 | |
| if: ${{ github.event_name != 'pull_request' }} | |
| with: | |
| github_token: ${{ secrets.GITHUB_TOKEN }} | |
| publish_dir: ./build | |
| publish_branch: gh-pages | |
| - name: Upload Pages artifact | |
| if: ${{ github.event_name != 'pull_request' }} | |
| uses: actions/upload-pages-artifact@v3 | |
| with: | |
| path: build | |
| deploy: | |
| needs: build | |
| if: >- | |
| github.repository == 'OpenScienceLabs/opensciencelabs.github.io' && | |
| github.ref == 'refs/heads/main' && | |
| (github.event_name == 'push' || github.event_name == 'schedule' || | |
| github.event_name == 'workflow_dispatch') | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| permissions: | |
| pages: write | |
| id-token: write | |
| environment: | |
| name: github-pages | |
| url: ${{ steps.deployment.outputs.page_url }} | |
| steps: | |
| # Direct deployment in this run: GITHUB_TOKEN commits do not trigger a | |
| # second workflow. One-time setup: Pages source must be GitHub Actions. | |
| - name: Publish to GitHub Pages | |
| id: deployment | |
| uses: actions/deploy-pages@v4 | |
| - name: Flag failed refresh after safely publishing retained data | |
| if: ${{ needs.build.outputs.refresh_failed == 'true' }} | |
| run: | | |
| echo "::error::Analytics refresh did not succeed. The retained report (or first-run unavailable state) was published. See the build job and docs/analytics.md." | |
| exit 1 |