From 1bf8f324d67daa9b0a3c7c31f0fd395f614d3072 Mon Sep 17 00:00:00 2001 From: Imani Manyara Date: Wed, 16 Sep 2026 06:18:54 -0400 Subject: [PATCH 1/3] 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/tests.yml | 42 +++++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 640b1ba..b446daf 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -4,7 +4,7 @@ on: pull_request: branches: [main] paths-ignore: - - '*.md' + - '**.md' concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -15,17 +15,13 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 15 strategy: - fail-fast: true + # One cell failing must not cancel the others; which cells fail is the signal. + fail-fast: false matrix: php: [ 8.4, 8.5 ] - laravel: [ 13.* ] stability: [ prefer-stable ] - include: - - laravel: 13.* - testbench: 11.* - carbon: ^3.0 - name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} + name: P${{ matrix.php }} - ${{ matrix.stability }} steps: - name: Checkout code @@ -36,17 +32,41 @@ jobs: with: php-version: ${{ matrix.php }} extensions: dom, curl, libxml, mbstring, sqlite3, pdo_sqlite, zip - coverage: pcov + # No driver: nothing consumed the report, and pcov instruments every + # file on every run. Restore with an upload or a --min gate. + coverage: none - name: Setup problem matchers run: | echo "::add-matcher::${{ runner.tool_cache }}/php.json" echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + - 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 "laravel/framework:${{ matrix.laravel }}" --no-interaction --no-update composer update --${{ matrix.stability }} --prefer-dist --no-interaction - name: Execute tests - run: ./vendor/bin/pest --coverage + run: ./vendor/bin/pest From 8558a0c9dec1a3b8f40d4ca9bbbf8c4336861752 Mon Sep 17 00:00:00 2001 From: Imani Manyara Date: Wed, 16 Sep 2026 06:28:05 -0400 Subject: [PATCH 2/3] Test against the floor, not only the ceiling The matrix only ever resolved prefer-stable, so nothing installed the minimum versions composer.json advertises. A floor can be wrong for months that way: the constraint says a consumer may use the old release, and no run ever proved it. Adding the leg is the only thing that checks the claim. Paired with --prefer-stable deliberately. Bare `composer update --prefer-lowest` resolves the lowest UNSTABLE release of every dependency, which fails for a reason that has nothing to do with this package. Verified on a scratch project: --prefer-lowest --prefer-stable installs psr/log 3.0.0 where plain --prefer-stable installs 3.0.2, and composer accepts the flag twice, so the one-line form needs no branch. Not applied where it would mean nothing: three packages install from a lock with `composer install`, where there is no resolution to steer, and tenancy-boilerplate is an application whose tracked lock is the point. Four packages already carry a single floor cell in `include`, which is the cheaper shape and is left alone. --- .github/workflows/tests.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b446daf..bb42a39 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -19,8 +19,10 @@ jobs: fail-fast: false matrix: php: [ 8.4, 8.5 ] - stability: [ prefer-stable ] - + # prefer-lowest is what actually installs the floor composer.json declares. + # Without it nothing ever resolves the minimum this package tells consumers + # it supports, so a wrong floor can ship for months unnoticed. + stability: [prefer-lowest, prefer-stable] name: P${{ matrix.php }} - ${{ matrix.stability }} steps: @@ -66,7 +68,7 @@ jobs: - name: Install dependencies run: | - composer update --${{ matrix.stability }} --prefer-dist --no-interaction + composer update --${{ matrix.stability }} --prefer-stable --prefer-dist --no-interaction - name: Execute tests run: ./vendor/bin/pest From ea60d0ebabe4132ddce35ecb38c0ef97d721ef22 Mon Sep 17 00:00:00 2001 From: Imani Manyara Date: Wed, 16 Sep 2026 06:31:08 -0400 Subject: [PATCH 3/3] Call the shared tests workflow instead of restating it laranail/.github has shipped a reusable tests workflow for months and one package of fifty-one called it. That is why a single defect -- pinning the Laravel version into composer.json before the suite ran -- had to be fixed in twenty-two places, and why prefer-lowest had to be added in twenty-seven. Everything this file used to spell out now lives in one definition: the PHP matrix, the composer cache and the laranail archive eviction it needs, the prefer-lowest leg, fail-fast, the timeout. What stays here is what is genuinely this package's own -- its PHP versions, its extensions, its test command -- passed as inputs. REQUIRES the corrected laranail/.github tests.yml. The version dated 2026-08-28 defaults laravel-versions to ["13.*"] and runs `composer require illuminate/contracts` before the suite, which is the pin this change exists to remove, and it has no laranail cache eviction, so adopting it unfixed would serve stale archives from the moving v0.1.0 tag. Land that first. --- .github/workflows/tests.yml | 76 ++++++++----------------------------- 1 file changed, 15 insertions(+), 61 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bb42a39..4077079 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -5,70 +5,24 @@ on: branches: [main] paths-ignore: - '**.md' + workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +# Calls the one shared definition in laranail/.github. Everything this file used +# to spell out -- the PHP matrix, the composer cache and its laranail eviction, +# prefer-lowest, fail-fast, timeouts -- lives there now, so a change to how this +# family tests is one edit instead of fifty. jobs: - tests: - runs-on: ubuntu-latest - timeout-minutes: 15 - strategy: - # One cell failing must not cancel the others; which cells fail is the signal. - fail-fast: false - matrix: - php: [ 8.4, 8.5 ] - # prefer-lowest is what actually installs the floor composer.json declares. - # Without it nothing ever resolves the minimum this package tells consumers - # it supports, so a wrong floor can ship for months unnoticed. - stability: [prefer-lowest, prefer-stable] - name: P${{ matrix.php }} - ${{ matrix.stability }} - - steps: - - name: Checkout code - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - - name: Setup PHP - uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2 - with: - php-version: ${{ matrix.php }} - extensions: dom, curl, libxml, mbstring, sqlite3, pdo_sqlite, zip - # No driver: nothing consumed the report, and pcov instruments every - # file on every run. Restore with an upload or a --min gate. - coverage: none - - - name: Setup problem matchers - run: | - echo "::add-matcher::${{ runner.tool_cache }}/php.json" - echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" - - - 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 --${{ matrix.stability }} --prefer-stable --prefer-dist --no-interaction - - - name: Execute tests - run: ./vendor/bin/pest + pest: + uses: laranail/.github/.github/workflows/tests.yml@main + with: + # Pin nothing: resolve whatever composer.json allows. The shared workflow + # still defaults laravel-versions to ["13.*"], which runs + # `composer require illuminate/contracts` and REWRITES composer.json, so + # the suite would test a manifest this package does not ship. Remove this + # line only once that default is ["*"]. + laravel-versions: '["*"]' + php-extensions: 'dom, curl, libxml, mbstring, sqlite3, pdo_sqlite, zip'