Commercial launch core: repository hardening and release programme (#… #298
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: Test All | |
| on: | |
| push: | |
| branches: | |
| - main | |
| pull_request: | |
| branches: | |
| - main | |
| types: [opened, synchronize, reopened, ready_for_review] | |
| paths: | |
| - 'packages/**/*.php' | |
| - 'packages/**/composer.json' | |
| - 'tests/**/*.php' | |
| - 'composer.json' | |
| - 'composer.lock' | |
| - 'docs/packages/catalog.md' | |
| - 'scripts/**/*.php' | |
| - 'docs/getting-started/install.md' | |
| - 'docs/getting-started/quickstart.md' | |
| - '.docker/Dockerfile' | |
| - '.github/workflows/test-fast-pr.yml' | |
| - '.github/workflows/test-full.yml' | |
| - 'phpunit.xml' | |
| - 'phpunit-coverage.xml' | |
| workflow_dispatch: | |
| inputs: | |
| cell: | |
| description: 'Optional exact behaviour or unit cell (for example: l12-feature-admin). Leave blank for the complete workflow.' | |
| required: false | |
| type: string | |
| concurrency: | |
| group: >- | |
| ${{ github.event_name == 'workflow_dispatch' && inputs.cell != '' | |
| && format('{0}-phpunit-target-{1}', github.ref, inputs.cell) | |
| || format('{0}-phpunit-full', github.head_ref || github.ref) }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| env: | |
| CACHE_STORE: array | |
| QUEUE_CONNECTION: sync | |
| SESSION_DRIVER: array | |
| jobs: | |
| fast-pr: | |
| if: github.event_name == 'pull_request' && github.event.pull_request.draft == false | |
| name: Fast PR gate | |
| uses: ./.github/workflows/test-fast-pr.yml | |
| matrix: | |
| name: Resolve repository-owned Test All matrix | |
| runs-on: ubuntu-latest | |
| outputs: | |
| behaviour: ${{ steps.matrix.outputs.behaviour }} | |
| unit: ${{ steps.matrix.outputs.unit }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Setup PHP | |
| uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2 | |
| with: | |
| php-version: 8.4 | |
| coverage: none | |
| - name: Export shared matrix | |
| id: matrix | |
| shell: bash | |
| env: | |
| TARGET_CELL: ${{ inputs.cell }} | |
| run: | | |
| cell_argument=() | |
| if [ -n "$TARGET_CELL" ]; then | |
| php scripts/test-all-matrix.php target --cell="$TARGET_CELL" > /dev/null | |
| cell_argument=(--cell="$TARGET_CELL") | |
| fi | |
| echo "behaviour=$(php scripts/test-all-matrix.php behaviour "${cell_argument[@]}")" >> "$GITHUB_OUTPUT" | |
| echo "unit=$(php scripts/test-all-matrix.php unit "${cell_argument[@]}")" >> "$GITHUB_OUTPUT" | |
| sentinel: | |
| needs: [fast-pr, matrix] | |
| if: >- | |
| always() | |
| && needs.matrix.result == 'success' | |
| && (needs.fast-pr.result == 'success' || needs.fast-pr.result == 'skipped') | |
| && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) | |
| && !(github.event_name == 'workflow_dispatch' && inputs.cell != '') | |
| name: Sentinel - portability, cache, migrations, destructive schema | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| env: | |
| COMPOSER_AUTH: '{"github-oauth": {"github.com": "${{ secrets.ACCESS_TOKEN }}"} }' | |
| PAO_DISABLE: 1 | |
| services: | |
| mysql: | |
| image: mysql:8.0 | |
| env: | |
| MYSQL_ROOT_PASSWORD: root | |
| MYSQL_DATABASE: test_cms_sentinel | |
| ports: | |
| - 3306/tcp | |
| options: >- | |
| --health-cmd "mysqladmin ping -h 127.0.0.1 -proot || exit 1" | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 10 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Setup PHP | |
| uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2 | |
| with: | |
| php-version: 8.4 | |
| extensions: curl, dom, fileinfo, gd, intl, json, libxml, mbstring, pdo_mysql, pdo_sqlite, simplexml, sqlite3, zip | |
| tools: composer:v2 | |
| coverage: none | |
| - name: Setup problem matchers | |
| run: | | |
| echo "::add-matcher::${{ runner.tool_cache }}/php.json" | |
| echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" | |
| - name: Prepare exact Laravel 13 dependencies | |
| run: php scripts/prepare-test-all-dependencies.php --laravel='13.*' --testbench='11.*' | |
| - name: Run both sentinel cells | |
| shell: bash | |
| env: | |
| DB_HOST: 127.0.0.1 | |
| DB_PORT: ${{ job.services.mysql.ports['3306'] }} | |
| DB_DATABASE: test_cms_sentinel | |
| DB_USERNAME: root | |
| DB_PASSWORD: root | |
| run: | | |
| set +e | |
| php scripts/run-test-all-cell.php --cell=sentinel-unit --output-dir=test-all-results | |
| unit_exit=$? | |
| php scripts/run-test-all-cell.php --cell=sentinel-database --output-dir=test-all-results | |
| database_exit=$? | |
| if [ "$unit_exit" -ne 0 ] || [ "$database_exit" -ne 0 ]; then | |
| exit 1 | |
| fi | |
| - name: Upload sentinel evidence | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: test-all-sentinel | |
| path: test-all-results | |
| if-no-files-found: warn | |
| test-behaviour: | |
| needs: [sentinel, matrix] | |
| if: >- | |
| always() | |
| && needs.matrix.result == 'success' | |
| && (needs.sentinel.result == 'success' | |
| || (github.event_name == 'workflow_dispatch' && inputs.cell != '')) | |
| && (inputs.cell == '' || !contains(inputs.cell, '-unit-')) | |
| name: Test All - P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.test_suite }} - ${{ matrix.package }} - DBmysql | |
| runs-on: ubuntu-latest | |
| env: | |
| COMPOSER_AUTH: '{"github-oauth": {"github.com": "${{ secrets.ACCESS_TOKEN }}"} }' | |
| PAO_DISABLE: 1 | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{ fromJSON(needs.matrix.outputs.behaviour) }} | |
| services: | |
| mysql: | |
| image: mysql:8.0 | |
| env: | |
| MYSQL_ROOT_PASSWORD: root | |
| MYSQL_DATABASE: test_cms_multi | |
| ports: | |
| - 3306/tcp | |
| options: >- | |
| --health-cmd "mysqladmin ping -h 127.0.0.1 -proot || exit 1" | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 10 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Setup PHP | |
| uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2 | |
| with: | |
| php-version: ${{ matrix.php }} | |
| extensions: curl, dom, fileinfo, gd, intl, json, libxml, mbstring, pdo_mysql, pdo_sqlite, simplexml, sqlite3, zip | |
| tools: composer:v2 | |
| coverage: none | |
| - name: Setup problem matchers | |
| run: | | |
| echo "::add-matcher::${{ runner.tool_cache }}/php.json" | |
| echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" | |
| - name: Prepare exact framework dependencies | |
| run: php scripts/prepare-test-all-dependencies.php --laravel='${{ matrix.laravel }}' --testbench='${{ matrix.testbench }}' | |
| - name: Tune MySQL for the write-heavy test suite | |
| run: | | |
| mysql \ | |
| --host=127.0.0.1 \ | |
| --port="${{ job.services.mysql.ports['3306'] }}" \ | |
| --user=root \ | |
| --password=root \ | |
| --execute=" | |
| SET GLOBAL innodb_redo_log_capacity = 2147483648; | |
| SET GLOBAL innodb_flush_log_at_trx_commit = 2; | |
| SET GLOBAL sync_binlog = 0; | |
| " | |
| - name: Run ${{ matrix.test_suite }} tests | |
| env: | |
| DB_HOST: 127.0.0.1 | |
| DB_PORT: ${{ job.services.mysql.ports['3306'] }} | |
| DB_DATABASE: test_cms_multi | |
| DB_USERNAME: root | |
| DB_PASSWORD: root | |
| run: php scripts/run-test-all-cell.php --cell='${{ matrix.id }}' --output-dir=test-all-results | |
| - name: Upload Test All evidence | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: junit-${{ matrix.artifact_slug }} | |
| path: test-all-results | |
| if-no-files-found: warn | |
| - name: Extract engineering metrics | |
| id: engineering-metrics | |
| if: success() && matrix.laravel == '13.*' | |
| run: | | |
| php scripts/extract-pest-metrics.php "test-all-results/${{ matrix.log }}" --github-output | |
| php scripts/extract-phpstan-level.php --github-output | |
| - name: Write engineering metrics artifact | |
| if: success() && matrix.laravel == '13.*' | |
| env: | |
| TESTS: ${{ steps.engineering-metrics.outputs.tests }} | |
| ASSERTIONS: ${{ steps.engineering-metrics.outputs.assertions }} | |
| PHPSTAN_LEVEL: ${{ steps.engineering-metrics.outputs.phpstan_level }} | |
| run: | | |
| php -r 'file_put_contents( | |
| "engineering-metrics-${{ matrix.test_suite_slug }}-${{ matrix.package }}.json", | |
| json_encode([ | |
| "tests" => (int) getenv("TESTS"), | |
| "assertions" => (int) getenv("ASSERTIONS"), | |
| "phpstan_level" => (int) getenv("PHPSTAN_LEVEL"), | |
| ], JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT) . PHP_EOL, | |
| );' | |
| - name: Upload engineering metrics artifact | |
| if: success() && matrix.laravel == '13.*' | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: engineering-metrics-${{ matrix.artifact_slug }} | |
| path: engineering-metrics-${{ matrix.test_suite_slug }}-${{ matrix.package }}.json | |
| if-no-files-found: error | |
| test-unit: | |
| needs: [sentinel, matrix] | |
| if: >- | |
| always() | |
| && needs.matrix.result == 'success' | |
| && (needs.sentinel.result == 'success' | |
| || (github.event_name == 'workflow_dispatch' && inputs.cell != '')) | |
| && (inputs.cell == '' || contains(inputs.cell, '-unit-')) | |
| name: Test All - P${{ matrix.php }} - L${{ matrix.laravel }} - Unit - ${{ matrix.package }} | |
| runs-on: ubuntu-latest | |
| env: | |
| COMPOSER_AUTH: '{"github-oauth": {"github.com": "${{ secrets.ACCESS_TOKEN }}"} }' | |
| PAO_DISABLE: 1 | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{ fromJSON(needs.matrix.outputs.unit) }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Setup PHP | |
| uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2 | |
| with: | |
| php-version: ${{ matrix.php }} | |
| extensions: curl, dom, fileinfo, gd, intl, json, libxml, mbstring, pdo_sqlite, simplexml, sqlite3, zip | |
| tools: composer:v2 | |
| coverage: none | |
| - name: Setup problem matchers | |
| run: | | |
| echo "::add-matcher::${{ runner.tool_cache }}/php.json" | |
| echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" | |
| - name: Prepare exact framework dependencies | |
| run: php scripts/prepare-test-all-dependencies.php --laravel='${{ matrix.laravel }}' --testbench='${{ matrix.testbench }}' | |
| - name: Run ${{ matrix.package }} Unit tests | |
| run: php scripts/run-test-all-cell.php --cell='${{ matrix.id }}' --output-dir=test-all-results | |
| - name: Upload Test All evidence | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: junit-${{ matrix.artifact_slug }} | |
| path: test-all-results | |
| if-no-files-found: warn | |
| - name: Extract engineering metrics | |
| id: engineering-metrics | |
| if: success() && matrix.laravel == '13.*' | |
| run: | | |
| php scripts/extract-pest-metrics.php "test-all-results/${{ matrix.log }}" --github-output | |
| php scripts/extract-phpstan-level.php --github-output | |
| - name: Write engineering metrics artifact | |
| if: success() && matrix.laravel == '13.*' | |
| env: | |
| TESTS: ${{ steps.engineering-metrics.outputs.tests }} | |
| ASSERTIONS: ${{ steps.engineering-metrics.outputs.assertions }} | |
| PHPSTAN_LEVEL: ${{ steps.engineering-metrics.outputs.phpstan_level }} | |
| run: | | |
| php -r 'file_put_contents( | |
| "engineering-metrics-unit-${{ matrix.package_slug }}.json", | |
| json_encode([ | |
| "tests" => (int) getenv("TESTS"), | |
| "assertions" => (int) getenv("ASSERTIONS"), | |
| "phpstan_level" => (int) getenv("PHPSTAN_LEVEL"), | |
| ], JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT) . PHP_EOL, | |
| );' | |
| - name: Upload engineering metrics artifact | |
| if: success() && matrix.laravel == '13.*' | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: engineering-metrics-${{ matrix.artifact_slug }} | |
| path: engineering-metrics-unit-${{ matrix.package_slug }}.json | |
| if-no-files-found: error | |
| engineering-metrics: | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' | |
| name: Aggregate engineering metrics | |
| needs: [test-behaviour, test-unit] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Download engineering metrics artifacts | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| pattern: engineering-metrics-l13-* | |
| path: engineering-metrics | |
| merge-multiple: true | |
| - name: Aggregate engineering metrics | |
| id: engineering-metrics | |
| run: | | |
| php <<'PHP' | |
| <?php | |
| $files = glob('engineering-metrics/*.json'); | |
| if ($files === false || count($files) !== 11) { | |
| throw new RuntimeException('Expected engineering metrics from five Feature packages, Integration, and five Unit packages.'); | |
| } | |
| $tests = 0; | |
| $assertions = 0; | |
| $phpstanLevels = []; | |
| foreach ($files as $file) { | |
| $metrics = json_decode((string) file_get_contents($file), true, flags: JSON_THROW_ON_ERROR); | |
| $tests += (int) ($metrics['tests'] ?? 0); | |
| $assertions += (int) ($metrics['assertions'] ?? 0); | |
| $phpstanLevels[] = (int) ($metrics['phpstan_level'] ?? 0); | |
| } | |
| $phpstanLevels = array_values(array_unique($phpstanLevels)); | |
| if (count($phpstanLevels) !== 1) { | |
| throw new RuntimeException('Laravel 13 test jobs reported inconsistent PHPStan levels.'); | |
| } | |
| file_put_contents( | |
| getenv('GITHUB_OUTPUT'), | |
| "tests={$tests}\nassertions={$assertions}\nphpstan_level={$phpstanLevels[0]}\n", | |
| FILE_APPEND, | |
| ); | |
| fwrite(STDOUT, "Aggregated engineering metrics: tests={$tests}, assertions={$assertions}, phpstan_level={$phpstanLevels[0]}\n"); | |
| PHP | |
| - name: Send repository_dispatch to capell-app | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 | |
| env: | |
| ENGINEERING_METRICS_DISPATCH_TOKEN: ${{ secrets.ACCESS_TOKEN }} | |
| with: | |
| github-token: ${{ secrets.ACCESS_TOKEN }} | |
| script: | | |
| if (!process.env.ENGINEERING_METRICS_DISPATCH_TOKEN) { | |
| core.setFailed('Missing ACCESS_TOKEN secret.'); | |
| return; | |
| } | |
| const client_payload = { | |
| source: 'capell', | |
| tests: Number(`${{ steps.engineering-metrics.outputs.tests }}`), | |
| assertions: Number(`${{ steps.engineering-metrics.outputs.assertions }}`), | |
| run_url: `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${process.env.GITHUB_RUN_ID}`, | |
| sha: context.sha, | |
| phpstan_level: Number(`${{ steps.engineering-metrics.outputs.phpstan_level }}`), | |
| }; | |
| await github.rest.repos.createDispatchEvent({ | |
| owner: 'capell-app', | |
| repo: 'capell-app', | |
| event_type: 'engineering-metrics', | |
| client_payload, | |
| }); | |
| core.info(`Dispatched engineering-metrics: ${JSON.stringify(client_payload)}`); |