diff --git a/.github/actions/determine-changed-files/action.yml b/.github/actions/determine-changed-files/action.yml new file mode 100644 index 0000000000..48acc78810 --- /dev/null +++ b/.github/actions/determine-changed-files/action.yml @@ -0,0 +1,84 @@ +name: Determine count of changed files + +description: Determine count of changed files based on the current branch and the base branch + +outputs: + count: + description: The count of changed files + value: ${{ steps.determine-file-counts.outputs.count }} + php-count: + description: The count of changed PHP files + value: ${{ steps.determine-file-counts.outputs.php-count }} + css-count: + description: The count of changed CSS files + value: ${{ steps.determine-file-counts.outputs.css-count }} + js-count: + description: The count of changed JS files + value: ${{ steps.determine-file-counts.outputs.js-count }} + gha-workflow-count: + description: The count of changed GHA workflow files + value: ${{ steps.determine-file-counts.outputs.gha-workflow-count }} + +runs: + using: 'composite' + steps: + - name: Fetch base branch + # Only fetch base ref if it's a PR. + if: ${{ github.base_ref != null }} + shell: bash + run: git fetch --depth=1 --no-tags origin ${{ github.base_ref }} + + - name: Determine modified files for PR + if: ${{ github.base_ref != null }} + shell: bash + run: echo "MODIFIED_FILES=$(git diff --name-only FETCH_HEAD HEAD | base64 -w 0)" >> $GITHUB_ENV + + - name: Determine modified files for commit + if: ${{ github.base_ref == null }} + shell: bash + run: echo "MODIFIED_FILES=$(git diff --name-only HEAD~1 HEAD | base64 -w 0)" >> $GITHUB_ENV + + - name: Determine if modified files should make the workflow run continue + id: determine-file-counts + shell: bash + run: | + # Get modified files. + MODIFIED_FILES=$(echo "$MODIFIED_FILES" | base64 -d) + + # Determine file counts. + FILE_COUNT=$(php -f ./.github/actions/determine-changed-files/determine-modified-files-count.php "$IGNORE_PATH_REGEX" "$MODIFIED_FILES" --invert) + PHP_FILE_COUNT=$(php -f ./.github/actions/determine-changed-files/determine-modified-files-count.php ".+\.php|composer\.(json|lock)|phpstan\.neon\.dist" "$MODIFIED_FILES") + CSS_FILE_COUNT=$(php -f ./.github/actions/determine-changed-files/determine-modified-files-count.php ".+\.s?css|package\.json|package-lock\.json" "$MODIFIED_FILES") + JS_FILE_COUNT=$(php -f ./.github/actions/determine-changed-files/determine-modified-files-count.php ".+\.(js|snap)|package\.json|package-lock\.json" "$MODIFIED_FILES") + GHA_WORKFLOW_COUNT=$(php -f ./.github/actions/determine-changed-files/determine-modified-files-count.php "(\.github\/workflows\/.+\.yml)" "$MODIFIED_FILES") + + # Set output variables. + echo "count=$FILE_COUNT" >> $GITHUB_OUTPUT + echo "php-count=$PHP_FILE_COUNT" >> $GITHUB_OUTPUT + echo "css-count=$CSS_FILE_COUNT" >> $GITHUB_OUTPUT + echo "js-count=$JS_FILE_COUNT" >> $GITHUB_OUTPUT + echo "gha-workflow-count=$GHA_WORKFLOW_COUNT" >> $GITHUB_OUTPUT + + # Add modified files summary. + echo "# Modified files summary" >> $GITHUB_STEP_SUMMARY + echo "## Modified files" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY + echo "$MODIFIED_FILES" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY + echo "## Modified files count" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY + echo "Total modified files: $FILE_COUNT" >> $GITHUB_STEP_SUMMARY + echo "PHP files: $PHP_FILE_COUNT" >> $GITHUB_STEP_SUMMARY + echo "CSS files: $CSS_FILE_COUNT" >> $GITHUB_STEP_SUMMARY + echo "JS files: $JS_FILE_COUNT" >> $GITHUB_STEP_SUMMARY + echo "GHA workflow files: $GHA_WORKFLOW_COUNT" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY + env: + # Ignore Paths: + # - .github/ + # - !.github/actions + # - !.github/workflows + # - .github/actions/draft-release/ + # - .wordpress-org/ + # - docs/ + IGNORE_PATH_REGEX: \.github\/(?!actions|workflows)|\.wordpress-org\/|docs\/|\.github\/actions\/draft-release\/ diff --git a/.github/actions/determine-changed-files/determine-modified-files-count.php b/.github/actions/determine-changed-files/determine-modified-files-count.php new file mode 100644 index 0000000000..3e0490b577 --- /dev/null +++ b/.github/actions/determine-changed-files/determine-modified-files-count.php @@ -0,0 +1,23 @@ + [--invert] + * + * For example: + * php -f determine-modified-files-count.php "foo\/bar|bar*" "foo/bar/baz\nquux" --invert + * + * Would output: 1 + * + * @codeCoverageIgnore + * @package AMP + */ + +$file_pattern = sprintf( '/^%s$/m', $argv[1] ); +$modified_files = explode( "\n", trim( $argv[2] ) ); +$preg_grep_flags = isset( $argv[3] ) && trim( $argv[3] ) === '--invert' ? PREG_GREP_INVERT : 0; + +$filtered_files = preg_grep( $file_pattern, $modified_files, $preg_grep_flags ); + +echo $filtered_files ? count( $filtered_files ) : 0; diff --git a/.github/actions/setup-node-npm/action.yml b/.github/actions/setup-node-npm/action.yml new file mode 100644 index 0000000000..73da9df2c7 --- /dev/null +++ b/.github/actions/setup-node-npm/action.yml @@ -0,0 +1,25 @@ +name: Setup Node.js and npm + +description: Setup Node.js and npm with caching + +runs: + using: 'composite' + steps: + - name: Configure Node.js cache + uses: actions/cache@v3.3.2 + id: node-npm-cache + env: + SEGMENT_DOWNLOAD_TIMEOUT_MINS: '5' + with: + path: node_modules + key: ${{ runner.os }}-node_modules-${{ hashFiles('package*.json') }}-${{ hashFiles('.github/actions/setup-node-npm/action.yml') }} + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + + - name: Install NodeJS dependencies + if: ${{ steps.node-npm-cache.outputs.cache-hit != 'true' }} + shell: bash + run: npm ci diff --git a/.github/actions/setup-php-composer/action.yml b/.github/actions/setup-php-composer/action.yml new file mode 100644 index 0000000000..06a1cc2298 --- /dev/null +++ b/.github/actions/setup-php-composer/action.yml @@ -0,0 +1,46 @@ +name: Setup PHP and Composer + +description: Setup PHP and Composer with caching + +inputs: + tools: + description: 'The tools to install' + required: false + default: 'composer' + php-version: + description: 'The PHP version to install' + required: true + default: '7.4' + extensions: + description: 'The PHP extensions to install' + required: false + default: 'curl, date, dom, gd, iconv, json, libxml, mysql, spl' + coverage: + description: 'Whether to install the PHP Xdebug extension' + required: false + default: 'none' + +runs: + using: 'composite' + steps: + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ inputs.php-version }} + extensions: ${{ inputs.extensions }} + coverage: ${{ inputs.coverage }} + tools: ${{ inputs.tools }} + + - name: Setup composer cache + uses: actions/cache@v3 + id: php-composer-cache + env: + SEGMENT_DOWNLOAD_TIMEOUT_MINS: '5' + with: + path: vendor + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}-${{ hashFiles('.github/actions/setup-php-composer/action.yml') }} + + - name: Install composer dependencies + if: ${{ steps.php-composer-cache.outputs.cache-hit != 'true' }} + shell: bash + run: composer install --ansi --no-interaction --prefer-dist --ignore-platform-reqs diff --git a/.github/bin/install-wp-tests.sh b/.github/bin/install-wp-tests.sh new file mode 100644 index 0000000000..08a4c234c9 --- /dev/null +++ b/.github/bin/install-wp-tests.sh @@ -0,0 +1,144 @@ +#!/usr/bin/env bash + +set -e + +if [ $# -lt 3 ]; then + echo "usage: $0 [db-host] [wp-version] [skip-database-creation]" + exit 1 +fi + +DB_NAME=$1 +DB_USER=$2 +DB_PASS=$3 +DB_HOST=${4-localhost} +WP_VERSION=${5-latest} +SKIP_DB_CREATE=${6-true} + +TMPDIR=${TMPDIR-/tmp} +TMPDIR=$(echo "$TMPDIR" | sed -e "s/\/$//") +WP_TESTS_DIR=${WP_TESTS_DIR-$TMPDIR/wordpress-tests-lib} +WP_CORE_DIR=${WP_CORE_DIR-$TMPDIR/wordpress} + +download() { + if [ $(which curl) ]; then + curl -s "$1" > "$2"; + elif [ $(which wget) ]; then + wget -nv -O "$2" "$1" + fi +} + +if [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+\-(beta|RC)[0-9]+$ ]]; then + WP_BRANCH=${WP_VERSION%\-*} + WP_TESTS_TAG="branches/$WP_BRANCH" + +elif [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+$ ]]; then + WP_TESTS_TAG="branches/$WP_VERSION" +elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0-9]+ ]]; then + if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then + # version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x + WP_TESTS_TAG="tags/${WP_VERSION%??}" + else + WP_TESTS_TAG="tags/$WP_VERSION" + fi +elif [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then + WP_TESTS_TAG="trunk" +else + # http serves a single offer, whereas https serves multiple. we only want one + download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json + grep -E '[0-9]+\.[0-9]+(\.[0-9]+)?' /tmp/wp-latest.json + LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//') + if [[ -z "$LATEST_VERSION" ]]; then + echo "Latest WordPress version could not be found" + exit 1 + fi + WP_TESTS_TAG="tags/$LATEST_VERSION" +fi + +install_wp() { + + if [ -d "$WP_CORE_DIR" ]; then + return; + fi + + if grep -isqE 'trunk|alpha|beta|rc' <<< "$WP_VERSION"; then + local SVN_URL=https://develop.svn.wordpress.org/trunk/ + elif [ "$WP_VERSION" == 'latest' ]; then + local TAG=$( svn ls https://develop.svn.wordpress.org/tags | tail -n 1 | sed 's:/$::' ) + local SVN_URL="https://develop.svn.wordpress.org/tags/$TAG/" + elif [[ "$WP_VERSION" =~ ^[0-9]+\.[0-9]+$ ]]; then + # Use the release branch if no patch version supplied. This is useful to keep testing the latest minor version. + local SVN_URL="https://develop.svn.wordpress.org/branches/$WP_VERSION/" + else + local SVN_URL="https://develop.svn.wordpress.org/tags/$WP_VERSION/" + fi + + echo "Installing WP from $SVN_URL to $WP_CORE_DIR" + + svn export -q "$SVN_URL" "$WP_CORE_DIR" + + # Download `wp-includes` folder from the WordPress Core SVN repo to include built internal dependencies. + local SVN_CORE_URL=${SVN_URL/develop/core} + svn export -q --force "${SVN_CORE_URL}wp-includes" "$WP_CORE_DIR/src/wp-includes" + + download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php "$WP_CORE_DIR"/src/wp-content/db.php +} + +install_test_suite() { + # portable in-place argument for both GNU sed and Mac OSX sed + if [[ $(uname -s) == 'Darwin' ]]; then + local ioption='-i.bak' + else + local ioption='-i' + fi + + # set up testing suite if it doesn't yet exist + if [ ! -d "$WP_TESTS_DIR" ]; then + # set up testing suite + mkdir -p "$WP_TESTS_DIR" + svn co --quiet --ignore-externals https://develop.svn.wordpress.org/"${WP_TESTS_TAG}"/tests/phpunit/includes/ "$WP_TESTS_DIR/includes" + svn co --quiet --ignore-externals https://develop.svn.wordpress.org/"${WP_TESTS_TAG}"/tests/phpunit/data/ "$WP_TESTS_DIR/data" + fi + + if [ ! -f wp-tests-config.php ]; then + download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php + # remove all forward slashes in the end + WP_CORE_DIR="$(echo $WP_CORE_DIR | sed "s:/\+$::")" + sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/src/':" "$WP_TESTS_DIR/wp-tests-config.php" + sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR/wp-tests-config.php" + sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR/wp-tests-config.php" + sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR/wp-tests-config.php" + sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR/wp-tests-config.php" + fi + +} + +install_db() { + + if [ "${SKIP_DB_CREATE}" = "true" ]; then + return 0 + fi + + # parse DB_HOST for port or socket references + local PARTS=("${DB_HOST//\:/ }") + local DB_HOSTNAME=${PARTS[0]}; + local DB_SOCK_OR_PORT=${PARTS[1]}; + local EXTRA="" + + if [ -n "$DB_HOSTNAME" ] ; then + if [ "$(echo "$DB_SOCK_OR_PORT" | grep -e '^[0-9]\{1,\}$')" ]; then + EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp" + elif [ -n "$DB_SOCK_OR_PORT" ] ; then + EXTRA=" --socket=$DB_SOCK_OR_PORT" + elif [ -n "$DB_HOSTNAME" ] ; then + EXTRA=" --host=$DB_HOSTNAME --protocol=tcp" + fi + fi + + # create database + mariadb-admin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA || \ + mysqladmin create "$DB_NAME" --user="$DB_USER" --password="$DB_PASS"$EXTRA +} + +install_wp +install_test_suite +install_db diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..c8cafb7766 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,62 @@ +version: 2 +updates: + +# Config for AMP plugin. +- package-ecosystem: composer + directory: "/" + schedule: + interval: monthly + time: "17:00" + timezone: America/Los_Angeles + open-pull-requests-limit: 10 + groups: + code-quality: + patterns: + - "phpstan/*" + - "szepeviktor/phpstan-wordpress" + - "phpcompatibility/php-compatibility" + - "slevomat/coding-standard" + - "dealerdirect/phpcodesniffer-composer-installer" + - "squizlabs/php_codesniffer" + - "wp-coding-standards/wpcs" + - "wp-phpunit/wp-phpunit" + - "yoast/phpunit-polyfills" + +- package-ecosystem: npm + directory: "/" + schedule: + interval: monthly + time: "17:00" + timezone: America/Los_Angeles + open-pull-requests-limit: 10 + groups: + wordpress-packages: + patterns: + - "@wordpress/*" + plugin-cli: + patterns: + - "@octokit/rest" + - "commander" + - "fs-extra" + - "fast-glob" + - "lodash" + code-quality: + patterns: + - "husky" + - "lint-staged" + ignore: + # Latest version of chalk is pure ESM. + - dependency-name: chalk + +# Config for GitHub Actions. +- package-ecosystem: github-actions + directory: "/" + schedule: + interval: monthly + time: "17:00" + timezone: America/Los_Angeles + open-pull-requests-limit: 10 + groups: + github-actions: + patterns: + - "actions/*" diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 0000000000..390bdc2bea --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,5 @@ +changelog: + exclude: + authors: + - dependabot + - dependabot-preview diff --git a/.github/workflows/cache-buster.yml b/.github/workflows/cache-buster.yml new file mode 100644 index 0000000000..4213077fcd --- /dev/null +++ b/.github/workflows/cache-buster.yml @@ -0,0 +1,40 @@ +--- +name: Bust Runner Caches + +on: + workflow_dispatch: + schedule: + # https://crontab.guru/#0_0_*_*_0 + - cron: '0 0 * * 0' + +# Disable permissions for all available scopes. +# Enable permissions for specific scopes as needed on job level. +permissions: {} + +concurrency: + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.ref }} + cancel-in-progress: true + +jobs: + bust-cache: + runs-on: ubuntu-latest + permissions: + actions: write + steps: + - name: Bust cache + uses: actions/github-script@v7 + with: + script: | + const caches = await github.rest.actions.getActionsCacheList({ + owner: context.repo.owner, + repo: context.repo.repo, + }) + for (const cache of caches.data.actions_caches) { + console.log(`Clearing ${cache.id}`) + github.rest.actions.deleteActionsCacheById({ + owner: context.repo.owner, + repo: context.repo.repo, + cache_id: cache.id, + }) + } + console.log(`Caches cleared at ${new Date().toISOString()}`) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000000..04361b7493 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,48 @@ +name: CodeQL + +# Cancel previous workflow run groups that have not completed. +concurrency: + # Group workflow runs by workflow name, along with the head branch ref of the pull request + # or otherwise the branch or tag ref. + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.ref }} + cancel-in-progress: true + +# Disable permissions for all available scopes. +# Enable permissions for specific scopes as needed on job level. +permissions: {} + +on: + push: + # Only run if JS or Python files changed. + paths: + - "**.js" + branches: + - develop + # Include all release branches. + - "[0-9]+.[0-9]+" + pull_request: + # Only run if JS or Python files changed. + paths: + - "**.js" + branches: + - develop + # Include all release branches. + - "[0-9]+.[0-9]+" + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + security-events: write + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: javascript + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/js-lint.yml b/.github/workflows/js-lint.yml deleted file mode 100644 index 8d3c77c532..0000000000 --- a/.github/workflows/js-lint.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: JS Code Linting - -on: - push: - branches: - - trunk - - 'release/**' - # Only run if JS/JSON/Lint/NVM files changed. - paths: - - '.github/workflows/js-lint.yml' - - '**.js' - - '**.json' - - '.eslint*' - - '.nvmrc' - - '.wp-env.json' - - '**/package.json' - - 'package-lock.json' - pull_request: - branches: - - trunk - - 'release/**' - - 'feature/**' - # Only run if JS/JSON/Lint/NVM files changed. - paths: - - '.github/workflows/js-lint.yml' - - '**.js' - - '**.json' - - '.eslint*' - - '.nvmrc' - - '.wp-env.json' - - '**/package.json' - - 'package-lock.json' - types: - - opened - - reopened - - synchronize - -jobs: - js-lint: - name: JS Lint - runs-on: ubuntu-latest - timeout-minutes: 20 - steps: - - uses: styfle/cancel-workflow-action@0.11.0 - - uses: actions/checkout@v3 - - name: Setup Node.js (via .nvmrc) - uses: actions/setup-node@v3 - with: - node-version-file: '.nvmrc' - cache: npm - - name: npm install - run: npm ci - - name: JS Lint - run: npm run lint-js \ No newline at end of file diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000000..54f808ae9d --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,126 @@ +--- +name: Lint + +on: + push: + branches: + - trunk + - 'release/**' + pull_request: + types: + - opened + - synchronize + - reopened + - ready_for_review + +concurrency: + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.ref }} + cancel-in-progress: true + +permissions: {} + +jobs: + pre-run: + name: 'Pre run' + runs-on: ubuntu-latest + outputs: + changed-file-count: ${{ steps.determine-file-counts.outputs.count }} + changed-php-count: ${{ steps.determine-file-counts.outputs.php-count }} + changed-css-count: ${{ steps.determine-file-counts.outputs.css-count }} + changed-js-count: ${{ steps.determine-file-counts.outputs.js-count }} + changed-gha-workflow-count: ${{ steps.determine-file-counts.outputs.gha-workflow-count }} + steps: + - name: Checkout including last 2 commits + # Fetch last 2 commits if it's not a PR, so that we can determine the list of modified files. + if: ${{ github.base_ref == null }} + uses: actions/checkout@v4 + with: + fetch-depth: 2 + + - name: Checkout + # Do usual checkout if it's a PR. + if: ${{ github.base_ref != null }} + uses: actions/checkout@v4 + + - name: Determine modified files + id: determine-file-counts + uses: ./.github/actions/determine-changed-files + +#----------------------------------------------------------------------------------------------------------------------- + + lint-js: + name: 'Lint: JS' + needs: pre-run + if: needs.pre-run.outputs.changed-js-count > 0 || needs.pre-run.outputs.changed-gha-workflow-count > 0 + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup NodeJS and NPM + uses: ./.github/actions/setup-node-npm + + - name: Validate package.json + run: npm run lint-js + +#----------------------------------------------------------------------------------------------------------------------- + + lint-php: + name: 'Lint: PHP' + needs: pre-run + if: needs.pre-run.outputs.changed-php-count > 0 || needs.pre-run.outputs.changed-gha-workflow-count > 0 + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP and Composer + uses: ./.github/actions/setup-php-composer + with: + tools: 'composer,cs2pr,composer-normalize' + php-version: '8.1' + + - name: Detect coding standard violations (PHPCS) + run: vendor/bin/phpcs -q --report=checkstyle --runtime-set ignore_errors_on_exit 1 --runtime-set ignore_warnings_on_exit 1 | cs2pr --graceful-warnings + + - name: Validate composer.json + run: composer validate --no-check-all --no-interaction + + - name: Normalize composer.json + run: | + composer config --no-interaction --no-plugins allow-plugins.ergebnis/composer-normalize true + composer-normalize --dry-run --diff + +#----------------------------------------------------------------------------------------------------------------------- + + static-analysis-php: + name: 'Static Analysis: PHP' + runs-on: ubuntu-latest + needs: pre-run + if: needs.pre-run.outputs.changed-php-count > 0 || needs.pre-run.outputs.changed-gha-workflow-count > 0 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP and Composer + uses: ./.github/actions/setup-php-composer + with: + tools: 'composer,phpstan' + php-version: '8.1' + + - name: Static Analysis (PHPStan) + run: phpstan analyze + +#----------------------------------------------------------------------------------------------------------------------- + + spell-check: + name: 'Spell Check with Typos' + runs-on: ubuntu-latest + needs: pre-run + if: needs.pre-run.outputs.changed-file-count > 0 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Search for typos + uses: crate-ci/typos@master \ No newline at end of file diff --git a/.github/workflows/php-lint.yml b/.github/workflows/php-lint.yml deleted file mode 100644 index 7fbe451866..0000000000 --- a/.github/workflows/php-lint.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: Code Linting - -on: - push: - branches: - - trunk - - 'release/**' - # Only run if PHP-related files changed. - paths: - - '.github/workflows/php-lint.yml' - - '**.php' - - 'phpcs.xml.dist' - - 'phpstan.neon.dist' - - 'composer.json' - - 'composer.lock' - pull_request: - branches: - - trunk - - 'release/**' - - 'feature/**' - # Only run if PHP-related files changed. - paths: - - '.github/workflows/php-lint.yml' - - '**.php' - - 'phpcs.xml.dist' - - 'phpstan.neon.dist' - - 'composer.json' - - 'composer.lock' - types: - - opened - - reopened - - synchronize - -jobs: - php-lint: - name: PHP - runs-on: ubuntu-latest - timeout-minutes: 20 - steps: - - uses: styfle/cancel-workflow-action@0.11.0 - - uses: actions/checkout@v3 - - uses: shivammathur/setup-php@v2 - with: - php-version: latest - - name: Get Composer Cache Directory - id: composer-cache - run: | - echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - - uses: actions/cache@v3 - with: - path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: | - ${{ runner.os }}-composer- - - name: Validate Composer configuration - run: composer validate --strict - - name: Composer Install - run: composer install --no-interaction --no-progress - - name: PHP Lint - run: composer lint - - name: PHPStan - run: composer phpstan diff --git a/.github/workflows/php-test-standalone-plugins.yml b/.github/workflows/php-test-standalone-plugins.yml deleted file mode 100644 index 3065f9f39a..0000000000 --- a/.github/workflows/php-test-standalone-plugins.yml +++ /dev/null @@ -1,60 +0,0 @@ -name: Integration Tests for Standalone Plugins - -on: - push: - branches: - - trunk - - 'release/**' - # Only run if PHP-related files changed. - paths: - - '.github/workflows/php-test-standalone-plugins.yml' - - 'plugin-tests/**' - - '**.php' - - '.wp-env.json' - - '**/package.json' - - 'package-lock.json' - - 'phpunit.xml.dist' - - 'composer.json' - - 'composer.lock' - pull_request: - branches: - - trunk - - 'release/**' - - 'feature/**' - # Only run if PHP-related files changed. - paths: - - '.github/workflows/php-test-standalone-plugins.yml' - - 'plugin-tests/**' - - '**.php' - - '.wp-env.json' - - '**/package.json' - - 'package-lock.json' - - 'phpunit.xml.dist' - - 'composer.json' - - 'composer.lock' - types: - - opened - - reopened - - synchronize - -jobs: - php-test: - name: PHP Integration Tests for Standalone Plugins - runs-on: ubuntu-latest - timeout-minutes: 20 - steps: - - uses: styfle/cancel-workflow-action@0.11.0 - - uses: actions/checkout@v3 - - name: Setup Node.js (.nvmrc) - uses: actions/setup-node@v3 - with: - node-version-file: '.nvmrc' - cache: npm - - name: npm install - run: npm ci - - name: Building standalone plugins - run: npm run build-plugins - - name: Running single site standalone plugin integration tests - run: npm run test-plugins - - name: Running multisite standalone plugin integration tests - run: npm run test-plugins-multisite diff --git a/.github/workflows/php-test.yml b/.github/workflows/php-test.yml deleted file mode 100644 index 4c249687b0..0000000000 --- a/.github/workflows/php-test.yml +++ /dev/null @@ -1,60 +0,0 @@ -name: Unit Testing - -on: - push: - branches: - - trunk - - 'release/**' - # Only run if PHP-related files changed. - paths: - - '.github/workflows/php-test.yml' - - '**.php' - - '.wp-env.json' - - '**/package.json' - - 'package-lock.json' - - 'phpunit.xml.dist' - - 'tests/multisite.xml' - - 'composer.json' - - 'composer.lock' - pull_request: - branches: - - trunk - - 'release/**' - - 'feature/**' - # Only run if PHP-related files changed. - paths: - - '.github/workflows/php-test.yml' - - '**.php' - - '.wp-env.json' - - '**/package.json' - - 'package-lock.json' - - 'phpunit.xml.dist' - - 'tests/multisite.xml' - - 'composer.json' - - 'composer.lock' - types: - - opened - - reopened - - synchronize - -jobs: - php-test: - name: PHP - runs-on: ubuntu-latest - timeout-minutes: 20 - steps: - - uses: styfle/cancel-workflow-action@0.11.0 - - uses: actions/checkout@v3 - - name: Setup Node.js (.nvmrc) - uses: actions/setup-node@v3 - with: - node-version-file: '.nvmrc' - cache: npm - - name: npm install - run: npm ci - - name: Install WordPress - run: npm run wp-env start - - name: Running single site unit tests - run: npm run test-php - - name: Running multisite unit tests - run: npm run test-php-multisite diff --git a/.github/workflows/spell-check.yml b/.github/workflows/spell-check.yml deleted file mode 100644 index 7fe8b06719..0000000000 --- a/.github/workflows/spell-check.yml +++ /dev/null @@ -1,12 +0,0 @@ -name: Spell Check - -on: [pull_request] - -jobs: - spell-checker: - name: Spell Check with Typos - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Search for misspellings - uses: crate-ci/typos@master diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000000..9b72ecf16b --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,206 @@ +--- +name: Test + +on: + push: + branches: + - trunk + - 'release/**' + pull_request: + types: + - opened + - synchronize + - reopened + - ready_for_review + +concurrency: + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.ref }} + cancel-in-progress: true + +permissions: {} + +jobs: + pre-run: + name: 'Pre run' + runs-on: ubuntu-latest + outputs: + changed-file-count: ${{ steps.determine-file-counts.outputs.count }} + changed-php-count: ${{ steps.determine-file-counts.outputs.php-count }} + changed-css-count: ${{ steps.determine-file-counts.outputs.css-count }} + changed-js-count: ${{ steps.determine-file-counts.outputs.js-count }} + changed-gha-workflow-count: ${{ steps.determine-file-counts.outputs.gha-workflow-count }} + steps: + - name: Checkout including last 2 commits + # Fetch last 2 commits if it's not a PR, so that we can determine the list of modified files. + if: ${{ github.base_ref == null }} + uses: actions/checkout@v4 + with: + fetch-depth: 2 + + - name: Checkout + # Do usual checkout if it's a PR. + if: ${{ github.base_ref != null }} + uses: actions/checkout@v4 + + - name: Determine modified files + id: determine-file-counts + uses: ./.github/actions/determine-changed-files + +#----------------------------------------------------------------------------------------------------------------------- + + unit-test-php: + name: "Unit test: PHP ${{ matrix.php }}, WP ${{ matrix.wp }}" + runs-on: ubuntu-latest + needs: pre-run + env: + WP_CORE_DIR: /tmp/wordpress + WP_TESTS_DIR: /tmp/wordpress-tests-lib + WP_ENVIRONMENT_TYPE: local + services: + mysql: + image: mariadb:latest + env: + MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: true + MARIADB_DATABASE: wordpress_test + MARIADB_MYSQL_LOCALHOST_USER: 1 + MARIADB_MYSQL_LOCALHOST_GRANTS: USAGE + ports: + - 3306 + options: --health-cmd="healthcheck.sh --su-mysql --connect --innodb_initialized" --health-interval=10s --health-timeout=5s --health-retries=3 + continue-on-error: ${{ matrix.experimental == true }} + strategy: + fail-fast: false + matrix: + php: ['8.0'] + wp: ['latest'] + phpunit: ['9.3'] + include: + - php: '8.3' + wp: 'trunk' + phpunit: '9.6' + experimental: true + + - php: '8.2' + wp: 'trunk' + phpunit: '9.6' + + - php: '8.1' + wp: 'trunk' + phpunit: '9.6' + + - php: '8.0' + wp: 'trunk' + phpunit: '9.3' + multisite: true + + - php: '7.4' + wp: 'latest' + phpunit: '9.3' + multisite: true + + - php: '7.4' + wp: 'latest' + phpunit: '9.3' + + - php: '7.4' + wp: '5.3' + phpunit: '7' + + - php: '7.4' + wp: '5.3' + phpunit: '7' + steps: + - name: Shutdown default MySQL service + run: sudo service mysql stop + + - name: Verify MariaDB connection + run: | + retry_count=0 + max_retries=5 + + while [ $retry_count -lt $max_retries ]; do + if mysqladmin ping -h"127.0.0.1" -P"${{ job.services.mysql.ports[3306] }}" --silent; then + break + else + ((retry_count++)) + sleep 5 + fi + done + + if [ $retry_count -ge $max_retries ]; then + echo "::error::Failed to establish a MariaDB connection after $max_retries retries." + exit 1 + fi + + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP and Composer + uses: ./.github/actions/setup-php-composer + with: + php-version: ${{ matrix.php }} + tools: 'composer,phpunit:${{ matrix.phpunit }}' + + - name: Setup NodeJS and NPM + uses: ./.github/actions/setup-node-npm + + # Avoid conflicts with globally installed PHPUnit. + - name: Remove locally installed PHPUnit + run: | + rm -rf vendor/phpunit + composer dump-autoload -o + + - name: Setup problem matchers + run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + + - name: Install WP tests + run: bash .github/bin/install-wp-tests.sh wordpress_test root '' 127.0.0.1:${{ job.services.mysql.ports['3306'] }} ${{ matrix.wp }} true + + - name: Copy plugin to WP plugins directory + run: cp -r "$PWD" "$WP_CORE_DIR/src/wp-content/plugins/performance" + + - name: Override default PHPUnit configuration + if: ${{ matrix.experimental == true && needs.pre-run.outputs.changed-php-count > 0 }} + run: | + cp phpunit.xml.dist phpunit.xml + + # Avoid converting deprecations, errors, notices, and warnings to exceptions in experimental mode. + sed -i 's/convertDeprecationsToExceptions="true"/convertDeprecationsToExceptions="false"/g' phpunit.xml + sed -i 's/convertErrorsToExceptions="true"/convertErrorsToExceptions="false"/g' phpunit.xml + sed -i 's/convertNoticesToExceptions="true"/convertNoticesToExceptions="false"/g' phpunit.xml + sed -i 's/convertWarningsToExceptions="true"/convertWarningsToExceptions="false"/g' phpunit.xml + working-directory: ${{ env.WP_CORE_DIR }}/src/wp-content/plugins/performance + + - name: Run Single Site tests + if: ${{ matrix.multisite == false && needs.pre-run.outputs.changed-php-count > 0 }} + run: | + phpunit --verbose + working-directory: ${{ env.WP_CORE_DIR }}/src/wp-content/plugins/performance + + - name: Run multisite tests + if: ${{ matrix.multisite == true && needs.pre-run.outputs.changed-php-count > 0 }} + run: | + phpunit -c tests/multisite.xml --verbose + working-directory: ${{ env.WP_CORE_DIR }}/src/wp-content/plugins/performance + env: + WP_MULTISITE: 1 + + - name: Build plugins + if: ${{ matrix.multisite == false && needs.pre-run.outputs.changed-php-count == 0 && always() }} + run: | + npm run build-plugins + working-directory: ${{ env.WP_CORE_DIR }}/src/wp-content/plugins/performance + + - name: Run standalone tests + if: ${{ matrix.multisite == false && needs.pre-run.outputs.changed-php-count == 0 && always() }} + run: | + node bin/plugin/cli.js test-plugins --command="phpunit --verbose" + working-directory: ${{ env.WP_CORE_DIR }}/src/wp-content/plugins/performance + + - name: Run standalone multisite tests + if: ${{ matrix.multisite == true && needs.pre-run.outputs.changed-php-count == 0 && always() }} + run: | + node bin/plugin/cli.js test-plugins --command="phpunit -c multisite.xml --verbose" --sitetype=multi + working-directory: ${{ env.WP_CORE_DIR }}/src/wp-content/plugins/performance + env: + WP_MULTISITE: 1 \ No newline at end of file