From 44dd1fdc5167f48e36cae86bcbeef979ba8d4afd Mon Sep 17 00:00:00 2001 From: Thomas Hauschild <7961978+Morgy93@users.noreply.github.com> Date: Fri, 10 Jul 2026 08:28:22 +0200 Subject: [PATCH 01/12] fix: update actions versions in workflows --- .github/workflows/functional-tests.yml | 4 ++-- .github/workflows/lint.yml | 13 +++++++++---- .github/workflows/magento-compatibility.yml | 6 +++--- .github/workflows/phpcs.yml | 3 +++ .github/workflows/phpunit.yml | 2 ++ 5 files changed, 19 insertions(+), 9 deletions(-) diff --git a/.github/workflows/functional-tests.yml b/.github/workflows/functional-tests.yml index e9d17254..d99c75e7 100644 --- a/.github/workflows/functional-tests.yml +++ b/.github/workflows/functional-tests.yml @@ -42,7 +42,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: path: mageforge @@ -60,7 +60,7 @@ jobs: - name: Cache Composer packages id: composer-cache - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ~/.composer/cache/files key: ${{ runner.os }}-composer-functional-${{ hashFiles('**/composer.json') }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 8442c71e..6c5729a4 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -2,8 +2,10 @@ name: Lint on: pull_request: + branches: [main] push: branches: [main] + workflow_dispatch: permissions: contents: read @@ -15,7 +17,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Set up PHP uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2 @@ -24,7 +26,7 @@ jobs: tools: composer:v2 - name: Cache Composer packages - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ~/.composer/cache/files key: ${{ runner.os }}-composer-lint-${{ hashFiles('composer.json') }} @@ -42,13 +44,16 @@ jobs: trunk: name: Trunk Check (yaml, markdown, shell, actions, …) runs-on: ubuntu-latest + # No `checks: write`: with it, trunk-action posts a *second* check-run (for + # inline annotations) on top of this job, so one finding turns two statuses + # red. Findings still surface in the job log and fail this job via its exit + # code. Re-add `checks: write` only if inline PR annotations are wanted back. permissions: contents: read - checks: write steps: - name: Checkout code - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Trunk Check uses: trunk-io/trunk-action@04ba50e7658c81db7356da96657e6e77f220bfa3 # v1.3.1 diff --git a/.github/workflows/magento-compatibility.yml b/.github/workflows/magento-compatibility.yml index a4b8fdf6..4184786e 100644 --- a/.github/workflows/magento-compatibility.yml +++ b/.github/workflows/magento-compatibility.yml @@ -55,7 +55,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: path: mageforge @@ -68,7 +68,7 @@ jobs: - name: Cache Composer packages id: composer-cache - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ~/.composer/cache/files key: ${{ runner.os }}-composer-${{ matrix.magento-version }}-${{ hashFiles('**/composer.json') }} @@ -165,4 +165,4 @@ jobs: - name: Test Summary run: | - echo "MageForge module compatibility test with Magento 2.4.8 completed" + echo "MageForge module compatibility test with Magento ${{ matrix.magento-version }} (PHP ${{ matrix.php-version }}) completed" diff --git a/.github/workflows/phpcs.yml b/.github/workflows/phpcs.yml index d4fcbb27..0fcc3542 100644 --- a/.github/workflows/phpcs.yml +++ b/.github/workflows/phpcs.yml @@ -2,8 +2,10 @@ name: PHPCS on: pull_request: + branches: [main] push: branches: [main] + workflow_dispatch: permissions: contents: read @@ -13,6 +15,7 @@ env: jobs: check-phpcs: + name: PHPCS runs-on: ubuntu-latest steps: diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index bd0d6c47..abbf133d 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -2,8 +2,10 @@ name: PHPUnit on: pull_request: + branches: [main] push: branches: [main] + workflow_dispatch: permissions: contents: read From d091dfed5ee1f9e04650f78afb8d6d442354ddb6 Mon Sep 17 00:00:00 2001 From: Thomas Hauschild <7961978+Morgy93@users.noreply.github.com> Date: Fri, 10 Jul 2026 08:40:42 +0200 Subject: [PATCH 02/12] fix: add concurrency settings to workflows --- .github/workflows/functional-tests.yml | 9 ++++++--- .github/workflows/label.yml | 6 ++++++ .github/workflows/lint.yml | 9 ++++++--- .github/workflows/magento-compatibility.yml | 9 ++++++--- .github/workflows/phpcs.yml | 9 ++++++--- .github/workflows/phpunit.yml | 9 ++++++++- .github/workflows/release-please.yml | 6 ++++++ .github/workflows/static-analysis.yml | 9 ++++++--- 8 files changed, 50 insertions(+), 16 deletions(-) diff --git a/.github/workflows/functional-tests.yml b/.github/workflows/functional-tests.yml index d99c75e7..f7495e84 100644 --- a/.github/workflows/functional-tests.yml +++ b/.github/workflows/functional-tests.yml @@ -2,11 +2,14 @@ name: Functional Tests on: pull_request: - branches: [main] - push: - branches: [main] workflow_dispatch: +concurrency: + # Heavy Magento bootstrap — cancel superseded PR runs to save CI minutes; + # manual (workflow_dispatch) runs are left to finish. + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + permissions: contents: read diff --git a/.github/workflows/label.yml b/.github/workflows/label.yml index 93002363..1125a9ab 100644 --- a/.github/workflows/label.yml +++ b/.github/workflows/label.yml @@ -6,6 +6,12 @@ name: Labeler on: [pull_request_target] +concurrency: + # One labeling run per PR; a newer event on the same PR supersedes an + # in-flight one so labels can't race each other. + group: labeler-${{ github.event.pull_request.number }} + cancel-in-progress: true + permissions: contents: read diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 6c5729a4..5ebf09fe 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -2,11 +2,14 @@ name: Lint on: pull_request: - branches: [main] - push: - branches: [main] workflow_dispatch: +concurrency: + # Cancel superseded PR runs to save CI minutes; manual (workflow_dispatch) + # runs are left to finish. + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + permissions: contents: read diff --git a/.github/workflows/magento-compatibility.yml b/.github/workflows/magento-compatibility.yml index 4184786e..48ffa47c 100644 --- a/.github/workflows/magento-compatibility.yml +++ b/.github/workflows/magento-compatibility.yml @@ -2,11 +2,14 @@ name: Magento Compatibility Test on: pull_request: - branches: [main] - push: - branches: [main] workflow_dispatch: +concurrency: + # Heavy Magento matrix — cancel superseded PR runs to save CI minutes; + # manual (workflow_dispatch) runs are left to finish. + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + permissions: contents: read diff --git a/.github/workflows/phpcs.yml b/.github/workflows/phpcs.yml index 0fcc3542..faac3593 100644 --- a/.github/workflows/phpcs.yml +++ b/.github/workflows/phpcs.yml @@ -2,11 +2,14 @@ name: PHPCS on: pull_request: - branches: [main] - push: - branches: [main] workflow_dispatch: +concurrency: + # Cancel superseded PR runs to save CI minutes; manual (workflow_dispatch) + # runs are left to finish. + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + permissions: contents: read diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index abbf133d..9796f72b 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -2,11 +2,18 @@ name: PHPUnit on: pull_request: - branches: [main] + # Runs on main too — the badges job needs the coverage + mutation artifacts + # produced by the phpunit/mutation jobs to publish the badge data. push: branches: [main] workflow_dispatch: +concurrency: + # Cancel superseded PR runs; let main pushes finish so the badges job + # (publishes coverage/MSI to the badges branch) is never cut off. + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + permissions: contents: read diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index d77e0595..a7634c95 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -5,6 +5,12 @@ on: branches: - main +concurrency: + # Serialize releases: never run two passes at once and never cancel one + # mid-flight — it may be cutting a release or (re)opening the release PR. + group: release-please + cancel-in-progress: false + permissions: contents: write pull-requests: write diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 8c410ede..863dbd57 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -8,11 +8,14 @@ name: Static Analysis on: pull_request: - branches: [main] - push: - branches: [main] workflow_dispatch: +concurrency: + # Heavy shared Magento build — cancel superseded PR runs to save CI minutes; + # manual (workflow_dispatch) runs are left to finish. + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + permissions: contents: read From 953341ca2ec21520fb3307f59c971debafbf0db7 Mon Sep 17 00:00:00 2001 From: Thomas Hauschild <7961978+Morgy93@users.noreply.github.com> Date: Fri, 10 Jul 2026 08:56:38 +0200 Subject: [PATCH 03/12] feat: add setup-magento action for CI workflows --- .github/actions/setup-magento/action.yml | 98 +++++++++++++++++++++ .github/workflows/functional-tests.yml | 53 ++--------- .github/workflows/magento-compatibility.yml | 58 ++---------- .github/workflows/static-analysis.yml | 54 ++---------- 4 files changed, 114 insertions(+), 149 deletions(-) create mode 100644 .github/actions/setup-magento/action.yml diff --git a/.github/actions/setup-magento/action.yml b/.github/actions/setup-magento/action.yml new file mode 100644 index 00000000..2d50c0b0 --- /dev/null +++ b/.github/actions/setup-magento/action.yml @@ -0,0 +1,98 @@ +name: Set up Magento +description: >- + Provisions a Magento install for CI: sets up PHP + Composer, restores the + Composer download cache, fetches Magento (git clone or the Mage-OS mirror) and + runs `bin/magento setup:install`. The calling job must provide the MySQL/MariaDB + and OpenSearch service containers (reachable at 127.0.0.1:3306 / localhost:9200). + Leaves a ready install in ./magento2 next to the checked-out module. + +inputs: + php-version: + description: PHP version passed to shivammathur/setup-php. + required: true + magento-version: + description: >- + Magento version. With magento-source=git it is the branch/tag to clone; + with magento-source=mage-os it is only used to namespace the Composer cache + (create-project always pulls the latest stable). + required: true + magento-source: + description: >- + How to fetch Magento: "git" (github.com/magento/magento2) or "mage-os" + (composer create-project via the Mage-OS mirror). + required: false + default: git + php-extensions: + description: PHP extensions to install. + required: false + default: mbstring, intl, gd, xml, soap, zip, bcmath, pdo_mysql, curl, sockets + composer-auth: + description: >- + COMPOSER_AUTH JSON (Magento Marketplace keys) for authenticated Composer + downloads. Pass ${{ secrets.COMPOSER_AUTH }}. + required: false + default: "" + +runs: + using: composite + steps: + - name: Setup PHP + uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2 + with: + php-version: ${{ inputs.php-version }} + extensions: ${{ inputs.php-extensions }} + tools: composer:v2 + + - name: Cache Composer packages + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + with: + path: ~/.composer/cache/files + key: ${{ runner.os }}-composer-${{ inputs.magento-version }}-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-composer-${{ inputs.magento-version }} + + - name: Download Magento (git clone) + if: ${{ inputs.magento-source == 'git' }} + shell: bash + run: | + git clone --depth=1 --branch=${{ inputs.magento-version }} \ + https://github.com/magento/magento2.git magento2 + + - name: Download Magento (Mage-OS mirror) + if: ${{ inputs.magento-source == 'mage-os' }} + shell: bash + run: | + composer create-project \ + --repository-url=https://mirror.mage-os.org/ \ + magento/project-community-edition \ + magento2 + + - name: Install Magento + working-directory: magento2 + shell: bash + env: + COMPOSER_AUTH: ${{ inputs.composer-auth }} + run: | + composer config minimum-stability stable + composer config prefer-stable true + composer install --no-interaction --no-progress + bin/magento setup:install \ + --base-url=http://localhost \ + --db-host=127.0.0.1 \ + --db-name=magento \ + --db-user=root \ + --db-password=magento \ + --admin-firstname=Admin \ + --admin-lastname=User \ + --admin-email=admin@example.com \ + --admin-user=admin \ + --admin-password=admin12345 \ + --language=en_US \ + --currency=USD \ + --timezone=Europe/Berlin \ + --use-rewrites=1 \ + --backend-frontname=admin \ + --search-engine=opensearch \ + --opensearch-host=localhost \ + --opensearch-port=9200 \ + --opensearch-index-prefix=magento \ + --cleanup-database diff --git a/.github/workflows/functional-tests.yml b/.github/workflows/functional-tests.yml index f7495e84..a7fea9f1 100644 --- a/.github/workflows/functional-tests.yml +++ b/.github/workflows/functional-tests.yml @@ -49,59 +49,18 @@ jobs: with: path: mageforge - - name: Setup PHP - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f - with: - php-version: "8.4" - extensions: mbstring, intl, gd, xml, soap, zip, bcmath, pdo_mysql, curl, sockets - tools: composer:v2 - - name: Setup Node.js uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af with: node-version: "20" - - name: Cache Composer packages - id: composer-cache - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + - name: Set up Magento + uses: ./mageforge/.github/actions/setup-magento with: - path: ~/.composer/cache/files - key: ${{ runner.os }}-composer-functional-${{ hashFiles('**/composer.json') }} - restore-keys: ${{ runner.os }}-composer-functional - - - name: Clone Magento - run: | - git clone --depth=1 --branch=2.4.8 https://github.com/magento/magento2.git magento2 - - - name: Install Magento - working-directory: magento2 - env: - COMPOSER_AUTH: ${{ secrets.COMPOSER_AUTH }} - run: | - composer config minimum-stability stable - composer config prefer-stable true - composer install --no-interaction --no-progress - bin/magento setup:install \ - --base-url=http://localhost \ - --db-host=127.0.0.1 \ - --db-name=magento \ - --db-user=root \ - --db-password=magento \ - --admin-firstname=Admin \ - --admin-lastname=User \ - --admin-email=admin@example.com \ - --admin-user=admin \ - --admin-password=admin12345 \ - --language=en_US \ - --currency=USD \ - --timezone=Europe/Berlin \ - --use-rewrites=1 \ - --backend-frontname=admin \ - --search-engine=opensearch \ - --opensearch-host=localhost \ - --opensearch-port=9200 \ - --opensearch-index-prefix=magento \ - --cleanup-database + php-version: "8.4" + magento-version: "2.4.8" + magento-source: git + composer-auth: ${{ secrets.COMPOSER_AUTH }} - name: Install MageForge Module working-directory: magento2 diff --git a/.github/workflows/magento-compatibility.yml b/.github/workflows/magento-compatibility.yml index 48ffa47c..130cc355 100644 --- a/.github/workflows/magento-compatibility.yml +++ b/.github/workflows/magento-compatibility.yml @@ -23,13 +23,10 @@ jobs: include: - magento-version: 2.4.7-p10 php-version: "8.3" - search-engine-name: opensearch - magento-version: 2.4.8-p5 php-version: "8.4" - search-engine-name: opensearch - magento-version: 2.4.9 php-version: "8.5" - search-engine-name: opensearch services: mysql: @@ -62,58 +59,13 @@ jobs: with: path: mageforge - - name: Setup PHP - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f + - name: Set up Magento + uses: ./mageforge/.github/actions/setup-magento with: php-version: ${{ matrix.php-version }} - extensions: mbstring, intl, gd, xml, soap, zip, bcmath, pdo_mysql, curl, sockets - tools: composer:v2 - - - name: Cache Composer packages - id: composer-cache - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 - with: - path: ~/.composer/cache/files - key: ${{ runner.os }}-composer-${{ matrix.magento-version }}-${{ hashFiles('**/composer.json') }} - restore-keys: ${{ runner.os }}-composer-${{ matrix.magento-version }} - - - name: Clone Magento - run: | - git clone --depth=1 --branch=${{ matrix.magento-version }} https://github.com/magento/magento2.git magento2 - - - name: Check Search Engine status - run: | - curl -s http://localhost:9200/_cluster/health - - - name: Install Magento - working-directory: magento2 - env: - COMPOSER_AUTH: ${{ secrets.COMPOSER_AUTH }} - run: | - composer config minimum-stability stable - composer config prefer-stable true - composer install --no-interaction --no-progress - bin/magento setup:install \ - --base-url=http://localhost \ - --db-host=127.0.0.1 \ - --db-name=magento \ - --db-user=root \ - --db-password=magento \ - --admin-firstname=Admin \ - --admin-lastname=User \ - --admin-email=admin@example.com \ - --admin-user=admin \ - --admin-password=admin12345 \ - --language=en_US \ - --currency=USD \ - --timezone=Europe/Berlin \ - --use-rewrites=1 \ - --backend-frontname=admin \ - --search-engine=opensearch \ - --opensearch-host=localhost \ - --opensearch-port=9200 \ - --opensearch-index-prefix=magento \ - --cleanup-database + magento-version: ${{ matrix.magento-version }} + magento-source: git + composer-auth: ${{ secrets.COMPOSER_AUTH }} - name: Install MageForge Module from current commit working-directory: magento2 diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 863dbd57..d7b40f7a 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -50,57 +50,13 @@ jobs: with: path: mageforge - - name: Setup PHP - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2 + - name: Set up Magento + uses: ./mageforge/.github/actions/setup-magento with: php-version: "8.4" - extensions: mbstring, intl, gd, xml, soap, zip, bcmath, pdo_mysql, curl, sockets - tools: composer:v2 - - - name: Cache Composer packages - id: composer-cache - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 - with: - path: ~/.composer/cache/files - key: ${{ runner.os }}-composer-2.4.8-${{ hashFiles('**/composer.json') }} - restore-keys: ${{ runner.os }}-composer-2.4.8 - - - name: Download Magento - run: | - composer create-project \ - --repository-url=https://mirror.mage-os.org/ \ - magento/project-community-edition \ - magento2 - - - name: Install Magento - working-directory: magento2 - env: - COMPOSER_AUTH: ${{ secrets.COMPOSER_AUTH }} - run: | - composer config minimum-stability stable - composer config prefer-stable true - composer install --no-interaction --no-progress - bin/magento setup:install \ - --base-url=http://localhost \ - --db-host=127.0.0.1 \ - --db-name=magento \ - --db-user=root \ - --db-password=magento \ - --admin-firstname=Admin \ - --admin-lastname=User \ - --admin-email=admin@example.com \ - --admin-user=admin \ - --admin-password=admin12345 \ - --language=en_US \ - --currency=USD \ - --timezone=Europe/Berlin \ - --use-rewrites=1 \ - --backend-frontname=admin \ - --search-engine=opensearch \ - --opensearch-host=localhost \ - --opensearch-port=9200 \ - --opensearch-index-prefix=magento \ - --cleanup-database + magento-version: "2.4.8" + magento-source: mage-os + composer-auth: ${{ secrets.COMPOSER_AUTH }} - name: Install MageForge module and PHPStan tooling working-directory: magento2 From 1946ffb6fdc22bf3f154229862e1f10f98780329 Mon Sep 17 00:00:00 2001 From: Thomas Hauschild <7961978+Morgy93@users.noreply.github.com> Date: Fri, 10 Jul 2026 08:59:47 +0200 Subject: [PATCH 04/12] fix: update workflows to validate pushes to main --- .github/workflows/functional-tests.yml | 4 +++- .github/workflows/lint.yml | 6 ++++-- .github/workflows/magento-compatibility.yml | 4 +++- .github/workflows/phpcs.yml | 6 ++++-- .github/workflows/static-analysis.yml | 4 +++- 5 files changed, 17 insertions(+), 7 deletions(-) diff --git a/.github/workflows/functional-tests.yml b/.github/workflows/functional-tests.yml index a7fea9f1..6315e4c6 100644 --- a/.github/workflows/functional-tests.yml +++ b/.github/workflows/functional-tests.yml @@ -2,11 +2,13 @@ name: Functional Tests on: pull_request: + push: + branches: [main] workflow_dispatch: concurrency: # Heavy Magento bootstrap — cancel superseded PR runs to save CI minutes; - # manual (workflow_dispatch) runs are left to finish. + # let push/main runs finish so main is always validated directly. group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 5ebf09fe..7af67cb9 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -2,11 +2,13 @@ name: Lint on: pull_request: + push: + branches: [main] workflow_dispatch: concurrency: - # Cancel superseded PR runs to save CI minutes; manual (workflow_dispatch) - # runs are left to finish. + # Cancel superseded PR runs to save CI minutes; let push/main runs finish so + # main is always validated directly (catches anything pushed straight to main). group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} diff --git a/.github/workflows/magento-compatibility.yml b/.github/workflows/magento-compatibility.yml index 130cc355..a28e54e8 100644 --- a/.github/workflows/magento-compatibility.yml +++ b/.github/workflows/magento-compatibility.yml @@ -2,11 +2,13 @@ name: Magento Compatibility Test on: pull_request: + push: + branches: [main] workflow_dispatch: concurrency: # Heavy Magento matrix — cancel superseded PR runs to save CI minutes; - # manual (workflow_dispatch) runs are left to finish. + # let push/main runs finish so main is always validated directly. group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} diff --git a/.github/workflows/phpcs.yml b/.github/workflows/phpcs.yml index faac3593..dd9b67d5 100644 --- a/.github/workflows/phpcs.yml +++ b/.github/workflows/phpcs.yml @@ -2,11 +2,13 @@ name: PHPCS on: pull_request: + push: + branches: [main] workflow_dispatch: concurrency: - # Cancel superseded PR runs to save CI minutes; manual (workflow_dispatch) - # runs are left to finish. + # Cancel superseded PR runs to save CI minutes; let push/main runs finish so + # main is always validated directly (catches anything pushed straight to main). group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index d7b40f7a..56ea6262 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -8,11 +8,13 @@ name: Static Analysis on: pull_request: + push: + branches: [main] workflow_dispatch: concurrency: # Heavy shared Magento build — cancel superseded PR runs to save CI minutes; - # manual (workflow_dispatch) runs are left to finish. + # let push/main runs finish so main is always validated directly. group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} From 9e76c374aa5116ab1e2beee3f36ae8fa08242f14 Mon Sep 17 00:00:00 2001 From: Thomas Hauschild <7961978+Morgy93@users.noreply.github.com> Date: Fri, 10 Jul 2026 09:07:47 +0200 Subject: [PATCH 05/12] feat: add composer-auth support for Mage-OS mirror --- .github/actions/setup-magento/action.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/actions/setup-magento/action.yml b/.github/actions/setup-magento/action.yml index 2d50c0b0..ad1d8017 100644 --- a/.github/actions/setup-magento/action.yml +++ b/.github/actions/setup-magento/action.yml @@ -60,9 +60,16 @@ runs: - name: Download Magento (Mage-OS mirror) if: ${{ inputs.magento-source == 'mage-os' }} shell: bash + env: + COMPOSER_AUTH: ${{ inputs.composer-auth }} run: | + # --no-install: only scaffold the project here. The dedicated "Install + # Magento" step below installs dependencies exactly once (with + # COMPOSER_AUTH available), instead of create-project installing them + # unauthenticated and the install step installing them a second time. composer create-project \ --repository-url=https://mirror.mage-os.org/ \ + --no-install \ magento/project-community-edition \ magento2 From 18f674b812b37e78c90c9eaa1064bafc67349f98 Mon Sep 17 00:00:00 2001 From: Thomas Hauschild <7961978+Morgy93@users.noreply.github.com> Date: Fri, 10 Jul 2026 09:09:22 +0200 Subject: [PATCH 06/12] fix: clarify composer-auth input description --- .github/actions/setup-magento/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/setup-magento/action.yml b/.github/actions/setup-magento/action.yml index ad1d8017..35a28e79 100644 --- a/.github/actions/setup-magento/action.yml +++ b/.github/actions/setup-magento/action.yml @@ -29,7 +29,7 @@ inputs: composer-auth: description: >- COMPOSER_AUTH JSON (Magento Marketplace keys) for authenticated Composer - downloads. Pass ${{ secrets.COMPOSER_AUTH }}. + downloads. Pass the calling workflow's secrets.COMPOSER_AUTH secret. required: false default: "" From 8f92367fa042a054af30a0cfb763fd2c261a2456 Mon Sep 17 00:00:00 2001 From: Thomas Hauschild <7961978+Morgy93@users.noreply.github.com> Date: Fri, 10 Jul 2026 09:23:37 +0200 Subject: [PATCH 07/12] fix: update action versions and fix magento version format --- .github/workflows/functional-tests.yml | 4 ++-- .github/workflows/label.yml | 2 +- .github/workflows/lint.yml | 12 ++++++++---- .github/workflows/release-please.yml | 2 +- .github/workflows/static-analysis.yml | 2 +- 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/.github/workflows/functional-tests.yml b/.github/workflows/functional-tests.yml index 6315e4c6..eb8e1105 100644 --- a/.github/workflows/functional-tests.yml +++ b/.github/workflows/functional-tests.yml @@ -52,7 +52,7 @@ jobs: path: mageforge - name: Setup Node.js - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af + uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 with: node-version: "20" @@ -60,7 +60,7 @@ jobs: uses: ./mageforge/.github/actions/setup-magento with: php-version: "8.4" - magento-version: "2.4.8" + magento-version: 2.4.8 magento-source: git composer-auth: ${{ secrets.COMPOSER_AUTH }} diff --git a/.github/workflows/label.yml b/.github/workflows/label.yml index 1125a9ab..598baed9 100644 --- a/.github/workflows/label.yml +++ b/.github/workflows/label.yml @@ -24,7 +24,7 @@ jobs: steps: - name: Label by changed files and branch name - uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5 + uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5.0.0 with: repo-token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 7af67cb9..132bff47 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -49,10 +49,6 @@ jobs: trunk: name: Trunk Check (yaml, markdown, shell, actions, …) runs-on: ubuntu-latest - # No `checks: write`: with it, trunk-action posts a *second* check-run (for - # inline annotations) on top of this job, so one finding turns two statuses - # red. Findings still surface in the job log and fail this job via its exit - # code. Re-add `checks: write` only if inline PR annotations are wanted back. permissions: contents: read @@ -60,5 +56,13 @@ jobs: - name: Checkout code uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + # save-annotations stores findings as an artifact instead of posting them + # to the GitHub Checks API. That avoids the *second* check-run that used to + # duplicate this job's red status — and the 403 it would otherwise throw + # without `checks: write`. Findings still print in the job log and fail + # this job via its exit code; grant `checks: write` + drop this only if you + # want inline PR annotations (and the extra check-run) back. - name: Trunk Check uses: trunk-io/trunk-action@04ba50e7658c81db7356da96657e6e77f220bfa3 # v1.3.1 + with: + save-annotations: true diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index a7634c95..a50bf1e4 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Run Release Please - uses: googleapis/release-please-action@5c625bfb5d1ff62eadeeb3772007f7f66fdcf071 # v4 + uses: googleapis/release-please-action@5c625bfb5d1ff62eadeeb3772007f7f66fdcf071 # v4.4.1 with: # The GitHub token for creating releases and pull requests token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 56ea6262..12b6fed6 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -56,7 +56,7 @@ jobs: uses: ./mageforge/.github/actions/setup-magento with: php-version: "8.4" - magento-version: "2.4.8" + magento-version: 2.4.8 magento-source: mage-os composer-auth: ${{ secrets.COMPOSER_AUTH }} From 7126ca23fa4c47aa96c1e0ff03af58eca0a86587 Mon Sep 17 00:00:00 2001 From: Thomas Hauschild <7961978+Morgy93@users.noreply.github.com> Date: Fri, 10 Jul 2026 09:36:25 +0200 Subject: [PATCH 08/12] fix: improve magento version handling in setup action --- .github/actions/setup-magento/action.yml | 31 +++++++++++++++--------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/.github/actions/setup-magento/action.yml b/.github/actions/setup-magento/action.yml index 35a28e79..f8dc8947 100644 --- a/.github/actions/setup-magento/action.yml +++ b/.github/actions/setup-magento/action.yml @@ -12,9 +12,10 @@ inputs: required: true magento-version: description: >- - Magento version. With magento-source=git it is the branch/tag to clone; - with magento-source=mage-os it is only used to namespace the Composer cache - (create-project always pulls the latest stable). + Magento version. With magento-source=git it is the branch/tag to clone. + With magento-source=mage-os it pins `composer create-project` to that + release (falling back to the latest stable only if the mirror lacks that + exact version). Also namespaces the Composer cache. required: true magento-source: description: >- @@ -63,15 +64,21 @@ runs: env: COMPOSER_AUTH: ${{ inputs.composer-auth }} run: | - # --no-install: only scaffold the project here. The dedicated "Install - # Magento" step below installs dependencies exactly once (with - # COMPOSER_AUTH available), instead of create-project installing them - # unauthenticated and the install step installing them a second time. - composer create-project \ - --repository-url=https://mirror.mage-os.org/ \ - --no-install \ - magento/project-community-edition \ - magento2 + # Pin create-project to the requested version so runs are reproducible, + # falling back to the latest stable only if the mirror lacks that exact + # release. --no-install: scaffold only here; the "Install Magento" step + # below installs dependencies exactly once (with COMPOSER_AUTH), instead + # of create-project installing them unauthenticated and the install step + # installing them a second time. + mirror=https://mirror.mage-os.org/ + version="${{ inputs.magento-version }}" + if ! composer create-project --no-install --repository-url="$mirror" \ + magento/project-community-edition magento2 "$version"; then + echo "::warning::Mage-OS mirror has no project-community-edition ${version}; using latest stable" + rm -rf magento2 + composer create-project --no-install --repository-url="$mirror" \ + magento/project-community-edition magento2 + fi - name: Install Magento working-directory: magento2 From 42c9de1153475ac82044b59a386fa8a5970baa95 Mon Sep 17 00:00:00 2001 From: Thomas Hauschild <7961978+Morgy93@users.noreply.github.com> Date: Fri, 10 Jul 2026 10:20:27 +0200 Subject: [PATCH 09/12] fix: extract metadata decoding to a separate method --- .../Decorator/InspectorHintsTest.php | 36 +++++++++++++------ 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/tests/Unit/Model/TemplateEngine/Decorator/InspectorHintsTest.php b/tests/Unit/Model/TemplateEngine/Decorator/InspectorHintsTest.php index df4f898c..0938bee2 100644 --- a/tests/Unit/Model/TemplateEngine/Decorator/InspectorHintsTest.php +++ b/tests/Unit/Model/TemplateEngine/Decorator/InspectorHintsTest.php @@ -90,6 +90,28 @@ private function createBlock(): BlockInterface&MockObject return $this->createMock(BlockInterface::class); } + /** + * Extracts and JSON-decodes the metadata embedded in the + * data-mageforge-block attribute of rendered inspector output. + * + * @return array + */ + private function decodeBlockMetadata(string $result): array + { + // Guarding on the preg_match return value narrows $matches to the + // matched shape for every PHPStan version: capture group 1 is then + // known to exist, so no null-coalesce fallback is needed (which newer + // PHPStan/bleedingEdge flags as redundant via nullCoalesce.offset). + if (preg_match('/data-mageforge-block="([^"]*)"/', $result, $matches) !== 1) { + $this->fail('render output should contain a data-mageforge-block attribute'); + } + + $metadata = json_decode(html_entity_decode($matches[1], ENT_QUOTES), true); + $this->assertIsArray($metadata); + + return $metadata; + } + public function testRenderReturnsSubjectResultWhenShowBlockHintsDisabled(): void { $block = $this->createBlock(); @@ -176,11 +198,7 @@ public function testRenderEmbedsMetadataAsEscapedJson(): void $inspector = $this->createInspectorHints(); $result = $inspector->render($block, '/var/www/html/some/template.phtml'); - preg_match('/data-mageforge-block="([^"]*)"/', $result, $matches); - $this->assertNotEmpty($matches); - $decodedJson = html_entity_decode($matches[1] ?? '', ENT_QUOTES); - $metadata = json_decode($decodedJson, true); - $this->assertIsArray($metadata); + $metadata = $this->decodeBlockMetadata($result); $this->assertSame('mageforge-xyz', $metadata['id']); $this->assertSame('some/template.phtml', $metadata['template']); @@ -199,9 +217,7 @@ public function testRenderExtractsModuleNameFromRealBlockClassName(): void $inspector = $this->createInspectorHints(); $result = $inspector->render($block, '/var/www/html/template.phtml'); - preg_match('/data-mageforge-block="([^"]*)"/', $result, $matches); - $metadata = json_decode(html_entity_decode($matches[1] ?? '', ENT_QUOTES), true); - $this->assertIsArray($metadata); + $metadata = $this->decodeBlockMetadata($result); $this->assertSame('OpenForgeProject_MageForge', $metadata['module']); } @@ -227,9 +243,7 @@ public function testRenderDetectsParentBlockAndAlias(): void $inspector = $this->createInspectorHints(); $result = $inspector->render($block, '/var/www/html/template.phtml'); - preg_match('/data-mageforge-block="([^"]*)"/', $result, $matches); - $metadata = json_decode(html_entity_decode($matches[1] ?? '', ENT_QUOTES), true); - $this->assertIsArray($metadata); + $metadata = $this->decodeBlockMetadata($result); $this->assertSame('parent.block', $metadata['parent']); $this->assertSame('child.block', $metadata['alias']); From ba3fea71334a83756576e6adf9d676674785f69d Mon Sep 17 00:00:00 2001 From: Thomas Hauschild <7961978+Morgy93@users.noreply.github.com> Date: Fri, 10 Jul 2026 10:51:21 +0200 Subject: [PATCH 10/12] fix: update phpstan dependencies to pinned versions --- .github/actions/setup-magento/action.yml | 10 +++++++++- .github/workflows/static-analysis.yml | 8 ++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/.github/actions/setup-magento/action.yml b/.github/actions/setup-magento/action.yml index f8dc8947..e0e59786 100644 --- a/.github/actions/setup-magento/action.yml +++ b/.github/actions/setup-magento/action.yml @@ -44,6 +44,11 @@ runs: extensions: ${{ inputs.php-extensions }} tools: composer:v2 + # Caches ~/.composer/cache/files — Composer's content-addressed download + # cache, not a resolved dependency set. If the mage-os fallback installs a + # different release than magento-version implies, a mismatched key only + # causes cache misses (re-downloads), never an incorrect install: Composer + # always installs exactly what composer.json resolves to. - name: Cache Composer packages uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: @@ -55,7 +60,10 @@ runs: if: ${{ inputs.magento-source == 'git' }} shell: bash run: | - git clone --depth=1 --branch=${{ inputs.magento-version }} \ + # Assign to a shell variable and quote it so an unexpected value can't + # word-split or glob into extra git arguments. + version="${{ inputs.magento-version }}" + git clone --depth=1 --branch="$version" \ https://github.com/magento/magento2.git magento2 - name: Download Magento (Mage-OS mirror) diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 12b6fed6..e94d6a50 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -71,8 +71,12 @@ jobs: # Allow the PHPStan extension installer plugin composer config --no-plugins allow-plugins.phpstan/extension-installer true - # PHPStan + Magento extension (consumed by the phpstan job) - composer require --dev --no-update bitexpert/phpstan-magento "phpstan/phpstan:^2.0" phpstan/extension-installer + # PHPStan + Magento extension (consumed by the phpstan job). Pinned to + # exact versions: phpstan.neon opts into bleedingEdge, whose rules + # change between releases, so an unpinned `^2.0` lets a new PHPStan + # release fail unrelated PRs. Bump these deliberately (keep in sync + # with the locally installed PHPStan) instead of floating. + composer require --dev --no-update "bitexpert/phpstan-magento:0.43.0" "phpstan/phpstan:2.2.2" phpstan/extension-installer composer update --with-dependencies bin/magento setup:upgrade From 050bf7d8b8fd5942a415a8260f69a18a1e74568f Mon Sep 17 00:00:00 2001 From: Thomas Hauschild <7961978+Morgy93@users.noreply.github.com> Date: Fri, 10 Jul 2026 12:01:38 +0200 Subject: [PATCH 11/12] fix: validate magento-source input for setup action --- .github/actions/setup-magento/action.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.github/actions/setup-magento/action.yml b/.github/actions/setup-magento/action.yml index e0e59786..409d7e00 100644 --- a/.github/actions/setup-magento/action.yml +++ b/.github/actions/setup-magento/action.yml @@ -37,6 +37,22 @@ inputs: runs: using: composite steps: + # Fail fast on a bad magento-source: otherwise both download steps' `if` + # conditions are false, no magento2/ is created, and the failure only + # surfaces later as a confusing "working-directory magento2 not found". + - name: Validate inputs + shell: bash + env: + MAGENTO_SOURCE: ${{ inputs.magento-source }} + run: | + case "$MAGENTO_SOURCE" in + git | mage-os) ;; + *) + echo "::error::Invalid magento-source '${MAGENTO_SOURCE}' (expected 'git' or 'mage-os')" + exit 1 + ;; + esac + - name: Setup PHP uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2 with: From 829ba053e15670f98c815eaa2ed307a837cf6069 Mon Sep 17 00:00:00 2001 From: Thomas Hauschild <7961978+Morgy93@users.noreply.github.com> Date: Fri, 10 Jul 2026 12:01:45 +0200 Subject: [PATCH 12/12] fix: specify types in process run callback --- src/Service/ThemeBuilder/HyvaThemes/Builder.php | 2 +- src/Service/ThemeBuilder/TailwindCSS/Builder.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Service/ThemeBuilder/HyvaThemes/Builder.php b/src/Service/ThemeBuilder/HyvaThemes/Builder.php index 0b65b3a9..53bd43dd 100644 --- a/src/Service/ThemeBuilder/HyvaThemes/Builder.php +++ b/src/Service/ThemeBuilder/HyvaThemes/Builder.php @@ -276,7 +276,7 @@ public function watch( } } - $exitCode = $process->run(static function ($type, $buffer) use ($output): void { + $exitCode = $process->run(static function (string $type, string $buffer) use ($output): void { $output->write($buffer); }); diff --git a/src/Service/ThemeBuilder/TailwindCSS/Builder.php b/src/Service/ThemeBuilder/TailwindCSS/Builder.php index 75f0ec94..34cb8638 100644 --- a/src/Service/ThemeBuilder/TailwindCSS/Builder.php +++ b/src/Service/ThemeBuilder/TailwindCSS/Builder.php @@ -244,7 +244,7 @@ public function watch( } } - $exitCode = $process->run(static function ($type, $buffer) use ($output): void { + $exitCode = $process->run(static function (string $type, string $buffer) use ($output): void { $output->write($buffer); });