From 896615cb1c6e42dd90865dce63a5df9c49a1d023 Mon Sep 17 00:00:00 2001 From: Imani Manyara Date: Wed, 16 Sep 2026 06:18:59 -0400 Subject: [PATCH] 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/workflows/security.yml | 23 +++++++ .github/workflows/static-analysis.yml | 92 +++++++++++++++++++++++++ .github/workflows/tests.yml | 97 +++++++++++++++++++++++++-- 3 files changed, 208 insertions(+), 4 deletions(-) diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index c4a220e..c1da7d5 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -27,5 +27,28 @@ 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: composer audit 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 1041d8d..b4cb9e9 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 @@ -75,6 +95,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 @@ -113,6 +156,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 without the rendering engines run: | composer remove --dev --no-update gotenberg/gotenberg-php dompdf/dompdf @@ -141,6 +207,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