From e3a785c44f772901b050bbe617a1b20f5a3c7c49 Mon Sep 17 00:00:00 2001 From: Frugan Date: Sun, 14 Sep 2025 19:25:53 +0200 Subject: [PATCH 1/3] build: refactor workflow - add Snyk and Qlty - fix release jobs - remove deprecated pascalgn/merge-action --- .github/workflows/auto-merge.yml | 10 ++-- .github/workflows/labeler.yml | 4 +- .github/workflows/main.yml | 90 +++++++++++++++++++++++++++++--- .github/workflows/release.yml | 56 +++++--------------- .github/workflows/stale.yml | 6 +-- codecov.yml | 2 + 6 files changed, 108 insertions(+), 60 deletions(-) create mode 100644 codecov.yml diff --git a/.github/workflows/auto-merge.yml b/.github/workflows/auto-merge.yml index 53cee45..eb64135 100644 --- a/.github/workflows/auto-merge.yml +++ b/.github/workflows/auto-merge.yml @@ -14,7 +14,7 @@ jobs: steps: - name: Get PR info id: pr - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: script: | const pr = await github.rest.pulls.get({ @@ -42,7 +42,7 @@ jobs: - name: Auto-merge if: fromJSON(steps.pr.outputs.result).shouldMerge && steps.wait-for-checks.outputs.conclusion == 'success' - uses: pascalgn/merge-action@v0.15.6 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - merge_method: squash \ No newline at end of file + run: | + gh pr merge ${{ github.event.pull_request.number }} --squash --auto + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 5d23c53..0dbc7ff 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -12,6 +12,6 @@ jobs: pull-requests: write steps: - - uses: actions/labeler@v5 + - uses: actions/labeler@v6 with: - repo-token: "${{ secrets.GITHUB_TOKEN }}" \ No newline at end of file + repo-token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0c29f6b..a0ba97a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,10 +1,24 @@ -name: CI/CD +name: CI on: - push: - branches: [ main, develop ] + # Run tests on PR pull_request: - branches: [ main, develop ] + branches: + - main + - develop + - 'support/*' + + # Optional: Only for post-merge verification (after PR is already approved). + # Note: PR merge is technically a "push", so this trigger will fire after successful merges. + # In a proper PR/GitHub workflow, protected branches should only receive PR merges. + # This trigger only makes sense for: + # - Hotfixes that bypass PR workflow in emergencies + # - Unprotected branches that still accept direct pushes (not recommended) + # - Post-merge verification (redundant if PR tests passed) + # Remove this entirely if all branches are properly protected with required PR reviews. + # push: + # branches: + # - develop jobs: tests: @@ -60,9 +74,9 @@ jobs: composer test fi - - name: Run tests with coverage + - name: Run tests with coverage (PHP 8.4) if: matrix.php == '8.4' - run: composer test-coverage + run: composer test:coverage - name: Upload coverage to Codecov if: matrix.php == '8.4' @@ -70,6 +84,23 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} files: ./coverage.xml + fail_ci_if_error: true + + # Alternative + - name: Upload coverage to Qlty + if: matrix.php == '8.4' + uses: qltysh/qlty-action/coverage@v2 + with: + token: ${{ secrets.QLTY_TOKEN }} + files: ./coverage.xml + + # Alternative + #- name: Upload coverage to Scrutinizer + # if: matrix.php == '8.4' + # uses: scrutinizer-ci/ocular@v1 + # with: + # access-token: ${{ secrets.SCRUTINIZER_ACCESS_TOKEN }} + # coverage-file: ./coverage.xml code-quality: name: Code Quality @@ -94,8 +125,51 @@ jobs: - name: Run static analysis run: composer analysis + - name: Run code quality checks + run: composer quality + + security: + name: Security + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.4' + extensions: mbstring, intl + + - name: Install dependencies + run: composer install --prefer-dist --no-progress + - name: Run security check run: composer security - - name: Run code quality - run: composer quality + # Security Compliance Note for Snyk Action: + # Snyk requires using full commit SHA instead of version tags or @master/@main + # for security compliance and to prevent supply chain attacks. + # + # To update SHA hash: + # - Visit: https://github.com/snyk/actions/commits/master/ + # - Copy the full 40-character commit SHA from the latest commit + # + # SARIF Upload Benefits: + # - Integrates vulnerability results directly into GitHub's Security tab + # - Provides detailed vulnerability information in pull request reviews + # - Enables security-focused code review workflow + # - Creates security alerts for repository maintainers + - name: Run Snyk to check for vulnerabilities (PHP) + continue-on-error: true + uses: snyk/actions/php@e2221410bff24446ba09102212d8bc75a567237d + env: + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + with: + args: --severity-threshold=high --sarif-file-output=snyk.sarif --file=composer.lock + + - name: Upload Snyk results to GitHub Code Scanning + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: snyk.sarif diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 10c47d5..ccf516a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,7 +2,9 @@ name: Release on: push: - branches: [ main ] + branches: + - main + - 'support/*' permissions: contents: write @@ -28,31 +30,18 @@ jobs: persist-credentials: false - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: - node-version: '22' + node-version: 'lts/*' - - name: Setup PHP - uses: shivammathur/setup-php@v2 + - name: Semantic Release + uses: cycjimmy/semantic-release-action@v5 + id: semantic with: - php-version: '8.4' - extensions: mbstring, intl - - - name: Install dependencies - run: composer install --prefer-dist --no-progress - - - name: Run tests - run: composer test - - - name: Install semantic-release - run: | - npm install -g semantic-release@latest - npm install -g @semantic-release/changelog@latest - npm install -g @semantic-release/git@latest - npm install -g @semantic-release/github@latest - npm install -g conventional-changelog-conventionalcommits@latest - - - name: Release + extra_plugins: | + @semantic-release/changelog@latest + @semantic-release/git@latest + conventional-changelog-conventionalcommits@latest env: # Use the token with bypass permissions GITHUB_TOKEN: ${{ secrets.SEMANTIC_RELEASE_TOKEN || secrets.GITHUB_TOKEN }} @@ -60,27 +49,10 @@ jobs: GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com - run: | - git config --global user.name "github-actions[bot]" - git config --global user.email "github-actions[bot]@users.noreply.github.com" - # Disable GrumPHP git hooks for semantic-release - ./vendor/bin/grumphp git:deinit - npx semantic-release - - - name: Get released version - id: version - run: | - VERSION=$(git describe --tags --abbrev=0 2>/dev/null || echo "") - if [ -n "$VERSION" ]; then - echo "version=${VERSION#v}" >> $GITHUB_OUTPUT - echo "released=true" >> $GITHUB_OUTPUT - else - echo "released=false" >> $GITHUB_OUTPUT - fi # Packagist update is automatic if GitHub app is configured # Uncomment the following step only if manual trigger is needed # - name: Trigger Packagist update - # if: steps.version.outputs.released == 'true' + # if: steps.semantic.outputs.new_release_published == 'true' # run: | - # curl -XPOST -H'content-type:application/json' 'https://packagist.org/api/update-package?username=${{ secrets.PACKAGIST_USERNAME }}&apiToken=${{ secrets.PACKAGIST_TOKEN }}' -d'{"repository":{"url":"https://packagist.org/packages/wp-spaghetti/wp-env"}}' \ No newline at end of file + # curl -XPOST -H'content-type:application/json' 'https://packagist.org/api/update-package?username=${{ secrets.PACKAGIST_USERNAME }}&apiToken=${{ secrets.PACKAGIST_TOKEN }}' -d'{"repository":{"url":"https://packagist.org/packages/wp-spaghetti/wp-env"}}' diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 9d3bd2e..f5105d3 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -2,7 +2,7 @@ name: Mark stale issues and pull requests on: schedule: - - cron: '0 0 * * 0' # Weekly on Sunday + - cron: '0 0 * * 0' # Weekly on Sunday workflow_dispatch: jobs: @@ -13,7 +13,7 @@ jobs: pull-requests: write steps: - - uses: actions/stale@v9 + - uses: actions/stale@v10 with: repo-token: ${{ secrets.GITHUB_TOKEN }} @@ -37,4 +37,4 @@ jobs: # Exemptions exempt-issue-labels: 'enhancement,bug,pinned,security' - exempt-pr-labels: 'enhancement,bug,pinned' \ No newline at end of file + exempt-pr-labels: 'enhancement,bug,pinned' diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..ba6f0b2 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,2 @@ +comment: + require_changes: true From 10c349a167e23f3cc32a0bcb04480676473bbff3 Mon Sep 17 00:00:00 2001 From: Frugan Date: Sun, 14 Sep 2025 19:26:21 +0200 Subject: [PATCH 2/3] docs: update badges in README --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 849387d..eb6a677 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,18 @@ ![PHP Version](https://img.shields.io/packagist/php-v/wp-spaghetti/wp-env) -![GitHub Downloads (all assets, all releases)](https://img.shields.io/github/downloads/wp-spaghetti/wp-env/total) +![Packagist Downloads](https://img.shields.io/packagist/dt/wp-spaghetti/wp-env) +![Packagist Stars](https://img.shields.io/packagist/stars/wp-spaghetti/wp-env) ![GitHub Actions Workflow Status](https://github.com/wp-spaghetti/wp-env/actions/workflows/main.yml/badge.svg) ![Coverage Status](https://img.shields.io/codecov/c/github/wp-spaghetti/wp-env) +![Known Vulnerabilities](https://snyk.io/test/github/wp-spaghetti/wp-env/badge.svg) ![GitHub Issues](https://img.shields.io/github/issues/wp-spaghetti/wp-env) -![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen) + ![GitHub Release](https://img.shields.io/github/v/release/wp-spaghetti/wp-env) ![License](https://img.shields.io/github/license/wp-spaghetti/wp-env) # WP Env From bfa9ee02d74db9f138f01fdcfc38a50e089f7c35 Mon Sep 17 00:00:00 2001 From: Frugan Date: Sun, 14 Sep 2025 19:27:02 +0200 Subject: [PATCH 3/3] perf: add archive:exclude in composer --- .php-cs-fixer.dist.php | 2 ++ composer.json | 46 +++++++++++++++++++++++++++++------------- tests/bootstrap.php | 8 ++++++-- 3 files changed, 40 insertions(+), 16 deletions(-) diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 4f4932d..f60afb0 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -43,6 +43,8 @@ '@PHP80Migration' => true, '@PHP80Migration:risky' => true, 'header_comment' => ['header' => $header], + // Avoid breaking @psalm-suppress + 'phpdoc_to_comment' => ['ignored_tags' => ['psalm-suppress']], ]) ->setFinder($finder) ; diff --git a/composer.json b/composer.json index 78ba55e..7e47794 100644 --- a/composer.json +++ b/composer.json @@ -26,6 +26,12 @@ "source": "https://github.com/wp-spaghetti/wp-env", "docs": "https://github.com/wp-spaghetti/wp-env#readme" }, + "funding": [ + { + "type": "custom", + "url": "https://buymeacoff.ee/frugan" + } + ], "require": { "php": ">=8.0" }, @@ -63,19 +69,31 @@ "sort-packages": true }, "scripts": { - "analysis": "grumphp run --tasks=phpstan", - "check": "grumphp run", - "ci": "grumphp run --no-interaction", - "lint": "grumphp run --tasks=phpcsfixer,phplint,phpstan,rector", - "quality": "grumphp run --tasks=phpmnd,phpparser", - "security": "grumphp run --tasks=securitychecker_roave", - "test": "grumphp run --tasks=phpunit", - "test-coverage": "vendor/bin/phpunit --coverage-clover=coverage.xml" + "analysis": "@check --tasks=phpstan", + "check": "@php -d max_execution_time=0 -d memory_limit=-1 -f ./vendor/bin/grumphp -- run", + "ci": "@check --no-interaction", + "lint": "@check --tasks=phpcsfixer,phplint,phpstan,rector", + "quality": "@check --tasks=phpmnd,phpparser", + "security": "@check --tasks=securitychecker_roave", + "test": "@check --tasks=phpunit", + "test:coverage": "vendor/bin/phpunit --coverage-clover=coverage.xml" }, - "funding": [ - { - "type": "custom", - "url": "https://buymeacoff.ee/frugan" - } - ] + "archive": { + "exclude": [ + ".github/*", + "docs/*", + "examples/*", + "tests/*", + ".gitignore", + ".php-cs-fixer.dist.php", + ".releaserc.json", + "codecov.yml", + "commitlint.config.mjs", + "grumphp.yml.dist", + "phpunit*.xml*", + "phpstan.neon.dist", + "psalm.xml.dist", + "rector.php" + ] + } } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 1b0e140..2b037fc 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -13,7 +13,11 @@ // Define WordPress constants for testing if (!defined('ABSPATH')) { - define('ABSPATH', '/tmp/'); + define('ABSPATH', '/tmp/wordpress/'); +} + +if (!defined('WPINC')) { + define('WPINC', 'wp-includes'); } if (!defined('WP_DEBUG')) { @@ -29,7 +33,7 @@ } // Autoload Composer dependencies -require_once __DIR__.'/../vendor/autoload.php'; +require_once dirname(__DIR__).'/vendor/autoload.php'; // Global test variables for mocking global $applied_filters, $triggered_actions, $mock_constants, $mock_env_vars, $mock_files, $mock_environment_vars;