diff --git a/.ahoy.yml b/.ahoy.yml index 02af3ab..c1c3faf 100644 --- a/.ahoy.yml +++ b/.ahoy.yml @@ -171,7 +171,7 @@ commands: usage: Watch front-end assets during development. cmd: | ahoy cli "pkill -9 -f grunt" || true - ahoy cli "cd ${WEBROOT}/themes/custom/${DRUPAL_THEME} && yarn run dist:watch drush cc css-js" + ahoy cli "cd ${WEBROOT}/themes/custom/${DRUPAL_THEME} && yarn run watch" lint: usage: Lint back-end and front-end code. @@ -284,14 +284,14 @@ commands: confirm: cmd: | if [ -z "${AHOY_CONFIRM_RESPONSE}" ]; then - read -r -p ">> $1 [y/N] " AHOY_CONFIRM_RESPONSE + read -r -p "$( echo -e "\033[31m>> $1\033[0m \033[2m[y/N]\033[0m " )" AHOY_CONFIRM_RESPONSE if [ "${AHOY_CONFIRM_RESPONSE}" != "y" ] && [ "${AHOY_CONFIRM_RESPONSE}" != "true" ]; then echo "The operation was canceled." exit 1 fi else if [ "${AHOY_CONFIRM_WAIT_SKIP}" != "1" ]; then - echo ">> $1 [y/N] ${AHOY_CONFIRM_RESPONSE}" + echo -e "\033[31m>> $1\033[0m \033[2m[y/N] ${AHOY_CONFIRM_RESPONSE}\033[0m" echo "Waiting for 3 seconds... Press Ctrl+C to cancel." sleep 3 fi diff --git a/.docker/clamav.dockerfile b/.docker/clamav.dockerfile index 4012b92..80103c8 100644 --- a/.docker/clamav.dockerfile +++ b/.docker/clamav.dockerfile @@ -10,9 +10,9 @@ # @see https://hub.docker.com/r/uselagoon/commons/tags # @see https://github.com/uselagoon/lagoon-images/tree/main/images/commons -FROM uselagoon/commons:26.1.0 AS commons +FROM uselagoon/commons:26.2.0 AS commons -FROM clamav/clamav-debian:1.5.1-28 +FROM clamav/clamav-debian:1.5.2-32 COPY --from=commons /lagoon /lagoon COPY --from=commons /bin/fix-permissions /bin/ep /bin/docker-sleep /bin/wait-for /bin/ diff --git a/.docker/cli.dockerfile b/.docker/cli.dockerfile index 17963b0..c40aeef 100644 --- a/.docker/cli.dockerfile +++ b/.docker/cli.dockerfile @@ -7,7 +7,7 @@ # @see https://hub.docker.com/r/uselagoon/php-8.3-cli-drupal/tags # @see https://github.com/uselagoon/lagoon-images/tree/main/images/php-cli-drupal -FROM uselagoon/php-8.3-cli-drupal:26.1.0 +FROM uselagoon/php-8.3-cli-drupal:26.2.0 # Add missing variables. # @todo Remove once https://github.com/uselagoon/lagoon/issues/3121 is resolved. diff --git a/.docker/database.dockerfile b/.docker/database.dockerfile index af6069d..a8c1ef5 100644 --- a/.docker/database.dockerfile +++ b/.docker/database.dockerfile @@ -5,7 +5,7 @@ # # The ARG value will be updated with a value passed from docker-compose.yml -ARG IMAGE=uselagoon/mysql-8.4:26.1.0 +ARG IMAGE=uselagoon/mysql-8.4:26.2.0 # hadolint ignore=DL3006 FROM ${IMAGE} diff --git a/.docker/nginx-drupal.dockerfile b/.docker/nginx-drupal.dockerfile index 1c11c45..0e18588 100644 --- a/.docker/nginx-drupal.dockerfile +++ b/.docker/nginx-drupal.dockerfile @@ -11,7 +11,7 @@ ARG CLI_IMAGE # hadolint ignore=DL3006 FROM ${CLI_IMAGE:-cli} AS cli -FROM uselagoon/nginx-drupal:26.1.0 +FROM uselagoon/nginx-drupal:26.2.0 # Webroot is used for Nginx web root configuration. ARG WEBROOT=web diff --git a/.docker/php.dockerfile b/.docker/php.dockerfile index 4938bf9..2ed3803 100644 --- a/.docker/php.dockerfile +++ b/.docker/php.dockerfile @@ -12,7 +12,7 @@ ARG CLI_IMAGE # hadolint ignore=DL3006 FROM ${CLI_IMAGE:-cli} AS cli -FROM uselagoon/php-8.3-fpm:26.1.0 +FROM uselagoon/php-8.3-fpm:26.2.0 RUN apk add --no-cache tzdata diff --git a/.docker/solr.dockerfile b/.docker/solr.dockerfile index 0dba222..b700fa9 100644 --- a/.docker/solr.dockerfile +++ b/.docker/solr.dockerfile @@ -5,7 +5,7 @@ # @see https://hub.docker.com/r/uselagoon/solr-9-drupal/tags # @see https://github.com/uselagoon/lagoon-images/blob/main/images/solr-drupal/9.Dockerfile -FROM uselagoon/solr-9-drupal:26.1.0 +FROM uselagoon/solr-9-drupal:26.2.0 # Solr jump-start config needs to be manually copied from the search_api_solr # Drupal module to .docker/config/solr/config-set. diff --git a/.github/workflows/build-test-deploy.yml b/.github/workflows/build-test-deploy.yml index ee0df4a..a41378b 100644 --- a/.github/workflows/build-test-deploy.yml +++ b/.github/workflows/build-test-deploy.yml @@ -31,6 +31,8 @@ on: - develop - release/** - hotfix/** + - feature/** + - bugfix/** - project/** workflow_dispatch: @@ -47,7 +49,110 @@ defaults: run: shell: bash +# Workaround for the Actions runner creating /root/.docker/config.json +# with permissions that prevent the container from reading it. +# https://github.com/actions/runner/issues/863 +env: + DOCKER_CONFIG: /tmp/.docker + jobs: + + lint: + runs-on: ubuntu-latest + if: ${{ github.event_name != 'schedule' && (github.event_name == 'push' || !startsWith(github.head_ref, 'project/')) }} + + container: + # https://hub.docker.com/r/drevops/ci-runner + image: drevops/ci-runner:26.2.0@sha256:fe1561c2984a1023e84eebe6461056b0b55afedbb2512e6c2c7f19aca6beb398 + env: + PACKAGE_TOKEN: ${{ secrets.PACKAGE_TOKEN }} + VORTEX_CONTAINER_REGISTRY_USER: ${{ secrets.VORTEX_CONTAINER_REGISTRY_USER }} + VORTEX_CONTAINER_REGISTRY_PASS: ${{ secrets.VORTEX_CONTAINER_REGISTRY_PASS }} + TZ: ${{ vars.TZ || 'UTC' }} + TERM: xterm-256color + VORTEX_DEBUG: ${{ vars.VORTEX_DEBUG }} + + steps: + - name: Preserve $HOME set in the container + run: echo HOME=/root >> "$GITHUB_ENV" # https://github.com/actions/runner/issues/863 + + - name: Check out code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + + - name: Fix Git ownership permissions + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + + - name: Process the codebase to run in CI + run: find . -name "docker-compose.yml" -print0 | xargs -0 -I {} sh -c "sed -i -e '/###/d' {} && sed -i -e 's/##//' {}" + + - name: Load environment variables from .env + run: t=$(mktemp) && export -p >"${t}" && set -a && . ./.env && set +a && . "${t}" && env >> "$GITHUB_ENV" + + - name: Validate Composer configuration + run: composer validate --strict + continue-on-error: ${{ vars.VORTEX_CI_COMPOSER_VALIDATE_IGNORE_FAILURE == '1' }} + + - name: Login to container registry + run: ./scripts/vortex/login-container-registry.sh + + - name: Lint Dockerfiles with Hadolint + run: | + find .docker -name 'Dockerfile' -o -name '*.dockerfile' | while read -r file; do + echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint + done + continue-on-error: ${{ vars.VORTEX_CI_HADOLINT_IGNORE_FAILURE == '1' }} + + - name: Lint Docker Compose files with DCLint + run: docker run --rm -v "${PWD}":/app zavoloklom/dclint:3.1.0 . + continue-on-error: ${{ vars.VORTEX_CI_DCLINT_IGNORE_FAILURE == '1' }} + + - name: Build stack + run: docker compose up --no-deps --detach cli + + - name: Install development dependencies + run: | + docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c " \ + if [ -n \"${PACKAGE_TOKEN:-}\" ]; then export COMPOSER_AUTH='{\"github-oauth\": {\"github.com\": \"${PACKAGE_TOKEN-}\"}}'; fi && \ + COMPOSER_MEMORY_LIMIT=-1 composer --ansi install --prefer-dist" + docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c "yarn install --frozen-lockfile" + + - name: Audit Composer packages + run: docker compose exec -T cli composer audit + continue-on-error: ${{ vars.VORTEX_CI_COMPOSER_AUDIT_IGNORE_FAILURE == '1' }} + + - name: Validate Composer configuration is normalized + run: docker compose exec -T cli composer normalize --dry-run + continue-on-error: ${{ vars.VORTEX_CI_COMPOSER_NORMALIZE_IGNORE_FAILURE == '1' }} + + - name: Lint code with PHPCS + run: docker compose exec -T cli vendor/bin/phpcs + continue-on-error: ${{ vars.VORTEX_CI_PHPCS_IGNORE_FAILURE == '1' }} + + - name: Lint code with PHPStan + run: docker compose exec -T cli vendor/bin/phpstan + continue-on-error: ${{ vars.VORTEX_CI_PHPSTAN_IGNORE_FAILURE == '1' }} + + - name: Lint code with Rector + run: docker compose exec -T cli vendor/bin/rector --dry-run + continue-on-error: ${{ vars.VORTEX_CI_RECTOR_IGNORE_FAILURE == '1' }} + + - name: Lint code with Twig CS Fixer + run: docker compose exec -T cli vendor/bin/twig-cs-fixer + continue-on-error: ${{ vars.VORTEX_CI_TWIG_CS_FIXER_IGNORE_FAILURE == '1' }} + + - name: Lint code with Gherkin Lint + run: docker compose exec -T cli vendor/bin/gherkinlint lint tests/behat/features + continue-on-error: ${{ vars.VORTEX_CI_GHERKIN_LINT_IGNORE_FAILURE == '1' }} + + - name: Lint module code with NodeJS linters + run: docker compose exec -T cli bash -c "yarn run lint" + continue-on-error: ${{ vars.VORTEX_CI_NODEJS_LINT_IGNORE_FAILURE == '1' }} + + - name: Lint theme code with NodeJS linters + if: ${{ vars.VORTEX_FRONTEND_BUILD_SKIP != '1' }} + run: docker compose exec -T cli bash -c "yarn --cwd=\${WEBROOT}/themes/custom/\${DRUPAL_THEME} run lint" + continue-on-error: ${{ vars.VORTEX_CI_NODEJS_LINT_IGNORE_FAILURE == '1' }} + database: runs-on: ubuntu-latest if: ${{ github.event_name == 'push' || !startsWith(github.head_ref, 'project/') }} @@ -83,9 +188,12 @@ jobs: # Do not keep SSH credentials after checkout to allow adding custom SSH keys. persist-credentials: false + - name: Fix Git ownership permissions + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + - name: Add SSH private key used to download database if: ${{ env.VORTEX_DOWNLOAD_DB_SSH_PRIVATE_KEY != '' }} - uses: shimataro/ssh-key-action@v2 + uses: shimataro/ssh-key-action@6b84f2e793b32fa0b03a379cadadec75cc539391 # v2.8.0 with: key: ${{ secrets.VORTEX_DOWNLOAD_DB_SSH_PRIVATE_KEY }} known_hosts: unnecessary @@ -189,6 +297,9 @@ jobs: - name: Check out code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - name: Fix Git ownership permissions + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + - name: Process the codebase to run in CI run: find . -name "docker-compose.yml" -print0 | xargs -0 -I {} sh -c "sed -i -e '/###/d' {} && sed -i -e 's/##//' {}" @@ -225,19 +336,6 @@ jobs: - name: Login to container registry run: ./scripts/vortex/login-container-registry.sh - - name: Lint Dockerfiles with Hadolint - if: ${{ matrix.instance == 0 || strategy.job-total == 1 }} - run: | - find .docker -name 'Dockerfile' -o -name '*.dockerfile' | while read -r file; do - echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint - done - continue-on-error: ${{ vars.VORTEX_CI_HADOLINT_IGNORE_FAILURE == '1' }} - - - name: Lint Docker Compose files with DCLint - if: ${{ matrix.instance == 0 || strategy.job-total == 1 }} - run: docker run --rm -v "${PWD}":/app zavoloklom/dclint:3.1.0 . - continue-on-error: ${{ vars.VORTEX_CI_DCLINT_IGNORE_FAILURE == '1' }} - - name: Build stack run: docker compose up --detach && docker builder prune --all --force @@ -254,51 +352,6 @@ jobs: docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c " \ if [ -n \"${PACKAGE_TOKEN:-}\" ]; then export COMPOSER_AUTH='{\"github-oauth\": {\"github.com\": \"${PACKAGE_TOKEN-}\"}}'; fi && \ COMPOSER_MEMORY_LIMIT=-1 composer --ansi install --prefer-dist" - docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c "yarn install --frozen-lockfile" - - - name: Audit Composer packages - run: docker compose exec -T cli composer audit - continue-on-error: ${{ vars.VORTEX_CI_COMPOSER_AUDIT_IGNORE_FAILURE == '1' }} - - - name: Validate Composer configuration is normalized - if: ${{ matrix.instance == 0 || strategy.job-total == 1 }} - run: docker compose exec -T cli composer normalize --dry-run - continue-on-error: ${{ vars.VORTEX_CI_COMPOSER_NORMALIZE_IGNORE_FAILURE == '1' }} - - - name: Lint code with PHPCS - if: ${{ matrix.instance == 0 || strategy.job-total == 1 }} - run: docker compose exec -T cli vendor/bin/phpcs - continue-on-error: ${{ vars.VORTEX_CI_PHPCS_IGNORE_FAILURE == '1' }} - - - name: Lint code with PHPStan - if: ${{ matrix.instance == 0 || strategy.job-total == 1 }} - run: docker compose exec -T cli vendor/bin/phpstan - continue-on-error: ${{ vars.VORTEX_CI_PHPSTAN_IGNORE_FAILURE == '1' }} - - - name: Lint code with Rector - if: ${{ matrix.instance == 0 || strategy.job-total == 1 }} - run: docker compose exec -T cli vendor/bin/rector --dry-run - continue-on-error: ${{ vars.VORTEX_CI_RECTOR_IGNORE_FAILURE == '1' }} - - - name: Lint code with Twig CS Fixer - if: ${{ matrix.instance == 0 || strategy.job-total == 1 }} - run: docker compose exec -T cli vendor/bin/twig-cs-fixer - continue-on-error: ${{ vars.VORTEX_CI_TWIG_CS_FIXER_IGNORE_FAILURE == '1' }} - - - name: Lint code with Gherkin Lint - if: ${{ matrix.instance == 0 || strategy.job-total == 1 }} - run: docker compose exec -T cli vendor/bin/gherkinlint lint tests/behat/features - continue-on-error: ${{ vars.VORTEX_CI_GHERKIN_LINT_IGNORE_FAILURE == '1' }} - - - name: Lint module code with NodeJS linters - if: ${{ matrix.instance == 0 || strategy.job-total == 1 }} - run: docker compose exec -T cli bash -c "yarn run lint" - continue-on-error: ${{ vars.VORTEX_CI_NODEJS_LINT_IGNORE_FAILURE == '1' }} - - - name: Lint theme code with NodeJS linters - if: ${{ (matrix.instance == 0 || strategy.job-total == 1) && vars.VORTEX_FRONTEND_BUILD_SKIP != '1' }} - run: docker compose exec -T cli bash -c "yarn --cwd=\${WEBROOT}/themes/custom/\${DRUPAL_THEME} run lint" - continue-on-error: ${{ vars.VORTEX_CI_NODEJS_LINT_IGNORE_FAILURE == '1' }} - name: Provision site run: | @@ -310,57 +363,36 @@ jobs: timeout-minutes: 30 - name: Test with PHPUnit + if: ${{ matrix.instance == 0 || strategy.job-total == 1 }} run: docker compose exec -T cli vendor/bin/phpunit continue-on-error: ${{ vars.VORTEX_CI_PHPUNIT_IGNORE_FAILURE == '1' }} - - name: Test with Behat - run: | - # shellcheck disable=SC2170 - if [ ${{ strategy.job-total }} -gt 1 ]; then export VORTEX_CI_BEHAT_PROFILE="${VORTEX_CI_BEHAT_PROFILE:-p${{ strategy.job-index }}}"; fi - echo "Running with ${VORTEX_CI_BEHAT_PROFILE:-default} profile" - docker compose exec -T cli php -d memory_limit=-1 vendor/bin/behat --colors --strict --profile="${VORTEX_CI_BEHAT_PROFILE:-default}" || \ - docker compose exec -T cli php -d memory_limit=-1 vendor/bin/behat --colors --strict --rerun --profile="${VORTEX_CI_BEHAT_PROFILE:-default}" - env: - VORTEX_CI_BEHAT_PROFILE: ${{ vars.VORTEX_CI_BEHAT_PROFILE }} - continue-on-error: ${{ vars.VORTEX_CI_BEHAT_IGNORE_FAILURE == '1' }} - timeout-minutes: 30 - - - name: Process test logs and artifacts - if: always() + - name: Process PHPUnit logs and coverage + if: ${{ matrix.instance == 0 || strategy.job-total == 1 }} run: | mkdir -p ".logs" if docker compose ps --services --filter "status=running" | grep -q cli && docker compose exec cli test -d /app/.logs; then docker compose cp cli:/app/.logs/. ".logs/" fi - - name: Upload test artifacts - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 - if: always() - with: - name: test-artifacts-${{ matrix.instance }} - path: .logs - include-hidden-files: true - if-no-files-found: error - - - name: Check code coverage threshold + - name: Extract code coverage if: ${{ matrix.instance == 0 || strategy.job-total == 1 }} run: | RATE=$(grep -om1 'line-rate="[0-9.]*"' .logs/coverage/phpunit/cobertura.xml | tr -cd '0-9.') PERCENT=$(awk "BEGIN {printf \"%.2f\", $RATE*100}") echo "Coverage: $PERCENT% (threshold: $VORTEX_CI_CODE_COVERAGE_THRESHOLD%)" | tee -a "$GITHUB_STEP_SUMMARY" - if [ "${PERCENT//./}" -lt "$((VORTEX_CI_CODE_COVERAGE_THRESHOLD*100))" ]; then - echo "FAIL: coverage too low" - exit 1 - fi + echo "COVERAGE_PERCENT=${PERCENT}" >> "$GITHUB_ENV" { echo "COVERAGE_CONTENT<> "$GITHUB_ENV" env: VORTEX_CI_CODE_COVERAGE_THRESHOLD: ${{ vars.VORTEX_CI_CODE_COVERAGE_THRESHOLD || '90' }} - name: Post coverage summary as PR comment if: ${{ github.event_name == 'pull_request' && (matrix.instance == 0 || strategy.job-total == 1) && vars.VORTEX_CI_CODE_COVERAGE_PR_COMMENT_SKIP != '1' }} - uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2 + uses: marocchino/sticky-pull-request-comment@70d2764d1a7d5d9560b100cbea0077fc8f633987 # v3 with: + header: coverage-gha message: | + **Code coverage (GitHub Actions)** ``` ${{ env.COVERAGE_CONTENT }} ``` @@ -368,7 +400,7 @@ jobs: - name: Upload coverage report to Codecov uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5 - if: ${{ env.CODECOV_TOKEN != '' }} + if: ${{ (matrix.instance == 0 || strategy.job-total == 1) && env.CODECOV_TOKEN != '' }} with: directory: .logs/coverage fail_ci_if_error: true @@ -376,8 +408,47 @@ jobs: env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + - name: Check code coverage threshold + if: ${{ matrix.instance == 0 || strategy.job-total == 1 }} + run: | + if [ "${COVERAGE_PERCENT//.}" -lt "$((VORTEX_CI_CODE_COVERAGE_THRESHOLD*100))" ]; then + echo "FAIL: coverage ${COVERAGE_PERCENT}% is below threshold ${VORTEX_CI_CODE_COVERAGE_THRESHOLD}%" + exit 1 + fi + env: + VORTEX_CI_CODE_COVERAGE_THRESHOLD: ${{ vars.VORTEX_CI_CODE_COVERAGE_THRESHOLD || '90' }} + + - name: Test with Behat + run: | + # shellcheck disable=SC2170 + if [ ${{ strategy.job-total }} -gt 1 ]; then export VORTEX_CI_BEHAT_PROFILE="${VORTEX_CI_BEHAT_PROFILE:-p${{ strategy.job-index }}}"; fi + echo "Running with ${VORTEX_CI_BEHAT_PROFILE:-default} profile" + docker compose exec -T cli php -d memory_limit=-1 vendor/bin/behat --colors --strict --profile="${VORTEX_CI_BEHAT_PROFILE:-default}" || \ + docker compose exec -T cli php -d memory_limit=-1 vendor/bin/behat --colors --strict --rerun --profile="${VORTEX_CI_BEHAT_PROFILE:-default}" + env: + VORTEX_CI_BEHAT_PROFILE: ${{ vars.VORTEX_CI_BEHAT_PROFILE }} + continue-on-error: ${{ vars.VORTEX_CI_BEHAT_IGNORE_FAILURE == '1' }} + timeout-minutes: 30 + + - name: Process test logs and artifacts + if: always() + run: | + mkdir -p ".logs" + if docker compose ps --services --filter "status=running" | grep -q cli && docker compose exec cli test -d /app/.logs; then + docker compose cp cli:/app/.logs/. ".logs/" + fi + + - name: Upload test artifacts + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + if: always() + with: + name: test-artifacts-${{ matrix.instance }} + path: .logs + include-hidden-files: true + if-no-files-found: error + - name: Upload exported codebase as an artifact - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 if: ${{ matrix.instance == 0 && !startsWith(github.head_ref || github.ref_name, 'deps/') && contains(env.VORTEX_DEPLOY_TYPES, 'artifact') }} with: name: code-artifact @@ -395,7 +466,7 @@ jobs: deploy: runs-on: ubuntu-latest - needs: build + needs: [build, lint] if: ${{ github.event_name != 'schedule' && !startsWith(github.head_ref || github.ref_name, 'deps/') && (github.event_name == 'push' || !startsWith(github.head_ref, 'project/')) }} container: @@ -421,11 +492,14 @@ jobs: persist-credentials: false ref: ${{ github.head_ref || github.ref_name }} + - name: Fix Git ownership permissions + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + - name: Load environment variables from .env run: t=$(mktemp) && export -p >"${t}" && set -a && . ./.env && set +a && . "${t}" && env >> "$GITHUB_ENV" - name: Download exported codebase as an artifact - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 if: ${{ contains(env.VORTEX_DEPLOY_TYPES, 'artifact') }} with: name: code-artifact @@ -439,7 +513,7 @@ jobs: - name: Add SSH private key to the runner if: ${{ env.VORTEX_DEPLOY_SSH_PRIVATE_KEY != '' }} - uses: shimataro/ssh-key-action@v2 + uses: shimataro/ssh-key-action@6b84f2e793b32fa0b03a379cadadec75cc539391 # v2.8.0 with: key: ${{ secrets.VORTEX_DEPLOY_SSH_PRIVATE_KEY }} known_hosts: unnecessary diff --git a/.github/workflows/draft-release-notes.yml b/.github/workflows/draft-release-notes.yml index 79e948b..a76ccb2 100644 --- a/.github/workflows/draft-release-notes.yml +++ b/.github/workflows/draft-release-notes.yml @@ -36,7 +36,7 @@ jobs: echo "Version set to ${VERSION}" - name: Draft release notes - uses: release-drafter/release-drafter@6db134d15f3909ccc9eefd369f02bd1e9cffdf97 # v6 + uses: release-drafter/release-drafter@3a7fb5c85b80b1dda66e1ccb94009adbbd32fce3 # v7 with: tag: ${{ steps.calver.outputs.version }} name: ${{ steps.calver.outputs.version }} diff --git a/.github/workflows/label-merge-conflict.yml b/.github/workflows/label-merge-conflict.yml index 3bfe01f..b0537d3 100644 --- a/.github/workflows/label-merge-conflict.yml +++ b/.github/workflows/label-merge-conflict.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check if PRs have conflicts - uses: eps1lon/actions-label-merge-conflict@v3 + uses: eps1lon/actions-label-merge-conflict@1df065ebe6e3310545d4f4c4e862e43bdca146f0 # v3.0.3 with: dirtyLabel: "CONFLICT" removeOnDirtyLabel: "Needs review" diff --git a/README.md b/README.md index 7943044..50c2ae1 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Drupal 11 implementation of DrevOps Website for DrevOps [//]: # (DO NOT REMOVE THE BADGE BELOW. IT IS USED BY VORTEX TO TRACK INTEGRATION) -[![Vortex](https://img.shields.io/badge/Vortex-1.36.0-65ACBC.svg)](https://github.com/drevops/vortex/tree/1.36.0) +[![Vortex](https://img.shields.io/badge/Vortex-1.37.0-65ACBC.svg)](https://github.com/drevops/vortex/tree/1.36.0) diff --git a/behat.yml b/behat.yml index 722e88f..e531883 100644 --- a/behat.yml +++ b/behat.yml @@ -40,9 +40,11 @@ default: 'goog:chromeOptions': args: - '--disable-extensions' # Prevents interference from browser extensions. + - '--disable-popup-blocking' # Allows tests to open popups without being blocked. - '--disable-translate' # Prevents the translation bar from appearing on non-English pages. - '--force-prefers-reduced-motion' # Disables CSS animations and transitions for test stability. - '--test-type' # Suppresses error dialogs and crash recovery prompts. + - '--window-size=1920,1080' # Sets the browser window size for consistent test screenshots. # Provides integration with Drupal APIs. Drupal\DrupalExtension: diff --git a/composer.json b/composer.json index 833c3fd..0e01521 100644 --- a/composer.json +++ b/composer.json @@ -6,19 +6,19 @@ "require": { "php": ">=8.3", "composer/installers": "^2.3", - "cweagans/composer-patches": "^2", + "cweagans/composer-patches": "^2.0", "drevops/test-private-package": "^1.0", "drupal/admin_toolbar": "^3.6.3", "drupal/ai_image_alt_text": "^1.0.2", - "drupal/ai_provider_openai": "^1.2", + "drupal/ai_provider_openai": "^1.2.1", "drupal/civictheme": "~1.12.2", "drupal/clamav": "^2.1", "drupal/cloudflare": "^2@beta", "drupal/coffee": "^2.0.1", "drupal/config_split": "^2.0.2", "drupal/config_update": "^2@alpha", - "drupal/core-composer-scaffold": "~11.3.3", - "drupal/core-recommended": "~11.3.3", + "drupal/core-composer-scaffold": "~11.3.5", + "drupal/core-recommended": "~11.3.5", "drupal/devel": "^5.5.0", "drupal/diff": "^1.10", "drupal/entity_clone": "^2.1@beta", @@ -38,7 +38,7 @@ "drupal/redirect": "^1.12", "drupal/redis": "^1.11", "drupal/robotstxt": "^1.6", - "drupal/scheduled_transitions": "^2.8.3", + "drupal/scheduled_transitions": "^2.8.4", "drupal/search_api": "^1.40", "drupal/search_api_solr": "^4.3.10", "drupal/seckit": "^2.0.3", @@ -49,7 +49,7 @@ "drupal/webform": "^6.3@beta", "drush/drush": "^13.7.1", "oomphinc/composer-installers-extender": "^2.0.1", - "symfony/http-client": "^6.4.33", + "symfony/http-client": "^6.4.34", "webflo/drupal-finder": "^1.3.1" }, "require-dev": { @@ -58,24 +58,24 @@ "dealerdirect/phpcodesniffer-composer-installer": "^1.2.0", "drevops/behat-format-progress-fail": "^1.4", "drevops/behat-screenshot": "^2.2", - "drevops/behat-steps": "^3.5.0", + "drevops/behat-steps": "^3.5.2", "drevops/phpcs-standard": "^0.6.2", "drupal/coder": "^9@alpha", - "drupal/drupal-extension": "^5.1", + "drupal/drupal-extension": "^5.2.1", "ergebnis/composer-normalize": "^2.50.0", "lullabot/mink-selenium2-driver": "^1.7.4", "lullabot/php-webdriver": "^2.0.7", - "mglaman/phpstan-drupal": "^2.0.10", + "mglaman/phpstan-drupal": "^2.0.11", "mikey179/vfsstream": "^1.6.12", "palantirnet/drupal-rector": "^0.21.1", "phpcompatibility/php-compatibility": "^10.0@alpha", "phpspec/prophecy-phpunit": "^2.5", "phpstan/extension-installer": "^1.4.3", - "phpstan/phpstan": "^2.1.39", + "phpstan/phpstan": "^2.1.40", "phpunit/phpunit": "^11.5.55", "pyrech/composer-changelogs": "^2.2", - "rector/rector": "^2.3.7", - "vincentlanglet/twig-cs-fixer": "^3.13" + "rector/rector": "^2.3.8", + "vincentlanglet/twig-cs-fixer": "^3.14" }, "conflict": { "drupal/drupal": "*" @@ -164,9 +164,7 @@ "type:drupal-core" ], "web/libraries/{$name}": [ - "type:bower-asset", - "type:drupal-library", - "type:npm-asset" + "type:drupal-library" ], "web/modules/contrib/{$name}": [ "type:drupal-module" @@ -194,8 +192,6 @@ ] }, "installer-types": [ - "bower-asset", - "npm-asset", "drupal-library" ], "patchLevel": { diff --git a/composer.lock b/composer.lock index af944c9..41e967e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "474cd1297c55cbee29ed7c04a25c8450", + "content-hash": "fe9d7f46c02a0e5fef6257c72e52d129", "packages": [ { "name": "asm89/stack-cors", @@ -1589,20 +1589,20 @@ }, { "name": "drupal/ai", - "version": "1.2.9", + "version": "1.3.0", "source": { "type": "git", "url": "https://git.drupalcode.org/project/ai.git", - "reference": "1.2.9" + "reference": "1.3.0" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/ai-1.2.9.zip", - "reference": "1.2.9", - "shasum": "633b7584ab2977cd7b778d8beedeed850443793d" + "url": "https://ftp.drupal.org/files/projects/ai-1.3.0.zip", + "reference": "1.3.0", + "shasum": "0585d2d799305c3275b0966f8b41959fd7fa45ab" }, "require": { - "drupal/core": "^10.4 || ^11", + "drupal/core": "^10.5 || ^11.2", "drupal/key": "^1.18", "league/html-to-markdown": "^5.1", "openai-php/client": ">=v0.10.1", @@ -1610,13 +1610,16 @@ }, "require-dev": { "allanpichardo/mysql-vector": "^2.0", + "drupal/address": "^2.0", "drupal/ai_assistant_api": "*", - "drupal/eca": "^2.0", + "drupal/eca": "*", "drupal/eca_content-eca_content": "*", "drupal/field_validation": "^3.0@beta", "drupal/key": "*", + "drupal/opentelemetry": "^1.0@beta", "drupal/search_api": "^1.x-dev", "drupal/search_api_solr": "^4.0", + "drupal/test_helpers": "^1.5", "drupal/token": "^1.14", "drush/drush": "^12|^13" }, @@ -1626,8 +1629,8 @@ "type": "drupal-module", "extra": { "drupal": { - "version": "1.2.9", - "datestamp": "1771425862", + "version": "1.3.0", + "datestamp": "1773325312", "security-coverage": { "status": "covered", "message": "Covered by Drupal's security advisory policy" @@ -1757,6 +1760,14 @@ "homepage": "https://dxpr.com", "role": "Maintainer" }, + { + "name": "anybody", + "homepage": "https://www.drupal.org/user/291091" + }, + { + "name": "grevil", + "homepage": "https://www.drupal.org/user/3668491" + }, { "name": "jurriaanroelofs", "homepage": "https://www.drupal.org/user/52638" @@ -1781,17 +1792,17 @@ }, { "name": "drupal/ai_provider_openai", - "version": "1.2.0", + "version": "1.2.1", "source": { "type": "git", "url": "https://git.drupalcode.org/project/ai_provider_openai.git", - "reference": "1.2.0" + "reference": "1.2.1" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/ai_provider_openai-1.2.0.zip", - "reference": "1.2.0", - "shasum": "de8caa2a9adb0cece69d2f36b27afaeeb1c3eadf" + "url": "https://ftp.drupal.org/files/projects/ai_provider_openai-1.2.1.zip", + "reference": "1.2.1", + "shasum": "9f1d467d7e8f78cb4e29b53741eda96df9de8e59" }, "require": { "drupal/ai": "^1.2.0", @@ -1801,8 +1812,8 @@ "type": "drupal-module", "extra": { "drupal": { - "version": "1.2.0", - "datestamp": "1760354152", + "version": "1.2.1", + "datestamp": "1772029063", "security-coverage": { "status": "covered", "message": "Covered by Drupal's security advisory policy" @@ -1874,7 +1885,7 @@ "extra": { "drupal": { "version": "2.0.10", - "datestamp": "1770647534", + "datestamp": "1772046216", "security-coverage": { "status": "covered", "message": "Covered by Drupal's security advisory policy" @@ -2421,16 +2432,16 @@ }, { "name": "drupal/core", - "version": "11.3.3", + "version": "11.3.5", "source": { "type": "git", "url": "https://github.com/drupal/core.git", - "reference": "7067385162ad51020c78052fe15334671bdf3552" + "reference": "6de8a6ff360ad1c2719af077259c3ef77ba3d23f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/drupal/core/zipball/7067385162ad51020c78052fe15334671bdf3552", - "reference": "7067385162ad51020c78052fe15334671bdf3552", + "url": "https://api.github.com/repos/drupal/core/zipball/6de8a6ff360ad1c2719af077259c3ef77ba3d23f", + "reference": "6de8a6ff360ad1c2719af077259c3ef77ba3d23f", "shasum": "" }, "require": { @@ -2588,22 +2599,22 @@ ], "description": "Drupal is an open source content management platform powering millions of websites and applications.", "support": { - "source": "https://github.com/drupal/core/tree/11.3.3" + "source": "https://github.com/drupal/core/tree/11.3.5" }, - "time": "2026-02-05T08:05:30+00:00" + "time": "2026-03-06T09:54:42+00:00" }, { "name": "drupal/core-composer-scaffold", - "version": "11.3.3", + "version": "11.3.5", "source": { "type": "git", "url": "https://github.com/drupal/core-composer-scaffold.git", - "reference": "445bec7d1cf903d990398b68b0a7fd98270f986f" + "reference": "cb417c20910980aa6d40dc0626af795f9308bcca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/drupal/core-composer-scaffold/zipball/445bec7d1cf903d990398b68b0a7fd98270f986f", - "reference": "445bec7d1cf903d990398b68b0a7fd98270f986f", + "url": "https://api.github.com/repos/drupal/core-composer-scaffold/zipball/cb417c20910980aa6d40dc0626af795f9308bcca", + "reference": "cb417c20910980aa6d40dc0626af795f9308bcca", "shasum": "" }, "require": { @@ -2638,29 +2649,29 @@ "drupal" ], "support": { - "source": "https://github.com/drupal/core-composer-scaffold/tree/11.3.3" + "source": "https://github.com/drupal/core-composer-scaffold/tree/11.3.5" }, - "time": "2026-01-26T12:09:35+00:00" + "time": "2026-02-10T11:39:53+00:00" }, { "name": "drupal/core-recommended", - "version": "11.3.3", + "version": "11.3.5", "source": { "type": "git", "url": "https://github.com/drupal/core-recommended.git", - "reference": "c5614e888df11a5e293faa97c8014a148eaefa8d" + "reference": "957d0a4e92e90fcf8375a64d63bdff47f7c88b52" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/drupal/core-recommended/zipball/c5614e888df11a5e293faa97c8014a148eaefa8d", - "reference": "c5614e888df11a5e293faa97c8014a148eaefa8d", + "url": "https://api.github.com/repos/drupal/core-recommended/zipball/957d0a4e92e90fcf8375a64d63bdff47f7c88b52", + "reference": "957d0a4e92e90fcf8375a64d63bdff47f7c88b52", "shasum": "" }, "require": { "asm89/stack-cors": "~v2.3.0", "composer/semver": "~3.4.4", "doctrine/lexer": "~3.0.1", - "drupal/core": "11.3.3", + "drupal/core": "11.3.5", "egulias/email-validator": "~4.0.4", "guzzlehttp/guzzle": "~7.10.0", "guzzlehttp/promises": "~2.3.0", @@ -2722,23 +2733,23 @@ ], "description": "Core and its dependencies with known-compatible minor versions. Require this project INSTEAD OF drupal/core.", "support": { - "source": "https://github.com/drupal/core-recommended/tree/11.3.3" + "source": "https://github.com/drupal/core-recommended/tree/11.3.5" }, - "time": "2026-02-05T08:05:30+00:00" + "time": "2026-03-06T09:54:42+00:00" }, { "name": "drupal/crop", - "version": "2.5.0", + "version": "2.6.0", "source": { "type": "git", "url": "https://git.drupalcode.org/project/crop.git", - "reference": "8.x-2.5" + "reference": "8.x-2.6" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/crop-8.x-2.5.zip", - "reference": "8.x-2.5", - "shasum": "759c8add182d9b74c04a58c26d1c402f9d1653e6" + "url": "https://ftp.drupal.org/files/projects/crop-8.x-2.6.zip", + "reference": "8.x-2.6", + "shasum": "767827cc8f88e1dd5c4aa47aa0ae2f3148b96aaf" }, "require": { "drupal/core": "^9.3 || ^10 || ^11" @@ -2746,8 +2757,8 @@ "type": "drupal-module", "extra": { "drupal": { - "version": "8.x-2.5", - "datestamp": "1763154892", + "version": "8.x-2.6", + "datestamp": "1773071544", "security-coverage": { "status": "covered", "message": "Covered by Drupal's security advisory policy" @@ -3945,6 +3956,10 @@ "homepage": "https://www.drupal.org/u/eiriksm", "role": "Maintainer" }, + { + "name": "daddison", + "homepage": "https://www.drupal.org/user/51447" + }, { "name": "eiriksm", "homepage": "https://www.drupal.org/user/1014468" @@ -3999,17 +4014,17 @@ }, { "name": "drupal/linkit", - "version": "7.0.12", + "version": "7.0.13", "source": { "type": "git", "url": "https://git.drupalcode.org/project/linkit.git", - "reference": "7.0.12" + "reference": "7.0.13" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/linkit-7.0.12.zip", - "reference": "7.0.12", - "shasum": "be60722e708e12be1596a804e816652c3c7ea815" + "url": "https://ftp.drupal.org/files/projects/linkit-7.0.13.zip", + "reference": "7.0.13", + "shasum": "918d001248b4a394eb1c4e097e7d243959280489" }, "require": { "drupal/core": "^10.1 || ^11" @@ -4017,8 +4032,8 @@ "type": "drupal-module", "extra": { "drupal": { - "version": "7.0.12", - "datestamp": "1765468273", + "version": "7.0.13", + "datestamp": "1773055628", "security-coverage": { "status": "covered", "message": "Covered by Drupal's security advisory policy" @@ -4705,17 +4720,17 @@ }, { "name": "drupal/scheduled_transitions", - "version": "2.8.3", + "version": "2.8.4", "source": { "type": "git", "url": "https://git.drupalcode.org/project/scheduled_transitions.git", - "reference": "2.8.3" + "reference": "2.8.4" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/scheduled_transitions-2.8.3.zip", - "reference": "2.8.3", - "shasum": "045bd7d47feb6240a1f8b5106b9170e5ca66f599" + "url": "https://ftp.drupal.org/files/projects/scheduled_transitions-2.8.4.zip", + "reference": "2.8.4", + "shasum": "ee8202c391b1ab56c57baf9e5a06a8175aac7821" }, "require": { "drupal/core": "^11.1", @@ -4726,6 +4741,7 @@ "composer/installers": "^2", "drupal/core-dev": ">=11.1", "drush/drush": ">=11", + "mglaman/phpstan-drupal": "dev-narrow-id-return-by-isnew--only-existing as 2.9999.9999", "mockery/mockery": "^1.5", "phpstan/phpstan-deprecation-rules": "^1 || ^2", "phpstan/phpstan-mockery": "^1.1 || ^2", @@ -4738,8 +4754,8 @@ "type": "drupal-module", "extra": { "drupal": { - "version": "2.8.3", - "datestamp": "1753787485", + "version": "2.8.4", + "datestamp": "1773373222", "security-coverage": { "status": "covered", "message": "Covered by Drupal's security advisory policy" @@ -5373,17 +5389,17 @@ }, { "name": "drupal/webform", - "version": "6.3.0-beta7", + "version": "6.3.0-beta8", "source": { "type": "git", "url": "https://git.drupalcode.org/project/webform.git", - "reference": "6.3.0-beta7" + "reference": "6.3.0-beta8" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/webform-6.3.0-beta7.zip", - "reference": "6.3.0-beta7", - "shasum": "405d46b32e0536f9f854cf1f2620eddd87cc853b" + "url": "https://ftp.drupal.org/files/projects/webform-6.3.0-beta8.zip", + "reference": "6.3.0-beta8", + "shasum": "533ae32d1c0868b98b97c22cc18539e21b4600c2" }, "require": { "drupal/core": "^10.3 || ^11.0" @@ -5392,24 +5408,24 @@ "drupal/address": "^2.0", "drupal/captcha": "^2.0", "drupal/chosen": "^4.0", - "drupal/clientside_validation": "^4.1", + "drupal/clientside_validation": "^4.0", "drupal/clientside_validation_jquery": "*", - "drupal/devel": "^5.3", - "drupal/entity": "^1.5", - "drupal/entity_print": "^2.15", + "drupal/devel": "^5.0", + "drupal/entity": "^1.0", + "drupal/entity_print": "^2.0", "drupal/hal": "^2.0", - "drupal/jquery_ui": "^1.7", - "drupal/jquery_ui_button": "^2.1", - "drupal/jquery_ui_checkboxradio": "^2.1", - "drupal/jquery_ui_datepicker": "^2.1", - "drupal/mailsystem": "^4.5", + "drupal/jquery_ui": "^1.0", + "drupal/jquery_ui_button": "^2.0", + "drupal/jquery_ui_checkboxradio": "^2.0", + "drupal/jquery_ui_datepicker": "^2.0", + "drupal/mailsystem": "^4.0", "drupal/metatag": "^2.0", - "drupal/paragraphs": "^1.18", - "drupal/select2": "1.x-dev", - "drupal/smtp": "^1.4", - "drupal/styleguide": "^2.1", - "drupal/telephone_validation": "2.x-dev", - "drupal/token": "^1.15", + "drupal/paragraphs": "^1.0", + "drupal/select2": "^1.0 || ^2.0", + "drupal/smtp": "^1.0", + "drupal/styleguide": "^2.0", + "drupal/telephone_validation": "^2.0", + "drupal/token": "^1.0", "drupal/webform_access": "*", "drupal/webform_attachment": "*", "drupal/webform_clientside_validation": "*", @@ -5428,8 +5444,8 @@ "type": "drupal-module", "extra": { "drupal": { - "version": "6.3.0-beta7", - "datestamp": "1768489801", + "version": "6.3.0-beta8", + "datestamp": "1772723922", "security-coverage": { "status": "not-covered", "message": "Beta releases are not covered by Drupal security advisories." @@ -6126,16 +6142,16 @@ }, { "name": "guzzlehttp/psr7", - "version": "2.8.0", + "version": "2.8.1", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "21dc724a0583619cd1652f673303492272778051" + "reference": "718f1ee6a878be5290af3557aeda0c91278361d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/21dc724a0583619cd1652f673303492272778051", - "reference": "21dc724a0583619cd1652f673303492272778051", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/718f1ee6a878be5290af3557aeda0c91278361d9", + "reference": "718f1ee6a878be5290af3557aeda0c91278361d9", "shasum": "" }, "require": { @@ -6222,7 +6238,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.8.0" + "source": "https://github.com/guzzle/psr7/tree/2.8.1" }, "funding": [ { @@ -6238,7 +6254,7 @@ "type": "tidelift" } ], - "time": "2025-08-23T21:21:41+00:00" + "time": "2026-03-10T09:55:26+00:00" }, { "name": "halaxa/json-machine", @@ -6363,16 +6379,16 @@ }, { "name": "laravel/prompts", - "version": "v0.3.13", + "version": "v0.3.14", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "ed8c466571b37e977532fb2fd3c272c784d7050d" + "reference": "9f0e371244eedfe2ebeaa72c79c54bb5df6e0176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/ed8c466571b37e977532fb2fd3c272c784d7050d", - "reference": "ed8c466571b37e977532fb2fd3c272c784d7050d", + "url": "https://api.github.com/repos/laravel/prompts/zipball/9f0e371244eedfe2ebeaa72c79c54bb5df6e0176", + "reference": "9f0e371244eedfe2ebeaa72c79c54bb5df6e0176", "shasum": "" }, "require": { @@ -6416,9 +6432,9 @@ "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.3.13" + "source": "https://github.com/laravel/prompts/tree/v0.3.14" }, - "time": "2026-02-06T12:17:10+00:00" + "time": "2026-03-01T09:02:38+00:00" }, { "name": "league/container", @@ -8092,16 +8108,16 @@ }, { "name": "psy/psysh", - "version": "v0.12.20", + "version": "v0.12.21", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "19678eb6b952a03b8a1d96ecee9edba518bb0373" + "reference": "4821fab5b7cd8c49a673a9fd5754dc9162bb9e97" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/19678eb6b952a03b8a1d96ecee9edba518bb0373", - "reference": "19678eb6b952a03b8a1d96ecee9edba518bb0373", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/4821fab5b7cd8c49a673a9fd5754dc9162bb9e97", + "reference": "4821fab5b7cd8c49a673a9fd5754dc9162bb9e97", "shasum": "" }, "require": { @@ -8165,9 +8181,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.12.20" + "source": "https://github.com/bobthecow/psysh/tree/v0.12.21" }, - "time": "2026-02-11T15:05:28+00:00" + "time": "2026-03-06T21:21:28+00:00" }, { "name": "ralouphie/getallheaders", @@ -8427,16 +8443,16 @@ }, { "name": "symfony/console", - "version": "v7.4.4", + "version": "v7.4.7", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "41e38717ac1dd7a46b6bda7d6a82af2d98a78894" + "reference": "e1e6770440fb9c9b0cf725f81d1361ad1835329d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/41e38717ac1dd7a46b6bda7d6a82af2d98a78894", - "reference": "41e38717ac1dd7a46b6bda7d6a82af2d98a78894", + "url": "https://api.github.com/repos/symfony/console/zipball/e1e6770440fb9c9b0cf725f81d1361ad1835329d", + "reference": "e1e6770440fb9c9b0cf725f81d1361ad1835329d", "shasum": "" }, "require": { @@ -8501,7 +8517,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.4.4" + "source": "https://github.com/symfony/console/tree/v7.4.7" }, "funding": [ { @@ -8521,20 +8537,20 @@ "type": "tidelift" } ], - "time": "2026-01-13T11:36:38+00:00" + "time": "2026-03-06T14:06:20+00:00" }, { "name": "symfony/dependency-injection", - "version": "v7.4.5", + "version": "v7.4.7", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "76a02cddca45a5254479ad68f9fa274ead0a7ef2" + "reference": "0f651e58f4917fb0e2cd261ccbfe3d71e6e0f5db" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/76a02cddca45a5254479ad68f9fa274ead0a7ef2", - "reference": "76a02cddca45a5254479ad68f9fa274ead0a7ef2", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/0f651e58f4917fb0e2cd261ccbfe3d71e6e0f5db", + "reference": "0f651e58f4917fb0e2cd261ccbfe3d71e6e0f5db", "shasum": "" }, "require": { @@ -8585,7 +8601,7 @@ "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v7.4.5" + "source": "https://github.com/symfony/dependency-injection/tree/v7.4.7" }, "funding": [ { @@ -8605,7 +8621,7 @@ "type": "tidelift" } ], - "time": "2026-01-27T16:16:02+00:00" + "time": "2026-03-03T07:48:48+00:00" }, { "name": "symfony/deprecation-contracts", @@ -8919,16 +8935,16 @@ }, { "name": "symfony/filesystem", - "version": "v7.4.0", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "d551b38811096d0be9c4691d406991b47c0c630a" + "reference": "3ebc794fa5315e59fd122561623c2e2e4280538e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/d551b38811096d0be9c4691d406991b47c0c630a", - "reference": "d551b38811096d0be9c4691d406991b47c0c630a", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/3ebc794fa5315e59fd122561623c2e2e4280538e", + "reference": "3ebc794fa5315e59fd122561623c2e2e4280538e", "shasum": "" }, "require": { @@ -8965,7 +8981,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.4.0" + "source": "https://github.com/symfony/filesystem/tree/v7.4.6" }, "funding": [ { @@ -8985,20 +9001,20 @@ "type": "tidelift" } ], - "time": "2025-11-27T13:27:24+00:00" + "time": "2026-02-25T16:50:00+00:00" }, { "name": "symfony/finder", - "version": "v7.4.5", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "ad4daa7c38668dcb031e63bc99ea9bd42196a2cb" + "reference": "8655bf1076b7a3a346cb11413ffdabff50c7ffcf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/ad4daa7c38668dcb031e63bc99ea9bd42196a2cb", - "reference": "ad4daa7c38668dcb031e63bc99ea9bd42196a2cb", + "url": "https://api.github.com/repos/symfony/finder/zipball/8655bf1076b7a3a346cb11413ffdabff50c7ffcf", + "reference": "8655bf1076b7a3a346cb11413ffdabff50c7ffcf", "shasum": "" }, "require": { @@ -9033,7 +9049,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.4.5" + "source": "https://github.com/symfony/finder/tree/v7.4.6" }, "funding": [ { @@ -9053,20 +9069,20 @@ "type": "tidelift" } ], - "time": "2026-01-26T15:07:59+00:00" + "time": "2026-01-29T09:40:50+00:00" }, { "name": "symfony/http-client", - "version": "v6.4.33", + "version": "v6.4.34", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "50e5386dbef6361b6c2d9a2eb22954f8525b8921" + "reference": "0dc71f52e5d35bb045fd0f82b1a80c027971d551" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/50e5386dbef6361b6c2d9a2eb22954f8525b8921", - "reference": "50e5386dbef6361b6c2d9a2eb22954f8525b8921", + "url": "https://api.github.com/repos/symfony/http-client/zipball/0dc71f52e5d35bb045fd0f82b1a80c027971d551", + "reference": "0dc71f52e5d35bb045fd0f82b1a80c027971d551", "shasum": "" }, "require": { @@ -9131,7 +9147,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v6.4.33" + "source": "https://github.com/symfony/http-client/tree/v6.4.34" }, "funding": [ { @@ -9151,7 +9167,7 @@ "type": "tidelift" } ], - "time": "2026-01-27T15:43:08+00:00" + "time": "2026-02-18T07:27:25+00:00" }, { "name": "symfony/http-client-contracts", @@ -9233,16 +9249,16 @@ }, { "name": "symfony/http-foundation", - "version": "v7.4.5", + "version": "v7.4.7", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "446d0db2b1f21575f1284b74533e425096abdfb6" + "reference": "f94b3e7b7dafd40e666f0c9ff2084133bae41e81" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/446d0db2b1f21575f1284b74533e425096abdfb6", - "reference": "446d0db2b1f21575f1284b74533e425096abdfb6", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/f94b3e7b7dafd40e666f0c9ff2084133bae41e81", + "reference": "f94b3e7b7dafd40e666f0c9ff2084133bae41e81", "shasum": "" }, "require": { @@ -9291,7 +9307,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.4.5" + "source": "https://github.com/symfony/http-foundation/tree/v7.4.7" }, "funding": [ { @@ -9311,20 +9327,20 @@ "type": "tidelift" } ], - "time": "2026-01-27T16:16:02+00:00" + "time": "2026-03-06T13:15:18+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.4.5", + "version": "v7.4.7", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "229eda477017f92bd2ce7615d06222ec0c19e82a" + "reference": "3b3fcf386c809be990c922e10e4c620d6367cab1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/229eda477017f92bd2ce7615d06222ec0c19e82a", - "reference": "229eda477017f92bd2ce7615d06222ec0c19e82a", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/3b3fcf386c809be990c922e10e4c620d6367cab1", + "reference": "3b3fcf386c809be990c922e10e4c620d6367cab1", "shasum": "" }, "require": { @@ -9366,7 +9382,7 @@ "symfony/config": "^6.4|^7.0|^8.0", "symfony/console": "^6.4|^7.0|^8.0", "symfony/css-selector": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4.1|^7.0.1|^8.0", "symfony/dom-crawler": "^6.4|^7.0|^8.0", "symfony/expression-language": "^6.4|^7.0|^8.0", "symfony/finder": "^6.4|^7.0|^8.0", @@ -9410,7 +9426,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.4.5" + "source": "https://github.com/symfony/http-kernel/tree/v7.4.7" }, "funding": [ { @@ -9430,20 +9446,20 @@ "type": "tidelift" } ], - "time": "2026-01-28T10:33:42+00:00" + "time": "2026-03-06T16:33:18+00:00" }, { "name": "symfony/mailer", - "version": "v7.4.4", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "7b750074c40c694ceb34cb926d6dffee231c5cd6" + "reference": "b02726f39a20bc65e30364f5c750c4ddbf1f58e9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/7b750074c40c694ceb34cb926d6dffee231c5cd6", - "reference": "7b750074c40c694ceb34cb926d6dffee231c5cd6", + "url": "https://api.github.com/repos/symfony/mailer/zipball/b02726f39a20bc65e30364f5c750c4ddbf1f58e9", + "reference": "b02726f39a20bc65e30364f5c750c4ddbf1f58e9", "shasum": "" }, "require": { @@ -9494,7 +9510,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.4.4" + "source": "https://github.com/symfony/mailer/tree/v7.4.6" }, "funding": [ { @@ -9514,20 +9530,20 @@ "type": "tidelift" } ], - "time": "2026-01-08T08:25:11+00:00" + "time": "2026-02-25T16:50:00+00:00" }, { "name": "symfony/mime", - "version": "v7.4.5", + "version": "v7.4.7", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "b18c7e6e9eee1e19958138df10412f3c4c316148" + "reference": "da5ab4fde3f6c88ab06e96185b9922f48b677cd1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/b18c7e6e9eee1e19958138df10412f3c4c316148", - "reference": "b18c7e6e9eee1e19958138df10412f3c4c316148", + "url": "https://api.github.com/repos/symfony/mime/zipball/da5ab4fde3f6c88ab06e96185b9922f48b677cd1", + "reference": "da5ab4fde3f6c88ab06e96185b9922f48b677cd1", "shasum": "" }, "require": { @@ -9538,7 +9554,7 @@ }, "conflict": { "egulias/email-validator": "~3.0.0", - "phpdocumentor/reflection-docblock": "<5.2|>=6", + "phpdocumentor/reflection-docblock": "<5.2|>=7", "phpdocumentor/type-resolver": "<1.5.1", "symfony/mailer": "<6.4", "symfony/serializer": "<6.4.3|>7.0,<7.0.3" @@ -9546,7 +9562,7 @@ "require-dev": { "egulias/email-validator": "^2.1.10|^3.1|^4", "league/html-to-markdown": "^5.0", - "phpdocumentor/reflection-docblock": "^5.2", + "phpdocumentor/reflection-docblock": "^5.2|^6.0", "symfony/dependency-injection": "^6.4|^7.0|^8.0", "symfony/process": "^6.4|^7.0|^8.0", "symfony/property-access": "^6.4|^7.0|^8.0", @@ -9583,7 +9599,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.4.5" + "source": "https://github.com/symfony/mime/tree/v7.4.7" }, "funding": [ { @@ -9603,7 +9619,7 @@ "type": "tidelift" } ], - "time": "2026-01-27T08:59:58+00:00" + "time": "2026-03-05T15:24:09+00:00" }, { "name": "symfony/polyfill-ctype", @@ -10586,16 +10602,16 @@ }, { "name": "symfony/routing", - "version": "v7.4.4", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "0798827fe2c79caeed41d70b680c2c3507d10147" + "reference": "238d749c56b804b31a9bf3e26519d93b65a60938" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/0798827fe2c79caeed41d70b680c2c3507d10147", - "reference": "0798827fe2c79caeed41d70b680c2c3507d10147", + "url": "https://api.github.com/repos/symfony/routing/zipball/238d749c56b804b31a9bf3e26519d93b65a60938", + "reference": "238d749c56b804b31a9bf3e26519d93b65a60938", "shasum": "" }, "require": { @@ -10647,7 +10663,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.4.4" + "source": "https://github.com/symfony/routing/tree/v7.4.6" }, "funding": [ { @@ -10667,20 +10683,20 @@ "type": "tidelift" } ], - "time": "2026-01-12T12:19:02+00:00" + "time": "2026-02-25T16:50:00+00:00" }, { "name": "symfony/serializer", - "version": "v7.4.5", + "version": "v7.4.7", "source": { "type": "git", "url": "https://github.com/symfony/serializer.git", - "reference": "480cd1237c98ab1219c20945b92c9d4480a44f47" + "reference": "bd395bbc6fabd136a48e1a6f91b09f88b5050b0b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/serializer/zipball/480cd1237c98ab1219c20945b92c9d4480a44f47", - "reference": "480cd1237c98ab1219c20945b92c9d4480a44f47", + "url": "https://api.github.com/repos/symfony/serializer/zipball/bd395bbc6fabd136a48e1a6f91b09f88b5050b0b", + "reference": "bd395bbc6fabd136a48e1a6f91b09f88b5050b0b", "shasum": "" }, "require": { @@ -10690,17 +10706,18 @@ "symfony/polyfill-php84": "^1.30" }, "conflict": { - "phpdocumentor/reflection-docblock": "<5.2|>=6", + "phpdocumentor/reflection-docblock": "<5.2|>=7", "phpdocumentor/type-resolver": "<1.5.1", "symfony/dependency-injection": "<6.4", "symfony/property-access": "<6.4", "symfony/property-info": "<6.4", + "symfony/type-info": "<7.2.5", "symfony/uid": "<6.4", "symfony/validator": "<6.4", "symfony/yaml": "<6.4" }, "require-dev": { - "phpdocumentor/reflection-docblock": "^5.2", + "phpdocumentor/reflection-docblock": "^5.2|^6.0", "phpstan/phpdoc-parser": "^1.0|^2.0", "seld/jsonlint": "^1.10", "symfony/cache": "^6.4|^7.0|^8.0", @@ -10717,7 +10734,7 @@ "symfony/property-access": "^6.4|^7.0|^8.0", "symfony/property-info": "^6.4|^7.0|^8.0", "symfony/translation-contracts": "^2.5|^3", - "symfony/type-info": "^7.1.8|^8.0", + "symfony/type-info": "^7.2.5|^8.0", "symfony/uid": "^6.4|^7.0|^8.0", "symfony/validator": "^6.4|^7.0|^8.0", "symfony/var-dumper": "^6.4|^7.0|^8.0", @@ -10750,7 +10767,7 @@ "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/serializer/tree/v7.4.5" + "source": "https://github.com/symfony/serializer/tree/v7.4.7" }, "funding": [ { @@ -10770,7 +10787,7 @@ "type": "tidelift" } ], - "time": "2026-01-27T08:59:58+00:00" + "time": "2026-03-06T13:15:18+00:00" }, { "name": "symfony/service-contracts", @@ -10861,16 +10878,16 @@ }, { "name": "symfony/string", - "version": "v7.4.4", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "1c4b10461bf2ec27537b5f36105337262f5f5d6f" + "reference": "9f209231affa85aa930a5e46e6eb03381424b30b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/1c4b10461bf2ec27537b5f36105337262f5f5d6f", - "reference": "1c4b10461bf2ec27537b5f36105337262f5f5d6f", + "url": "https://api.github.com/repos/symfony/string/zipball/9f209231affa85aa930a5e46e6eb03381424b30b", + "reference": "9f209231affa85aa930a5e46e6eb03381424b30b", "shasum": "" }, "require": { @@ -10928,7 +10945,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.4.4" + "source": "https://github.com/symfony/string/tree/v7.4.6" }, "funding": [ { @@ -10948,7 +10965,7 @@ "type": "tidelift" } ], - "time": "2026-01-12T10:54:30+00:00" + "time": "2026-02-09T09:33:46+00:00" }, { "name": "symfony/translation-contracts", @@ -11034,16 +11051,16 @@ }, { "name": "symfony/validator", - "version": "v7.4.5", + "version": "v7.4.7", "source": { "type": "git", "url": "https://github.com/symfony/validator.git", - "reference": "fcec92c40df1c93507857da08226005573b655c6" + "reference": "3a1a460a9f8c5e5611e15c52c4baa5a62fa3c203" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/fcec92c40df1c93507857da08226005573b655c6", - "reference": "fcec92c40df1c93507857da08226005573b655c6", + "url": "https://api.github.com/repos/symfony/validator/zipball/3a1a460a9f8c5e5611e15c52c4baa5a62fa3c203", + "reference": "3a1a460a9f8c5e5611e15c52c4baa5a62fa3c203", "shasum": "" }, "require": { @@ -11114,7 +11131,7 @@ "description": "Provides tools to validate values", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/validator/tree/v7.4.5" + "source": "https://github.com/symfony/validator/tree/v7.4.7" }, "funding": [ { @@ -11134,20 +11151,20 @@ "type": "tidelift" } ], - "time": "2026-01-27T08:59:58+00:00" + "time": "2026-03-06T11:10:17+00:00" }, { "name": "symfony/var-dumper", - "version": "v7.4.4", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "0e4769b46a0c3c62390d124635ce59f66874b282" + "reference": "045321c440ac18347b136c63d2e9bf28a2dc0291" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/0e4769b46a0c3c62390d124635ce59f66874b282", - "reference": "0e4769b46a0c3c62390d124635ce59f66874b282", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/045321c440ac18347b136c63d2e9bf28a2dc0291", + "reference": "045321c440ac18347b136c63d2e9bf28a2dc0291", "shasum": "" }, "require": { @@ -11201,7 +11218,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.4.4" + "source": "https://github.com/symfony/var-dumper/tree/v7.4.6" }, "funding": [ { @@ -11221,7 +11238,7 @@ "type": "tidelift" } ], - "time": "2026-01-01T22:13:48+00:00" + "time": "2026-02-15T10:53:20+00:00" }, { "name": "symfony/var-exporter", @@ -11306,16 +11323,16 @@ }, { "name": "symfony/yaml", - "version": "v7.4.1", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "24dd4de28d2e3988b311751ac49e684d783e2345" + "reference": "58751048de17bae71c5aa0d13cb19d79bca26391" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/24dd4de28d2e3988b311751ac49e684d783e2345", - "reference": "24dd4de28d2e3988b311751ac49e684d783e2345", + "url": "https://api.github.com/repos/symfony/yaml/zipball/58751048de17bae71c5aa0d13cb19d79bca26391", + "reference": "58751048de17bae71c5aa0d13cb19d79bca26391", "shasum": "" }, "require": { @@ -11358,7 +11375,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.4.1" + "source": "https://github.com/symfony/yaml/tree/v7.4.6" }, "funding": [ { @@ -11378,7 +11395,7 @@ "type": "tidelift" } ], - "time": "2025-12-04T18:11:45+00:00" + "time": "2026-02-09T09:33:46+00:00" }, { "name": "twig/twig", @@ -12581,16 +12598,16 @@ }, { "name": "drevops/behat-steps", - "version": "3.5.0", + "version": "3.5.2", "source": { "type": "git", "url": "https://github.com/drevops/behat-steps.git", - "reference": "c221cc024f4076a92be80163845aefbd811271b9" + "reference": "ca461bb310f03f0cc270a7ecda4926a1ebaa1c49" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/drevops/behat-steps/zipball/c221cc024f4076a92be80163845aefbd811271b9", - "reference": "c221cc024f4076a92be80163845aefbd811271b9", + "url": "https://api.github.com/repos/drevops/behat-steps/zipball/ca461bb310f03f0cc270a7ecda4926a1ebaa1c49", + "reference": "ca461bb310f03f0cc270a7ecda4926a1ebaa1c49", "shasum": "" }, "require": { @@ -12661,7 +12678,7 @@ "type": "patreon" } ], - "time": "2026-01-22T22:14:46+00:00" + "time": "2026-03-03T15:05:05+00:00" }, { "name": "drevops/phpcs-standard", @@ -12795,23 +12812,23 @@ }, { "name": "drupal/drupal-driver", - "version": "v2.3.0", + "version": "v2.4.0", "source": { "type": "git", "url": "https://github.com/jhedstrom/DrupalDriver.git", - "reference": "52835be563891d74656fbaaf045dacc43ac5f701" + "reference": "a240ad259295db7730ee3c32ecf514281900df25" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jhedstrom/DrupalDriver/zipball/52835be563891d74656fbaaf045dacc43ac5f701", - "reference": "52835be563891d74656fbaaf045dacc43ac5f701", + "url": "https://api.github.com/repos/jhedstrom/DrupalDriver/zipball/a240ad259295db7730ee3c32ecf514281900df25", + "reference": "a240ad259295db7730ee3c32ecf514281900df25", "shasum": "" }, "require": { "drupal/core-utility": "^8.4 || ^9 || ^10 || ^11", "php": ">=7.4", - "symfony/dependency-injection": "~2.6 || ~3.0 || ~4.4 || ^6 || ^7.1", - "symfony/process": "~2.5 || ~3.0 || ~4.4 || ^6 || ^7.1" + "symfony/dependency-injection": "^2.6 || ^3.0 || ^4.4 || ^6 || ^7.1", + "symfony/process": "^2.5 || ^3.0 || ^4.4 || ^6 || ^7" }, "conflict": { "drupal/core": ">=8.0 <9.3" @@ -12824,15 +12841,19 @@ "drupal/core-recommended": "^8.4 || ^9 || ^10 || ^11", "drupal/mailsystem": "^4.4 || 4.x-dev", "drush-ops/behat-drush-endpoint": "*", + "ergebnis/composer-normalize": "^2.50", "mockery/mockery": "^1.5", - "palantirnet/drupal-rector": "^0.13", + "palantirnet/drupal-rector": "^0.20", "php-parallel-lint/php-parallel-lint": "^1.0", - "phpspec/phpspec": "~2.0 || ~4.0 || ~6.1 || dev-main", - "phpunit/phpunit": "~6.0 || ~7.0 || ^9 || ^10", + "phpspec/phpspec": "^2.0 || ^4.0 || ^6.1 || ^7.5 || dev-main", + "phpunit/phpunit": "^6.0 || ^7.0 || ^9 || ^10", "symfony/phpunit-bridge": "^6.1" }, "type": "library", "extra": { + "branch-alias": { + "dev-master": "2.3.x-dev" + }, "drupal-scaffold": { "locations": { "web-root": "drupal/" @@ -12872,7 +12893,7 @@ ], "support": { "issues": "https://github.com/jhedstrom/DrupalDriver/issues", - "source": "https://github.com/jhedstrom/DrupalDriver/tree/v2.3.0" + "source": "https://github.com/jhedstrom/DrupalDriver/tree/v2.4.0" }, "funding": [ { @@ -12880,50 +12901,123 @@ "type": "github" } ], - "time": "2024-10-21T16:17:44+00:00" + "time": "2026-03-02T23:56:34+00:00" }, { "name": "drupal/drupal-extension", - "version": "v5.1.0", + "version": "v5.2.1", "source": { "type": "git", "url": "https://github.com/jhedstrom/drupalextension.git", - "reference": "454a3ce03f59b26dc3f0b739d778afebe0e35cb9" + "reference": "1ffcce1ddd253a9ed3325b56ffb0a675f3ed948c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jhedstrom/drupalextension/zipball/454a3ce03f59b26dc3f0b739d778afebe0e35cb9", - "reference": "454a3ce03f59b26dc3f0b739d778afebe0e35cb9", + "url": "https://api.github.com/repos/jhedstrom/drupalextension/zipball/1ffcce1ddd253a9ed3325b56ffb0a675f3ed948c", + "reference": "1ffcce1ddd253a9ed3325b56ffb0a675f3ed948c", "shasum": "" }, "require": { - "behat/behat": "~3.2", + "behat/behat": "^3.22", + "behat/gherkin": "^4.13", + "behat/mink": "^1.12", "behat/mink-browserkit-driver": "^2.1.0", - "behat/mink-selenium2-driver": "~1.1", - "drupal/drupal-driver": "^2.2.1 || dev-master", + "drupal/drupal-driver": "^2.4", "friends-of-behat/mink-extension": "^2.7.1", - "symfony/http-client": "~4.4 || ^5 || ^6", - "webflo/drupal-finder": "^1.2" + "lullabot/mink-selenium2-driver": "^1.7", + "php": ">=8.2", + "symfony/css-selector": "^6.4.3 || ^7", + "symfony/dom-crawler": "^6.4.3 || ^7", + "symfony/http-client": "^6.4.3 || ^7", + "webflo/drupal-finder": "^1.3.1" }, "require-dev": { - "composer/installers": "^2", - "drupal/coder": "^8.3", - "drupal/core": "^10", - "drupal/core-composer-scaffold": "^10", - "drush/drush": "^11.6.0 || ^12.4 || ^13", - "php-parallel-lint/php-parallel-lint": "^1.3", - "phpspec/phpspec": "^4.0 || ^6.0 || ^7.0" + "composer/installers": "^2.3", + "dantleech/gherkin-lint": "^0.2.3", + "drevops/behat-screenshot": "^2.2", + "drevops/phpcs-standard": "^0.6.2", + "drupal/coder": "^8.3.27", + "drupal/core": "^11", + "drupal/core-composer-scaffold": "^11", + "drush/drush": "^12.5.2 || ^13.7", + "ergebnis/composer-normalize": "^2.50", + "grasmash/yaml-cli": "^3.2.1", + "guzzlehttp/guzzle": "^7.8.2", + "guzzlehttp/promises": "^2.0.3", + "guzzlehttp/psr7": "^2.6.3", + "justinrainbow/json-schema": "^5.3", + "league/container": "^4.2", + "masterminds/html5": "^2.8", + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpspec/phpspec": "^7.0 || ^8.0", + "phpstan/phpstan": "^2.1", + "rector/rector": "^2.0", + "symfony/browser-kit": "^6.4.3 || ^7", + "symfony/config": "^6.4.3 || ^7", + "symfony/console": "^6.4.3 || ^7", + "symfony/dependency-injection": "^6.4.3 || ^7", + "symfony/event-dispatcher": "^6.4.3 || ^7", + "symfony/event-dispatcher-contracts": "^3.4.2", + "symfony/filesystem": "^6.4.3 || ^7", + "symfony/http-kernel": "^6.4.3 || ^7", + "symfony/mime": "^6.4.3 || ^7", + "symfony/routing": "^6.4.3 || ^7", + "symfony/translation-contracts": "^3.4.2", + "symfony/var-exporter": "^6.4.3 || ^7", + "symfony/yaml": "^6.4.3 || ^7", + "webmozart/assert": "^2" }, "type": "behat-extension", "extra": { + "branch-alias": { + "dev-master": "5.1.x-dev" + }, "drupal-scaffold": { "locations": { - "web-root": "drupal/" + "web-root": "build/web" + }, + "file-mapping": { + "[web-root]/.htaccess": false, + "[web-root]/README.md": false, + "[web-root]/.csslintrc": false, + "[web-root]/README.txt": false, + "[web-root]/robots.txt": false, + "[web-root]/update.php": false, + "[web-root]/web.config": false, + "[web-root]/INSTALL.txt": false, + "[web-root]/.editorconfig": false, + "[web-root]/.eslintignore": false, + "[web-root]/.eslintrc.json": false, + "[web-root]/.gitattributes": false, + "[web-root]/.ht.router.php": false, + "[project-root]/.editorconfig": false, + "[web-root]/example.gitignore": false, + "[project-root]/.gitattributes": false, + "[web-root]/sites/example.sites.php": false, + "[web-root]/sites/example.settings.local.php": false } }, "installer-paths": { - "drupal/core": [ + "build/web/core": [ "type:drupal-core" + ], + "build/recipes/{$name}": [ + "type:drupal-recipe" + ], + "build/web/libraries/{$name}": [ + "type:drupal-library" + ], + "build/web/themes/contrib/{$name}": [ + "type:drupal-theme" + ], + "build/web/modules/contrib/{$name}": [ + "type:drupal-module" + ], + "build/web/profiles/contrib/{$name}": [ + "type:drupal-profile" + ], + "build/drush/Commands/contrib/{$name}": [ + "type:drupal-drush" ] } }, @@ -12962,7 +13056,7 @@ ], "support": { "issues": "https://github.com/jhedstrom/drupalextension/issues", - "source": "https://github.com/jhedstrom/drupalextension/tree/v5.1.0" + "source": "https://github.com/jhedstrom/drupalextension/tree/v5.2.1" }, "funding": [ { @@ -12970,7 +13064,7 @@ "type": "github" } ], - "time": "2024-10-21T21:00:55+00:00" + "time": "2026-03-08T05:27:14+00:00" }, { "name": "ergebnis/composer-normalize", @@ -13827,24 +13921,24 @@ }, { "name": "mglaman/phpstan-drupal", - "version": "2.0.10", + "version": "2.0.11", "source": { "type": "git", "url": "https://github.com/mglaman/phpstan-drupal.git", - "reference": "2574aacbacede545490017df4387361698b67fef" + "reference": "4e1caa0940cfc0837e1890b489a7d95c629e2b92" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mglaman/phpstan-drupal/zipball/2574aacbacede545490017df4387361698b67fef", - "reference": "2574aacbacede545490017df4387361698b67fef", + "url": "https://api.github.com/repos/mglaman/phpstan-drupal/zipball/4e1caa0940cfc0837e1890b489a7d95c629e2b92", + "reference": "4e1caa0940cfc0837e1890b489a7d95c629e2b92", "shasum": "" }, "require": { "php": "^8.1", "phpstan/phpstan": "^2.1", "phpstan/phpstan-deprecation-rules": "^2.0", - "symfony/finder": "^6.2 || ^7.0", - "symfony/yaml": "^6.2 || ^7.0", + "symfony/finder": "^6.2 || ^7.0 || ^8.0", + "symfony/yaml": "^6.2 || ^7.0 || ^8.0", "webflo/drupal-finder": "^1.3.1" }, "require-dev": { @@ -13857,7 +13951,7 @@ "phpunit/phpunit": "^9 || ^10 || ^11", "slevomat/coding-standard": "^8.6", "squizlabs/php_codesniffer": "^3.7", - "symfony/phpunit-bridge": "^6.2 || ^7.0" + "symfony/phpunit-bridge": "^6.2 || ^7.0 || ^8.0" }, "suggest": { "jangregor/phpstan-prophecy": "Provides a prophecy/prophecy extension for phpstan/phpstan.", @@ -13908,7 +14002,7 @@ "description": "Drupal extension and rules for PHPStan", "support": { "issues": "https://github.com/mglaman/phpstan-drupal/issues", - "source": "https://github.com/mglaman/phpstan-drupal/tree/2.0.10" + "source": "https://github.com/mglaman/phpstan-drupal/tree/2.0.11" }, "funding": [ { @@ -13924,7 +14018,7 @@ "type": "tidelift" } ], - "time": "2025-10-22T17:33:43+00:00" + "time": "2026-02-25T16:38:53+00:00" }, { "name": "mikey179/vfsstream", @@ -14478,16 +14572,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.6.6", + "version": "6.0.2", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "5cee1d3dfc2d2aa6599834520911d246f656bcb8" + "reference": "897b5986ece6b4f9d8413fea345c7d49c757d6bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/5cee1d3dfc2d2aa6599834520911d246f656bcb8", - "reference": "5cee1d3dfc2d2aa6599834520911d246f656bcb8", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/897b5986ece6b4f9d8413fea345c7d49c757d6bf", + "reference": "897b5986ece6b4f9d8413fea345c7d49c757d6bf", "shasum": "" }, "require": { @@ -14495,8 +14589,8 @@ "ext-filter": "*", "php": "^7.4 || ^8.0", "phpdocumentor/reflection-common": "^2.2", - "phpdocumentor/type-resolver": "^1.7", - "phpstan/phpdoc-parser": "^1.7|^2.0", + "phpdocumentor/type-resolver": "^2.0", + "phpstan/phpdoc-parser": "^2.0", "webmozart/assert": "^1.9.1 || ^2" }, "require-dev": { @@ -14506,7 +14600,8 @@ "phpstan/phpstan-mockery": "^1.1", "phpstan/phpstan-webmozart-assert": "^1.2", "phpunit/phpunit": "^9.5", - "psalm/phar": "^5.26" + "psalm/phar": "^5.26", + "shipmonk/dead-code-detector": "^0.5.1" }, "type": "library", "extra": { @@ -14536,44 +14631,44 @@ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.6" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/6.0.2" }, - "time": "2025-12-22T21:13:58+00:00" + "time": "2026-03-01T18:43:49+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "1.12.0", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195" + "reference": "327a05bbee54120d4786a0dc67aad30226ad4cf9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/92a98ada2b93d9b201a613cb5a33584dde25f195", - "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/327a05bbee54120d4786a0dc67aad30226ad4cf9", + "reference": "327a05bbee54120d4786a0dc67aad30226ad4cf9", "shasum": "" }, "require": { "doctrine/deprecations": "^1.0", - "php": "^7.3 || ^8.0", + "php": "^7.4 || ^8.0", "phpdocumentor/reflection-common": "^2.0", - "phpstan/phpdoc-parser": "^1.18|^2.0" + "phpstan/phpdoc-parser": "^2.0" }, "require-dev": { "ext-tokenizer": "*", "phpbench/phpbench": "^1.2", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-phpunit": "^1.1", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-phpunit": "^2.0", "phpunit/phpunit": "^9.5", - "rector/rector": "^0.13.9", - "vimeo/psalm": "^4.25" + "psalm/phar": "^4" }, "type": "library", "extra": { "branch-alias": { - "dev-1.x": "1.x-dev" + "dev-1.x": "1.x-dev", + "dev-2.x": "2.x-dev" } }, "autoload": { @@ -14594,28 +14689,28 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.12.0" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/2.0.0" }, - "time": "2025-11-21T15:09:14+00:00" + "time": "2026-01-06T21:53:42+00:00" }, { "name": "phpspec/prophecy", - "version": "v1.25.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "7ab965042096282307992f1b9abff020095757f0" + "reference": "0da07c10d5fe64cd0c748f0523b47599400f2ed1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/7ab965042096282307992f1b9abff020095757f0", - "reference": "7ab965042096282307992f1b9abff020095757f0", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/0da07c10d5fe64cd0c748f0523b47599400f2ed1", + "reference": "0da07c10d5fe64cd0c748f0523b47599400f2ed1", "shasum": "" }, "require": { "doctrine/instantiator": "^1.2 || ^2.0", "php": "8.2.* || 8.3.* || 8.4.* || 8.5.*", - "phpdocumentor/reflection-docblock": "^5.2", + "phpdocumentor/reflection-docblock": "^5.2 || ^6.0", "sebastian/comparator": "^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0", "sebastian/recursion-context": "^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0", "symfony/deprecation-contracts": "^2.5 || ^3.1" @@ -14665,9 +14760,9 @@ ], "support": { "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/v1.25.0" + "source": "https://github.com/phpspec/prophecy/tree/v1.26.0" }, - "time": "2026-02-09T11:58:00+00:00" + "time": "2026-02-24T15:40:48+00:00" }, { "name": "phpspec/prophecy-phpunit", @@ -14821,11 +14916,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.39", + "version": "2.1.40", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/c6f73a2af4cbcd99c931d0fb8f08548cc0fa8224", - "reference": "c6f73a2af4cbcd99c931d0fb8f08548cc0fa8224", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9b2c7aeb83a75d8680ea5e7c9b7fca88052b766b", + "reference": "9b2c7aeb83a75d8680ea5e7c9b7fca88052b766b", "shasum": "" }, "require": { @@ -14870,7 +14965,7 @@ "type": "github" } ], - "time": "2026-02-11T14:48:56+00:00" + "time": "2026-02-23T15:04:35+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", @@ -15439,16 +15534,16 @@ }, { "name": "rector/rector", - "version": "2.3.7", + "version": "2.3.8", "source": { "type": "git", "url": "https://github.com/rectorphp/rector.git", - "reference": "9c46ad17f57963932c9788fd1b0f1d07ff450370" + "reference": "bbd37aedd8df749916cffa2a947cfc4714d1ba2c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/9c46ad17f57963932c9788fd1b0f1d07ff450370", - "reference": "9c46ad17f57963932c9788fd1b0f1d07ff450370", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/bbd37aedd8df749916cffa2a947cfc4714d1ba2c", + "reference": "bbd37aedd8df749916cffa2a947cfc4714d1ba2c", "shasum": "" }, "require": { @@ -15487,7 +15582,7 @@ ], "support": { "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/2.3.7" + "source": "https://github.com/rectorphp/rector/tree/2.3.8" }, "funding": [ { @@ -15495,7 +15590,7 @@ "type": "github" } ], - "time": "2026-02-19T14:44:16+00:00" + "time": "2026-02-22T09:45:50+00:00" }, { "name": "sebastian/cli-parser", @@ -16474,32 +16569,32 @@ }, { "name": "slevomat/coding-standard", - "version": "8.27.1", + "version": "8.28.0", "source": { "type": "git", "url": "https://github.com/slevomat/coding-standard.git", - "reference": "29bdaee8b65e7ed2b8e702b01852edba8bae1769" + "reference": "0cd4b30cc1037eca54091c188d260d570e61770c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/29bdaee8b65e7ed2b8e702b01852edba8bae1769", - "reference": "29bdaee8b65e7ed2b8e702b01852edba8bae1769", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/0cd4b30cc1037eca54091c188d260d570e61770c", + "reference": "0cd4b30cc1037eca54091c188d260d570e61770c", "shasum": "" }, "require": { "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || ^1.2.0", "php": "^7.4 || ^8.0", - "phpstan/phpdoc-parser": "^2.3.1", + "phpstan/phpdoc-parser": "^2.3.2", "squizlabs/php_codesniffer": "^4.0.1" }, "require-dev": { - "phing/phing": "3.0.1|3.1.1", + "phing/phing": "3.0.1|3.1.2", "php-parallel-lint/php-parallel-lint": "1.4.0", - "phpstan/phpstan": "2.1.37", - "phpstan/phpstan-deprecation-rules": "2.0.3", - "phpstan/phpstan-phpunit": "2.0.12", - "phpstan/phpstan-strict-rules": "2.0.7", - "phpunit/phpunit": "9.6.31|10.5.60|11.4.4|11.5.49|12.5.7" + "phpstan/phpstan": "2.1.40", + "phpstan/phpstan-deprecation-rules": "2.0.4", + "phpstan/phpstan-phpunit": "2.0.16", + "phpstan/phpstan-strict-rules": "2.0.10", + "phpunit/phpunit": "9.6.34|10.5.63|11.4.4|11.5.50|12.5.14" }, "type": "phpcodesniffer-standard", "extra": { @@ -16523,7 +16618,7 @@ ], "support": { "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/8.27.1" + "source": "https://github.com/slevomat/coding-standard/tree/8.28.0" }, "funding": [ { @@ -16535,7 +16630,7 @@ "type": "tidelift" } ], - "time": "2026-01-25T15:57:07+00:00" + "time": "2026-02-23T21:35:24+00:00" }, { "name": "squizlabs/php_codesniffer", @@ -16743,16 +16838,16 @@ }, { "name": "symfony/config", - "version": "v7.4.4", + "version": "v7.4.7", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "4275b53b8ab0cf37f48bf273dc2285c8178efdfb" + "reference": "6c17162555bfb58957a55bb0e43e00035b6ae3d5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/4275b53b8ab0cf37f48bf273dc2285c8178efdfb", - "reference": "4275b53b8ab0cf37f48bf273dc2285c8178efdfb", + "url": "https://api.github.com/repos/symfony/config/zipball/6c17162555bfb58957a55bb0e43e00035b6ae3d5", + "reference": "6c17162555bfb58957a55bb0e43e00035b6ae3d5", "shasum": "" }, "require": { @@ -16798,7 +16893,7 @@ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v7.4.4" + "source": "https://github.com/symfony/config/tree/v7.4.7" }, "funding": [ { @@ -16818,20 +16913,20 @@ "type": "tidelift" } ], - "time": "2026-01-13T11:36:38+00:00" + "time": "2026-03-06T10:41:14+00:00" }, { "name": "symfony/css-selector", - "version": "v7.4.0", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "ab862f478513e7ca2fe9ec117a6f01a8da6e1135" + "reference": "2e7c52c647b406e2107dd867db424a4dbac91864" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/ab862f478513e7ca2fe9ec117a6f01a8da6e1135", - "reference": "ab862f478513e7ca2fe9ec117a6f01a8da6e1135", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/2e7c52c647b406e2107dd867db424a4dbac91864", + "reference": "2e7c52c647b406e2107dd867db424a4dbac91864", "shasum": "" }, "require": { @@ -16867,7 +16962,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v7.4.0" + "source": "https://github.com/symfony/css-selector/tree/v7.4.6" }, "funding": [ { @@ -16887,20 +16982,20 @@ "type": "tidelift" } ], - "time": "2025-10-30T13:39:42+00:00" + "time": "2026-02-17T07:53:42+00:00" }, { "name": "symfony/dom-crawler", - "version": "v7.4.4", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "71fd6a82fc357c8b5de22f78b228acfc43dee965" + "reference": "487ba8fa43da9a8e6503fe939b45ecd96875410e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/71fd6a82fc357c8b5de22f78b228acfc43dee965", - "reference": "71fd6a82fc357c8b5de22f78b228acfc43dee965", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/487ba8fa43da9a8e6503fe939b45ecd96875410e", + "reference": "487ba8fa43da9a8e6503fe939b45ecd96875410e", "shasum": "" }, "require": { @@ -16939,7 +17034,7 @@ "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v7.4.4" + "source": "https://github.com/symfony/dom-crawler/tree/v7.4.6" }, "funding": [ { @@ -16959,20 +17054,20 @@ "type": "tidelift" } ], - "time": "2026-01-05T08:47:25+00:00" + "time": "2026-02-17T07:53:42+00:00" }, { "name": "symfony/translation", - "version": "v7.4.4", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "bfde13711f53f549e73b06d27b35a55207528877" + "reference": "1888cf064399868af3784b9e043240f1d89d25ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/bfde13711f53f549e73b06d27b35a55207528877", - "reference": "bfde13711f53f549e73b06d27b35a55207528877", + "url": "https://api.github.com/repos/symfony/translation/zipball/1888cf064399868af3784b9e043240f1d89d25ce", + "reference": "1888cf064399868af3784b9e043240f1d89d25ce", "shasum": "" }, "require": { @@ -17039,7 +17134,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.4.4" + "source": "https://github.com/symfony/translation/tree/v7.4.6" }, "funding": [ { @@ -17059,7 +17154,7 @@ "type": "tidelift" } ], - "time": "2026-01-13T10:40:19+00:00" + "time": "2026-02-17T07:53:42+00:00" }, { "name": "theseer/tokenizer", @@ -17113,22 +17208,22 @@ }, { "name": "vincentlanglet/twig-cs-fixer", - "version": "3.13.0", + "version": "3.14.0", "source": { "type": "git", "url": "https://github.com/VincentLanglet/Twig-CS-Fixer.git", - "reference": "511e002e1e18203d26b173763ade29cbfd297750" + "reference": "599f110f192c31af5deb5736d6c1a970afdf51f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/VincentLanglet/Twig-CS-Fixer/zipball/511e002e1e18203d26b173763ade29cbfd297750", - "reference": "511e002e1e18203d26b173763ade29cbfd297750", + "url": "https://api.github.com/repos/VincentLanglet/Twig-CS-Fixer/zipball/599f110f192c31af5deb5736d6c1a970afdf51f3", + "reference": "599f110f192c31af5deb5736d6c1a970afdf51f3", "shasum": "" }, "require": { "composer-runtime-api": "^2.0.0", "ext-ctype": "*", - "php": ">=8.0", + "php": ">=8.1", "symfony/console": "^5.4.9 || ^6.4 || ^7.0 || ^8.0", "symfony/filesystem": "^5.4 || ^6.4 || ^7.0 || ^8.0", "symfony/finder": "^5.4 || ^6.4 || ^7.0 || ^8.0", @@ -17143,6 +17238,7 @@ "friendsofphp/php-cs-fixer": "^3.13.0", "infection/infection": "^0.26.16 || ^0.32.0", "phpstan/phpstan": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0", "phpstan/phpstan-phpunit": "^2.0", "phpstan/phpstan-strict-rules": "^2.0", "phpstan/phpstan-symfony": "^2.0", @@ -17177,7 +17273,7 @@ "homepage": "https://github.com/VincentLanglet/Twig-CS-Fixer", "support": { "issues": "https://github.com/VincentLanglet/Twig-CS-Fixer/issues", - "source": "https://github.com/VincentLanglet/Twig-CS-Fixer/tree/3.13.0" + "source": "https://github.com/VincentLanglet/Twig-CS-Fixer/tree/3.14.0" }, "funding": [ { @@ -17185,20 +17281,20 @@ "type": "github" } ], - "time": "2026-01-24T00:18:59+00:00" + "time": "2026-02-23T13:21:35+00:00" }, { "name": "webmozart/assert", - "version": "2.1.5", + "version": "2.1.6", "source": { "type": "git", "url": "https://github.com/webmozarts/assert.git", - "reference": "79155f94852fa27e2f73b459f6503f5e87e2c188" + "reference": "ff31ad6efc62e66e518fbab1cde3453d389bcdc8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/79155f94852fa27e2f73b459f6503f5e87e2c188", - "reference": "79155f94852fa27e2f73b459f6503f5e87e2c188", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/ff31ad6efc62e66e518fbab1cde3453d389bcdc8", + "reference": "ff31ad6efc62e66e518fbab1cde3453d389bcdc8", "shasum": "" }, "require": { @@ -17245,9 +17341,9 @@ ], "support": { "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/2.1.5" + "source": "https://github.com/webmozarts/assert/tree/2.1.6" }, - "time": "2026-02-18T14:09:36+00:00" + "time": "2026-02-27T10:28:38+00:00" } ], "aliases": [], diff --git a/phpunit.xml b/phpunit.xml index 9efac31..62f6855 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -61,7 +61,7 @@ - + diff --git a/rector.php b/rector.php index fd93180..d53dbdf 100644 --- a/rector.php +++ b/rector.php @@ -36,6 +36,7 @@ use Rector\Php80\Rector\Switch_\ChangeSwitchToMatchRector; use Rector\Php81\Rector\Array_\ArrayToFirstClassCallableRector; use Rector\Php83\Rector\ClassMethod\AddOverrideAttributeToOverriddenMethodsRector; +use Rector\PHPUnit\CodeQuality\Rector\Class_\YieldDataProviderRector; use Rector\Privatization\Rector\ClassMethod\PrivatizeFinalClassMethodRector; use Rector\Privatization\Rector\MethodCall\PrivatizeLocalGetterToPropertyRector; use Rector\Privatization\Rector\Property\PrivatizeFinalClassPropertyRector; @@ -98,6 +99,7 @@ // Additional rules. ->withRules([ DeclareStrictTypesRector::class, + YieldDataProviderRector::class, ]) // Configure Drupal autoloading. ->withAutoloadPaths((function (): array { diff --git a/renovate.json b/renovate.json index 7fef9a4..7e2858f 100644 --- a/renovate.json +++ b/renovate.json @@ -23,16 +23,31 @@ ], "packageRules": [ { + "groupName": "PHP - Language version - Skipped to update manually", + "groupSlug": "php-language-version", "matchDepNames": [ "php" ], - "matchFileNames": [ - "composer.json" + "matchManagers": [ + "composer" + ], + "enabled": false + }, + { + "groupName": "JavaScript - Language versions - Skipped to update manually", + "groupSlug": "js-language-versions", + "matchDepNames": [ + "node", + "yarn" + ], + "matchManagers": [ + "npm" ], "enabled": false }, { - "groupName": "Major all - skipped to update manually", + "groupName": "PHP - All packages - Major - Skipped to update manually", + "groupSlug": "php-all-major", "matchDatasources": [ "packagist" ], @@ -48,8 +63,8 @@ ] }, { - "groupName": "Minor and Patch Contrib", - "groupSlug": "drupal-minor-patch-contrib", + "groupName": "PHP - All packages except core - Minor and patch", + "groupSlug": "php-all-except-core-minor-patch", "matchDatasources": [ "packagist" ], @@ -69,8 +84,8 @@ ] }, { - "groupName": "Minor and Patch Core", - "groupSlug": "drupal-minor-patch-core", + "groupName": "PHP - Drupal core - Minor and patch", + "groupSlug": "php-drupal-core-minor-patch", "matchFileNames": [ "composer.json" ], @@ -92,7 +107,8 @@ ] }, { - "groupName": "Non-root npm - skipped", + "groupName": "JavaScript - Non-root packages - Skipped to update manually", + "groupSlug": "js-non-root", "matchDatasources": [ "npm" ], @@ -105,7 +121,8 @@ ] }, { - "groupName": "Major npm - skipped to update manually", + "groupName": "JavaScript - All packages - Major - Skipped to update manually", + "groupSlug": "js-all-major", "matchDatasources": [ "npm" ], @@ -121,8 +138,8 @@ ] }, { - "groupName": "Minor and Patch npm", - "groupSlug": "npm-minor-patch", + "groupName": "JavaScript - All packages - Minor and patch", + "groupSlug": "js-all-minor-patch", "matchDatasources": [ "npm" ], @@ -138,8 +155,8 @@ ] }, { - "groupName": "Container images", - "groupSlug": "docker", + "groupName": "Container images - All - Major, minor and patch", + "groupSlug": "container-images-all-major-minor-patch", "matchFileNames": [ ".docker/**" ], @@ -153,8 +170,8 @@ ] }, { - "groupName": "GitHub Actions", - "groupSlug": "github-actions", + "groupName": "GitHub Actions - All - Major, minor and patch", + "groupSlug": "github-actions-all-major-minor-patch", "schedule": [ "before 3am" ], diff --git a/scripts/vortex/deploy-artifact.sh b/scripts/vortex/deploy-artifact.sh index 82014c1..6a61d4e 100755 --- a/scripts/vortex/deploy-artifact.sh +++ b/scripts/vortex/deploy-artifact.sh @@ -51,6 +51,12 @@ VORTEX_DEPLOY_ARTIFACT_SSH_FINGERPRINT="${VORTEX_DEPLOY_ARTIFACT_SSH_FINGERPRINT # Default SSH file used if custom fingerprint is not provided. VORTEX_DEPLOY_ARTIFACT_SSH_FILE="${VORTEX_DEPLOY_ARTIFACT_SSH_FILE:-${VORTEX_DEPLOY_SSH_FILE:-${HOME}/.ssh/id_rsa}}" +# Version of git-artifact to download. +VORTEX_DEPLOY_ARTIFACT_GIT_ARTIFACT_VERSION="${VORTEX_DEPLOY_ARTIFACT_GIT_ARTIFACT_VERSION:-1.4.0}" + +# SHA256 checksum of the git-artifact binary. +VORTEX_DEPLOY_ARTIFACT_GIT_ARTIFACT_SHA256="${VORTEX_DEPLOY_ARTIFACT_GIT_ARTIFACT_SHA256:-1fa99ff2a6f8dc6c1a42bcfc87ce75d04b2eab375216b0e3195a0e3b51a47646}" + # ------------------------------------------------------------------------------ # @formatter:off @@ -63,6 +69,12 @@ fail() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\03 info "Started ARTIFACT deployment." +# shellcheck disable=SC2043 +for cmd in curl; do command -v "${cmd}" >/dev/null || { + fail "Command ${cmd} is not available." + exit 1 +}; done + # Check all required values. [ -z "${VORTEX_DEPLOY_ARTIFACT_GIT_REMOTE}" ] && fail "Missing required value for VORTEX_DEPLOY_ARTIFACT_GIT_REMOTE." && exit 1 [ -z "${VORTEX_DEPLOY_ARTIFACT_DST_BRANCH}" ] && fail "Missing required value for VORTEX_DEPLOY_ARTIFACT_DST_BRANCH." && exit 1 @@ -79,7 +91,12 @@ info "Started ARTIFACT deployment." export VORTEX_SSH_PREFIX="DEPLOY_ARTIFACT" && . ./scripts/vortex/setup-ssh.sh task "Installing artifact builder." -composer global require --dev -n --ansi --prefer-source --ignore-platform-reqs drevops/git-artifact:~1.2 +curl -sS -L "https://github.com/drevops/git-artifact/releases/download/${VORTEX_DEPLOY_ARTIFACT_GIT_ARTIFACT_VERSION}/git-artifact" -o "${TMPDIR:-/tmp}"/git-artifact +if ! echo "${VORTEX_DEPLOY_ARTIFACT_GIT_ARTIFACT_SHA256} ${TMPDIR:-/tmp}/git-artifact" | sha256sum -c; then + fail "SHA256 checksum verification failed for git-artifact binary." + exit 1 +fi +chmod +x "${TMPDIR:-/tmp}"/git-artifact # Try resolving absolute paths. if command -v realpath >/dev/null 2>&1; then @@ -99,7 +116,7 @@ cp -a "${VORTEX_DEPLOY_ARTIFACT_ROOT}"/.gitignore.artifact "${VORTEX_DEPLOY_ARTI task "Running artifact builder." # Add --debug to debug any deployment issues. -"${HOME}/.composer/vendor/bin/git-artifact" "${VORTEX_DEPLOY_ARTIFACT_GIT_REMOTE}" \ +"${TMPDIR:-/tmp}"/git-artifact "${VORTEX_DEPLOY_ARTIFACT_GIT_REMOTE}" \ --root="${VORTEX_DEPLOY_ARTIFACT_ROOT}" \ --src="${VORTEX_DEPLOY_ARTIFACT_SRC}" \ --branch="${VORTEX_DEPLOY_ARTIFACT_DST_BRANCH}" \ diff --git a/scripts/vortex/download-db-acquia.sh b/scripts/vortex/download-db-acquia.sh index 218896b..5d5404e 100755 --- a/scripts/vortex/download-db-acquia.sh +++ b/scripts/vortex/download-db-acquia.sh @@ -22,42 +22,56 @@ t=$(mktemp) && export -p >"${t}" && set -a && . ./.env && if [ -f ./.env.local ]; then . ./.env.local; fi && set +a && . "${t}" && rm "${t}" && unset t -_vortex_var_prefix_default="VORTEX_DOWNLOAD_DB" -VORTEX_VAR_PREFIX="${VORTEX_VAR_PREFIX:-${_vortex_var_prefix_default}}" -for v in $(env | grep "^${VORTEX_VAR_PREFIX}_" | cut -d= -f1); do export "${_vortex_var_prefix_default}_${v#"${VORTEX_VAR_PREFIX}"_}=${!v}"; done - set -eu [ "${VORTEX_DEBUG-}" = "1" ] && set -x +# Database index suffix. When set (e.g., "2"), all DB-related variable lookups +# use the indexed variant. +_db_index="${VORTEX_DB_INDEX:-}" + # Acquia Cloud API key. -VORTEX_DOWNLOAD_DB_ACQUIA_KEY="${VORTEX_DOWNLOAD_DB_ACQUIA_KEY:-${VORTEX_ACQUIA_KEY:-}}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_ACQUIA_KEY" +VORTEX_DOWNLOAD_DB_ACQUIA_KEY="${!_v:-${VORTEX_ACQUIA_KEY:-}}" # Acquia Cloud API secret. -VORTEX_DOWNLOAD_DB_ACQUIA_SECRET="${VORTEX_DOWNLOAD_DB_ACQUIA_SECRET:-${VORTEX_ACQUIA_SECRET:-}}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_ACQUIA_SECRET" +VORTEX_DOWNLOAD_DB_ACQUIA_SECRET="${!_v:-${VORTEX_ACQUIA_SECRET:-}}" # Application name. Used to discover UUID. -VORTEX_DOWNLOAD_DB_ACQUIA_APP_NAME="${VORTEX_DOWNLOAD_DB_ACQUIA_APP_NAME:-${VORTEX_ACQUIA_APP_NAME:-}}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_ACQUIA_APP_NAME" +VORTEX_DOWNLOAD_DB_ACQUIA_APP_NAME="${!_v:-${VORTEX_ACQUIA_APP_NAME:-}}" # Source environment name used to download the database dump from. -VORTEX_DOWNLOAD_DB_ENVIRONMENT="${VORTEX_DOWNLOAD_DB_ENVIRONMENT:-}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_ENVIRONMENT" +VORTEX_DOWNLOAD_DB_ENVIRONMENT="${!_v:-}" # Database name within source environment used to download the database dump. -VORTEX_DOWNLOAD_DB_ACQUIA_DB_NAME="${VORTEX_DOWNLOAD_DB_ACQUIA_DB_NAME:-}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_ACQUIA_DB_NAME" +VORTEX_DOWNLOAD_DB_ACQUIA_DB_NAME="${!_v:-}" # Directory where DB dumps are stored. -VORTEX_DOWNLOAD_DB_ACQUIA_DB_DIR="${VORTEX_DOWNLOAD_DB_ACQUIA_DB_DIR:-${VORTEX_DOWNLOAD_DB_DIR:-${VORTEX_DB_DIR:-./.data}}}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_ACQUIA_DB_DIR" +_vs="VORTEX_DOWNLOAD_DB${_db_index}_DIR" +_vss="VORTEX_DB${_db_index}_DIR" +VORTEX_DOWNLOAD_DB_ACQUIA_DB_DIR="${!_v:-${!_vs:-${!_vss:-./.data}}}" # Database dump file name. -VORTEX_DOWNLOAD_DB_ACQUIA_DB_FILE="${VORTEX_DOWNLOAD_DB_ACQUIA_DB_FILE:-${VORTEX_DOWNLOAD_DB_FILE:-${VORTEX_DB_FILE:-db.sql}}}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_ACQUIA_DB_FILE" +_vs="VORTEX_DOWNLOAD_DB${_db_index}_FILE" +_vss="VORTEX_DB${_db_index}_FILE" +VORTEX_DOWNLOAD_DB_ACQUIA_DB_FILE="${!_v:-${!_vs:-${!_vss:-db.sql}}}" # Flag to download a fresh copy of the database by triggering a new backup. -VORTEX_DOWNLOAD_DB_FRESH="${VORTEX_DOWNLOAD_DB_FRESH:-}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_FRESH" +VORTEX_DOWNLOAD_DB_FRESH="${!_v:-}" # Interval in seconds to wait between backup status checks. -VORTEX_DOWNLOAD_DB_ACQUIA_BACKUP_WAIT_INTERVAL="${VORTEX_DOWNLOAD_DB_ACQUIA_BACKUP_WAIT_INTERVAL:-10}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_ACQUIA_BACKUP_WAIT_INTERVAL" +VORTEX_DOWNLOAD_DB_ACQUIA_BACKUP_WAIT_INTERVAL="${!_v:-10}" # Maximum time in seconds to wait for backup completion. -VORTEX_DOWNLOAD_DB_ACQUIA_BACKUP_MAX_WAIT="${VORTEX_DOWNLOAD_DB_ACQUIA_BACKUP_MAX_WAIT:-600}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_ACQUIA_BACKUP_MAX_WAIT" +VORTEX_DOWNLOAD_DB_ACQUIA_BACKUP_MAX_WAIT="${!_v:-600}" #------------------------------------------------------------------------------- diff --git a/scripts/vortex/download-db-container-registry.sh b/scripts/vortex/download-db-container-registry.sh index a6b8a7d..963337c 100755 --- a/scripts/vortex/download-db-container-registry.sh +++ b/scripts/vortex/download-db-container-registry.sh @@ -8,32 +8,42 @@ t=$(mktemp) && export -p >"${t}" && set -a && . ./.env && if [ -f ./.env.local ]; then . ./.env.local; fi && set +a && . "${t}" && rm "${t}" && unset t -_vortex_var_prefix_default="VORTEX_DOWNLOAD_DB" -VORTEX_VAR_PREFIX="${VORTEX_VAR_PREFIX:-${_vortex_var_prefix_default}}" -for v in $(env | grep "^${VORTEX_VAR_PREFIX}_" | cut -d= -f1); do export "${_vortex_var_prefix_default}_${v#"${VORTEX_VAR_PREFIX}"_}=${!v}"; done - set -eu [ "${VORTEX_DEBUG-}" = "1" ] && set -x +# Database index suffix. When set (e.g., "2"), all DB-related variable lookups +# use the indexed variant (e.g., VORTEX_DB2_IMAGE instead of VORTEX_DB_IMAGE). +_db_index="${VORTEX_DB_INDEX:-}" + # The container image containing database passed in a form of `/`. -VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_IMAGE="${VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_IMAGE:-${VORTEX_DB_IMAGE:-}}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_CONTAINER_REGISTRY_IMAGE" +_vs="VORTEX_DB${_db_index}_IMAGE" +VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_IMAGE="${!_v:-${!_vs:-}}" # Container registry name. # # Provide port, if required as `:`. -VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY="${VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY:-${VORTEX_CONTAINER_REGISTRY:-docker.io}}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_CONTAINER_REGISTRY" +VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY="${!_v:-${VORTEX_CONTAINER_REGISTRY:-docker.io}}" # The username to login into the container registry. -VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_USER="${VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_USER:-${VORTEX_CONTAINER_REGISTRY_USER:-}}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_CONTAINER_REGISTRY_USER" +VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_USER="${!_v:-${VORTEX_CONTAINER_REGISTRY_USER:-}}" # The password to login into the container registry. -VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_PASS="${VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_PASS:-${VORTEX_CONTAINER_REGISTRY_PASS:-}}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_CONTAINER_REGISTRY_PASS" +VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_PASS="${!_v:-${VORTEX_CONTAINER_REGISTRY_PASS:-}}" # Directory with database dump file. -VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_DB_DIR="${VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_DB_DIR:-${VORTEX_DOWNLOAD_DB_DIR:-${VORTEX_DB_DIR:-./.data}}}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_CONTAINER_REGISTRY_DB_DIR" +_vs="VORTEX_DOWNLOAD_DB${_db_index}_DIR" +_vss="VORTEX_DB${_db_index}_DIR" +VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_DB_DIR="${!_v:-${!_vs:-${!_vss:-./.data}}}" # The base container image used as a fallback when the archive does not exist. -VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_IMAGE_BASE="${VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_IMAGE_BASE:-${VORTEX_DB_IMAGE_BASE:-}}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_CONTAINER_REGISTRY_IMAGE_BASE" +_vs="VORTEX_DB${_db_index}_IMAGE_BASE" +VORTEX_DOWNLOAD_DB_CONTAINER_REGISTRY_IMAGE_BASE="${!_v:-${!_vs:-}}" #------------------------------------------------------------------------------- diff --git a/scripts/vortex/download-db-ftp.sh b/scripts/vortex/download-db-ftp.sh index e9edcc7..f4a6927 100755 --- a/scripts/vortex/download-db-ftp.sh +++ b/scripts/vortex/download-db-ftp.sh @@ -8,33 +8,44 @@ t=$(mktemp) && export -p >"${t}" && set -a && . ./.env && if [ -f ./.env.local ]; then . ./.env.local; fi && set +a && . "${t}" && rm "${t}" && unset t -_vortex_var_prefix_default="VORTEX_DOWNLOAD_DB" -VORTEX_VAR_PREFIX="${VORTEX_VAR_PREFIX:-${_vortex_var_prefix_default}}" -for v in $(env | grep "^${VORTEX_VAR_PREFIX}_" | cut -d= -f1); do export "${_vortex_var_prefix_default}_${v#"${VORTEX_VAR_PREFIX}"_}=${!v}"; done - set -eu [ "${VORTEX_DEBUG-}" = "1" ] && set -x +# Database index suffix. When set (e.g., "2"), all DB-related variable lookups +# use the indexed variant. +_db_index="${VORTEX_DB_INDEX:-}" + # The FTP user. -VORTEX_DOWNLOAD_DB_FTP_USER="${VORTEX_DOWNLOAD_DB_FTP_USER:-}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_FTP_USER" +VORTEX_DOWNLOAD_DB_FTP_USER="${!_v:-}" # The FTP password. -VORTEX_DOWNLOAD_DB_FTP_PASS="${VORTEX_DOWNLOAD_DB_FTP_PASS:-}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_FTP_PASS" +VORTEX_DOWNLOAD_DB_FTP_PASS="${!_v:-}" # The FTP host. -VORTEX_DOWNLOAD_DB_FTP_HOST="${VORTEX_DOWNLOAD_DB_FTP_HOST:-}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_FTP_HOST" +VORTEX_DOWNLOAD_DB_FTP_HOST="${!_v:-}" # The FTP port. -VORTEX_DOWNLOAD_DB_FTP_PORT="${VORTEX_DOWNLOAD_DB_FTP_PORT:-}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_FTP_PORT" +VORTEX_DOWNLOAD_DB_FTP_PORT="${!_v:-}" # The file name, including any directories. -VORTEX_DOWNLOAD_DB_FTP_FILE="${VORTEX_DOWNLOAD_DB_FTP_FILE:-}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_FTP_FILE" +VORTEX_DOWNLOAD_DB_FTP_FILE="${!_v:-}" # Directory with database dump file. -VORTEX_DOWNLOAD_DB_FTP_DB_DIR="${VORTEX_DOWNLOAD_DB_FTP_DB_DIR:-${VORTEX_DOWNLOAD_DB_DIR:-${VORTEX_DB_DIR:-./.data}}}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_FTP_DB_DIR" +_vs="VORTEX_DOWNLOAD_DB${_db_index}_DIR" +_vss="VORTEX_DB${_db_index}_DIR" +VORTEX_DOWNLOAD_DB_FTP_DB_DIR="${!_v:-${!_vs:-${!_vss:-./.data}}}" # Database dump file name. -VORTEX_DOWNLOAD_DB_FTP_DB_FILE="${VORTEX_DOWNLOAD_DB_FTP_DB_FILE:-${VORTEX_DOWNLOAD_DB_FILE:-${VORTEX_DB_FILE:-db.sql}}}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_FTP_DB_FILE" +_vs="VORTEX_DOWNLOAD_DB${_db_index}_FILE" +_vss="VORTEX_DB${_db_index}_FILE" +VORTEX_DOWNLOAD_DB_FTP_DB_FILE="${!_v:-${!_vs:-${!_vss:-db.sql}}}" #------------------------------------------------------------------------------- diff --git a/scripts/vortex/download-db-lagoon.sh b/scripts/vortex/download-db-lagoon.sh index bf627cd..30f40d5 100755 --- a/scripts/vortex/download-db-lagoon.sh +++ b/scripts/vortex/download-db-lagoon.sh @@ -15,58 +15,74 @@ # # IMPORTANT! This script runs outside the container on the host system. # -# shellcheck disable=SC1090,SC1091,SC2029,SC2124,SC2140 +# shellcheck disable=SC1090,SC1091,SC2029,SC2034,SC2124,SC2140 t=$(mktemp) && export -p >"${t}" && set -a && . ./.env && if [ -f ./.env.local ]; then . ./.env.local; fi && set +a && . "${t}" && rm "${t}" && unset t -_vortex_var_prefix_default="VORTEX_DOWNLOAD_DB" -VORTEX_VAR_PREFIX="${VORTEX_VAR_PREFIX:-${_vortex_var_prefix_default}}" -for v in $(env | grep "^${VORTEX_VAR_PREFIX}_" | cut -d= -f1); do export "${_vortex_var_prefix_default}_${v#"${VORTEX_VAR_PREFIX}"_}=${!v}"; done - set -eu [ "${VORTEX_DEBUG-}" = "1" ] && set -x +# Database index suffix. When set (e.g., "2"), all DB-related variable lookups +# use the indexed variant. +_db_index="${VORTEX_DB_INDEX:-}" + # Flag to download a fresh copy of the database. -VORTEX_DOWNLOAD_DB_FRESH="${VORTEX_DOWNLOAD_DB_FRESH:-}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_FRESH" +VORTEX_DOWNLOAD_DB_FRESH="${!_v:-}" # Lagoon project name. -VORTEX_DOWNLOAD_DB_LAGOON_PROJECT="${VORTEX_DOWNLOAD_DB_LAGOON_PROJECT:-${LAGOON_PROJECT:?Missing required environment variable LAGOON_PROJECT.}}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_LAGOON_PROJECT" +VORTEX_DOWNLOAD_DB_LAGOON_PROJECT="${!_v:-${LAGOON_PROJECT:?Missing required environment variable LAGOON_PROJECT.}}" # The source environment branch for the database source. -VORTEX_DOWNLOAD_DB_ENVIRONMENT="${VORTEX_DOWNLOAD_DB_ENVIRONMENT:-main}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_ENVIRONMENT" +VORTEX_DOWNLOAD_DB_ENVIRONMENT="${!_v:-main}" # Remote DB dump directory location. VORTEX_DOWNLOAD_DB_LAGOON_REMOTE_DIR="/tmp" # Remote DB dump file name. Cached by the date suffix. -VORTEX_DOWNLOAD_DB_LAGOON_REMOTE_FILE="${VORTEX_DOWNLOAD_DB_LAGOON_REMOTE_FILE:-db_$(date +%Y%m%d).sql}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_LAGOON_REMOTE_FILE" +VORTEX_DOWNLOAD_DB_LAGOON_REMOTE_FILE="${!_v:-db_$(date +%Y%m%d).sql}" # Wildcard file name to cleanup previously created dump files. # # Cleanup runs only if the variable is set and $VORTEX_DOWNLOAD_DB_LAGOON_REMOTE_FILE # does not exist. -VORTEX_DOWNLOAD_DB_LAGOON_REMOTE_FILE_CLEANUP="${VORTEX_DOWNLOAD_DB_LAGOON_REMOTE_FILE_CLEANUP:-db_*.sql}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_LAGOON_REMOTE_FILE_CLEANUP" +VORTEX_DOWNLOAD_DB_LAGOON_REMOTE_FILE_CLEANUP="${!_v:-db_*.sql}" # SSH key fingerprint used to connect to a remote. -VORTEX_DOWNLOAD_DB_SSH_FINGERPRINT="${VORTEX_DOWNLOAD_DB_SSH_FINGERPRINT:-}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_SSH_FINGERPRINT" +VORTEX_DOWNLOAD_DB_SSH_FINGERPRINT="${!_v:-}" # Default SSH file used if custom fingerprint is not provided. -VORTEX_DOWNLOAD_DB_SSH_FILE="${VORTEX_DOWNLOAD_DB_SSH_FILE:-${HOME}/.ssh/id_rsa}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_SSH_FILE" +VORTEX_DOWNLOAD_DB_SSH_FILE="${!_v:-${HOME}/.ssh/id_rsa}" # The SSH host of the Lagoon environment. -VORTEX_DOWNLOAD_DB_LAGOON_SSH_HOST="${VORTEX_DOWNLOAD_DB_LAGOON_SSH_HOST:-ssh.lagoon.amazeeio.cloud}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_LAGOON_SSH_HOST" +VORTEX_DOWNLOAD_DB_LAGOON_SSH_HOST="${!_v:-ssh.lagoon.amazeeio.cloud}" # The SSH port of the Lagoon environment. -VORTEX_DOWNLOAD_DB_LAGOON_SSH_PORT="${VORTEX_DOWNLOAD_DB_LAGOON_SSH_PORT:-32222}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_LAGOON_SSH_PORT" +VORTEX_DOWNLOAD_DB_LAGOON_SSH_PORT="${!_v:-32222}" # The SSH user of the Lagoon environment. -VORTEX_DOWNLOAD_DB_LAGOON_SSH_USER="${VORTEX_DOWNLOAD_DB_LAGOON_SSH_USER:-${VORTEX_DOWNLOAD_DB_LAGOON_PROJECT}-${VORTEX_DOWNLOAD_DB_ENVIRONMENT}}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_LAGOON_SSH_USER" +VORTEX_DOWNLOAD_DB_LAGOON_SSH_USER="${!_v:-${VORTEX_DOWNLOAD_DB_LAGOON_PROJECT}-${VORTEX_DOWNLOAD_DB_ENVIRONMENT}}" # Directory where DB dumps are stored on the host. -VORTEX_DOWNLOAD_DB_LAGOON_DB_DIR="${VORTEX_DOWNLOAD_DB_LAGOON_DB_DIR:-${VORTEX_DOWNLOAD_DB_DIR:-${VORTEX_DB_DIR:-./.data}}}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_LAGOON_DB_DIR" +_vs="VORTEX_DOWNLOAD_DB${_db_index}_DIR" +_vss="VORTEX_DB${_db_index}_DIR" +VORTEX_DOWNLOAD_DB_LAGOON_DB_DIR="${!_v:-${!_vs:-${!_vss:-./.data}}}" # Database dump file name on the host. -VORTEX_DOWNLOAD_DB_LAGOON_DB_FILE="${VORTEX_DOWNLOAD_DB_LAGOON_DB_FILE:-${VORTEX_DOWNLOAD_DB_FILE:-${VORTEX_DB_FILE:-db.sql}}}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_LAGOON_DB_FILE" +_vs="VORTEX_DOWNLOAD_DB${_db_index}_FILE" +_vss="VORTEX_DB${_db_index}_FILE" +VORTEX_DOWNLOAD_DB_LAGOON_DB_FILE="${!_v:-${!_vs:-${!_vss:-db.sql}}}" # Name of the webroot directory with Drupal codebase. WEBROOT="${WEBROOT:-web}" diff --git a/scripts/vortex/download-db-s3.sh b/scripts/vortex/download-db-s3.sh index a503bb4..79f6971 100755 --- a/scripts/vortex/download-db-s3.sh +++ b/scripts/vortex/download-db-s3.sh @@ -10,33 +10,44 @@ t=$(mktemp) && export -p >"${t}" && set -a && . ./.env && if [ -f ./.env.local ]; then . ./.env.local; fi && set +a && . "${t}" && rm "${t}" && unset t -_vortex_var_prefix_default="VORTEX_DOWNLOAD_DB" -VORTEX_VAR_PREFIX="${VORTEX_VAR_PREFIX:-${_vortex_var_prefix_default}}" -for v in $(env | grep "^${VORTEX_VAR_PREFIX}_" | cut -d= -f1); do export "${_vortex_var_prefix_default}_${v#"${VORTEX_VAR_PREFIX}"_}=${!v}"; done - set -eu [ "${VORTEX_DEBUG-}" = "1" ] && set -x +# Database index suffix. When set (e.g., "2"), all DB-related variable lookups +# use the indexed variant. +_db_index="${VORTEX_DB_INDEX:-}" + # AWS access key. -VORTEX_DOWNLOAD_DB_S3_ACCESS_KEY="${VORTEX_DOWNLOAD_DB_S3_ACCESS_KEY:-${S3_ACCESS_KEY:-}}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_S3_ACCESS_KEY" +VORTEX_DOWNLOAD_DB_S3_ACCESS_KEY="${!_v:-${S3_ACCESS_KEY:-}}" # AWS secret key. -VORTEX_DOWNLOAD_DB_S3_SECRET_KEY="${VORTEX_DOWNLOAD_DB_S3_SECRET_KEY:-${S3_SECRET_KEY:-}}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_S3_SECRET_KEY" +VORTEX_DOWNLOAD_DB_S3_SECRET_KEY="${!_v:-${S3_SECRET_KEY:-}}" # S3 bucket name. -VORTEX_DOWNLOAD_DB_S3_BUCKET="${VORTEX_DOWNLOAD_DB_S3_BUCKET:-${S3_BUCKET:-}}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_S3_BUCKET" +VORTEX_DOWNLOAD_DB_S3_BUCKET="${!_v:-${S3_BUCKET:-}}" # S3 region. -VORTEX_DOWNLOAD_DB_S3_REGION="${VORTEX_DOWNLOAD_DB_S3_REGION:-${S3_REGION:-}}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_S3_REGION" +VORTEX_DOWNLOAD_DB_S3_REGION="${!_v:-${S3_REGION:-}}" # S3 prefix (path within the bucket). -VORTEX_DOWNLOAD_DB_S3_PREFIX="${VORTEX_DOWNLOAD_DB_S3_PREFIX:-${S3_PREFIX:-}}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_S3_PREFIX" +VORTEX_DOWNLOAD_DB_S3_PREFIX="${!_v:-${S3_PREFIX:-}}" # Directory with database dump file. -VORTEX_DOWNLOAD_DB_S3_DB_DIR="${VORTEX_DOWNLOAD_DB_S3_DB_DIR:-${VORTEX_DOWNLOAD_DB_DIR:-${VORTEX_DB_DIR:-./.data}}}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_S3_DB_DIR" +_vs="VORTEX_DOWNLOAD_DB${_db_index}_DIR" +_vss="VORTEX_DB${_db_index}_DIR" +VORTEX_DOWNLOAD_DB_S3_DB_DIR="${!_v:-${!_vs:-${!_vss:-./.data}}}" # Database dump file name. -VORTEX_DOWNLOAD_DB_S3_DB_FILE="${VORTEX_DOWNLOAD_DB_S3_DB_FILE:-${VORTEX_DOWNLOAD_DB_FILE:-${VORTEX_DB_FILE:-db.sql}}}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_S3_DB_FILE" +_vs="VORTEX_DOWNLOAD_DB${_db_index}_FILE" +_vss="VORTEX_DB${_db_index}_FILE" +VORTEX_DOWNLOAD_DB_S3_DB_FILE="${!_v:-${!_vs:-${!_vss:-db.sql}}}" # ------------------------------------------------------------------------------ diff --git a/scripts/vortex/download-db-url.sh b/scripts/vortex/download-db-url.sh index 1f3d13a..8f49cfd 100755 --- a/scripts/vortex/download-db-url.sh +++ b/scripts/vortex/download-db-url.sh @@ -8,25 +8,33 @@ t=$(mktemp) && export -p >"${t}" && set -a && . ./.env && if [ -f ./.env.local ]; then . ./.env.local; fi && set +a && . "${t}" && rm "${t}" && unset t -_vortex_var_prefix_default="VORTEX_DOWNLOAD_DB" -VORTEX_VAR_PREFIX="${VORTEX_VAR_PREFIX:-${_vortex_var_prefix_default}}" -for v in $(env | grep "^${VORTEX_VAR_PREFIX}_" | cut -d= -f1); do export "${_vortex_var_prefix_default}_${v#"${VORTEX_VAR_PREFIX}"_}=${!v}"; done - set -eu [ "${VORTEX_DEBUG-}" = "1" ] && set -x +# Database index suffix. When set (e.g., "2"), all DB-related variable lookups +# use the indexed variant. +_db_index="${VORTEX_DB_INDEX:-}" + # URL of the remote database. If HTTP authentication required, it must be # included in the variable. -VORTEX_DOWNLOAD_DB_URL="${VORTEX_DOWNLOAD_DB_URL:-}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_URL" +VORTEX_DOWNLOAD_DB_URL="${!_v:-}" # Directory with database dump file. -VORTEX_DOWNLOAD_DB_URL_DB_DIR="${VORTEX_DOWNLOAD_DB_URL_DB_DIR:-${VORTEX_DOWNLOAD_DB_DIR:-${VORTEX_DB_DIR:-./.data}}}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_URL_DB_DIR" +_vs="VORTEX_DOWNLOAD_DB${_db_index}_DIR" +_vss="VORTEX_DB${_db_index}_DIR" +VORTEX_DOWNLOAD_DB_URL_DB_DIR="${!_v:-${!_vs:-${!_vss:-./.data}}}" # Database dump file name. -VORTEX_DOWNLOAD_DB_URL_DB_FILE="${VORTEX_DOWNLOAD_DB_URL_DB_FILE:-${VORTEX_DOWNLOAD_DB_FILE:-${VORTEX_DB_FILE:-db.sql}}}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_URL_DB_FILE" +_vs="VORTEX_DOWNLOAD_DB${_db_index}_FILE" +_vss="VORTEX_DB${_db_index}_FILE" +VORTEX_DOWNLOAD_DB_URL_DB_FILE="${!_v:-${!_vs:-${!_vss:-db.sql}}}" # Password for unzipping password-protected zip files. -VORTEX_DOWNLOAD_DB_UNZIP_PASSWORD="${VORTEX_DOWNLOAD_DB_UNZIP_PASSWORD:-}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_UNZIP_PASSWORD" +VORTEX_DOWNLOAD_DB_UNZIP_PASSWORD="${!_v:-}" #------------------------------------------------------------------------------- diff --git a/scripts/vortex/download-db.sh b/scripts/vortex/download-db.sh index d770d2a..e402bdf 100755 --- a/scripts/vortex/download-db.sh +++ b/scripts/vortex/download-db.sh @@ -10,29 +10,37 @@ t=$(mktemp) && export -p >"${t}" && set -a && . ./.env && if [ -f ./.env.local ]; then . ./.env.local; fi && set +a && . "${t}" && rm "${t}" && unset t -_vortex_var_prefix_default="VORTEX_DOWNLOAD_DB" -VORTEX_VAR_PREFIX="${VORTEX_VAR_PREFIX:-${_vortex_var_prefix_default}}" -for v in $(env | grep "^${VORTEX_VAR_PREFIX}_" | cut -d= -f1); do export "${_vortex_var_prefix_default}_${v#"${VORTEX_VAR_PREFIX}"_}=${!v}"; done - set -eu [ "${VORTEX_DEBUG-}" = "1" ] && set -x +# Database index suffix. When set (e.g., "2"), all DB-related variable lookups +# use the indexed variant (e.g., VORTEX_DOWNLOAD_DB2_SOURCE instead of +# VORTEX_DOWNLOAD_DB_SOURCE). +_db_index="${VORTEX_DB_INDEX:-}" + # Note that `container_registry` works only for database-in-image # database storage (when $VORTEX_DB_IMAGE variable has a value). -VORTEX_DOWNLOAD_DB_SOURCE="${VORTEX_DOWNLOAD_DB_SOURCE:-url}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_SOURCE" +VORTEX_DOWNLOAD_DB_SOURCE="${!_v:-url}" # Force DB download even if the cache exists. # Usually set in CircleCI UI to override per build cache. -VORTEX_DOWNLOAD_DB_FORCE="${VORTEX_DOWNLOAD_DB_FORCE:-}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_FORCE" +VORTEX_DOWNLOAD_DB_FORCE="${!_v:-}" # Proceed with download. -VORTEX_DOWNLOAD_DB_PROCEED="${VORTEX_DOWNLOAD_DB_PROCEED:-1}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_PROCEED" +VORTEX_DOWNLOAD_DB_PROCEED="${!_v:-1}" # Database dump file name. -VORTEX_DOWNLOAD_DB_FILE="${VORTEX_DOWNLOAD_DB_FILE:-${VORTEX_DB_FILE:-db.sql}}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_FILE" +_vs="VORTEX_DB${_db_index}_FILE" +VORTEX_DOWNLOAD_DB_FILE="${!_v:-${!_vs:-db.sql}}" # Directory with database dump file. -VORTEX_DOWNLOAD_DB_DIR="${VORTEX_DOWNLOAD_DB_DIR:-${VORTEX_DB_DIR:-./.data}}" +_v="VORTEX_DOWNLOAD_DB${_db_index}_DIR" +_vs="VORTEX_DB${_db_index}_DIR" +VORTEX_DOWNLOAD_DB_DIR="${!_v:-${!_vs:-./.data}}" # ------------------------------------------------------------------------------ @@ -44,24 +52,28 @@ pass() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\03 fail() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[31m[FAIL] %s\033[0m\n" "${1}" || printf "[FAIL] %s\n" "${1}"; } # @formatter:on -info "Started database download." +info "Started database${_db_index:+ ${_db_index}} download." [ "${VORTEX_DOWNLOAD_DB_PROCEED}" != "1" ] && pass "Skipping database download as DB_DOWNLOAD_PROCEED is not set to 1." && exit 0 -db_file_basename="${VORTEX_DOWNLOAD_DB_FILE%.*}" -[ -d "${VORTEX_DOWNLOAD_DB_DIR:-}" ] && found_db=$(find "${VORTEX_DOWNLOAD_DB_DIR}" -name "${db_file_basename}.sql" -o -name "${db_file_basename}.tar") - -if [ -n "${found_db:-}" ]; then - note "Found existing database dump file(s)." - ls -Alh "${VORTEX_DOWNLOAD_DB_DIR}" 2>/dev/null || true - - if [ "${VORTEX_DOWNLOAD_DB_FORCE}" != "1" ]; then - note "Using existing database dump file(s)." - note "Download will not proceed." - note "Remove existing database file or set VORTEX_DOWNLOAD_DB_FORCE value to 1 to force download." - exit 0 - else - note "Will download a fresh copy of the database." +# Skip file existence check for container_registry source as the database is +# stored as a Docker image, not a file. +if [ "${VORTEX_DOWNLOAD_DB_SOURCE}" != "container_registry" ]; then + db_file_basename="${VORTEX_DOWNLOAD_DB_FILE%.*}" + [ -d "${VORTEX_DOWNLOAD_DB_DIR:-}" ] && found_db=$(find "${VORTEX_DOWNLOAD_DB_DIR}" -name "${db_file_basename}.sql" -o -name "${db_file_basename}.tar") + + if [ -n "${found_db:-}" ]; then + note "Found existing database dump file(s)." + ls -Alh "${VORTEX_DOWNLOAD_DB_DIR}" 2>/dev/null || true + + if [ "${VORTEX_DOWNLOAD_DB_FORCE}" != "1" ]; then + note "Using existing database dump file(s)." + note "Download will not proceed." + note "Remove existing database file or set VORTEX_DOWNLOAD_DB_FORCE value to 1 to force download." + exit 0 + else + note "Will download a fresh copy of the database." + fi fi fi @@ -94,4 +106,4 @@ ls -Alh "${VORTEX_DOWNLOAD_DB_DIR}" || true # Create a semaphore file to indicate that the database has been downloaded. [ -n "${VORTEX_DOWNLOAD_DB_SEMAPHORE:-}" ] && touch "${VORTEX_DOWNLOAD_DB_SEMAPHORE}" -pass "Finished database download." +pass "Finished database${_db_index:+ ${_db_index}} download." diff --git a/scripts/vortex/upload-db-s3.sh b/scripts/vortex/upload-db-s3.sh index 7d4a3f4..cf7b3f4 100755 --- a/scripts/vortex/upload-db-s3.sh +++ b/scripts/vortex/upload-db-s3.sh @@ -125,7 +125,7 @@ ${canonical_request_hash}" signature=$(aws_sign4 "${VORTEX_UPLOAD_DB_S3_SECRET_KEY}" "${date_short}" "${VORTEX_UPLOAD_DB_S3_REGION}" "${service}" "${string_to_sign}") -curl --silent --location --proto-redir =https --request "${request_type}" --upload-file "${local_file}" \ +response=$(curl --silent --location --proto-redir =https --request "${request_type}" --upload-file "${local_file}" \ --header "Content-Type: ${content_type}" \ --header "Host: ${VORTEX_UPLOAD_DB_S3_BUCKET}${base_url}" \ --header "X-Amz-Content-SHA256: ${payload_hash}" \ @@ -133,6 +133,19 @@ curl --silent --location --proto-redir =https --request "${request_type}" --uplo --header "X-Amz-Server-Side-Encryption: AES256" \ --header "X-Amz-Storage-Class: ${VORTEX_UPLOAD_DB_S3_STORAGE_CLASS}" \ --header "Authorization: ${auth_type} Credential=${VORTEX_UPLOAD_DB_S3_ACCESS_KEY}/${date_short}/${VORTEX_UPLOAD_DB_S3_REGION}/${service}/aws4_request, SignedHeaders=${header_list}, Signature=${signature}" \ - "${object_url}" + --write-out "\n%{http_code}" \ + "${object_url}") + +http_code=$(echo "${response}" | tail -1) +response_body=$(echo "${response}" | sed '$d') + +case "${http_code}" in + 2*) ;; + *) + echo "ERROR: S3 upload failed with HTTP status ${http_code}." + [ -n "${response_body}" ] && echo "Response: ${response_body}" + exit 1 + ;; +esac pass "Finished database dump upload to S3." diff --git a/tests/phpunit/Drupal/DatabaseSettingsTest.php b/tests/phpunit/Drupal/DatabaseSettingsTest.php index eaf054b..9efa33e 100644 --- a/tests/phpunit/Drupal/DatabaseSettingsTest.php +++ b/tests/phpunit/Drupal/DatabaseSettingsTest.php @@ -30,108 +30,107 @@ public function testDatabases(array $vars, array $expected): void { /** * Data provider for resulting database settings. */ - public static function dataProviderDatabases(): array { - return [ + public static function dataProviderDatabases(): \Iterator { + yield [ + [], [ - [], - [ + 'default' => [ 'default' => [ - 'default' => [ - 'database' => 'drupal', - 'username' => 'drupal', - 'password' => 'drupal', - 'host' => 'localhost', - 'port' => '3306', - 'charset' => 'utf8mb4', - 'collation' => 'utf8mb4_general_ci', - 'driver' => 'mysql', - 'prefix' => '', - ], + 'database' => 'drupal', + 'username' => 'drupal', + 'password' => 'drupal', + 'host' => 'localhost', + 'port' => '3306', + 'charset' => 'utf8mb4', + 'collation' => 'utf8mb4_general_ci', + 'driver' => 'mysql', + 'prefix' => '', ], ], ], + ]; + yield [ [ - [ - 'DATABASE_NAME' => 'database_db_name', - 'DATABASE_USERNAME' => 'database_db_user', - 'DATABASE_PASSWORD' => 'database_db_pass', - 'DATABASE_HOST' => 'database_db_host', - 'DATABASE_PORT' => 'database_db_port', - 'DATABASE_CHARSET' => 'database_utf8', - 'DATABASE_COLLATION' => 'database_utf8_unicode_ci', - ], - [ + 'DATABASE_NAME' => 'database_db_name', + 'DATABASE_USERNAME' => 'database_db_user', + 'DATABASE_PASSWORD' => 'database_db_pass', + 'DATABASE_HOST' => 'database_db_host', + 'DATABASE_PORT' => 'database_db_port', + 'DATABASE_CHARSET' => 'database_utf8', + 'DATABASE_COLLATION' => 'database_utf8_unicode_ci', + ], + [ + 'default' => [ 'default' => [ - 'default' => [ - 'database' => 'database_db_name', - 'username' => 'database_db_user', - 'password' => 'database_db_pass', - 'host' => 'database_db_host', - 'port' => 'database_db_port', - 'charset' => 'database_utf8', - 'collation' => 'database_utf8_unicode_ci', - 'driver' => 'mysql', - 'prefix' => '', - ], + 'database' => 'database_db_name', + 'username' => 'database_db_user', + 'password' => 'database_db_pass', + 'host' => 'database_db_host', + 'port' => 'database_db_port', + 'charset' => 'database_utf8', + 'collation' => 'database_utf8_unicode_ci', + 'driver' => 'mysql', + 'prefix' => '', ], ], ], + ]; + yield [ [ - [ - 'MARIADB_DATABASE' => 'mariadb_db_name', - 'MARIADB_USERNAME' => 'mariadb_db_user', - 'MARIADB_PASSWORD' => 'mariadb_db_pass', - 'MARIADB_HOST' => 'mariadb_db_host', - 'MARIADB_PORT' => 'mariadb_db_port', - 'MARIADB_CHARSET' => 'mariadb_latin1', - 'MARIADB_COLLATION' => 'mariadb_latin1_swedish_ci', - ], - [ + 'MARIADB_DATABASE' => 'mariadb_db_name', + 'MARIADB_USERNAME' => 'mariadb_db_user', + 'MARIADB_PASSWORD' => 'mariadb_db_pass', + 'MARIADB_HOST' => 'mariadb_db_host', + 'MARIADB_PORT' => 'mariadb_db_port', + 'MARIADB_CHARSET' => 'mariadb_latin1', + 'MARIADB_COLLATION' => 'mariadb_latin1_swedish_ci', + ], + [ + 'default' => [ 'default' => [ - 'default' => [ - 'database' => 'mariadb_db_name', - 'username' => 'mariadb_db_user', - 'password' => 'mariadb_db_pass', - 'host' => 'mariadb_db_host', - 'port' => 'mariadb_db_port', - 'charset' => 'mariadb_latin1', - 'collation' => 'mariadb_latin1_swedish_ci', - 'driver' => 'mysql', - 'prefix' => '', - ], + 'database' => 'mariadb_db_name', + 'username' => 'mariadb_db_user', + 'password' => 'mariadb_db_pass', + 'host' => 'mariadb_db_host', + 'port' => 'mariadb_db_port', + 'charset' => 'mariadb_latin1', + 'collation' => 'mariadb_latin1_swedish_ci', + 'driver' => 'mysql', + 'prefix' => '', ], ], ], + ]; + yield [ [ - [ - 'DATABASE_DATABASE' => 'database_db_name', - 'DATABASE_USERNAME' => 'database_db_user', - 'DATABASE_PASSWORD' => 'database_db_pass', - 'DATABASE_HOST' => 'database_db_host', - 'DATABASE_PORT' => 'database_db_port', - 'MYSQL_CHARSET' => 'mysql_utf8mb3', - 'MYSQL_COLLATION' => 'mysql_utf8mb3_bin', - ], - [ + 'DATABASE_DATABASE' => 'database_db_name', + 'DATABASE_USERNAME' => 'database_db_user', + 'DATABASE_PASSWORD' => 'database_db_pass', + 'DATABASE_HOST' => 'database_db_host', + 'DATABASE_PORT' => 'database_db_port', + 'MYSQL_CHARSET' => 'mysql_utf8mb3', + 'MYSQL_COLLATION' => 'mysql_utf8mb3_bin', + ], + [ + 'default' => [ 'default' => [ - 'default' => [ - 'database' => 'database_db_name', - 'username' => 'database_db_user', - 'password' => 'database_db_pass', - 'host' => 'database_db_host', - 'port' => 'database_db_port', - 'charset' => 'mysql_utf8mb3', - 'collation' => 'mysql_utf8mb3_bin', - 'driver' => 'mysql', - 'prefix' => '', - ], + 'database' => 'database_db_name', + 'username' => 'database_db_user', + 'password' => 'database_db_pass', + 'host' => 'database_db_host', + 'port' => 'database_db_port', + 'charset' => 'mysql_utf8mb3', + 'collation' => 'mysql_utf8mb3_bin', + 'driver' => 'mysql', + 'prefix' => '', ], ], ], ]; + } } diff --git a/tests/phpunit/Drupal/EnvironmentSettingsTest.php b/tests/phpunit/Drupal/EnvironmentSettingsTest.php index 3d4b2b9..adbb01e 100644 --- a/tests/phpunit/Drupal/EnvironmentSettingsTest.php +++ b/tests/phpunit/Drupal/EnvironmentSettingsTest.php @@ -38,207 +38,205 @@ public function testEnvironmentTypeDetection(array $vars, string $expected_env): /** * Data provider for testing environment type detection. */ - public static function dataProviderEnvironmentTypeDetection(): array { - return [ - // By default, the default environment type is local. - [[], self::ENVIRONMENT_LOCAL], + public static function dataProviderEnvironmentTypeDetection(): \Iterator { + // By default, the default environment type is local. + yield [[], self::ENVIRONMENT_LOCAL]; - // CI. + // CI. + yield [ [ - [ - 'CI' => 1, - ], - self::ENVIRONMENT_CI, + 'CI' => 1, ], + self::ENVIRONMENT_CI, + ]; - // Container. + // Container. + yield [ [ - [ - 'VORTEX_LOCALDEV_URL' => 'https://example-site.docker.amazee.io', - ], - self::ENVIRONMENT_LOCAL, + 'VORTEX_LOCALDEV_URL' => 'https://example-site.docker.amazee.io', ], + self::ENVIRONMENT_LOCAL, + ]; - // Lagoon. + // Lagoon. + yield [ [ - [ - 'LAGOON_KUBERNETES' => 1, - ], - self::ENVIRONMENT_DEV, + 'LAGOON_KUBERNETES' => 1, ], + self::ENVIRONMENT_DEV, + ]; + yield [ [ - [ - 'LAGOON_KUBERNETES' => 1, - 'LAGOON_ENVIRONMENT_TYPE' => 'production', - ], - self::ENVIRONMENT_PROD, + 'LAGOON_KUBERNETES' => 1, + 'LAGOON_ENVIRONMENT_TYPE' => 'production', ], + self::ENVIRONMENT_PROD, + ]; + yield [ [ - [ - 'LAGOON_KUBERNETES' => 1, - 'LAGOON_GIT_BRANCH' => 'main', - 'VORTEX_LAGOON_PRODUCTION_BRANCH' => 'main', - 'LAGOON_ENVIRONMENT_TYPE' => 'development', - ], - self::ENVIRONMENT_PROD, + 'LAGOON_KUBERNETES' => 1, + 'LAGOON_GIT_BRANCH' => 'main', + 'VORTEX_LAGOON_PRODUCTION_BRANCH' => 'main', + 'LAGOON_ENVIRONMENT_TYPE' => 'development', ], + self::ENVIRONMENT_PROD, + ]; + yield [ [ - [ - 'LAGOON_KUBERNETES' => 1, - 'LAGOON_GIT_BRANCH' => 'main', - 'VORTEX_LAGOON_PRODUCTION_BRANCH' => 'master', - 'LAGOON_ENVIRONMENT_TYPE' => 'development', - ], - self::ENVIRONMENT_STAGE, + 'LAGOON_KUBERNETES' => 1, + 'LAGOON_GIT_BRANCH' => 'main', + 'VORTEX_LAGOON_PRODUCTION_BRANCH' => 'master', + 'LAGOON_ENVIRONMENT_TYPE' => 'development', ], + self::ENVIRONMENT_STAGE, + ]; + yield [ [ - [ - 'LAGOON_KUBERNETES' => 1, - 'LAGOON_GIT_BRANCH' => 'master', - 'VORTEX_LAGOON_PRODUCTION_BRANCH' => FALSE, - 'LAGOON_ENVIRONMENT_TYPE' => 'development', - ], - self::ENVIRONMENT_STAGE, + 'LAGOON_KUBERNETES' => 1, + 'LAGOON_GIT_BRANCH' => 'master', + 'VORTEX_LAGOON_PRODUCTION_BRANCH' => FALSE, + 'LAGOON_ENVIRONMENT_TYPE' => 'development', ], + self::ENVIRONMENT_STAGE, + ]; + yield [ [ - [ - 'LAGOON_KUBERNETES' => 1, - 'LAGOON_GIT_BRANCH' => 'master', - 'VORTEX_LAGOON_PRODUCTION_BRANCH' => FALSE, - 'LAGOON_ENVIRONMENT_TYPE' => 'production', - ], - self::ENVIRONMENT_PROD, + 'LAGOON_KUBERNETES' => 1, + 'LAGOON_GIT_BRANCH' => 'master', + 'VORTEX_LAGOON_PRODUCTION_BRANCH' => FALSE, + 'LAGOON_ENVIRONMENT_TYPE' => 'production', ], + self::ENVIRONMENT_PROD, + ]; + yield [ [ - [ - 'LAGOON_KUBERNETES' => 1, - 'LAGOON_GIT_BRANCH' => 'main', - 'VORTEX_LAGOON_PRODUCTION_BRANCH' => FALSE, - 'LAGOON_ENVIRONMENT_TYPE' => 'development', - ], - self::ENVIRONMENT_STAGE, + 'LAGOON_KUBERNETES' => 1, + 'LAGOON_GIT_BRANCH' => 'main', + 'VORTEX_LAGOON_PRODUCTION_BRANCH' => FALSE, + 'LAGOON_ENVIRONMENT_TYPE' => 'development', ], + self::ENVIRONMENT_STAGE, + ]; + yield [ [ - [ - 'LAGOON_KUBERNETES' => 1, - 'LAGOON_GIT_BRANCH' => 'main', - 'VORTEX_LAGOON_PRODUCTION_BRANCH' => FALSE, - 'LAGOON_ENVIRONMENT_TYPE' => 'production', - ], - self::ENVIRONMENT_PROD, + 'LAGOON_KUBERNETES' => 1, + 'LAGOON_GIT_BRANCH' => 'main', + 'VORTEX_LAGOON_PRODUCTION_BRANCH' => FALSE, + 'LAGOON_ENVIRONMENT_TYPE' => 'production', ], + self::ENVIRONMENT_PROD, + ]; + yield [ [ - [ - 'LAGOON_KUBERNETES' => 1, - 'LAGOON_ENVIRONMENT_TYPE' => 'development', - 'LAGOON_GIT_BRANCH' => 'release', - ], - self::ENVIRONMENT_DEV, + 'LAGOON_KUBERNETES' => 1, + 'LAGOON_ENVIRONMENT_TYPE' => 'development', + 'LAGOON_GIT_BRANCH' => 'release', ], + self::ENVIRONMENT_DEV, + ]; + yield [ [ - [ - 'LAGOON_KUBERNETES' => 1, - 'LAGOON_ENVIRONMENT_TYPE' => 'development', - 'LAGOON_GIT_BRANCH' => 'release/1.2.3', - ], - self::ENVIRONMENT_STAGE, + 'LAGOON_KUBERNETES' => 1, + 'LAGOON_ENVIRONMENT_TYPE' => 'development', + 'LAGOON_GIT_BRANCH' => 'release/1.2.3', ], + self::ENVIRONMENT_STAGE, + ]; + yield [ [ - [ - 'LAGOON_KUBERNETES' => 1, - 'LAGOON_ENVIRONMENT_TYPE' => 'development', - 'LAGOON_GIT_BRANCH' => 'hotfix', - ], - self::ENVIRONMENT_DEV, + 'LAGOON_KUBERNETES' => 1, + 'LAGOON_ENVIRONMENT_TYPE' => 'development', + 'LAGOON_GIT_BRANCH' => 'hotfix', ], + self::ENVIRONMENT_DEV, + ]; + yield [ [ - [ - 'LAGOON_KUBERNETES' => 1, - 'LAGOON_ENVIRONMENT_TYPE' => 'development', - 'LAGOON_GIT_BRANCH' => 'hotfix/1.2.3', - ], - self::ENVIRONMENT_STAGE, + 'LAGOON_KUBERNETES' => 1, + 'LAGOON_ENVIRONMENT_TYPE' => 'development', + 'LAGOON_GIT_BRANCH' => 'hotfix/1.2.3', ], + self::ENVIRONMENT_STAGE, + ]; + yield [ [ - [ - 'LAGOON_KUBERNETES' => 1, - 'LAGOON_ENVIRONMENT_TYPE' => 'development', - 'LAGOON_GIT_BRANCH' => FALSE, - ], - self::ENVIRONMENT_DEV, + 'LAGOON_KUBERNETES' => 1, + 'LAGOON_ENVIRONMENT_TYPE' => 'development', + 'LAGOON_GIT_BRANCH' => FALSE, ], + self::ENVIRONMENT_DEV, + ]; + yield [ [ - [ - 'LAGOON_KUBERNETES' => 1, - 'LAGOON_ENVIRONMENT_TYPE' => 'development', - 'VORTEX_LAGOON_PRODUCTION_BRANCH' => FALSE, - ], - self::ENVIRONMENT_DEV, + 'LAGOON_KUBERNETES' => 1, + 'LAGOON_ENVIRONMENT_TYPE' => 'development', + 'VORTEX_LAGOON_PRODUCTION_BRANCH' => FALSE, ], + self::ENVIRONMENT_DEV, + ]; + yield [ [ - [ - 'LAGOON_KUBERNETES' => 1, - 'LAGOON_ENVIRONMENT_TYPE' => 'development', - 'LAGOON_GIT_BRANCH' => FALSE, - 'VORTEX_LAGOON_PRODUCTION_BRANCH' => FALSE, - ], - self::ENVIRONMENT_DEV, + 'LAGOON_KUBERNETES' => 1, + 'LAGOON_ENVIRONMENT_TYPE' => 'development', + 'LAGOON_GIT_BRANCH' => FALSE, + 'VORTEX_LAGOON_PRODUCTION_BRANCH' => FALSE, ], + self::ENVIRONMENT_DEV, + ]; + yield [ [ - [ - 'LAGOON_KUBERNETES' => 1, - 'LAGOON_ENVIRONMENT_TYPE' => 'development', - 'LAGOON_GIT_BRANCH' => 'somebranch', - 'VORTEX_LAGOON_PRODUCTION_BRANCH' => FALSE, - ], - self::ENVIRONMENT_DEV, + 'LAGOON_KUBERNETES' => 1, + 'LAGOON_ENVIRONMENT_TYPE' => 'development', + 'LAGOON_GIT_BRANCH' => 'somebranch', + 'VORTEX_LAGOON_PRODUCTION_BRANCH' => FALSE, ], + self::ENVIRONMENT_DEV, + ]; + yield [ [ - [ - 'LAGOON_KUBERNETES' => 1, - 'LAGOON_ENVIRONMENT_TYPE' => 'development', - 'LAGOON_GIT_BRANCH' => FALSE, - 'VORTEX_LAGOON_PRODUCTION_BRANCH' => 'otherbranch', - ], - self::ENVIRONMENT_DEV, + 'LAGOON_KUBERNETES' => 1, + 'LAGOON_ENVIRONMENT_TYPE' => 'development', + 'LAGOON_GIT_BRANCH' => FALSE, + 'VORTEX_LAGOON_PRODUCTION_BRANCH' => 'otherbranch', ], + self::ENVIRONMENT_DEV, + ]; + yield [ [ - [ - 'LAGOON_KUBERNETES' => 1, - 'LAGOON_ENVIRONMENT_TYPE' => 'development', - 'LAGOON_GIT_BRANCH' => 'somebranch', - 'VORTEX_LAGOON_PRODUCTION_BRANCH' => 'otherbranch', - ], - self::ENVIRONMENT_DEV, + 'LAGOON_KUBERNETES' => 1, + 'LAGOON_ENVIRONMENT_TYPE' => 'development', + 'LAGOON_GIT_BRANCH' => 'somebranch', + 'VORTEX_LAGOON_PRODUCTION_BRANCH' => 'otherbranch', ], + self::ENVIRONMENT_DEV, + ]; + yield [ [ - [ - 'LAGOON_KUBERNETES' => 1, - 'LAGOON_ENVIRONMENT_TYPE' => 'development', - 'LAGOON_GIT_BRANCH' => '', - 'VORTEX_LAGOON_PRODUCTION_BRANCH' => '', - ], - self::ENVIRONMENT_DEV, + 'LAGOON_KUBERNETES' => 1, + 'LAGOON_ENVIRONMENT_TYPE' => 'development', + 'LAGOON_GIT_BRANCH' => '', + 'VORTEX_LAGOON_PRODUCTION_BRANCH' => '', ], + self::ENVIRONMENT_DEV, + ]; + yield [ [ - [ - 'LAGOON_KUBERNETES' => 1, - 'LAGOON_ENVIRONMENT_TYPE' => 'development', - 'LAGOON_GIT_BRANCH' => 'mainbranch', - 'VORTEX_LAGOON_PRODUCTION_BRANCH' => 'mainbranch', - ], - self::ENVIRONMENT_PROD, + 'LAGOON_KUBERNETES' => 1, + 'LAGOON_ENVIRONMENT_TYPE' => 'development', + 'LAGOON_GIT_BRANCH' => 'mainbranch', + 'VORTEX_LAGOON_PRODUCTION_BRANCH' => 'mainbranch', ], + self::ENVIRONMENT_PROD, + ]; + yield [ [ - [ - 'LAGOON_KUBERNETES' => 1, - 'LAGOON_ENVIRONMENT_TYPE' => 'development', - ], - self::ENVIRONMENT_DEV, + 'LAGOON_KUBERNETES' => 1, + 'LAGOON_ENVIRONMENT_TYPE' => 'development', ], + self::ENVIRONMENT_DEV, ]; } @@ -378,12 +376,12 @@ public function testEnvironmentLocal(): void { $config['environment_indicator.indicator']['name'] = self::ENVIRONMENT_LOCAL; $config['environment_indicator.settings']['favicon'] = TRUE; $config['environment_indicator.settings']['toolbar_integration'] = [TRUE]; - $config['purge_control.settings']['disable_purge'] = TRUE; - $config['purge_control.settings']['purge_auto_control'] = FALSE; $config['robotstxt.settings']['content'] = "User-agent: *\nDisallow: /"; $config['shield.settings']['shield_enable'] = FALSE; $config['system.logging']['error_level'] = 'all'; $config['system.performance']['cache']['page']['max_age'] = 900; + $config['purge_control.settings']['disable_purge'] = TRUE; + $config['purge_control.settings']['purge_auto_control'] = FALSE; $config['seckit.settings']['seckit_xss']['csp']['upgrade-req'] = FALSE; $this->assertConfig($config); @@ -429,12 +427,12 @@ public function testEnvironmentLocalContainer(): void { $config['environment_indicator.indicator']['name'] = self::ENVIRONMENT_LOCAL; $config['environment_indicator.settings']['favicon'] = TRUE; $config['environment_indicator.settings']['toolbar_integration'] = [TRUE]; - $config['purge_control.settings']['disable_purge'] = TRUE; - $config['purge_control.settings']['purge_auto_control'] = FALSE; $config['robotstxt.settings']['content'] = "User-agent: *\nDisallow: /"; $config['shield.settings']['shield_enable'] = FALSE; $config['system.logging']['error_level'] = 'all'; $config['system.performance']['cache']['page']['max_age'] = 900; + $config['purge_control.settings']['disable_purge'] = TRUE; + $config['purge_control.settings']['purge_auto_control'] = FALSE; $config['seckit.settings']['seckit_xss']['csp']['upgrade-req'] = FALSE; $this->assertConfig($config); @@ -482,12 +480,12 @@ public function testEnvironmentGha(): void { $config['environment_indicator.indicator']['name'] = self::ENVIRONMENT_CI; $config['environment_indicator.settings']['favicon'] = TRUE; $config['environment_indicator.settings']['toolbar_integration'] = [TRUE]; - $config['purge_control.settings']['disable_purge'] = TRUE; - $config['purge_control.settings']['purge_auto_control'] = FALSE; $config['robotstxt.settings']['content'] = "User-agent: *\nDisallow: /"; $config['shield.settings']['shield_enable'] = FALSE; $config['system.logging']['error_level'] = 'all'; $config['system.performance']['cache']['page']['max_age'] = 900; + $config['purge_control.settings']['disable_purge'] = TRUE; + $config['purge_control.settings']['purge_auto_control'] = FALSE; $config['seckit.settings']['seckit_xss']['csp']['upgrade-req'] = FALSE; $this->assertConfig($config); diff --git a/tests/phpunit/Drupal/SettingsTestCase.php b/tests/phpunit/Drupal/SettingsTestCase.php index 737274d..17fbfb3 100644 --- a/tests/phpunit/Drupal/SettingsTestCase.php +++ b/tests/phpunit/Drupal/SettingsTestCase.php @@ -199,7 +199,7 @@ protected function unsetEnvVars(): void { /** * Require settings file. */ - protected function requireSettingsFile(array $pre_settings = []): void { + protected function requireSettingsFile(array $pre_settings = [], array $pre_config = []): void { $app_root = getcwd() . '/web'; if (!file_exists($app_root)) { @@ -207,11 +207,13 @@ protected function requireSettingsFile(array $pre_settings = []): void { } $site_path = 'sites/default'; - $config = []; + $config = $pre_config; $settings = $pre_settings; $databases = []; + putenv('DRUPAL_SETTINGS_LOCAL_SKIP=1'); require $app_root . DIRECTORY_SEPARATOR . $site_path . DIRECTORY_SEPARATOR . 'settings.php'; + putenv('DRUPAL_SETTINGS_LOCAL_SKIP'); $this->app_root = $app_root; $this->site_path = $site_path; diff --git a/tests/phpunit/Drupal/SwitchableSettingsTest.php b/tests/phpunit/Drupal/SwitchableSettingsTest.php index 7964175..b772d28 100644 --- a/tests/phpunit/Drupal/SwitchableSettingsTest.php +++ b/tests/phpunit/Drupal/SwitchableSettingsTest.php @@ -93,71 +93,69 @@ public function testConfigSplit(string $env, array $expected_present, array $exp /** * Data provider for testConfigSplit(). */ - public static function dataProviderConfigSplit(): array { - return [ + public static function dataProviderConfigSplit(): \Iterator { + yield [ + self::ENVIRONMENT_LOCAL, [ - self::ENVIRONMENT_LOCAL, - [ - 'config_split.config_split.local' => ['status' => TRUE], - ], - [ - 'config_split.config_split.stage' => NULL, - 'config_split.config_split.dev' => NULL, - 'config_split.config_split.ci' => NULL, - ], + 'config_split.config_split.local' => ['status' => TRUE], ], [ - self::ENVIRONMENT_CI, - [ - 'config_split.config_split.ci' => ['status' => TRUE], - ], - [ - 'config_split.config_split.stage' => NULL, - 'config_split.config_split.dev' => NULL, - 'config_split.config_split.local' => NULL, - ], + 'config_split.config_split.stage' => NULL, + 'config_split.config_split.dev' => NULL, + 'config_split.config_split.ci' => NULL, ], + ]; + yield [ + self::ENVIRONMENT_CI, [ - self::ENVIRONMENT_DEV, - [ - 'config_split.config_split.dev' => ['status' => TRUE], - ], - [ - 'config_split.config_split.stage' => NULL, - 'config_split.config_split.ci' => NULL, - 'config_split.config_split.local' => NULL, - ], + 'config_split.config_split.ci' => ['status' => TRUE], ], [ - self::ENVIRONMENT_STAGE, - [ - 'config_split.config_split.stage' => ['status' => TRUE], - ], - [ - 'config_split.config_split.dev' => NULL, - 'config_split.config_split.ci' => NULL, - 'config_split.config_split.local' => NULL, - ], + 'config_split.config_split.stage' => NULL, + 'config_split.config_split.dev' => NULL, + 'config_split.config_split.local' => NULL, ], + ]; + yield [ + self::ENVIRONMENT_DEV, [ - self::ENVIRONMENT_PROD, - [], - [ - 'config_split.config_split.stage' => NULL, - 'config_split.config_split.dev' => NULL, - 'config_split.config_split.ci' => NULL, - 'config_split.config_split.local' => NULL, - ], + 'config_split.config_split.dev' => ['status' => TRUE], ], [ - self::ENVIRONMENT_SUT, - [], - [ - 'config_split.config_split.stage' => NULL, - 'config_split.config_split.dev' => NULL, - 'config_split.config_split.ci' => NULL, - 'config_split.config_split.local' => NULL, - ], + 'config_split.config_split.stage' => NULL, + 'config_split.config_split.ci' => NULL, + 'config_split.config_split.local' => NULL, + ], + ]; + yield [ + self::ENVIRONMENT_STAGE, + [ + 'config_split.config_split.stage' => ['status' => TRUE], + ], + [ + 'config_split.config_split.dev' => NULL, + 'config_split.config_split.ci' => NULL, + 'config_split.config_split.local' => NULL, + ], + ]; + yield [ + self::ENVIRONMENT_PROD, + [], + [ + 'config_split.config_split.stage' => NULL, + 'config_split.config_split.dev' => NULL, + 'config_split.config_split.ci' => NULL, + 'config_split.config_split.local' => NULL, + ], + ]; + yield [ + self::ENVIRONMENT_SUT, + [], + [ + 'config_split.config_split.stage' => NULL, + 'config_split.config_split.dev' => NULL, + 'config_split.config_split.ci' => NULL, + 'config_split.config_split.local' => NULL, ], ]; } @@ -180,49 +178,47 @@ public function testEnvironmentIndicator(string $env, array $expected_present, a /** * Data provider for testEnvironmentIndicator(). */ - public static function dataProviderEnvironmentIndicator(): array { - return [ + public static function dataProviderEnvironmentIndicator(): \Iterator { + yield [ + self::ENVIRONMENT_LOCAL, [ - self::ENVIRONMENT_LOCAL, - [ - 'environment_indicator.indicator' => ['name' => self::ENVIRONMENT_LOCAL, 'bg_color' => '#006600', 'fg_color' => '#ffffff'], - 'environment_indicator.settings' => ['toolbar_integration' => [TRUE], 'favicon' => TRUE], - ], + 'environment_indicator.indicator' => ['name' => self::ENVIRONMENT_LOCAL, 'bg_color' => '#006600', 'fg_color' => '#ffffff'], + 'environment_indicator.settings' => ['toolbar_integration' => [TRUE], 'favicon' => TRUE], ], + ]; + yield [ + self::ENVIRONMENT_CI, [ - self::ENVIRONMENT_CI, - [ - 'environment_indicator.indicator' => ['name' => self::ENVIRONMENT_CI, 'bg_color' => '#006600', 'fg_color' => '#ffffff'], - 'environment_indicator.settings' => ['toolbar_integration' => [TRUE], 'favicon' => TRUE], - ], + 'environment_indicator.indicator' => ['name' => self::ENVIRONMENT_CI, 'bg_color' => '#006600', 'fg_color' => '#ffffff'], + 'environment_indicator.settings' => ['toolbar_integration' => [TRUE], 'favicon' => TRUE], ], + ]; + yield [ + self::ENVIRONMENT_DEV, [ - self::ENVIRONMENT_DEV, - [ - 'environment_indicator.indicator' => ['name' => self::ENVIRONMENT_DEV, 'bg_color' => '#4caf50', 'fg_color' => '#000000'], - 'environment_indicator.settings' => ['toolbar_integration' => [TRUE], 'favicon' => TRUE], - ], + 'environment_indicator.indicator' => ['name' => self::ENVIRONMENT_DEV, 'bg_color' => '#4caf50', 'fg_color' => '#000000'], + 'environment_indicator.settings' => ['toolbar_integration' => [TRUE], 'favicon' => TRUE], ], + ]; + yield [ + self::ENVIRONMENT_STAGE, [ - self::ENVIRONMENT_STAGE, - [ - 'environment_indicator.indicator' => ['name' => self::ENVIRONMENT_STAGE, 'bg_color' => '#fff176', 'fg_color' => '#000000'], - 'environment_indicator.settings' => ['toolbar_integration' => [TRUE], 'favicon' => TRUE], - ], + 'environment_indicator.indicator' => ['name' => self::ENVIRONMENT_STAGE, 'bg_color' => '#fff176', 'fg_color' => '#000000'], + 'environment_indicator.settings' => ['toolbar_integration' => [TRUE], 'favicon' => TRUE], ], + ]; + yield [ + self::ENVIRONMENT_PROD, [ - self::ENVIRONMENT_PROD, - [ - 'environment_indicator.indicator' => ['name' => self::ENVIRONMENT_PROD, 'bg_color' => '#ef5350', 'fg_color' => '#000000'], - 'environment_indicator.settings' => ['toolbar_integration' => [TRUE], 'favicon' => TRUE], - ], + 'environment_indicator.indicator' => ['name' => self::ENVIRONMENT_PROD, 'bg_color' => '#ef5350', 'fg_color' => '#000000'], + 'environment_indicator.settings' => ['toolbar_integration' => [TRUE], 'favicon' => TRUE], ], + ]; + yield [ + self::ENVIRONMENT_SUT, [ - self::ENVIRONMENT_SUT, - [ - 'environment_indicator.indicator' => ['name' => self::ENVIRONMENT_SUT, 'bg_color' => '#006600', 'fg_color' => '#ffffff'], - 'environment_indicator.settings' => ['toolbar_integration' => [TRUE], 'favicon' => TRUE], - ], + 'environment_indicator.indicator' => ['name' => self::ENVIRONMENT_SUT, 'bg_color' => '#006600', 'fg_color' => '#ffffff'], + 'environment_indicator.settings' => ['toolbar_integration' => [TRUE], 'favicon' => TRUE], ], ]; } @@ -304,10 +300,10 @@ public function testRedisCustomPort(): void { * Test Shield config. */ #[DataProvider('dataProviderShield')] - public function testShield(string $env, array $vars, array $expected_present, array $expected_absent = []): void { + public function testShield(string $env, array $vars, array $expected_present, array $expected_absent = [], array $pre_config = []): void { $this->setEnvVars($vars + ['DRUPAL_ENVIRONMENT' => $env]); - $this->requireSettingsFile(); + $this->requireSettingsFile([], $pre_config); $this->assertConfigContains($expected_present); $this->assertConfigNotContains($expected_absent); @@ -316,303 +312,356 @@ public function testShield(string $env, array $vars, array $expected_present, ar /** * Data provider for testShield(). */ - public static function dataProviderShield(): array { - return [ + public static function dataProviderShield(): \Iterator { + yield [ + self::ENVIRONMENT_LOCAL, + [], [ - self::ENVIRONMENT_LOCAL, - [], - [ - 'shield.settings' => ['shield_enable' => FALSE], - ], - [ - 'shield.settings' => ['credentials' => ['shield' => ['user' => 'drupal_shield_user', 'pass' => 'drupal_shield_pass']], 'print' => 'drupal_shield_print'], - ], + 'shield.settings' => ['shield_enable' => FALSE], ], [ - self::ENVIRONMENT_LOCAL, - [ - 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', - ], - [ - 'shield.settings' => ['shield_enable' => FALSE], - ], - [ - 'shield.settings' => ['credentials' => ['shield' => ['user' => 'drupal_shield_user', 'pass' => 'drupal_shield_pass']], 'print' => 'drupal_shield_print'], - ], + 'shield.settings' => ['credentials' => ['shield' => ['user' => 'drupal_shield_user', 'pass' => 'drupal_shield_pass']], 'print' => 'drupal_shield_print'], ], + ]; + yield [ + self::ENVIRONMENT_LOCAL, [ - self::ENVIRONMENT_LOCAL, - [ - 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', - 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', - 'DRUPAL_SHIELD_PRINT' => 'drupal_shield_print', - ], - [ - 'shield.settings' => ['shield_enable' => FALSE, 'credentials' => ['shield' => ['user' => 'drupal_shield_user', 'pass' => 'drupal_shield_pass']], 'print' => 'drupal_shield_print'], - ], + 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', + ], + [ + 'shield.settings' => ['shield_enable' => FALSE], + ], + [ + 'shield.settings' => ['credentials' => ['shield' => ['user' => 'drupal_shield_user', 'pass' => 'drupal_shield_pass']], 'print' => 'drupal_shield_print'], ], + ]; + yield [ + self::ENVIRONMENT_LOCAL, + [ + 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', + 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', + 'DRUPAL_SHIELD_PRINT' => 'drupal_shield_print', + ], + [ + 'shield.settings' => ['shield_enable' => FALSE, 'credentials' => ['shield' => ['user' => 'drupal_shield_user', 'pass' => 'drupal_shield_pass']], 'print' => 'drupal_shield_print'], + ], + ]; + yield [ + self::ENVIRONMENT_CI, [ - self::ENVIRONMENT_CI, - [ - 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', - 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', - 'DRUPAL_SHIELD_PRINT' => 'drupal_shield_print', - ], - [ - 'shield.settings' => ['shield_enable' => FALSE, 'credentials' => ['shield' => ['user' => 'drupal_shield_user', 'pass' => 'drupal_shield_pass']], 'print' => 'drupal_shield_print'], - ], + 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', + 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', + 'DRUPAL_SHIELD_PRINT' => 'drupal_shield_print', ], + [ + 'shield.settings' => ['shield_enable' => FALSE, 'credentials' => ['shield' => ['user' => 'drupal_shield_user', 'pass' => 'drupal_shield_pass']], 'print' => 'drupal_shield_print'], + ], + ]; + yield [ + self::ENVIRONMENT_DEV, [ - self::ENVIRONMENT_DEV, - [ - 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', - 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', - 'DRUPAL_SHIELD_PRINT' => 'drupal_shield_print', - ], - [ - 'shield.settings' => ['shield_enable' => TRUE, 'credentials' => ['shield' => ['user' => 'drupal_shield_user', 'pass' => 'drupal_shield_pass']], 'print' => 'drupal_shield_print'], - ], + 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', + 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', + 'DRUPAL_SHIELD_PRINT' => 'drupal_shield_print', + ], + [ + 'shield.settings' => ['shield_enable' => TRUE, 'credentials' => ['shield' => ['user' => 'drupal_shield_user', 'pass' => 'drupal_shield_pass']], 'print' => 'drupal_shield_print'], ], + ]; + yield [ + self::ENVIRONMENT_STAGE, [ - self::ENVIRONMENT_STAGE, - [ - 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', - 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', - 'DRUPAL_SHIELD_PRINT' => 'drupal_shield_print', - ], - [ - 'shield.settings' => ['shield_enable' => TRUE, 'credentials' => ['shield' => ['user' => 'drupal_shield_user', 'pass' => 'drupal_shield_pass']], 'print' => 'drupal_shield_print'], - ], + 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', + 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', + 'DRUPAL_SHIELD_PRINT' => 'drupal_shield_print', + ], + [ + 'shield.settings' => ['shield_enable' => TRUE, 'credentials' => ['shield' => ['user' => 'drupal_shield_user', 'pass' => 'drupal_shield_pass']], 'print' => 'drupal_shield_print'], ], + ]; + yield [ + self::ENVIRONMENT_PROD, [ - self::ENVIRONMENT_PROD, - [ - 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', - 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', - 'DRUPAL_SHIELD_PRINT' => 'drupal_shield_print', - ], - [ - 'shield.settings' => ['credentials' => ['shield' => ['user' => 'drupal_shield_user', 'pass' => 'drupal_shield_pass']], 'print' => 'drupal_shield_print'], - ], - [ - 'shield.settings' => ['shield_enable' => FALSE], - ], + 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', + 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', + 'DRUPAL_SHIELD_PRINT' => 'drupal_shield_print', + ], + [ + 'shield.settings' => ['credentials' => ['shield' => ['user' => 'drupal_shield_user', 'pass' => 'drupal_shield_pass']], 'print' => 'drupal_shield_print'], + ], + [ + 'shield.settings' => ['shield_enable' => FALSE], ], + ]; + yield [ + self::ENVIRONMENT_SUT, [ - self::ENVIRONMENT_SUT, - [ - 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', - 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', - 'DRUPAL_SHIELD_PRINT' => 'drupal_shield_print', - ], - [ - 'shield.settings' => ['shield_enable' => TRUE, 'credentials' => ['shield' => ['user' => 'drupal_shield_user', 'pass' => 'drupal_shield_pass']], 'print' => 'drupal_shield_print'], - ], + 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', + 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', + 'DRUPAL_SHIELD_PRINT' => 'drupal_shield_print', ], + [ + 'shield.settings' => ['shield_enable' => TRUE, 'credentials' => ['shield' => ['user' => 'drupal_shield_user', 'pass' => 'drupal_shield_pass']], 'print' => 'drupal_shield_print'], + ], + ]; + yield [ + self::ENVIRONMENT_DEV, [ - self::ENVIRONMENT_DEV, - [ - 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', - 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', - 'DRUPAL_SHIELD_PRINT' => 'drupal_shield_print', - 'DRUPAL_SHIELD_DISABLED' => '', - ], - [ - 'shield.settings' => ['shield_enable' => TRUE, 'credentials' => ['shield' => ['user' => 'drupal_shield_user', 'pass' => 'drupal_shield_pass']], 'print' => 'drupal_shield_print'], - ], + 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', + 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', + 'DRUPAL_SHIELD_PRINT' => 'drupal_shield_print', + 'DRUPAL_SHIELD_DISABLED' => '', + ], + [ + 'shield.settings' => ['shield_enable' => TRUE, 'credentials' => ['shield' => ['user' => 'drupal_shield_user', 'pass' => 'drupal_shield_pass']], 'print' => 'drupal_shield_print'], ], + ]; + yield [ + self::ENVIRONMENT_DEV, [ - self::ENVIRONMENT_DEV, - [ - 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', - 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', - 'DRUPAL_SHIELD_PRINT' => 'drupal_shield_print', - 'DRUPAL_SHIELD_DISABLED' => 0, - ], - [ - 'shield.settings' => ['shield_enable' => TRUE, 'credentials' => ['shield' => ['user' => 'drupal_shield_user', 'pass' => 'drupal_shield_pass']], 'print' => 'drupal_shield_print'], - ], + 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', + 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', + 'DRUPAL_SHIELD_PRINT' => 'drupal_shield_print', + 'DRUPAL_SHIELD_DISABLED' => 0, ], [ - self::ENVIRONMENT_DEV, - [ - 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', - 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', - 'DRUPAL_SHIELD_PRINT' => 'drupal_shield_print', - 'DRUPAL_SHIELD_DISABLED' => 1, - ], - [ - 'shield.settings' => ['shield_enable' => FALSE, 'credentials' => ['shield' => ['user' => 'drupal_shield_user', 'pass' => 'drupal_shield_pass']], 'print' => 'drupal_shield_print'], - ], + 'shield.settings' => ['shield_enable' => TRUE, 'credentials' => ['shield' => ['user' => 'drupal_shield_user', 'pass' => 'drupal_shield_pass']], 'print' => 'drupal_shield_print'], + ], + ]; + yield [ + self::ENVIRONMENT_DEV, + [ + 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', + 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', + 'DRUPAL_SHIELD_PRINT' => 'drupal_shield_print', + 'DRUPAL_SHIELD_DISABLED' => 1, ], + [ + 'shield.settings' => ['shield_enable' => FALSE, 'credentials' => ['shield' => ['user' => 'drupal_shield_user', 'pass' => 'drupal_shield_pass']], 'print' => 'drupal_shield_print'], + ], + ]; + yield [ + self::ENVIRONMENT_DEV, [ - self::ENVIRONMENT_DEV, - [ - 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', - 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', - 'DRUPAL_SHIELD_PRINT' => 'drupal_shield_print', - 'DRUPAL_SHIELD_DISABLED' => '0', - ], - [ - 'shield.settings' => ['shield_enable' => TRUE, 'credentials' => ['shield' => ['user' => 'drupal_shield_user', 'pass' => 'drupal_shield_pass']], 'print' => 'drupal_shield_print'], - ], + 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', + 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', + 'DRUPAL_SHIELD_PRINT' => 'drupal_shield_print', + 'DRUPAL_SHIELD_DISABLED' => '0', ], [ - self::ENVIRONMENT_DEV, - [ - 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', - 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', - 'DRUPAL_SHIELD_PRINT' => 'drupal_shield_print', - 'DRUPAL_SHIELD_DISABLED' => '1', - ], - [ - 'shield.settings' => ['shield_enable' => FALSE, 'credentials' => ['shield' => ['user' => 'drupal_shield_user', 'pass' => 'drupal_shield_pass']], 'print' => 'drupal_shield_print'], - ], + 'shield.settings' => ['shield_enable' => TRUE, 'credentials' => ['shield' => ['user' => 'drupal_shield_user', 'pass' => 'drupal_shield_pass']], 'print' => 'drupal_shield_print'], ], + ]; + yield [ + self::ENVIRONMENT_DEV, [ - self::ENVIRONMENT_DEV, - [ - 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', - 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', - 'DRUPAL_SHIELD_PRINT' => 'drupal_shield_print', - 'DRUPAL_SHIELD_DISABLED' => 'false', - ], - [ - 'shield.settings' => ['shield_enable' => FALSE, 'credentials' => ['shield' => ['user' => 'drupal_shield_user', 'pass' => 'drupal_shield_pass']], 'print' => 'drupal_shield_print'], - ], + 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', + 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', + 'DRUPAL_SHIELD_PRINT' => 'drupal_shield_print', + 'DRUPAL_SHIELD_DISABLED' => '1', ], [ - self::ENVIRONMENT_DEV, - [ - 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', - 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', - 'DRUPAL_SHIELD_PRINT' => 'drupal_shield_print', - 'DRUPAL_SHIELD_DISABLED' => 'true', - ], - [ - 'shield.settings' => ['shield_enable' => FALSE, 'credentials' => ['shield' => ['user' => 'drupal_shield_user', 'pass' => 'drupal_shield_pass']], 'print' => 'drupal_shield_print'], - ], + 'shield.settings' => ['shield_enable' => FALSE, 'credentials' => ['shield' => ['user' => 'drupal_shield_user', 'pass' => 'drupal_shield_pass']], 'print' => 'drupal_shield_print'], + ], + ]; + yield [ + self::ENVIRONMENT_DEV, + [ + 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', + 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', + 'DRUPAL_SHIELD_PRINT' => 'drupal_shield_print', + 'DRUPAL_SHIELD_DISABLED' => 'false', ], + [ + 'shield.settings' => ['shield_enable' => FALSE, 'credentials' => ['shield' => ['user' => 'drupal_shield_user', 'pass' => 'drupal_shield_pass']], 'print' => 'drupal_shield_print'], + ], + ]; + yield [ + self::ENVIRONMENT_DEV, + [ + 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', + 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', + 'DRUPAL_SHIELD_PRINT' => 'drupal_shield_print', + 'DRUPAL_SHIELD_DISABLED' => 'true', + ], + [ + 'shield.settings' => ['shield_enable' => FALSE, 'credentials' => ['shield' => ['user' => 'drupal_shield_user', 'pass' => 'drupal_shield_pass']], 'print' => 'drupal_shield_print'], + ], + ]; + yield [ + self::ENVIRONMENT_DEV, [ - self::ENVIRONMENT_DEV, - [ - 'DRUPAL_SHIELD_DISABLED' => TRUE, - ], - [ - 'shield.settings' => ['shield_enable' => FALSE], - ], + 'DRUPAL_SHIELD_DISABLED' => TRUE, ], [ - self::ENVIRONMENT_STAGE, - [ - 'DRUPAL_SHIELD_DISABLED' => TRUE, - ], - [ - 'shield.settings' => ['shield_enable' => FALSE], - ], + 'shield.settings' => ['shield_enable' => FALSE], ], + ]; + yield [ + self::ENVIRONMENT_STAGE, [ - self::ENVIRONMENT_PROD, - [ - 'DRUPAL_SHIELD_DISABLED' => TRUE, - ], - [ - 'shield.settings' => ['shield_enable' => FALSE], - ], + 'DRUPAL_SHIELD_DISABLED' => TRUE, ], + [ + 'shield.settings' => ['shield_enable' => FALSE], + ], + ]; + yield [ + self::ENVIRONMENT_PROD, + [ + 'DRUPAL_SHIELD_DISABLED' => TRUE, + ], + [ + 'shield.settings' => ['shield_enable' => FALSE], + ], + ]; - // ACME challenge passthrough tests. + // ACME challenge passthrough tests. + yield [ + self::ENVIRONMENT_DEV, [ - self::ENVIRONMENT_DEV, - [ - 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', - 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', - 'DRUPAL_SHIELD_ALLOW_ACME_CHALLENGE' => 1, - ], - [ - 'shield.settings' => [ - 'shield_enable' => TRUE, - 'credentials' => ['shield' => ['user' => 'drupal_shield_user', 'pass' => 'drupal_shield_pass']], - 'method' => 0, - 'paths' => '/.well-known/acme-challenge/*', - ], - ], + 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', + 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', + 'DRUPAL_SHIELD_ALLOW_ACME_CHALLENGE' => 1, ], [ - self::ENVIRONMENT_LOCAL, - [ - 'DRUPAL_SHIELD_ALLOW_ACME_CHALLENGE' => 1, - ], - [ - 'shield.settings' => [ - 'shield_enable' => FALSE, - 'method' => 0, - 'paths' => '/.well-known/acme-challenge/*', - ], + 'shield.settings' => [ + 'shield_enable' => TRUE, + 'credentials' => ['shield' => ['user' => 'drupal_shield_user', 'pass' => 'drupal_shield_pass']], + 'method' => 0, + 'paths' => '/.well-known/acme-challenge/*', ], ], - // ACME challenge disabled - verify settings are absent. + ]; + yield [ + self::ENVIRONMENT_LOCAL, + [ + 'DRUPAL_SHIELD_ALLOW_ACME_CHALLENGE' => 1, + ], [ - self::ENVIRONMENT_DEV, - [ - 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', - 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', - ], - [ - 'shield.settings' => [ - 'shield_enable' => TRUE, - 'credentials' => ['shield' => ['user' => 'drupal_shield_user', 'pass' => 'drupal_shield_pass']], - ], - ], - [ - 'shield.settings' => ['method' => NULL, 'paths' => NULL], + 'shield.settings' => [ + 'shield_enable' => FALSE, + 'method' => 0, + 'paths' => '/.well-known/acme-challenge/*', ], ], - // ACME challenge with empty value - should not set. + ]; + // ACME challenge disabled - verify settings are absent. + yield [ + self::ENVIRONMENT_DEV, + [ + 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', + 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', + ], [ - self::ENVIRONMENT_DEV, - [ - 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', - 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', - 'DRUPAL_SHIELD_ALLOW_ACME_CHALLENGE' => '', + 'shield.settings' => [ + 'shield_enable' => TRUE, + 'credentials' => ['shield' => ['user' => 'drupal_shield_user', 'pass' => 'drupal_shield_pass']], ], - [ - 'shield.settings' => [ - 'shield_enable' => TRUE, - 'credentials' => ['shield' => ['user' => 'drupal_shield_user', 'pass' => 'drupal_shield_pass']], - ], + ], + [ + 'shield.settings' => ['method' => NULL, 'paths' => NULL], + ], + ]; + // ACME challenge with empty value - should not set. + yield [ + self::ENVIRONMENT_DEV, + [ + 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', + 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', + 'DRUPAL_SHIELD_ALLOW_ACME_CHALLENGE' => '', + ], + [ + 'shield.settings' => [ + 'shield_enable' => TRUE, + 'credentials' => ['shield' => ['user' => 'drupal_shield_user', 'pass' => 'drupal_shield_pass']], ], - [ - 'shield.settings' => ['method' => NULL, 'paths' => NULL], + ], + [ + 'shield.settings' => ['method' => NULL, 'paths' => NULL], + ], + ]; + // ACME challenge with 0 value - should not set. + yield [ + self::ENVIRONMENT_DEV, + [ + 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', + 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', + 'DRUPAL_SHIELD_ALLOW_ACME_CHALLENGE' => 0, + ], + [ + 'shield.settings' => [ + 'shield_enable' => TRUE, + 'credentials' => ['shield' => ['user' => 'drupal_shield_user', 'pass' => 'drupal_shield_pass']], ], ], - // ACME challenge with 0 value - should not set. [ - self::ENVIRONMENT_DEV, - [ - 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', - 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', - 'DRUPAL_SHIELD_ALLOW_ACME_CHALLENGE' => 0, + 'shield.settings' => ['method' => NULL, 'paths' => NULL], + ], + ]; + + yield [ + self::ENVIRONMENT_DEV, + [ + 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', + 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', + 'DRUPAL_SHIELD_ALLOW_ACME_CHALLENGE' => 1, + ], + [ + 'shield.settings' => [ + 'method' => 0, + 'paths' => "/custom/path/*\n/.well-known/acme-challenge/*", ], - [ - 'shield.settings' => [ - 'shield_enable' => TRUE, - 'credentials' => ['shield' => ['user' => 'drupal_shield_user', 'pass' => 'drupal_shield_pass']], - ], + ], + [], + [ + 'shield.settings' => ['paths' => '/custom/path/*'], + ], + ]; + yield [ + self::ENVIRONMENT_DEV, + [ + 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', + 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', + 'DRUPAL_SHIELD_ALLOW_ACME_CHALLENGE' => 1, + ], + [ + 'shield.settings' => [ + 'method' => 0, + 'paths' => "/.well-known/acme-challenge/*\n/other/path/*", ], - [ - 'shield.settings' => ['method' => NULL, 'paths' => NULL], + ], + [], + [ + 'shield.settings' => ['paths' => "/.well-known/acme-challenge/*\n/other/path/*"], + ], + ]; + yield [ + self::ENVIRONMENT_DEV, + [ + 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', + 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', + 'DRUPAL_SHIELD_ALLOW_ACME_CHALLENGE' => 1, + ], + [ + 'shield.settings' => [ + 'method' => 0, + 'paths' => "/admin/*\n/api/*\n/.well-known/acme-challenge/*", ], ], + [], + [ + 'shield.settings' => ['paths' => "/admin/*\n/api/*"], + ], ]; } @@ -632,114 +681,112 @@ public function testStageFileProxy(string $env, array $vars, array $expected_pre /** * Data provider for testStageFileProxy(). */ - public static function dataProviderStageFileProxy(): array { - return [ - [ - self::ENVIRONMENT_LOCAL, - [], - [], - [ - 'stage_file_proxy.settings' => ['hotlink' => FALSE, 'origin' => 'https://example.com/'], - ], + public static function dataProviderStageFileProxy(): \Iterator { + yield [ + self::ENVIRONMENT_LOCAL, + [], + [], + [ + 'stage_file_proxy.settings' => ['hotlink' => FALSE, 'origin' => 'https://example.com/'], ], + ]; + yield [ + self::ENVIRONMENT_LOCAL, [ - self::ENVIRONMENT_LOCAL, - [ - 'DRUPAL_STAGE_FILE_PROXY_ORIGIN' => 'https://example.com/', - ], - [ - 'stage_file_proxy.settings' => ['hotlink' => FALSE, 'origin' => 'https://example.com/'], - ], - [], + 'DRUPAL_STAGE_FILE_PROXY_ORIGIN' => 'https://example.com/', ], [ - self::ENVIRONMENT_LOCAL, - [ - 'DRUPAL_STAGE_FILE_PROXY_ORIGIN' => 'https://example.com/', - 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', - 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', - ], - [ - 'stage_file_proxy.settings' => ['hotlink' => FALSE, 'origin' => 'https://drupal_shield_user:drupal_shield_pass@example.com/'], - ], - [], + 'stage_file_proxy.settings' => ['hotlink' => FALSE, 'origin' => 'https://example.com/'], ], + [], + ]; + yield [ + self::ENVIRONMENT_LOCAL, [ - self::ENVIRONMENT_LOCAL, - [ - 'DRUPAL_STAGE_FILE_PROXY_ORIGIN' => 'https://example.com/', - 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', - ], - [ - 'stage_file_proxy.settings' => ['hotlink' => FALSE, 'origin' => 'https://example.com/'], - ], - [], + 'DRUPAL_STAGE_FILE_PROXY_ORIGIN' => 'https://example.com/', + 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', + 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', + ], + [ + 'stage_file_proxy.settings' => ['hotlink' => FALSE, 'origin' => 'https://drupal_shield_user:drupal_shield_pass@example.com/'], + ], + [], + ]; + yield [ + self::ENVIRONMENT_LOCAL, + [ + 'DRUPAL_STAGE_FILE_PROXY_ORIGIN' => 'https://example.com/', + 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', + ], + [ + 'stage_file_proxy.settings' => ['hotlink' => FALSE, 'origin' => 'https://example.com/'], ], + [], + ]; + yield [ + self::ENVIRONMENT_CI, [ - self::ENVIRONMENT_CI, - [ - 'DRUPAL_STAGE_FILE_PROXY_ORIGIN' => 'https://example.com/', - 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', - 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', - ], - [ - 'stage_file_proxy.settings' => ['hotlink' => FALSE, 'origin' => 'https://drupal_shield_user:drupal_shield_pass@example.com/'], - ], - [], + 'DRUPAL_STAGE_FILE_PROXY_ORIGIN' => 'https://example.com/', + 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', + 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', + ], + [ + 'stage_file_proxy.settings' => ['hotlink' => FALSE, 'origin' => 'https://drupal_shield_user:drupal_shield_pass@example.com/'], ], + [], + ]; + yield [ + self::ENVIRONMENT_DEV, [ - self::ENVIRONMENT_DEV, - [ - 'DRUPAL_STAGE_FILE_PROXY_ORIGIN' => 'https://example.com/', - 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', - 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', - ], - [ - 'stage_file_proxy.settings' => ['hotlink' => FALSE, 'origin' => 'https://drupal_shield_user:drupal_shield_pass@example.com/'], - ], - [], + 'DRUPAL_STAGE_FILE_PROXY_ORIGIN' => 'https://example.com/', + 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', + 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', + ], + [ + 'stage_file_proxy.settings' => ['hotlink' => FALSE, 'origin' => 'https://drupal_shield_user:drupal_shield_pass@example.com/'], ], + [], + ]; + yield [ + self::ENVIRONMENT_STAGE, [ - self::ENVIRONMENT_STAGE, - [ - 'DRUPAL_STAGE_FILE_PROXY_ORIGIN' => 'https://example.com/', - 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', - 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', - ], - [ - 'stage_file_proxy.settings' => ['hotlink' => FALSE, 'origin' => 'https://drupal_shield_user:drupal_shield_pass@example.com/'], - ], - [], + 'DRUPAL_STAGE_FILE_PROXY_ORIGIN' => 'https://example.com/', + 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', + 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', ], + [ + 'stage_file_proxy.settings' => ['hotlink' => FALSE, 'origin' => 'https://drupal_shield_user:drupal_shield_pass@example.com/'], + ], + [], + ]; + yield [ + self::ENVIRONMENT_PROD, [ - self::ENVIRONMENT_PROD, - [ - 'DRUPAL_STAGE_FILE_PROXY_ORIGIN' => 'https://example.com/', - 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', - 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', - ], - [], - [ - 'stage_file_proxy.settings' => ['hotlink' => FALSE, 'origin' => 'https://drupal_shield_user:drupal_shield_pass@example.com/'], - ], + 'DRUPAL_STAGE_FILE_PROXY_ORIGIN' => 'https://example.com/', + 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', + 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', + ], + [], + [ + 'stage_file_proxy.settings' => ['hotlink' => FALSE, 'origin' => 'https://drupal_shield_user:drupal_shield_pass@example.com/'], ], + ]; + yield [ + self::ENVIRONMENT_SUT, [ - self::ENVIRONMENT_SUT, - [ - 'DRUPAL_STAGE_FILE_PROXY_ORIGIN' => 'https://example.com/', - 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', - 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', - ], - [ - 'stage_file_proxy.settings' => ['hotlink' => FALSE, 'origin' => 'https://drupal_shield_user:drupal_shield_pass@example.com/'], - ], - [], + 'DRUPAL_STAGE_FILE_PROXY_ORIGIN' => 'https://example.com/', + 'DRUPAL_SHIELD_USER' => 'drupal_shield_user', + 'DRUPAL_SHIELD_PASS' => 'drupal_shield_pass', + ], + [ + 'stage_file_proxy.settings' => ['hotlink' => FALSE, 'origin' => 'https://drupal_shield_user:drupal_shield_pass@example.com/'], ], + [], ]; } @@ -758,83 +805,81 @@ public function testTrustedHostPatterns(array $vars, array $expected_patterns): /** * Data provider for testTrustedHostPatterns(). */ - public static function dataProviderTrustedHostPatterns(): array { - return [ - 'empty environment variable' => [ - [], - [ - '^localhost$', - ], + public static function dataProviderTrustedHostPatterns(): \Iterator { + yield 'empty environment variable' => [ + [], + [ + '^localhost$', ], - 'single domain' => [ - ['DRUPAL_TRUSTED_HOSTS' => 'example.com'], - [ - '^localhost$', - '^example\.com$', - ], + ]; + yield 'single domain' => [ + ['DRUPAL_TRUSTED_HOSTS' => 'example.com'], + [ + '^localhost$', + '^example\.com$', ], - 'multiple domains' => [ - ['DRUPAL_TRUSTED_HOSTS' => 'example.com,www.example.com,cdn.example.org'], - [ - '^localhost$', - '^example\.com$', - '^www\.example\.com$', - '^cdn\.example\.org$', - ], + ]; + yield 'multiple domains' => [ + ['DRUPAL_TRUSTED_HOSTS' => 'example.com,www.example.com,cdn.example.org'], + [ + '^localhost$', + '^example\.com$', + '^www\.example\.com$', + '^cdn\.example\.org$', ], - 'whitespace and empty values' => [ - ['DRUPAL_TRUSTED_HOSTS' => ' example.com , , www.example.com '], - [ - '^localhost$', - '^example\.com$', - '^www\.example\.com$', - ], + ]; + yield 'whitespace and empty values' => [ + ['DRUPAL_TRUSTED_HOSTS' => ' example.com , , www.example.com '], + [ + '^localhost$', + '^example\.com$', + '^www\.example\.com$', ], - 'special regex characters' => [ - ['DRUPAL_TRUSTED_HOSTS' => 'sub-domain.example.com,test.example-site.org'], - [ - '^localhost$', - '^sub\-domain\.example\.com$', - '^test\.example\-site\.org$', - ], + ]; + yield 'special regex characters' => [ + ['DRUPAL_TRUSTED_HOSTS' => 'sub-domain.example.com,test.example-site.org'], + [ + '^localhost$', + '^sub\-domain\.example\.com$', + '^test\.example\-site\.org$', ], - 'complex domains' => [ - ['DRUPAL_TRUSTED_HOSTS' => 'api.v2.example.com,cdn-assets.example-site.co.uk'], - [ - '^localhost$', - '^api\.v2\.example\.com$', - '^cdn\-assets\.example\-site\.co\.uk$', - ], + ]; + yield 'complex domains' => [ + ['DRUPAL_TRUSTED_HOSTS' => 'api.v2.example.com,cdn-assets.example-site.co.uk'], + [ + '^localhost$', + '^api\.v2\.example\.com$', + '^cdn\-assets\.example\-site\.co\.uk$', ], - 'duplicates' => [ - ['DRUPAL_TRUSTED_HOSTS' => 'example.com,test.org,example.com,another.com,test.org'], - [ - '^localhost$', - '^example\.com$', - '^test\.org$', - '^example\.com$', - '^another\.com$', - '^test\.org$', - ], + ]; + yield 'duplicates' => [ + ['DRUPAL_TRUSTED_HOSTS' => 'example.com,test.org,example.com,another.com,test.org'], + [ + '^localhost$', + '^example\.com$', + '^test\.org$', + '^example\.com$', + '^another\.com$', + '^test\.org$', ], - 'uppercase hosts' => [ - ['DRUPAL_TRUSTED_HOSTS' => 'EXAMPLE.COM,Test.ORG,www.EXAMPLE-SITE.CO.UK'], - [ - '^localhost$', - '^example\.com$', - '^test\.org$', - '^www\.example\-site\.co\.uk$', - ], + ]; + yield 'uppercase hosts' => [ + ['DRUPAL_TRUSTED_HOSTS' => 'EXAMPLE.COM,Test.ORG,www.EXAMPLE-SITE.CO.UK'], + [ + '^localhost$', + '^example\.com$', + '^test\.org$', + '^www\.example\-site\.co\.uk$', ], - 'explicit localhost' => [ - ['DRUPAL_TRUSTED_HOSTS' => 'localhost,example.com,localhost,test.org'], - [ - '^localhost$', - '^localhost$', - '^example\.com$', - '^localhost$', - '^test\.org$', - ], + ]; + yield 'explicit localhost' => [ + ['DRUPAL_TRUSTED_HOSTS' => 'localhost,example.com,localhost,test.org'], + [ + '^localhost$', + '^localhost$', + '^example\.com$', + '^localhost$', + '^test\.org$', ], ]; } diff --git a/web/modules/custom/do_feed/tests/src/Kernel/Form/FeedSettingsFormTest.php b/web/modules/custom/do_feed/tests/src/Kernel/Form/FeedSettingsFormTest.php index 5b07587..009c982 100644 --- a/web/modules/custom/do_feed/tests/src/Kernel/Form/FeedSettingsFormTest.php +++ b/web/modules/custom/do_feed/tests/src/Kernel/Form/FeedSettingsFormTest.php @@ -90,15 +90,13 @@ public function testSubmitAndBuildRoundTrip(string $prefix): void { /** * Data provider for `testSubmitAndBuildRoundTrip()`. * - * @return array + * @return \Iterator * Test cases with path prefix values. */ - public static function dataProviderSubmitAndBuildRoundTrip(): array { - return [ - 'short prefix' => ['rss'], - 'hyphenated prefix' => ['custom-feed'], - 'single character' => ['f'], - ]; + public static function dataProviderSubmitAndBuildRoundTrip(): \Iterator { + yield 'short prefix' => ['rss']; + yield 'hyphenated prefix' => ['custom-feed']; + yield 'single character' => ['f']; } } diff --git a/web/modules/custom/do_feed/tests/src/Unit/FeedUrlBuilderTest.php b/web/modules/custom/do_feed/tests/src/Unit/FeedUrlBuilderTest.php index 24e3476..ae3550b 100644 --- a/web/modules/custom/do_feed/tests/src/Unit/FeedUrlBuilderTest.php +++ b/web/modules/custom/do_feed/tests/src/Unit/FeedUrlBuilderTest.php @@ -103,26 +103,24 @@ public function testBuildInternalPath(?string $content_type, array $topic_ids, a /** * Data provider for testBuildInternalPath. */ - public static function dataProviderBuildInternalPath(): array { - return [ - 'blog list with single topic' => [ - 'civictheme_page', [1], [], 'feed/civictheme_page/1/all', - ], - 'multi-topic list' => [ - 'civictheme_page', [1, 2, 3], [], 'feed/civictheme_page/1+2+3/all', - ], - 'no topics' => [ - 'civictheme_page', [], [], 'feed/civictheme_page/all/all', - ], - 'no content type' => [ - NULL, [], [], 'feed/all/all/all', - ], - 'with site sections' => [ - 'civictheme_page', [1], [5, 6], 'feed/civictheme_page/1/5+6', - ], - 'custom path prefix' => [ - 'civictheme_page', [1], [], 'rss/civictheme_page/1/all', 'rss', - ], + public static function dataProviderBuildInternalPath(): \Iterator { + yield 'blog list with single topic' => [ + 'civictheme_page', [1], [], 'feed/civictheme_page/1/all', + ]; + yield 'multi-topic list' => [ + 'civictheme_page', [1, 2, 3], [], 'feed/civictheme_page/1+2+3/all', + ]; + yield 'no topics' => [ + 'civictheme_page', [], [], 'feed/civictheme_page/all/all', + ]; + yield 'no content type' => [ + NULL, [], [], 'feed/all/all/all', + ]; + yield 'with site sections' => [ + 'civictheme_page', [1], [5, 6], 'feed/civictheme_page/1/5+6', + ]; + yield 'custom path prefix' => [ + 'civictheme_page', [1], [], 'rss/civictheme_page/1/all', 'rss', ]; } diff --git a/web/modules/custom/do_feed/tests/src/Unit/Hook/PreprocessViewsViewRowRssHookTest.php b/web/modules/custom/do_feed/tests/src/Unit/Hook/PreprocessViewsViewRowRssHookTest.php index 7c71777..a8bc16b 100644 --- a/web/modules/custom/do_feed/tests/src/Unit/Hook/PreprocessViewsViewRowRssHookTest.php +++ b/web/modules/custom/do_feed/tests/src/Unit/Hook/PreprocessViewsViewRowRssHookTest.php @@ -33,37 +33,35 @@ public function testSvgStripping(string $input, string $expected): void { /** * Data provider for `testSvgStripping()`. * - * @return array + * @return \Iterator * Test cases: input description, expected output. */ - public static function dataProviderSvgStripping(): array { - return [ - 'no svg' => [ - '

Hello world

', - '

Hello world

', - ], - 'simple svg' => [ - '

Text

More

', - '

Text

More

', - ], - 'svg with attributes' => [ - '

Text

More

', - '

Text

More

', - ], - 'multiple svgs' => [ - '

Between

', - '

Between

', - ], - 'multiline svg' => [ - "

Text

\n\n \n\n

More

", - "

Text

\n\n

More

", - ], - 'svg with inner svg-like content' => [ - '

After

', - '

After

', - ], - 'empty string' => ['', ''], + public static function dataProviderSvgStripping(): \Iterator { + yield 'no svg' => [ + '

Hello world

', + '

Hello world

', ]; + yield 'simple svg' => [ + '

Text

More

', + '

Text

More

', + ]; + yield 'svg with attributes' => [ + '

Text

More

', + '

Text

More

', + ]; + yield 'multiple svgs' => [ + '

Between

', + '

Between

', + ]; + yield 'multiline svg' => [ + "

Text

\n\n \n\n

More

", + "

Text

\n\n

More

", + ]; + yield 'svg with inner svg-like content' => [ + '

After

', + '

After

', + ]; + yield 'empty string' => ['', '']; } /** @@ -81,23 +79,21 @@ public function testSkipsNonString(array $variables, array $expected): void { /** * Data provider for `testSkipsNonString()`. * - * @return array, array}> + * @return \Iterator, array}> * Test cases: input variables, expected variables. */ - public static function dataProviderSkipsNonString(): array { - return [ - 'no description key' => [ + public static function dataProviderSkipsNonString(): \Iterator { + yield 'no description key' => [ ['title' => 'Test'], ['title' => 'Test'], - ], - 'null description' => [ + ]; + yield 'null description' => [ ['description' => NULL], ['description' => NULL], - ], - 'array description' => [ + ]; + yield 'array description' => [ ['description' => ['#markup' => '']], ['description' => ['#markup' => '']], - ], ]; } diff --git a/web/modules/custom/do_feed/tests/src/Unit/Hook/ViewsPreViewHookTest.php b/web/modules/custom/do_feed/tests/src/Unit/Hook/ViewsPreViewHookTest.php index 6f2a9ae..6733c8f 100644 --- a/web/modules/custom/do_feed/tests/src/Unit/Hook/ViewsPreViewHookTest.php +++ b/web/modules/custom/do_feed/tests/src/Unit/Hook/ViewsPreViewHookTest.php @@ -52,15 +52,13 @@ public function testEarlyReturnConditions(string $view_id, string $display_id, b /** * Data provider for `testEarlyReturnConditions()`. * - * @return array + * @return \Iterator * Test cases: view_id, display_id, has_request. */ - public static function dataProviderEarlyReturnConditions(): array { - return [ - 'wrong view id' => ['other_view', 'feed_1', TRUE], - 'wrong display id' => ['feed', 'page_1', TRUE], - 'no request' => ['feed', 'feed_1', FALSE], - ]; + public static function dataProviderEarlyReturnConditions(): \Iterator { + yield 'wrong view id' => ['other_view', 'feed_1', TRUE]; + yield 'wrong display id' => ['feed', 'page_1', TRUE]; + yield 'no request' => ['feed', 'feed_1', FALSE]; } /** @@ -222,16 +220,14 @@ public function setTitle($title): bool { /** * Data provider for `testTitleAndDescriptionOverride()`. * - * @return array + * @return \Iterator * Test cases: title, description, expect_title_set, expect_description_set. */ - public static function dataProviderTitleAndDescriptionOverride(): array { - return [ - 'both title and description' => ['My Feed', 'Feed description', TRUE, TRUE], - 'title only' => ['My Feed', '', TRUE, FALSE], - 'description only' => ['', 'Feed description', FALSE, TRUE], - 'neither' => ['', '', FALSE, FALSE], - ]; + public static function dataProviderTitleAndDescriptionOverride(): \Iterator { + yield 'both title and description' => ['My Feed', 'Feed description', TRUE, TRUE]; + yield 'title only' => ['My Feed', '', TRUE, FALSE]; + yield 'description only' => ['', 'Feed description', FALSE, TRUE]; + yield 'neither' => ['', '', FALSE, FALSE]; } } diff --git a/web/sites/default/includes/modules/settings.shield.php b/web/sites/default/includes/modules/settings.shield.php index 4caa1fe..0754a32 100644 --- a/web/sites/default/includes/modules/settings.shield.php +++ b/web/sites/default/includes/modules/settings.shield.php @@ -49,5 +49,7 @@ // Allow ACME challenge path for Let's Encrypt certificate generation. if (!empty(getenv('DRUPAL_SHIELD_ALLOW_ACME_CHALLENGE'))) { $config['shield.settings']['method'] = 0; - $config['shield.settings']['paths'] = '/.well-known/acme-challenge/*'; + $acme_path = '/.well-known/acme-challenge/*'; + $existing = $config['shield.settings']['paths'] ?? ''; + $config['shield.settings']['paths'] = str_contains($existing, $acme_path) ? $existing : trim($existing . "\n" . $acme_path); } diff --git a/web/sites/default/settings.php b/web/sites/default/settings.php index 424c44f..06eac1a 100644 --- a/web/sites/default/settings.php +++ b/web/sites/default/settings.php @@ -180,7 +180,7 @@ // // Keep this code block at the end of this file to take full effect. // @codeCoverageIgnoreStart -if (file_exists($app_root . '/' . $site_path . '/settings.local.php')) { +if (file_exists($app_root . '/' . $site_path . '/settings.local.php') && getenv('DRUPAL_SETTINGS_LOCAL_SKIP') !== '1') { require $app_root . '/' . $site_path . '/settings.local.php'; } // @codeCoverageIgnoreEnd