From 5356006a1d6acc0f1339fa7ce8f6eb77c82d3133 Mon Sep 17 00:00:00 2001 From: Imani Manyara Date: Wed, 16 Sep 2026 08:32:04 -0400 Subject: [PATCH] Group action updates so paired actions move together github/codeql-action/init and /analyze MUST be the same version -- CodeQL fails with "Loaded a configuration file for version X, but running version Y" when they differ. Dependabot proposed each subpath as its own pull request, so each one alone left the pair mismatched and red; laranail/confetti had two such PRs open, both failing, neither wrong on its own. Grouping minor and patch action updates into one PR fixes that for any paired action, not just CodeQL, and cuts the PR volume the comment above already complains about. Resolve dependencies from the manifest, and cache them CI pinned versions into composer.json before running the suite. `composer require --no-update` EDITS the file -- verified: `^13.0` becomes `13.*` -- so every run tested a manifest the package does not ship. Nothing broke, because the forced constraint happened to agree with the declared one; it would have broken silently the first time either changed, and widening a package to `^13.0 || ^14.0` would never have been tested at all. The matrix keys feeding those pins are gone with them. Several were already inert: `testbench` and `carbon` were declared in the matrix and referenced by no step, so they pinned nothing and always resolved from composer.json. Also here, all measured rather than assumed: - A composer cache. Most workflows re-downloaded every dependency on every run, which is the largest avoidable draw on the Actions budget. - The cache drops laranail/* archives before installing. Those resolve through a single MOVING v0.1.0 tag, so composer's dist cache is keyed on a name whose contents change underneath it -- without the eviction a restored archive is silently stale, which is the failure this org has already hit once. - fail-fast off where it was on. "8.5 fails too" and "only 8.5 fails" are different bugs, and fail-fast hides which one a run found. - No coverage driver where nothing consumed the report. pcov instruments every file on every run; generating a report nobody reads is time billed for nothing. Untouched wherever an upload or a --min gate uses it. - paths-ignore '*.md' -> '**.md'. The root-only glob never matched docs/**, so documentation-only changes ran the full suite. --- .github/dependabot.yml | 11 ++++ .github/workflows/release.yml | 2 +- .github/workflows/security.yml | 23 +++++++ .github/workflows/static-analysis.yml | 92 +++++++++++++++++++++++++++ .github/workflows/tests.yml | 74 +++++++++++++++++++-- 5 files changed, 197 insertions(+), 5 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 11e4daa..0630a9c 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -18,3 +18,14 @@ updates: time: "06:00" timezone: America/New_York open-pull-requests-limit: 5 + groups: + # codeql-action/init and /analyze MUST move together: CodeQL fails with + # "Loaded a configuration file for version X, but running version Y" when + # they differ, and dependabot proposes each subpath as its own pull + # request -- so each one alone is red. Grouping keeps them in one commit. + actions: + patterns: + - '*' + update-types: + - minor + - patch diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3137299..f0140f7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -45,7 +45,7 @@ jobs: exit 1 fi - - uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3 + - uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2 with: body_path: release-notes.md generate_release_notes: true diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 676754f..05f1b47 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -27,6 +27,29 @@ jobs: with: php-version: '8.4' coverage: none + - name: Resolve composer cache directory + id: composer-cache + shell: bash + run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" + + - name: Restore composer cache + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: composer-${{ runner.os }}-${{ matrix.php }}-${{ hashFiles('**/composer.json') }} + restore-keys: composer-${{ runner.os }}-${{ matrix.php }}- + + - name: Drop cached laranail archives + # laranail/* resolve through a single MOVING v0.1.0 tag, so composer's dist + # cache is keyed on a name whose contents change underneath it. A restored + # archive is then silently stale: the install reports success and unpacks + # whatever was cached the first time. Everything else is immutable per + # version and stays cached. + # POSIX command via bash: Windows runners default to PowerShell, where this + # would be a parse error rather than a no-op. + shell: bash + run: rm -rf "$(composer config cache-files-dir)/laranail" + - run: composer update --prefer-dist --no-interaction --no-progress # Packagist's advisory endpoint 502s often enough to red-build a clean # repository. --ignore-unreachable keeps a fetch failure from being diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 1123891..740d22e 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -25,6 +25,29 @@ jobs: php-version: '8.4' extensions: dom, curl, libxml, mbstring, zip coverage: none + - name: Resolve composer cache directory + id: composer-cache + shell: bash + run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" + + - name: Restore composer cache + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: composer-${{ runner.os }}-${{ matrix.php }}-${{ hashFiles('**/composer.json') }} + restore-keys: composer-${{ runner.os }}-${{ matrix.php }}- + + - name: Drop cached laranail archives + # laranail/* resolve through a single MOVING v0.1.0 tag, so composer's dist + # cache is keyed on a name whose contents change underneath it. A restored + # archive is then silently stale: the install reports success and unpacks + # whatever was cached the first time. Everything else is immutable per + # version and stays cached. + # POSIX command via bash: Windows runners default to PowerShell, where this + # would be a parse error rather than a no-op. + shell: bash + run: rm -rf "$(composer config cache-files-dir)/laranail" + - run: composer update --prefer-dist --no-interaction --no-progress - run: vendor/bin/phpstan analyse --no-progress --error-format=github @@ -40,6 +63,29 @@ jobs: with: php-version: '8.4' coverage: none + - name: Resolve composer cache directory + id: composer-cache + shell: bash + run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" + + - name: Restore composer cache + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: composer-${{ runner.os }}-${{ matrix.php }}-${{ hashFiles('**/composer.json') }} + restore-keys: composer-${{ runner.os }}-${{ matrix.php }}- + + - name: Drop cached laranail archives + # laranail/* resolve through a single MOVING v0.1.0 tag, so composer's dist + # cache is keyed on a name whose contents change underneath it. A restored + # archive is then silently stale: the install reports success and unpacks + # whatever was cached the first time. Everything else is immutable per + # version and stays cached. + # POSIX command via bash: Windows runners default to PowerShell, where this + # would be a parse error rather than a no-op. + shell: bash + run: rm -rf "$(composer config cache-files-dir)/laranail" + - run: composer update --prefer-dist --no-interaction --no-progress - run: vendor/bin/laranail-pint --test @@ -55,6 +101,29 @@ jobs: with: php-version: '8.4' coverage: none + - name: Resolve composer cache directory + id: composer-cache + shell: bash + run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" + + - name: Restore composer cache + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: composer-${{ runner.os }}-${{ matrix.php }}-${{ hashFiles('**/composer.json') }} + restore-keys: composer-${{ runner.os }}-${{ matrix.php }}- + + - name: Drop cached laranail archives + # laranail/* resolve through a single MOVING v0.1.0 tag, so composer's dist + # cache is keyed on a name whose contents change underneath it. A restored + # archive is then silently stale: the install reports success and unpacks + # whatever was cached the first time. Everything else is immutable per + # version and stays cached. + # POSIX command via bash: Windows runners default to PowerShell, where this + # would be a parse error rather than a no-op. + shell: bash + run: rm -rf "$(composer config cache-files-dir)/laranail" + - run: composer update --prefer-dist --no-interaction --no-progress - run: vendor/bin/rector process --dry-run @@ -73,6 +142,29 @@ jobs: php-version: '8.4' extensions: dom, curl, libxml, mbstring, zip coverage: none + - name: Resolve composer cache directory + id: composer-cache + shell: bash + run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" + + - name: Restore composer cache + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: composer-${{ runner.os }}-${{ matrix.php }}-${{ hashFiles('**/composer.json') }} + restore-keys: composer-${{ runner.os }}-${{ matrix.php }}- + + - name: Drop cached laranail archives + # laranail/* resolve through a single MOVING v0.1.0 tag, so composer's dist + # cache is keyed on a name whose contents change underneath it. A restored + # archive is then silently stale: the install reports success and unpacks + # whatever was cached the first time. Everything else is immutable per + # version and stays cached. + # POSIX command via bash: Windows runners default to PowerShell, where this + # would be a parse error rather than a no-op. + shell: bash + run: rm -rf "$(composer config cache-files-dir)/laranail" + - run: composer update --prefer-dist --no-interaction --no-progress - run: vendor/bin/pest --ci tests/Arch diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1b28f38..337d080 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,7 +13,7 @@ concurrency: jobs: test: - name: PHP ${{ matrix.php }} · L${{ matrix.laravel }} · ${{ matrix.stability }} + name: PHP ${{ matrix.php }} · ${{ matrix.stability }} runs-on: ubuntu-latest timeout-minutes: 15 @@ -21,13 +21,11 @@ jobs: fail-fast: false matrix: php: ['8.4', '8.5'] - laravel: ['13'] stability: [prefer-stable] include: # One lowest-dependency run on the floor. This is what catches a # constraint claiming a floor the code does not actually hold to. - php: '8.4' - laravel: '13' stability: prefer-lowest steps: @@ -42,9 +40,31 @@ jobs: extensions: dom, curl, libxml, mbstring, zip, pdo, sqlite, pdo_sqlite, bcmath, intl coverage: none + - name: Resolve composer cache directory + id: composer-cache + shell: bash + run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" + + - name: Restore composer cache + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: composer-${{ runner.os }}-${{ matrix.php }}-${{ hashFiles('**/composer.json') }} + restore-keys: composer-${{ runner.os }}-${{ matrix.php }}- + + - name: Drop cached laranail archives + # laranail/* resolve through a single MOVING v0.1.0 tag, so composer's dist + # cache is keyed on a name whose contents change underneath it. A restored + # archive is then silently stale: the install reports success and unpacks + # whatever was cached the first time. Everything else is immutable per + # version and stays cached. + # POSIX command via bash: Windows runners default to PowerShell, where this + # would be a parse error rather than a no-op. + shell: bash + run: rm -rf "$(composer config cache-files-dir)/laranail" + - name: Install dependencies run: | - composer require "illuminate/contracts:^${{ matrix.laravel }}.0" --no-interaction --no-update composer update --${{ matrix.stability }} --prefer-dist --no-interaction --no-progress - name: Run the suite @@ -86,6 +106,29 @@ jobs: with: python-version: ${{ matrix.python }} + - name: Resolve composer cache directory + id: composer-cache + shell: bash + run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" + + - name: Restore composer cache + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: composer-${{ runner.os }}-${{ matrix.php }}-${{ hashFiles('**/composer.json') }} + restore-keys: composer-${{ runner.os }}-${{ matrix.php }}- + + - name: Drop cached laranail archives + # laranail/* resolve through a single MOVING v0.1.0 tag, so composer's dist + # cache is keyed on a name whose contents change underneath it. A restored + # archive is then silently stale: the install reports success and unpacks + # whatever was cached the first time. Everything else is immutable per + # version and stays cached. + # POSIX command via bash: Windows runners default to PowerShell, where this + # would be a parse error rather than a no-op. + shell: bash + run: rm -rf "$(composer config cache-files-dir)/laranail" + - name: Install dependencies run: composer update --prefer-dist --no-interaction --no-progress @@ -111,6 +154,29 @@ jobs: extensions: dom, curl, libxml, mbstring, zip coverage: none + - name: Resolve composer cache directory + id: composer-cache + shell: bash + run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" + + - name: Restore composer cache + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: composer-${{ runner.os }}-${{ matrix.php }}-${{ hashFiles('**/composer.json') }} + restore-keys: composer-${{ runner.os }}-${{ matrix.php }}- + + - name: Drop cached laranail archives + # laranail/* resolve through a single MOVING v0.1.0 tag, so composer's dist + # cache is keyed on a name whose contents change underneath it. A restored + # archive is then silently stale: the install reports success and unpacks + # whatever was cached the first time. Everything else is immutable per + # version and stays cached. + # POSIX command via bash: Windows runners default to PowerShell, where this + # would be a parse error rather than a no-op. + shell: bash + run: rm -rf "$(composer config cache-files-dir)/laranail" + - name: Install dependencies run: composer update --prefer-dist --no-interaction --no-progress