Sync generated files #3
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: Sync generated files | |
| # Daily sweep that regenerates fogproject's committed build artifacts - the | |
| # gettext .pot/.po files and PSR2 formatting - and pushes a fixup commit when | |
| # they are stale. | |
| # | |
| # WHY THIS EXISTS | |
| # | |
| # Those artifacts are produced by .githooks/pre-commit, which runs on the | |
| # committer's machine. A hook cannot install anything on a machine it does not | |
| # control, so it skips (loudly - see .githooks/lib/require-tools.sh) when | |
| # php-cs-fixer or gettext is absent. That makes a commit from a machine without | |
| # the tools silently incomplete, and the omission then surfaces later as a large | |
| # unrelated diff attributed to whoever next commits from a machine that has | |
| # them. Availability is only half of it: two machines that both have the tools | |
| # still disagree when the versions differ, which is why php-cs-fixer is pinned | |
| # below rather than taken from whatever the runner happens to ship. | |
| # | |
| # Regenerating centrally makes the tooling requirement advisory rather than | |
| # load-bearing: contribute from anything, and this corrects it once a day. | |
| # | |
| # WHY IT IS A SCHEDULE AND NOT A PUSH TRIGGER | |
| # | |
| # The same reason check-fog-version.yml is. A push-triggered stub living in | |
| # fogproject and calling a reusable workflow here means the bot's own fixup push | |
| # re-triggers the stub, which re-triggers this, which pushes another fixup. That | |
| # exact loop put 30 commits on dev-branch in ~20 minutes before it was caught. A | |
| # cron tick cannot look at what was last pushed, so it cannot feed itself. | |
| # | |
| # ORDERING AGAINST check-fog-version.yml | |
| # | |
| # Runs at 09:40 UTC, 30 minutes ahead of the version sweep's 10:10. A commit | |
| # pushed from here changes the branch's commit count, which is what FOG_VERSION | |
| # is derived from - so the version sweep must see this commit rather than race | |
| # it. Going first means the version is correct within the same half hour instead | |
| # of lagging a day. | |
| # | |
| # Deliberately a SEPARATE concurrency group from that workflow. Sharing the | |
| # group would look tidier but would deadlock the moment this one ever calls it | |
| # as a reusable workflow: the callee would queue behind the caller that is | |
| # waiting on it. | |
| # | |
| # Never touches `stable` - the branch filter below excludes it, exactly as the | |
| # version sweep does, because that branch's contents are owned by | |
| # stable-releases.yml. | |
| on: | |
| schedule: | |
| - cron: "40 9 * * *" | |
| workflow_dispatch: | |
| inputs: | |
| branch: | |
| description: "Single fogproject branch to sync (omit to sweep all watched branches)" | |
| required: false | |
| type: string | |
| concurrency: | |
| group: fog-generated-sync | |
| cancel-in-progress: false | |
| permissions: | |
| contents: write | |
| env: | |
| # Pinned, not "latest". An unpinned formatter is a second source of drift: | |
| # 3.x and 4.x disagree about the same file, so an unpinned CI run would | |
| # reformat whatever the previous run wrote. Bump this deliberately, and expect | |
| # one large commit when you do. | |
| PHP_CS_FIXER_VERSION: "3.62.0" | |
| jobs: | |
| discover-branches: | |
| runs-on: ubuntu-24.04 | |
| outputs: | |
| branches: ${{ steps.list.outputs.branches }} | |
| steps: | |
| - uses: actions/create-github-app-token@v3 | |
| id: app-token | |
| with: | |
| client-id: ${{ vars.FOG_WORKFLOWS_APPID }} | |
| private-key: ${{ secrets.FOG_WORKFLOWS_PRIVATE_KEY }} | |
| owner: FOGProject | |
| repositories: "fogproject" | |
| - name: List watched branches | |
| id: list | |
| env: | |
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | |
| SINGLE_BRANCH: ${{ inputs.branch }} | |
| run: | | |
| set -e | |
| if [ -n "$SINGLE_BRANCH" ]; then | |
| branches=$(jq -n --arg branch "$SINGLE_BRANCH" -c '[$branch]') | |
| else | |
| all=$(gh api repos/FOGProject/fogproject/branches --paginate --jq '.[].name') | |
| matched=$(printf '%s\n' "$all" | grep -E '^(working-1\.6|dev-branch)$|^(rc-|feature-)' || true) | |
| branches=$(printf '%s\n' "$matched" | jq -R -s -c 'split("\n") | map(select(length > 0))') | |
| fi | |
| echo "branches=$branches" >> "$GITHUB_OUTPUT" | |
| { | |
| echo "## Watched branches" | |
| echo "" | |
| if [ "$branches" = "[]" ]; then | |
| echo "None found." | |
| else | |
| printf '%s\n' "$branches" | jq -r '.[] | "- `\(.)`"' | |
| fi | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| sync: | |
| needs: discover-branches | |
| if: needs.discover-branches.outputs.branches != '[]' | |
| runs-on: ubuntu-24.04 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| branch: ${{ fromJson(needs.discover-branches.outputs.branches) }} | |
| steps: | |
| - uses: actions/create-github-app-token@v3 | |
| id: app-token | |
| with: | |
| client-id: ${{ vars.FOG_WORKFLOWS_APPID }} | |
| private-key: ${{ secrets.FOG_WORKFLOWS_PRIVATE_KEY }} | |
| owner: FOGProject | |
| repositories: "fogproject" | |
| - uses: actions/checkout@v7 | |
| with: | |
| token: ${{ steps.app-token.outputs.token }} | |
| repository: FOGProject/fogproject | |
| ref: ${{ matrix.branch }} | |
| # Shallow is fine here: unlike the version sweep this needs no tags and | |
| # no commit counting, only the tree at the branch tip. | |
| fetch-depth: 1 | |
| - name: Install tooling | |
| run: | | |
| set -e | |
| sudo apt-get update -qq | |
| sudo apt-get install -y -qq gettext php-cli | |
| curl -sSL -o /usr/local/bin/php-cs-fixer \ | |
| "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/releases/download/v${PHP_CS_FIXER_VERSION}/php-cs-fixer.phar" | |
| chmod +x /usr/local/bin/php-cs-fixer | |
| php-cs-fixer --version | |
| - name: Regenerate translation template | |
| id: lang | |
| run: | | |
| set -e | |
| # The same script .githooks/pre-commit calls, run from fogproject's own | |
| # checkout rather than reimplemented here -- so the hook and this job | |
| # cannot produce different output for the same tree. | |
| # | |
| # Branches cut before that script existed do not carry it, and rc-* and | |
| # feature-* branches can be cut from anywhere -- so its absence is a | |
| # normal state, not an error. Skip and say so rather than failing the | |
| # job: a workflow that goes red on branches nobody can fix is one | |
| # people learn to ignore. Deliberately NOT falling back to inlining the | |
| # commands here, because a second copy of them is exactly the drift | |
| # this shared script exists to prevent. | |
| if [ ! -f .githooks/lib/update-language.sh ]; then | |
| echo "skipped=true" >> "$GITHUB_OUTPUT" | |
| echo "::notice::.githooks/lib/update-language.sh is not on this branch; skipping translation regeneration." | |
| exit 0 | |
| fi | |
| echo "skipped=false" >> "$GITHUB_OUTPUT" | |
| sh .githooks/lib/update-language.sh "$PWD" | |
| - name: Apply PSR2 formatting | |
| env: | |
| # The phar refuses to run as a non-interactive "risky" job otherwise. | |
| PHP_CS_FIXER_IGNORE_ENV: "1" | |
| run: | | |
| set -e | |
| # Whole tree, deliberately. The hook fixes only the files staged for a | |
| # given commit, which is right locally but leaves anything a | |
| # tool-less machine committed untouched. This is the sweep that | |
| # catches those. | |
| # | |
| # No `|| true`: in fix mode (as opposed to --dry-run) the fixer exits | |
| # 0 whether or not it changed anything, so a non-zero here is a real | |
| # failure -- a parse error or a missing extension -- and swallowing it | |
| # would mean committing a half-formatted tree and reporting success. | |
| php-cs-fixer fix packages/web --rules=@PSR2 --quiet | |
| - name: Commit and push if stale | |
| id: commit | |
| env: | |
| BOT_SLUG: ${{ steps.app-token.outputs.app-slug }} | |
| run: | | |
| set -e | |
| if git diff --quiet; then | |
| echo "changed=false" >> "$GITHUB_OUTPUT" | |
| echo "Nothing to do on ${{ matrix.branch }}." | |
| exit 0 | |
| fi | |
| echo "changed=true" >> "$GITHUB_OUTPUT" | |
| echo "files=$(git diff --name-only | wc -l)" >> "$GITHUB_OUTPUT" | |
| git diff --stat | |
| git config user.name "${BOT_SLUG}[bot]" | |
| git config user.email "${BOT_SLUG}[bot]@users.noreply.github.com" | |
| git add -A | |
| git commit -m "Generated Files Sync: regenerate translations and PSR2 formatting on ${{ matrix.branch }}" | |
| git push origin "HEAD:${{ matrix.branch }}" | |
| - name: Summarize | |
| if: always() | |
| run: | | |
| { | |
| echo "## \`${{ matrix.branch }}\`" | |
| echo "" | |
| if [ "${{ steps.commit.outputs.changed }}" = "true" ]; then | |
| echo "🔧 **Regenerated** - ${{ steps.commit.outputs.files }} file(s) were stale and have been committed." | |
| elif [ "${{ steps.commit.outcome }}" = "success" ]; then | |
| echo "✅ Already current - nothing to regenerate." | |
| else | |
| echo "⚠️ The sync did not complete - check the job log." | |
| fi | |
| if [ "${{ steps.lang.outputs.skipped }}" = "true" ]; then | |
| echo "" | |
| echo "ℹ️ Translation regeneration was skipped - this branch predates \`.githooks/lib/update-language.sh\`. PSR2 formatting still ran." | |
| fi | |
| } >> "$GITHUB_STEP_SUMMARY" |