diff --git a/.ahoy.yml b/.ahoy.yml
index 05486d16e..20055a4e2 100644
--- a/.ahoy.yml
+++ b/.ahoy.yml
@@ -35,7 +35,7 @@ commands:
usage: Show information about this project.
cmd: |
COMPOSE_PROJECT_NAME=${COMPOSE_PROJECT_NAME:-${PWD##*/}} \
- VORTEX_HOST_DB_PORT=$(docker compose port mariadb 3306 2>/dev/null | cut -d : -f 2) \
+ VORTEX_HOST_DB_PORT=$(docker compose port database 3306 2>/dev/null | cut -d : -f 2) \
VORTEX_HOST_SOLR_PORT=$(docker compose port solr 8983 2>/dev/null | cut -d : -f 2) \
VORTEX_HOST_SELENIUM_VNC_PORT=$(docker compose port chrome 7900 2>/dev/null | cut -d : -f 2) \
VORTEX_HOST_HAS_SEQUELACE=$(uname -a | grep -i -q darwin && mdfind -name 'Sequel Ace' 2>/dev/null | grep -q "Ace" && echo 1 || true) \
@@ -45,8 +45,8 @@ commands:
usage: Open DB in Sequel Ace.
cmd: |
uname -a | grep -i -q darwin && mdfind -name 'Sequel Ace' 2>/dev/null |grep -q "Ace" \
- && VORTEX_HOST_DB_PORT="$(docker port $(docker compose ps -q mariadb 2>/dev/null) 3306 2>/dev/null | cut -d : -f 2)" \
- && open "mysql://${MARIADB_USERNAME:-drupal}:${MARIADB_PASSWORD:-drupal}@127.0.0.1:${VORTEX_HOST_DB_PORT}/drupal" -a "Sequel Ace" \
+ && VORTEX_HOST_DB_PORT="$(docker port $(docker compose ps -q database 2>/dev/null) 3306 2>/dev/null | cut -d : -f 2)" \
+ && open "mysql://${DATABASE_USERNAME:-drupal}:${DATABASE_PASSWORD:-drupal}@127.0.0.1:${VORTEX_HOST_DB_PORT}/drupal" -a "Sequel Ace" \
|| echo "Not a supported OS or Sequel Ace is not installed."
# ----------------------------------------------------------------------------
@@ -135,8 +135,8 @@ commands:
usage: Reload the database container using local database image.
cmd: |
ahoy confirm "Running this command will replace your current database. Are you sure?" &&
- docker compose rm --force --stop --volumes mariadb && \
- ahoy up -- --build mariadb && \
+ docker compose rm --force --stop --volumes database && \
+ ahoy up -- --build database && \
ahoy up wait_dependencies && \
sleep 15 && \
ahoy provision && \
diff --git a/.circleci/config.yml b/.circleci/config.yml
index ed724af1a..e40092ecd 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -16,12 +16,12 @@ aliases:
#;< !PROVISION_USE_PROFILE
# SSH key fingerprint to download the database.
# Replace this key fingerprint with your own and remove this comment.
- - &db_ssh_fingerprint "56:f3:3f:51:c3:8f:b3:75:01:90:6e:26:48:e7:48:e1"
+ - &db_ssh_fingerprint "SHA256:6d+U5QubT0eAWz+4N2wt+WM2qx6o4cvyvQ6xILETJ84"
#;> !PROVISION_USE_PROFILE
# SSH key fingerprint to deploy code.
# Replace this key fingerprint with your own and remove this comment.
- - &deploy_ssh_fingerprint "56:f3:3f:51:c3:8f:b3:75:01:90:6e:26:48:e7:48:e1"
+ - &deploy_ssh_fingerprint "SHA256:6d+U5QubT0eAWz+4N2wt+WM2qx6o4cvyvQ6xILETJ84"
#;< !PROVISION_USE_PROFILE
# Schedule to run nightly database build (to cache the database for the next day).
@@ -57,7 +57,7 @@ aliases:
# This container has all the necessary tools to run a dockerized environment.
# @see https://github.com/drevops/ci-runner
# @see https://hub.docker.com/repository/docker/drevops/ci-runner/tags
- - image: drevops/ci-runner:24.11.0
+ - image: drevops/ci-runner:25.1.0
auth:
username: ${VORTEX_CONTAINER_REGISTRY_USER}
password: ${VORTEX_CONTAINER_REGISTRY_PASS}
@@ -146,7 +146,7 @@ jobs:
echo "${VORTEX_CI_DB_CACHE_FALLBACK/no/${CIRCLE_BUILD_NUM}}" | tee /tmp/db_cache_fallback
echo "$(date ${VORTEX_CI_DB_CACHE_TIMESTAMP})" | tee /tmp/db_cache_timestamp
echo "yes" | tee /tmp/db_cache_fallback_yes
- echo 'v24.10.0-db10-{{ checksum "/tmp/db_cache_branch" }}-{{ checksum "/tmp/db_cache_fallback" }}-{{ checksum "/tmp/db_cache_timestamp" }}'
+ echo 'v24.11.0-db11-{{ checksum "/tmp/db_cache_branch" }}-{{ checksum "/tmp/db_cache_fallback" }}-{{ checksum "/tmp/db_cache_timestamp" }}'
- restore_cache:
keys:
@@ -155,10 +155,10 @@ jobs:
# Change 'v1' to 'v2', 'v3' etc., commit and push to force cache reset.
# Lookup cache based on the default branch and a timestamp. Allows
# to use cache from the very first build on the day (sanitized database dump, for example).
- - v24.10.0-db10-{{ checksum "/tmp/db_cache_branch" }}-{{ checksum "/tmp/db_cache_fallback" }}-{{ checksum "/tmp/db_cache_timestamp" }}
+ - v24.11.0-db11-{{ checksum "/tmp/db_cache_branch" }}-{{ checksum "/tmp/db_cache_fallback" }}-{{ checksum "/tmp/db_cache_timestamp" }}
# Fallback to caching by default branch name only. Allows to use
# cache from the branch build on the previous day.
- - v24.10.0-db10-{{ checksum "/tmp/db_cache_branch" }}-{{ checksum "/tmp/db_cache_fallback" }}-
+ - v24.11.0-db11-{{ checksum "/tmp/db_cache_branch" }}-{{ checksum "/tmp/db_cache_fallback" }}-
- run:
name: Download DB
@@ -188,7 +188,7 @@ jobs:
# The cache will not be saved if it already exists.
# Note that the cache fallback flag is enabled for this case in order
# to save cache even if the fallback is not used when restoring it.
- key: v24.10.0-db10-{{ checksum "/tmp/db_cache_branch" }}-{{ checksum "/tmp/db_cache_fallback_yes" }}-{{ checksum "/tmp/db_cache_timestamp" }}
+ key: v24.11.0-db11-{{ checksum "/tmp/db_cache_branch" }}-{{ checksum "/tmp/db_cache_fallback_yes" }}-{{ checksum "/tmp/db_cache_timestamp" }}
paths:
- /root/project/.data
@@ -231,8 +231,8 @@ jobs:
keys:
# Use cached artifacts from previous builds of this branch.
# @see https://circleci.com/docs/2.0/caching/#restoring-cache
- - v24.10.0-db10-{{ checksum "/tmp/db_cache_branch" }}-{{ checksum "/tmp/db_cache_fallback_yes" }}-{{ checksum "/tmp/db_cache_timestamp" }}
- - v24.10.0-db10-{{ checksum "/tmp/db_cache_branch" }}-{{ checksum "/tmp/db_cache_fallback_yes" }}-
+ - v24.11.0-db11-{{ checksum "/tmp/db_cache_branch" }}-{{ checksum "/tmp/db_cache_fallback_yes" }}-{{ checksum "/tmp/db_cache_timestamp" }}
+ - v24.11.0-db11-{{ checksum "/tmp/db_cache_branch" }}-{{ checksum "/tmp/db_cache_fallback_yes" }}-
#;> !PROVISION_USE_PROFILE
- run:
@@ -311,10 +311,7 @@ jobs:
- run:
name: Test with PHPUnit
- command: |
- XDEBUG_ENABLE=true docker compose up -d cli php nginx # Restart stack with XDEBUG enabled for coverage.
- docker compose exec -T -e XDEBUG_MODE=coverage cli vendor/bin/phpunit || [ "${VORTEX_CI_PHPUNIT_IGNORE_FAILURE:-0}" -eq 1 ]
- docker compose up -d cli php nginx # Restart stack without XDEBUG enabled for coverage.
+ command: docker compose exec -T cli vendor/bin/phpunit || [ "${VORTEX_CI_PHPUNIT_IGNORE_FAILURE:-0}" -eq 1 ]
- run:
name: Test with Behat
@@ -488,7 +485,7 @@ jobs:
# Note that here and below we are using "destination" demo image - this
# is to allow updating of this image from CI tests without jeopardizing
# main demo image.
- VORTEX_DB_IMAGE: drevops/vortex-dev-mariadb-drupal-data-demo-destination-10.x
+ VORTEX_DB_IMAGE: drevops/vortex-dev-mariadb-drupal-data-demo-destination-11.x
# Use a separate tag to make sure that pushed image does not affect
# other tests (pushing broken image as 'latest' would fail other tests).
VORTEX_DEPLOY_CONTAINER_REGISTRY_IMAGE_TAG: vortex-dev-didi-database-fi
@@ -506,7 +503,7 @@ jobs:
environment:
VORTEX_DB_DOWNLOAD_SOURCE: VORTEX_CONTAINER_REGISTRY
VORTEX_DB_DOWNLOAD_FORCE: 1
- VORTEX_DB_IMAGE: drevops/vortex-dev-mariadb-drupal-data-demo-destination-10.x
+ VORTEX_DB_IMAGE: drevops/vortex-dev-mariadb-drupal-data-demo-destination-11.x
VORTEX_DEPLOY_CONTAINER_REGISTRY_IMAGE_TAG: vortex-dev-database-ii
# Also, use this job to test pushing of the DB image to the container
# registry so replicate what database-nightly job would do.
@@ -522,7 +519,7 @@ jobs:
vortex-dev-didi-build-fi:
<<: *job_build
environment:
- VORTEX_DB_IMAGE: drevops/vortex-dev-mariadb-drupal-data-demo-destination-10.x:vortex-dev-didi-database-fi
+ VORTEX_DB_IMAGE: drevops/vortex-dev-mariadb-drupal-data-demo-destination-11.x:vortex-dev-didi-database-fi
# Use custom cache key for this workflow to make sure that caches from
# the main workflow are separated from this one.
VORTEX_CI_DB_CACHE_BRANCH: vortex-dev-didi-fi
@@ -530,7 +527,7 @@ jobs:
vortex-dev-didi-build-ii:
<<: *job_build
environment:
- VORTEX_DB_IMAGE: drevops/vortex-dev-mariadb-drupal-data-demo-destination-10.x:vortex-dev-database-ii
+ VORTEX_DB_IMAGE: drevops/vortex-dev-mariadb-drupal-data-demo-destination-11.x:vortex-dev-database-ii
# Use custom cache key for this workflow to make sure that caches from
# the main workflow are separated from this one.
VORTEX_CI_DB_CACHE_BRANCH: vortex-dev-didi-ii
diff --git a/.docker/clamav.dockerfile b/.docker/clamav.dockerfile
index 78b4b7dfd..e88570e42 100644
--- a/.docker/clamav.dockerfile
+++ b/.docker/clamav.dockerfile
@@ -9,7 +9,7 @@
#
# @see https://hub.docker.com/r/uselagoon/commons/tags
# @see https://github.com/uselagoon/lagoon-images/tree/main/images/commons
-FROM uselagoon/commons:24.10.0 AS commons
+FROM uselagoon/commons:24.12.0 AS commons
FROM clamav/clamav:1.4.1
diff --git a/.docker/cli.dockerfile b/.docker/cli.dockerfile
index bd73166ed..380651021 100644
--- a/.docker/cli.dockerfile
+++ b/.docker/cli.dockerfile
@@ -6,7 +6,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:24.10.0
+FROM uselagoon/php-8.3-cli-drupal:24.12.0
# Add missing variables.
# @todo Remove once https://github.com/uselagoon/lagoon/issues/3121 is resolved.
@@ -39,7 +39,7 @@ ENV DRUPAL_CONFIG_PATH=${DRUPAL_CONFIG_PATH}
ENV WEBROOT=${WEBROOT} \
COMPOSER_ALLOW_SUPERUSER=1 \
COMPOSER_CACHE_DIR=/tmp/.composer/cache \
- SIMPLETEST_DB=mysql://drupal:drupal@mariadb/drupal \
+ SIMPLETEST_DB=mysql://drupal:drupal@database/drupal \
SIMPLETEST_BASE_URL=http://nginx:8080 \
SYMFONY_DEPRECATIONS_HELPER=disabled
@@ -52,7 +52,10 @@ ENV WEBROOT=${WEBROOT} \
# reduce build time.
# Adding more tools.
-RUN apk add --no-cache ncurses pv tzdata
+RUN apk add --no-cache ncurses pv tzdata autoconf g++ make \
+ && pecl install pcov \
+ && docker-php-ext-enable pcov \
+ && apk del g++ make autoconf
# Adding patches and scripts.
COPY patches /app/patches
diff --git a/.docker/mariadb.dockerfile b/.docker/database.dockerfile
similarity index 88%
rename from .docker/mariadb.dockerfile
rename to .docker/database.dockerfile
index a19cebbdb..f6943d889 100644
--- a/.docker/mariadb.dockerfile
+++ b/.docker/database.dockerfile
@@ -1,4 +1,4 @@
-# MariaDB container.
+# Database container.
#
# @see https://hub.docker.com/r/uselagoon/mariadb-10.11-drupal/tags
# @see https://github.com/uselagoon/lagoon-images/tree/main/images/mariadb-drupal
@@ -8,7 +8,7 @@
# @see https://github.com/drevops/mariadb-drupal-data
#
# The ARG value will be updated with a value passed from docker-compose.yml
-ARG IMAGE=uselagoon/mariadb-10.11-drupal:24.10.0
+ARG IMAGE=uselagoon/mariadb-10.11-drupal:24.12.0
# hadolint ignore=DL3006
FROM ${IMAGE}
diff --git a/.docker/nginx-drupal.dockerfile b/.docker/nginx-drupal.dockerfile
index 2b599a486..fbe5bddb0 100644
--- a/.docker/nginx-drupal.dockerfile
+++ b/.docker/nginx-drupal.dockerfile
@@ -5,11 +5,11 @@
# hadolint global ignore=DL3018
ARG CLI_IMAGE
# hadolint ignore=DL3006
-FROM ${CLI_IMAGE:-cli} as cli
+FROM ${CLI_IMAGE:-cli} AS cli
# @see https://hub.docker.com/r/uselagoon/nginx-drupal/tags
# @see https://github.com/uselagoon/lagoon-images/tree/main/images/nginx-drupal
-FROM uselagoon/nginx-drupal:24.10.0
+FROM uselagoon/nginx-drupal:24.12.0
# Webroot is used for Nginx docroot configuration.
ARG WEBROOT=web
diff --git a/.docker/php.dockerfile b/.docker/php.dockerfile
index f9da8c0c5..83de7ef1e 100644
--- a/.docker/php.dockerfile
+++ b/.docker/php.dockerfile
@@ -6,11 +6,11 @@
# hadolint global ignore=DL3018
ARG CLI_IMAGE
# hadolint ignore=DL3006
-FROM ${CLI_IMAGE:-cli} as cli
+FROM ${CLI_IMAGE:-cli} AS cli
# @see https://hub.docker.com/r/uselagoon/php-8.3-fpm/tags
# @see https://github.com/uselagoon/lagoon-images/tree/main/images/php-fpm
-FROM uselagoon/php-8.3-fpm:24.10.0
+FROM uselagoon/php-8.3-fpm:24.12.0
RUN apk add --no-cache tzdata
diff --git a/.docker/solr.dockerfile b/.docker/solr.dockerfile
index 2f7256b5f..1577619ff 100644
--- a/.docker/solr.dockerfile
+++ b/.docker/solr.dockerfile
@@ -1,11 +1,11 @@
# Solr container.
ARG CLI_IMAGE
# hadolint ignore=DL3006
-FROM ${CLI_IMAGE} as cli
+FROM ${CLI_IMAGE:-cli} AS cli
# @see https://hub.docker.com/r/uselagoon/solr-8/tags
# @see https://github.com/uselagoon/lagoon-images/blob/main/images/solr/8.Dockerfile
-FROM uselagoon/solr-8:24.10.0
+FROM uselagoon/solr-8:24.12.0
# Solr Jump-start config needs to be manually copied from search_api_solr module
# /app/docroot/modules/contrib/search_api_solr/jump-start/solr8/config-set.
diff --git a/.env b/.env
index cc0d96a3e..df0538b61 100644
--- a/.env
+++ b/.env
@@ -236,7 +236,7 @@ VORTEX_NOTIFY_EMAIL_RECIPIENTS="webmaster@your-site-url.example"
#;< VORTEX_DB_DOWNLOAD_SOURCE_CURL
# URL of the demo database used for demonstration with CURL database
# dump as a type of file source.
-VORTEX_DB_DOWNLOAD_CURL_URL=https://github.com/drevops/vortex/releases/download/1.18.0/db_d10.demo.sql
+VORTEX_DB_DOWNLOAD_CURL_URL=https://github.com/drevops/vortex/releases/download/24.11.0/db_d11.demo.sql
#;> VORTEX_DB_DOWNLOAD_SOURCE_CURL
#;< VORTEX_DB_IMAGE
@@ -245,6 +245,6 @@ VORTEX_DB_DOWNLOAD_CURL_URL=https://github.com/drevops/vortex/releases/download/
#; The line below will be automatically uncommented for database-in-image
#; storage. It is commented out to allow running non-database-in-image
# workflow by default.
-##### VORTEX_DB_IMAGE=drevops/vortex-dev-mariadb-drupal-data-demo-10.x:latest
+##### VORTEX_DB_IMAGE=drevops/vortex-dev-mariadb-drupal-data-demo-11.x:latest
#;> VORTEX_DB_IMAGE
#;> DEMO
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 95cbae23b..1f5924580 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,13 +1,11 @@
## Checklist before requesting a review
-- [ ] I have formatted the subject to include ticket number as `[#123] Verb in past tense with dot at the end.`
-- [ ] I have added a link to the issue tracker
-- [ ] I have provided information in `Changed` section about WHY something was done if this was not a normal implementation
-- [ ] I have performed a self-review of my code
-- [ ] I have commented my code, particularly in hard-to-understand areas
-- [ ] I have added tests that prove my fix is effective or that my feature works
-- [ ] I have run new and existing relevant tests locally with my changes, and they passed
-- [ ] I have provided screenshots, where applicable
+- [ ] Subject includes ticket number as `[#123] Verb in past tense.`
+- [ ] Ticket number `#123` added to description
+- [ ] Added context in `Changed` section
+- [ ] Self-reviewed code and commented in commented complex areas.
+- [ ] Added tests for fix/feature.
+- [ ] Relevant tests run and passed locally.
## Changed
diff --git a/.github/workflows/build-test-deploy.yml b/.github/workflows/build-test-deploy.yml
index 57ee7a888..168cdbb4a 100644
--- a/.github/workflows/build-test-deploy.yml
+++ b/.github/workflows/build-test-deploy.yml
@@ -56,7 +56,7 @@ jobs:
runs-on: ubuntu-latest
container:
- image: drevops/ci-runner:24.11.0
+ image: drevops/ci-runner:25.1.0
env:
TZ: Australia/Melbourne
@@ -99,11 +99,11 @@ jobs:
uses: actions/cache/restore@v4
with:
path: .data
- key: v24.9.0-db10-${{ hashFiles('db_cache_branch') }}-${{ hashFiles('db_cache_fallback') }}-${{ hashFiles('db_cache_timestamp') }}
+ key: v24.11.0-db11-${{ hashFiles('db_cache_branch') }}-${{ hashFiles('db_cache_fallback') }}-${{ hashFiles('db_cache_timestamp') }}
# Fallback to caching by default branch name only. Allows to use
# cache from the branch build on the previous day.
restore-keys: |
- v24.9.0-db10-${{ hashFiles('db_cache_branch') }}-${{ hashFiles('db_cache_fallback') }}-
+ v24.11.0-db11-${{ hashFiles('db_cache_branch') }}-${{ hashFiles('db_cache_fallback') }}-
- name: Download DB
run: |
@@ -131,7 +131,7 @@ jobs:
if: env.db_hash != hashFiles('.data')
with:
path: .data
- key: v24.9.0-db10-${{ hashFiles('db_cache_branch') }}-${{ hashFiles('db_cache_fallback_yes') }}-${{ hashFiles('db_cache_timestamp') }}
+ key: v24.11.0-db11-${{ hashFiles('db_cache_branch') }}-${{ hashFiles('db_cache_fallback_yes') }}-${{ hashFiles('db_cache_timestamp') }}
#;> !PROVISION_USE_PROFILE
build:
@@ -147,7 +147,7 @@ jobs:
fail-fast: false
container:
- image: drevops/ci-runner:24.11.0
+ image: drevops/ci-runner:25.1.0
env:
TZ: Australia/Melbourne
@@ -184,7 +184,7 @@ jobs:
date "${VORTEX_CI_DB_CACHE_TIMESTAMP}" | tee db_cache_timestamp
- name: Show cache key for database caching
- run: echo 'v24.9.0-db10-${{ hashFiles('db_cache_branch') }}-${{ hashFiles('db_cache_fallback_yes') }}-${{ hashFiles('db_cache_timestamp') }}'
+ run: echo 'v24.11.0-db11-${{ hashFiles('db_cache_branch') }}-${{ hashFiles('db_cache_fallback_yes') }}-${{ hashFiles('db_cache_timestamp') }}'
# Restore DB cache based on the cache strategy set by the cache keys below.
# Change 'v1' to 'v2', 'v3' etc., commit and push to force cache reset.
@@ -196,9 +196,9 @@ jobs:
path: .data
fail-on-cache-miss: true
# Use cached database from previous builds of this branch.
- key: v24.9.0-db10-${{ hashFiles('db_cache_branch') }}-${{ hashFiles('db_cache_fallback_yes') }}-${{ hashFiles('db_cache_timestamp') }}
+ key: v24.11.0-db11-${{ hashFiles('db_cache_branch') }}-${{ hashFiles('db_cache_fallback_yes') }}-${{ hashFiles('db_cache_timestamp') }}
restore-keys: |
- v24.9.0-db10-${{ hashFiles('db_cache_branch') }}-${{ hashFiles('db_cache_fallback_yes') }}-
+ v24.11.0-db11-${{ hashFiles('db_cache_branch') }}-${{ hashFiles('db_cache_fallback_yes') }}-
#;> !PROVISION_USE_PROFILE
- name: Lint Dockerfiles with Hadolint
@@ -281,10 +281,7 @@ jobs:
timeout-minutes: 30
- name: Test with PHPUnit
- run: |
- XDEBUG_ENABLE=true docker compose up -d cli php nginx # Restart stack with XDEBUG enabled for coverage.
- docker compose exec -T -e XDEBUG_MODE=coverage cli vendor/bin/phpunit
- docker compose up -d cli php nginx # Restart stack without XDEBUG enabled for coverage.
+ run: docker compose exec -T cli vendor/bin/phpunit
continue-on-error: ${{ vars.VORTEX_CI_PHPUNIT_IGNORE_FAILURE == '1' }}
- name: Test with Behat
@@ -316,7 +313,7 @@ jobs:
if-no-files-found: error
- name: Upload coverage report to Codecov
- uses: codecov/codecov-action@v4
+ uses: codecov/codecov-action@v5
if: ${{ env.CODECOV_TOKEN != '' }}
with:
directory: .logs/coverage
@@ -342,7 +339,7 @@ jobs:
#;> !PROVISION_USE_PROFILE
container:
- image: drevops/ci-runner:24.11.0
+ image: drevops/ci-runner:25.1.0
env:
TZ: Australia/Melbourne
TERM: xterm-256color
@@ -389,5 +386,6 @@ jobs:
VORTEX_DEPLOY_ARTIFACT_GIT_REMOTE: ${{ vars.VORTEX_DEPLOY_ARTIFACT_GIT_REMOTE }}
VORTEX_DEPLOY_ARTIFACT_GIT_USER_EMAIL: ${{ vars.VORTEX_DEPLOY_ARTIFACT_GIT_USER_EMAIL }}
VORTEX_DEPLOY_ARTIFACT_GIT_USER_NAME: ${{ vars.VORTEX_DEPLOY_ARTIFACT_GIT_USER_NAME }}
+ VORTEX_DEPLOY_WEBHOOK_URL: ${{ vars.VORTEX_DEPLOY_WEBHOOK_URL }}
timeout-minutes: 30
#;> DEPLOYMENT
diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml
index 3906e4d40..43376e915 100644
--- a/.github/workflows/renovate.yml
+++ b/.github/workflows/renovate.yml
@@ -11,10 +11,10 @@ jobs:
- name: Checkout
uses: actions/checkout@v4.2.2
- - uses: suzuki-shunsuke/github-action-renovate-config-validator@v1.1.0
+ - uses: suzuki-shunsuke/github-action-renovate-config-validator@v1.1.1
- name: Self-hosted Renovate
- uses: renovatebot/github-action@v41.0.2
+ uses: renovatebot/github-action@v41.0.9
with:
configurationFile: renovate.json
token: ${{ secrets.RENOVATE_TOKEN }}
diff --git a/.github/workflows/vortex-test-common.yml b/.github/workflows/vortex-test-common.yml
index 9284c1abb..5dc7756de 100644
--- a/.github/workflows/vortex-test-common.yml
+++ b/.github/workflows/vortex-test-common.yml
@@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest
container:
- image: drevops/ci-runner:24.11.0
+ image: drevops/ci-runner:25.1.0
env:
# Prevent GitHub overriding the Docker config.
DOCKER_CONFIG: /root/.docker
@@ -74,7 +74,7 @@ jobs:
path: /tmp/.vortex-coverage-html
- name: Upload coverage report to Codecov
- uses: codecov/codecov-action@v4
+ uses: codecov/codecov-action@v5
with:
directory: /tmp/.vortex-coverage-html
fail_ci_if_error: false
@@ -85,11 +85,12 @@ jobs:
runs-on: ubuntu-latest
strategy:
+ fail-fast: false
matrix:
- batch: [0, 1, 2]
+ batch: [0, 1, 2, 3]
container:
- image: drevops/ci-runner:24.11.0
+ image: drevops/ci-runner:25.1.0
env:
# Prevent GitHub overriding the Docker config.
DOCKER_CONFIG: /root/.docker
@@ -126,7 +127,7 @@ jobs:
path: /tmp/.vortex-coverage-html
- name: Upload coverage report to Codecov
- uses: codecov/codecov-action@v4
+ uses: codecov/codecov-action@v5
with:
directory: /tmp/.vortex-coverage-html
fail_ci_if_error: false
@@ -137,11 +138,12 @@ jobs:
runs-on: ubuntu-latest
strategy:
+ fail-fast: false
matrix:
batch: [0, 1]
container:
- image: drevops/ci-runner:24.11.0
+ image: drevops/ci-runner:25.1.0
env:
# Prevent GitHub overriding the Docker config.
DOCKER_CONFIG: /root/.docker
@@ -188,7 +190,7 @@ jobs:
path: /tmp/.vortex-coverage-html
- name: Upload coverage report to Codecov
- uses: codecov/codecov-action@v4
+ uses: codecov/codecov-action@v5
with:
directory: /tmp/.vortex-coverage-html
fail_ci_if_error: false
diff --git a/.github/workflows/vortex-test-installer.yml b/.github/workflows/vortex-test-installer.yml
index 1579acfb8..b6b25f1b4 100644
--- a/.github/workflows/vortex-test-installer.yml
+++ b/.github/workflows/vortex-test-installer.yml
@@ -26,6 +26,8 @@ jobs:
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
+ coverage: pcov
+ ini-values: pcov.directory=.
- name: Install dependencies
run: composer install
@@ -36,7 +38,7 @@ jobs:
working-directory: .vortex/installer
- name: Run tests
- run: XDEBUG_MODE=coverage composer test
+ run: composer test
working-directory: .vortex/installer
- name: Upload coverage report as an artifact
@@ -46,7 +48,7 @@ jobs:
path: .vortex/installer/.coverage-html
- name: Upload coverage report to Codecov
- uses: codecov/codecov-action@v4
+ uses: codecov/codecov-action@v5
with:
files: .vortex/installer/cobertura.xml
fail_ci_if_error: true
diff --git a/.gitignore b/.gitignore
index 2ec36e6ad..5f541bb07 100644
--- a/.gitignore
+++ b/.gitignore
@@ -53,6 +53,7 @@ web/themes/**/node_modules
web/themes/**/build
.data
.logs
+.phpunit.cache
.twig-cs-fixer.cache
# Ignore local override files.
diff --git a/.twig-cs-fixer.php b/.twig-cs-fixer.php
index bf4b84a7e..cfd466f74 100644
--- a/.twig-cs-fixer.php
+++ b/.twig-cs-fixer.php
@@ -1,9 +1,23 @@
addStandard(new TwigCsFixer\Standard\Twig());
+$ruleset->addRule(new TwigCsFixer\Rules\Delimiter\BlockNameSpacingRule());
+$ruleset->addRule(new TwigCsFixer\Rules\Delimiter\DelimiterSpacingRule());
+$ruleset->addRule(new TwigCsFixer\Rules\Function\NamedArgumentSpacingRule());
+$ruleset->addRule(new TwigCsFixer\Rules\Operator\OperatorNameSpacingRule());
+$ruleset->addRule(new TwigCsFixer\Rules\Operator\OperatorSpacingRule());
+$ruleset->addRule(new TwigCsFixer\Rules\Punctuation\PunctuationSpacingRule());
+$ruleset->addRule(new TwigCsFixer\Rules\Punctuation\TrailingCommaMultiLineRule());
+$ruleset->addRule(new TwigCsFixer\Rules\Punctuation\TrailingCommaSingleLineRule());
+$ruleset->addRule(new TwigCsFixer\Rules\String\HashQuoteRule());
+$ruleset->addRule(new TwigCsFixer\Rules\String\SingleQuoteRule());
+$ruleset->addRule(new TwigCsFixer\Rules\Variable\VariableNameRule());
+$ruleset->addRule(new TwigCsFixer\Rules\Whitespace\BlankEOFRule());
+$ruleset->addRule(new TwigCsFixer\Rules\Whitespace\EmptyLinesRule());
+$ruleset->addRule(new TwigCsFixer\Rules\Whitespace\IndentRule(2));
+$ruleset->addRule(new TwigCsFixer\Rules\Whitespace\TrailingSpaceRule());
$finder = new TwigCsFixer\File\Finder();
$finder->in(__DIR__ . '/web/modules/custom');
diff --git a/.vortex/.ahoy.yml b/.vortex/.ahoy.yml
index 4c5fd4387..255255b0d 100644
--- a/.vortex/.ahoy.yml
+++ b/.vortex/.ahoy.yml
@@ -50,7 +50,7 @@ commands:
test-bats:
cmd: |
[ ! -d tests/node_modules ] && npm --prefix tests ci
- tests/node_modules/.bin/bats "$@"
+ tests/node_modules/.bin/bats --no-tempdir-cleanup --tap "$@"
test-common:
cmd: ./tests/test.common.sh
diff --git a/.vortex/docs/.utils/composer.lock b/.vortex/docs/.utils/composer.lock
index eaad50780..df5c2332d 100644
--- a/.vortex/docs/.utils/composer.lock
+++ b/.vortex/docs/.utils/composer.lock
@@ -8,27 +8,28 @@
"packages": [
{
"name": "alexskrypnyk/csvtable",
- "version": "0.3.0",
+ "version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/AlexSkrypnyk/CsvTable.git",
- "reference": "5303c3da8ac071215e103c77a83f89e553394d8d"
+ "reference": "0c38988ccd1bded988b31b6432d003516c99bb51"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/AlexSkrypnyk/CsvTable/zipball/5303c3da8ac071215e103c77a83f89e553394d8d",
- "reference": "5303c3da8ac071215e103c77a83f89e553394d8d",
+ "url": "https://api.github.com/repos/AlexSkrypnyk/CsvTable/zipball/0c38988ccd1bded988b31b6432d003516c99bb51",
+ "reference": "0c38988ccd1bded988b31b6432d003516c99bb51",
"shasum": ""
},
"require": {
- "php": ">=8.1"
+ "php": ">=8.2"
},
"require-dev": {
"drupal/coder": "^8.3",
+ "ergebnis/composer-normalize": "^2.44",
"phpmd/phpmd": "^2.15",
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^10.1",
- "rector/rector": "^0.19.0"
+ "rector/rector": "^1.0.0"
},
"type": "library",
"autoload": {
@@ -59,24 +60,24 @@
"type": "patreon"
}
],
- "time": "2024-01-16T12:00:12+00:00"
+ "time": "2024-11-17T00:27:25+00:00"
},
{
"name": "alexskrypnyk/shellvar",
- "version": "1.0.0",
+ "version": "1.2.0",
"source": {
"type": "git",
"url": "https://github.com/AlexSkrypnyk/shellvar.git",
- "reference": "0e1e389d164806a3a778df4dba7e32eba4ed9e95"
+ "reference": "2e9865ab3e0b51e43bd387fa813d5e6bc344a014"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/AlexSkrypnyk/shellvar/zipball/0e1e389d164806a3a778df4dba7e32eba4ed9e95",
- "reference": "0e1e389d164806a3a778df4dba7e32eba4ed9e95",
+ "url": "https://api.github.com/repos/AlexSkrypnyk/shellvar/zipball/2e9865ab3e0b51e43bd387fa813d5e6bc344a014",
+ "reference": "2e9865ab3e0b51e43bd387fa813d5e6bc344a014",
"shasum": ""
},
"require": {
- "alexskrypnyk/csvtable": "^0.3",
+ "alexskrypnyk/csvtable": "^1",
"php": ">=8.2",
"symfony/console": "^7"
},
@@ -87,12 +88,12 @@
"bamarni/composer-bin-plugin": "^1.8.2",
"dealerdirect/phpcodesniffer-composer-installer": "^1",
"drupal/coder": "^8.3",
+ "ergebnis/composer-normalize": "^2.44",
"mikey179/vfsstream": "^1.6",
- "opis/closure": "^3.6",
- "phpmd/phpmd": "^2.15",
- "phpstan/phpstan": "^1.10",
- "phpunit/phpunit": "^10.5",
- "rector/rector": "^1"
+ "opis/closure": "^4.0",
+ "phpstan/phpstan": "^2",
+ "phpunit/phpunit": "^11",
+ "rector/rector": "^2"
},
"bin": [
"shellvar"
@@ -127,7 +128,7 @@
"type": "patreon"
}
],
- "time": "2024-04-29T01:29:09+00:00"
+ "time": "2025-01-10T00:16:56+00:00"
},
{
"name": "psr/container",
@@ -184,16 +185,16 @@
},
{
"name": "symfony/console",
- "version": "v7.0.6",
+ "version": "v7.2.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "fde915cd8e7eb99b3d531d3d5c09531429c3f9e5"
+ "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/fde915cd8e7eb99b3d531d3d5c09531429c3f9e5",
- "reference": "fde915cd8e7eb99b3d531d3d5c09531429c3f9e5",
+ "url": "https://api.github.com/repos/symfony/console/zipball/fefcc18c0f5d0efe3ab3152f15857298868dc2c3",
+ "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3",
"shasum": ""
},
"require": {
@@ -257,7 +258,7 @@
"terminal"
],
"support": {
- "source": "https://github.com/symfony/console/tree/v7.0.6"
+ "source": "https://github.com/symfony/console/tree/v7.2.1"
},
"funding": [
{
@@ -273,24 +274,91 @@
"type": "tidelift"
}
],
- "time": "2024-04-01T11:04:53+00:00"
+ "time": "2024-12-11T03:49:26+00:00"
+ },
+ {
+ "name": "symfony/deprecation-contracts",
+ "version": "v3.5.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/deprecation-contracts.git",
+ "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6",
+ "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/contracts",
+ "name": "symfony/contracts"
+ },
+ "branch-alias": {
+ "dev-main": "3.5-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "function.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "A generic function and convention to trigger deprecation notices",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-25T14:20:29+00:00"
},
{
"name": "symfony/polyfill-ctype",
- "version": "v1.29.0",
+ "version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
- "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4"
+ "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4",
- "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4",
+ "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638",
+ "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638",
"shasum": ""
},
"require": {
- "php": ">=7.1"
+ "php": ">=7.2"
},
"provide": {
"ext-ctype": "*"
@@ -301,8 +369,8 @@
"type": "library",
"extra": {
"thanks": {
- "name": "symfony/polyfill",
- "url": "https://github.com/symfony/polyfill"
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
}
},
"autoload": {
@@ -336,7 +404,7 @@
"portable"
],
"support": {
- "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0"
+ "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0"
},
"funding": [
{
@@ -352,24 +420,24 @@
"type": "tidelift"
}
],
- "time": "2024-01-29T20:11:03+00:00"
+ "time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-intl-grapheme",
- "version": "v1.29.0",
+ "version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-grapheme.git",
- "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f"
+ "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/32a9da87d7b3245e09ac426c83d334ae9f06f80f",
- "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe",
+ "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe",
"shasum": ""
},
"require": {
- "php": ">=7.1"
+ "php": ">=7.2"
},
"suggest": {
"ext-intl": "For best performance"
@@ -377,8 +445,8 @@
"type": "library",
"extra": {
"thanks": {
- "name": "symfony/polyfill",
- "url": "https://github.com/symfony/polyfill"
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
}
},
"autoload": {
@@ -414,7 +482,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.29.0"
+ "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0"
},
"funding": [
{
@@ -430,24 +498,24 @@
"type": "tidelift"
}
],
- "time": "2024-01-29T20:11:03+00:00"
+ "time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-intl-normalizer",
- "version": "v1.29.0",
+ "version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
- "reference": "bc45c394692b948b4d383a08d7753968bed9a83d"
+ "reference": "3833d7255cc303546435cb650316bff708a1c75c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/bc45c394692b948b4d383a08d7753968bed9a83d",
- "reference": "bc45c394692b948b4d383a08d7753968bed9a83d",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c",
+ "reference": "3833d7255cc303546435cb650316bff708a1c75c",
"shasum": ""
},
"require": {
- "php": ">=7.1"
+ "php": ">=7.2"
},
"suggest": {
"ext-intl": "For best performance"
@@ -455,8 +523,8 @@
"type": "library",
"extra": {
"thanks": {
- "name": "symfony/polyfill",
- "url": "https://github.com/symfony/polyfill"
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
}
},
"autoload": {
@@ -495,7 +563,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.29.0"
+ "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0"
},
"funding": [
{
@@ -511,24 +579,24 @@
"type": "tidelift"
}
],
- "time": "2024-01-29T20:11:03+00:00"
+ "time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-mbstring",
- "version": "v1.29.0",
+ "version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
- "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec"
+ "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
- "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341",
+ "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341",
"shasum": ""
},
"require": {
- "php": ">=7.1"
+ "php": ">=7.2"
},
"provide": {
"ext-mbstring": "*"
@@ -539,8 +607,8 @@
"type": "library",
"extra": {
"thanks": {
- "name": "symfony/polyfill",
- "url": "https://github.com/symfony/polyfill"
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
}
},
"autoload": {
@@ -575,7 +643,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0"
+ "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0"
},
"funding": [
{
@@ -591,37 +659,38 @@
"type": "tidelift"
}
],
- "time": "2024-01-29T20:11:03+00:00"
+ "time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/service-contracts",
- "version": "v3.4.2",
+ "version": "v3.5.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/service-contracts.git",
- "reference": "11bbf19a0fb7b36345861e85c5768844c552906e"
+ "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/service-contracts/zipball/11bbf19a0fb7b36345861e85c5768844c552906e",
- "reference": "11bbf19a0fb7b36345861e85c5768844c552906e",
+ "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0",
+ "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0",
"shasum": ""
},
"require": {
"php": ">=8.1",
- "psr/container": "^1.1|^2.0"
+ "psr/container": "^1.1|^2.0",
+ "symfony/deprecation-contracts": "^2.5|^3"
},
"conflict": {
"ext-psr": "<1.1|>=2"
},
"type": "library",
"extra": {
- "branch-alias": {
- "dev-main": "3.4-dev"
- },
"thanks": {
- "name": "symfony/contracts",
- "url": "https://github.com/symfony/contracts"
+ "url": "https://github.com/symfony/contracts",
+ "name": "symfony/contracts"
+ },
+ "branch-alias": {
+ "dev-main": "3.5-dev"
}
},
"autoload": {
@@ -657,7 +726,7 @@
"standards"
],
"support": {
- "source": "https://github.com/symfony/service-contracts/tree/v3.4.2"
+ "source": "https://github.com/symfony/service-contracts/tree/v3.5.1"
},
"funding": [
{
@@ -673,20 +742,20 @@
"type": "tidelift"
}
],
- "time": "2023-12-19T21:51:00+00:00"
+ "time": "2024-09-25T14:20:29+00:00"
},
{
"name": "symfony/string",
- "version": "v7.0.4",
+ "version": "v7.2.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
- "reference": "f5832521b998b0bec40bee688ad5de98d4cf111b"
+ "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/string/zipball/f5832521b998b0bec40bee688ad5de98d4cf111b",
- "reference": "f5832521b998b0bec40bee688ad5de98d4cf111b",
+ "url": "https://api.github.com/repos/symfony/string/zipball/446e0d146f991dde3e73f45f2c97a9faad773c82",
+ "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82",
"shasum": ""
},
"require": {
@@ -700,6 +769,7 @@
"symfony/translation-contracts": "<2.5"
},
"require-dev": {
+ "symfony/emoji": "^7.1",
"symfony/error-handler": "^6.4|^7.0",
"symfony/http-client": "^6.4|^7.0",
"symfony/intl": "^6.4|^7.0",
@@ -743,7 +813,7 @@
"utf8"
],
"support": {
- "source": "https://github.com/symfony/string/tree/v7.0.4"
+ "source": "https://github.com/symfony/string/tree/v7.2.0"
},
"funding": [
{
@@ -759,18 +829,18 @@
"type": "tidelift"
}
],
- "time": "2024-02-01T13:17:36+00:00"
+ "time": "2024-11-13T13:31:26+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
- "stability-flags": [],
+ "stability-flags": {},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": ">=8.1"
},
- "platform-dev": [],
+ "platform-dev": {},
"plugin-api-version": "2.6.0"
}
diff --git a/.vortex/docs/.utils/update-docs.sh b/.vortex/docs/.utils/update-docs.sh
index 7be6e328f..a1dffb636 100755
--- a/.vortex/docs/.utils/update-docs.sh
+++ b/.vortex/docs/.utils/update-docs.sh
@@ -47,6 +47,7 @@ sed "${sed_opts[@]}" "s/.vortex\/docs\/.utils\/variables\/extra\/acquia.variable
sed "${sed_opts[@]}" "s/.vortex\/docs\/.utils\/variables\/extra\/lagoon.variables.sh/LAGOON ENVIRONMENT/g" "${OUTPUT_FILE}"
sed "${sed_opts[@]}" "s/.vortex\/docs\/.utils\/variables\/extra\/.env.local.example.variables.sh/.env.local.example/g" "${OUTPUT_FILE}"
sed "${sed_opts[@]}" "s/.vortex\/docs\/.utils\/variables\/extra\/.env.variables.sh/.env/g" "${OUTPUT_FILE}"
+sed "${sed_opts[@]}" "s/.vortex\/docs\/.utils\/variables\/extra\/docker-compose.variables.sh/docker-compose.yml/g" "${OUTPUT_FILE}"
sed "${sed_opts[@]}" "s/.vortex\/docs\/.utils\/variables\/extra\/ci.variables.sh/CI config/g" "${OUTPUT_FILE}"
echo "---" >>"${OUTPUT_FILE}"
diff --git a/.vortex/docs/.utils/variables/extra/.env.variables.sh b/.vortex/docs/.utils/variables/extra/.env.variables.sh
index a0c4f804d..d291ca9ea 100755
--- a/.vortex/docs/.utils/variables/extra/.env.variables.sh
+++ b/.vortex/docs/.utils/variables/extra/.env.variables.sh
@@ -49,12 +49,3 @@ VORTEX_COMPOSER_VERBOSE=1
# Print output from NPM install.
VORTEX_NPM_VERBOSE=0
-
-# Path to public files.
-DRUPAL_PUBLIC_FILES="${DRUPAL_PUBLIC_FILES:-./${VORTEX_WEBROOT}/sites/default/files}"
-
-# Path to private files.
-DRUPAL_PRIVATE_FILES="${DRUPAL_PRIVATE_FILES:-${DRUPAL_PUBLIC_FILES}/private}"
-
-# Path to temporary files.
-DRUPAL_TEMPORARY_FILES="${DRUPAL_TEMPORARY_FILES:-${DRUPAL_PRIVATE_FILES}/tmp}"
diff --git a/.vortex/docs/.utils/variables/extra/docker-compose.variables.sh b/.vortex/docs/.utils/variables/extra/docker-compose.variables.sh
new file mode 100755
index 000000000..7906041b3
--- /dev/null
+++ b/.vortex/docs/.utils/variables/extra/docker-compose.variables.sh
@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+##
+# Additional environment variables used in this project in docker-compose.yml
+#
+# shellcheck disable=SC2034
+
+# Path to public files.
+DRUPAL_PUBLIC_FILES="${DRUPAL_PUBLIC_FILES:-./${VORTEX_WEBROOT}/sites/default/files}"
+
+# Path to private files.
+DRUPAL_PRIVATE_FILES="${DRUPAL_PRIVATE_FILES:-${DRUPAL_PUBLIC_FILES}/private}"
+
+# Path to temporary files.
+DRUPAL_TEMPORARY_FILES="${DRUPAL_TEMPORARY_FILES:-${DRUPAL_PRIVATE_FILES}/tmp}"
+
+# Local database host.
+DATABASE_HOST=database
+
+# Local database name.
+DATABASE_NAME=drupal
+
+# Local database user.
+DATABASE_USERNAME=drupal
+
+# Local database password.
+DATABASE_PASSWORD=drupal
+
+# Local database port.
+DATABASE_PORT=3306
diff --git a/.vortex/docs/content/README.mdx b/.vortex/docs/content/README.mdx
index 41db109c9..9b0dabf53 100644
--- a/.vortex/docs/content/README.mdx
+++ b/.vortex/docs/content/README.mdx
@@ -34,6 +34,7 @@ on [Lagoon container images](https://github.com/uselagoon/lagoon-images)
- [Continuous integration](ci) configuration
- [Hosting integration](hosting)
- [Unified developer experience](workflows)
+- Documentation to help you get started and maintain your project
Refer to [Features](getting-started/features.mdx) for more details.
diff --git a/.vortex/docs/content/contributing/maintenance/release.mdx b/.vortex/docs/content/contributing/maintenance/release.mdx
index 2769a87a7..d454557d1 100644
--- a/.vortex/docs/content/contributing/maintenance/release.mdx
+++ b/.vortex/docs/content/contributing/maintenance/release.mdx
@@ -10,7 +10,7 @@ renovate --schedule= --force-cli=true drevops/vortex
3. Update PHP version in `composer.json` for `config.platform`.
4. Update PHP version in `phpcs.xml` for `testVersion`.
5. Update PHP version in `phpstan.neon` for `phpVersion`. Run `php -r "echo PHP_VERSION_ID . PHP_EOL;"` in the container to get the current PHP version.
-6. Increment minor version of all packages in `composer.json`.
+6. Increment minor version of all packages in `composer.json`. Run `composer update -W && composer bump`.
7. Update minor version of dependencies in theme's `package.json`.
8. Increment the cache version in `.circleci/config.yml`.
9. Updated documentation with `cd .vortex && ahoy update-docs`.
diff --git a/.vortex/docs/content/contributing/maintenance/tests.mdx b/.vortex/docs/content/contributing/maintenance/tests.mdx
index aabd90f0c..38ee271dc 100644
--- a/.vortex/docs/content/contributing/maintenance/tests.mdx
+++ b/.vortex/docs/content/contributing/maintenance/tests.mdx
@@ -38,36 +38,159 @@ bats --no-tempdir-cleanup .vortex/tests/bats/*.bats
## Updating test assets
-Some tests use test fixtures such as Drupal database snapshots.
+There are *demo* and *test* database dumps captured as *files* and *container images*.
-### Updating demo database file dump
+- Demo database dump file - *demonstration* of the database import capabilities from a *file* during a normal workflow.
+- Demo database container image - *demonstration* of the database import capabilities from a *container image* during a normal workflow.
+- Test database dump file - *test* of the database import capabilities from a *file* during the normal workflow.
+- Test database container image - *test* of the database import capabilities from a *container image* during the normal workflow.
+
+### Updating *demo* database dump *file*
+
+1. Run fresh build of **Vortex** locally:
+```bash
+rm .data/db.sql || true
+VORTEX_PROVISION_USE_PROFILE=1 AHOY_CONFIRM_RESPONSE=1 ahoy build
+```
+2. Update content and config:
+```bash
+ahoy cli
+
+drush eval "Drupal::entityTypeManager()->getStorage('node')->create([
+ 'type' => 'page',
+ 'title' => 'Welcome to the demo site!',
+ 'body' => [
+ 'value' => '
This demo page is sourced from the Vortex database dump file to demonstrate database importing capabilities.
',
+ 'format' => 'basic_html',
+ ],
+])->save();"
+
+drush config:set system.site page.front "/node/1" -y
+drush sql:query "SHOW TABLES LIKE 'cache_%'" | xargs -I{} drush sql:query "TRUNCATE TABLE {}" && drush sql:query "TRUNCATE TABLE watchdog"
+
+exit
+
+```
+3. Export DB:
+```bash
+ahoy export-db db.demo.sql
+```
+4. Upload `db.demo.sql` to the latest release as an asset and name it `db_d11.demo.sql`.
+
+### Updating *demo* database *container image*
1. Run fresh build of **Vortex** locally:
```bash
-echo "DRUPAL_PROFILE=standard">>.env.local
-echo "VORTEX_PROVISION_USE_PROFILE=1">>.env.local
-rm .data/db.sql
-AHOY_CONFIRM_RESPONSE=1 ahoy build
+rm .data/db.sql || true
+VORTEX_PROVISION_USE_PROFILE=1 AHOY_CONFIRM_RESPONSE=1 ahoy build
+```
+2. Update content and config:
+```bash
+ahoy cli
+
+drush eval "Drupal::entityTypeManager()->getStorage('node')->create([
+ 'type' => 'page',
+ 'title' => 'Welcome to the demo site!',
+ 'body' => [
+ 'value' => 'This demo page is sourced from the Vortex database container image to demonstrate database importing capabilities.
',
+ 'format' => 'basic_html',
+ ],
+])->save();"
+
+drush config:set system.site page.front "/node/1" -y
+drush sql:query "SHOW TABLES LIKE 'cache_%'" | xargs -I{} drush sql:query "TRUNCATE TABLE {}" && drush sql:query "TRUNCATE TABLE watchdog"
+
+exit
+
+```
+3. Export DB:
+```bash
+ahoy export-db db.demo_image.sql
+```
+4. Seed the database container image:
+```bash
+curl -LO https://github.com/drevops/mariadb-drupal-data/releases/latest/download/seed.sh
+chmod +x seed.sh
+./seed.sh .data/db.demo_image.sql drevops/vortex-dev-mariadb-drupal-data-demo-11.x:latest
```
-2. Check that everything looks correctly on the site.
-3. Export DB
+
+### Updating *test* database dump *file*
+
+1. Run a fresh install of **Vortex** into a new directory and name the project `Star Wars`:
+```bash
+mkdir /tmp/star-wars
+VORTEX_INSTALL_LOCAL_REPO="$(pwd)" .vortex/installer/install /tmp/star-wars --quiet
+cd /tmp/star-wars
+```
+2. Run fresh build of **Vortex** locally:
+```bash
+rm .data/db.sql || true
+VORTEX_PROVISION_USE_PROFILE=1 AHOY_CONFIRM_RESPONSE=1 ahoy build
+```
+3. Update content and config:
+```bash
+ahoy cli
+
+drush eval "Drupal::entityTypeManager()->getStorage('node')->create([
+ 'type' => 'page',
+ 'title' => 'Welcome to the test site!',
+ 'body' => [
+ 'value' => 'This test page is sourced from the Vortex database dump file to demonstrate database importing capabilities.
',
+ 'format' => 'basic_html',
+ ],
+])->save();"
+
+drush config:set system.site page.front "/node/1" -y
+drush sql:query "SHOW TABLES LIKE 'cache_%'" | xargs -I{} drush sql:query "TRUNCATE TABLE {}" && drush sql:query "TRUNCATE TABLE watchdog"
+
+exit
+
+```
+4. Export DB:
```bash
-ahoy export-db
+ahoy export-db db.test.sql
```
-4. Make sure that exported DB does not have data in `cache_*` and `watchdog` tables.
-5. Upload DB to https://github.com/drevops/vortex/wiki as a test file (`db.distN.sql`).
-6. Update references in code from `db.demo.sql` to `db.distN.sql`.
-7. Run CI build.
-8. Revert updated references to `db.demo.sql`.
-9. Update `db.demo.sql` in Wiki.
-10. Merge branch to `main`.
-11. Wait for CI to pass.
-12. Remove `db.distN.sql` from Wiki.
+5. Upload `db.test.sql` to the latest release as an asset and name it `db_d11.test.sql`.
-### Updating demo database container image
+### Updating *test* database *container image*
-:::note "Work in progress"
+1. Run a fresh install of **Vortex** into a new directory and name the project `Star Wars`:
+```bash
+mkdir /tmp/star-wars
+VORTEX_INSTALL_LOCAL_REPO="$(pwd)" .vortex/installer/install /tmp/star-wars --quiet
+cd /tmp/star-wars
+```
+2. Run fresh build of **Vortex** locally:
+```bash
+rm .data/db.sql || true
+VORTEX_PROVISION_USE_PROFILE=1 AHOY_CONFIRM_RESPONSE=1 ahoy build
+```
+3. Update content and config:
+```bash
+ahoy cli
+
+drush eval "Drupal::entityTypeManager()->getStorage('node')->create([
+ 'type' => 'page',
+ 'title' => 'Welcome to the test site!',
+ 'body' => [
+ 'value' => 'This test page is sourced from the Vortex database container image to demonstrate database importing capabilities.
',
+ 'format' => 'basic_html',
+ ],
+])->save();"
- The documentation section is still a work in progress.
+drush config:set system.site page.front "/node/1" -y
+drush sql:query "SHOW TABLES LIKE 'cache_%'" | xargs -I{} drush sql:query "TRUNCATE TABLE {}" && drush sql:query "TRUNCATE TABLE watchdog"
-:::
+exit
+
+```
+4. Export DB:
+```bash
+ahoy export-db db.test_image.sql
+```
+5. Seed the database container image:
+```bash
+curl -LO https://github.com/drevops/mariadb-drupal-data/releases/latest/download/seed.sh
+chmod +x seed.sh
+./seed.sh .data/db.test_image.sql drevops/vortex-dev-mariadb-drupal-data-test-11.x:latest
+```
diff --git a/.vortex/docs/content/drupal/composer.mdx b/.vortex/docs/content/drupal/composer.mdx
index 977d00d8a..ea8db58ba 100644
--- a/.vortex/docs/content/drupal/composer.mdx
+++ b/.vortex/docs/content/drupal/composer.mdx
@@ -26,7 +26,7 @@ provides a starter kit for managing your site dependencies with Composer.
**Vortex** extends the Drupal Composer project's `composer.json` to support
additional features and tools.
-**Vortex** team will keep the `composer.json` file up-to-date with the
+**Vortex** team keeps the `composer.json` file up-to-date with the
latest version of the `composer.json` in the [Drupal Composer project](https://github.com/drupal-composer/drupal-project),
so you can always make sure you are using the best community practices.
diff --git a/.vortex/docs/content/drupal/settings.mdx b/.vortex/docs/content/drupal/settings.mdx
index 9e836866a..984b8c6a6 100644
--- a/.vortex/docs/content/drupal/settings.mdx
+++ b/.vortex/docs/content/drupal/settings.mdx
@@ -12,9 +12,10 @@ the [`settings.php`](https://github.com/drevops/vortex/blob/develop/web/sites/de
[`services.yml`](https://github.com/drevops/vortex/blob/develop/web/sites/default/services.yml) files.
It also provides [Settings unit tests](#testing-settings-with-unit-tests) to ensure that
-the settings apply correctly per environment. These tests are supposed to be
-maintained within your project, ensuring that settings activated by specific
-environments and environment variables are applied accurately.
+the settings apply correctly per environment. These tests are intended to be
+maintained within your project, ensuring that the settings activated within a
+specific _environment type_ and with specific _environment variables_ are
+applied correctly.
The default **Drupal Scaffold**'s [`default.settings.php`](https://github.com/drevops/vortex/blob/develop/web/sites/default/default.settings.php)
and [`default.services.yml`](https://github.com/drevops/vortex/blob/develop/web/sites/default/default.services.yml)
@@ -24,28 +25,28 @@ The [`settings.php`](https://github.com/drevops/vortex/blob/develop/web/sites/de
into several sections:
import CodeBlock from '@theme/CodeBlock';
-import MyComponentSource from '!!raw-loader!./../../../../web/sites/default/settings.php';
+import SettingsExample from '!!raw-loader!./../../../../web/sites/default/settings.php';
Click here to see the contents of the `settings.php` file
- {MyComponentSource}
+ {SettingsExample}
-1. [Environment constants definitions](#1-environment-constants-definitions)
+1. [Environment type constants definitions](#1-environment-type-constants-definitions)
2. [Site-specific settings](#2-site-specific-settings)
-3. [Environment detection](#3-environment-detection)
+3. [Environment detection](#3-environment-type-detection)
4. [Per-environment overrides](#4-per-environment-overrides)
5. [Inclusion of generated Settings](#5-inclusion-of-per-module-settings)
6. [Inclusion of local settings](#6-inclusion-of-local-settings)
-### 1. Environment constants definitions
+### 1. Environment type constants definitions
-Constants for various environments are defined here. These can be used to alter
-site behavior based on the active environment.
+Constants for various _environment types_ are defined here. These can be used to
+alter site behavior based on the active _environment type_.
-Available constants include:
+Available _environment type_ constants are:
- `ENVIRONMENT_LOCAL`
- `ENVIRONMENT_CI`
@@ -53,16 +54,16 @@ Available constants include:
- `ENVIRONMENT_STAGE`
- `ENVIRONMENT_DEV`
-These are later used to set `$settings['environment']` variable, which can be
-used in the modules and outside scripts to target code execution to specific
-environments.
+These are later used to set `$settings['environment']`, which can be
+used in the modules and shell scripts to target code execution to specific
+_environments types_.
:::info[EXAMPLE]
```shell
environment="$(drush php:eval "print \Drupal\core\Site\Settings::get('environment');")"
if echo "${environment}" | grep -q -e ci -e local; then
- # Do something only in ci, or local environments.
+ # Do something only in ci or local environments.
fi
```
@@ -70,10 +71,10 @@ environments.
:::info
- The `$VORTEX_PROVISION_ENVIRONMENT` environment variable can be utilized
- within post-provision custom scripts, allowing targeted code execution based
- on specific environments. Refer to [Provision](provision.mdx) for additional
- information.
+ The `$VORTEX_PROVISION_ENVIRONMENT` _environment variable_ can be utilized
+ within post-provision custom scripts instead of Drush command evaluation,
+ allowing targeted code execution based on a specific _environment type_.
+ Refer to [Provision](provision.mdx) for additional information.
:::
@@ -84,58 +85,80 @@ ensuring security with trusted host patterns, setting performance optimizations
like aggregating CSS and JS files, and specifying essential directories for
Drupal's functionality.
-These settings are identical for all environments.
+These settings are identical for all _environment types_ .
+
+Use per-module settings files in the [`web/site/default/includes/modules`](https://github.com/drevops/vortex/tree/develop/web/sites/default/includes/modules)
+directory to override per-module settings.
+
+### 3. Environment type detection
+
+This section uses known hosting providers mechanisms to determine the
+_environment type_ where the site currently runs.
-Use per-module settings files in the `web/site/default/includes` directory to
-override per-module settings, including per-environment overrides.
+Settings for the supported hosting providers are stored in the
+[`web/site/default/includes/providers`](https://github.com/drevops/vortex/tree/develop/web/sites/default/includes/providers)
+directory. You can add your own custom provider _environment type_ detection logic
+by creating a new file `settings.[provider].php` in this directory.
-### 3. Environment detection
+Once a hosting provider is detected, the _environment type_
+`$settings['environment']` is set to
+`ENVIRONMENT_DEV` for all environments as a default.
-This section uses known hosting platform mechanisms to determine in which
-environment the site currently runs. The result is stored in the
-`$settings['environment']` variable.
+Higher-level environments types (`PROD`, `STAGE` etc.) are then set based on
+the **additional** detected provider-specific settings.
-By default, `$settings['environment']` is set to `ENVIRONMENT_LOCAL`.
-This value is then overridden by the environment detection logic.
+When the hosting provider is not detected, the default value is set to
+`ENVIRONMENT_LOCAL`.
:::note
- In any hosting environment, the default value of `$settings['environment']`
- changes to `ENVIRONMENT_DEV`, while further refinements designate more
- advanced environments.
+ Environment type detection settings are only used for _environment type_
+ detection and not for environment-specific settings. Those are defined in
+ the [Per-environment overrides](#4-per-environment-overrides) section.
+ This approach allows for a more flexible and maintainable configuration
+ independent of a specific hosting provider.
:::
-#### Environment detection override
+#### Overriding environment type
-It is also possible to force specific environment by setting
-`DRUPAL_ENVIRONMENT` environment variable. In this case, the environment
-detection will take place and will load any additional hosting platform settings,
-but the final value of `$settings['environment']` will be set to the value of
-`DRUPAL_ENVIRONMENT` variable.
+It is also possible to force specific _environment type_ by setting
+`DRUPAL_ENVIRONMENT` _environment variable_.
This is useful in cases where a certain behavior is required for a specific
-environment, but the environment detection logic does not provide it. Or as a
-temporary override during testing.
+environment, but the _environment type_ detection logic does not provide it.
+
+It is also useful when debugging _environment type_-specific issues locally.
### 4. Per-environment overrides
-Configurations in this section alter the site's behavior based on the
-environment. Out-of-the-box, **Vortex** provides overrides for CI and Local
+Configurations in this section alter the site's behavior based on the detected
+_environment type_ (see [Environment type detection](#3-environment-type-detection)
+above). Out-of-the-box, **Vortex** provides overrides for CI and Local
environments.
-You can add additional overrides for other environments as needed.
+You can add additional overrides for other _environment types_ as needed.
### 5. Inclusion of per-module settings
This section includes any additional module-specific settings from the
-`/includes` directory.
+[`web/site/default/includes/modules`](https://github.com/drevops/vortex/tree/develop/web/sites/default/includes/modules) directory.
+
+**Vortex** ships with settings overrides for several popular contributed
+modules.
-**Vortex** ships with settings overrides for several popular contributed modules
-used in almost every project.
+The per _environment type_ overrides for each module should be placed into the
+module-specific settings file.
+
+import ModuleSettingsExample from '!!raw-loader!./../../../../web/sites/default/includes/modules/settings.environment_indicator.php';
+
+
+ Example of the `Environment indicator` module settings file
+
+ {ModuleSettingsExample}
+
+
-The per environment overrides for modules should be also placed into files
-in the `/includes` directory.
### 6. Inclusion of local settings
@@ -146,12 +169,32 @@ copy `default.settings.local.php` and `default.services.local.yml`
to `settings.local.php` and `services.local.yml`, respectively, to utilize this
functionality.
+import LocalSettingsExample from '!!raw-loader!./../../../../web/sites/default/default.settings.local.php';
+
+
+ Click here to see the contents of the `default.settings.local.php` file
+
+ {LocalSettingsExample}
+
+
+
+
+import LocalServicesExample from '!!raw-loader!./../../../../web/sites/default/default.services.local.yml';
+
+
+ Click here to see the contents of the `default.services.local.yml` file
+
+ {LocalServicesExample}
+
+
+
+
## Testing settings with unit tests
**Vortex** provides a [set of unit tests](https://github.com/drevops/vortex/blob/develop/tests/phpunit/Drupal) that
-ensure that the settings apply correctly per environment. These tests are
-supposed to be maintained within your project, ensuring that settings activated
-by specific environments and environment variables are applied accurately.
+ensure that the settings apply correctly per environment type. These tests are
+expected to be maintained within your project, ensuring that settings activated
+by a specific _environment type_ and _environment variables_ are applied correctly.
After installing **Vortex**, run `vendor/bin/phpunit --group=drupal_settings` to
run the tests for the settings provided by **Vortex**.
diff --git a/.vortex/docs/content/tools/README.mdx b/.vortex/docs/content/tools/README.mdx
index b828ede96..527d3dc2e 100644
--- a/.vortex/docs/content/tools/README.mdx
+++ b/.vortex/docs/content/tools/README.mdx
@@ -15,21 +15,22 @@ Some of the tools run in CI and would fail the build if they detect issues.
Head over to the tool-specific documentation to learn more.
-| Name | Description |
-|-----------------------------------|------------------------------------------------------------------|
-| [Ahoy](ahoy.mdx) | CLI command wrapper |
-| [Behat](behat.mdx) | Testing framework for for auto-testing your business expectations |
-| [Docker](docker.mdx) | A platform for containerizing and running applications |
-| [Doctor](doctor.mdx) | Check **Vortex** project requirements or print info |
-| [Drush](drush.mdx) | Command line shell and Unix scripting interface for Drupal |
-| [Gherkin Lint](gherkin-lint.mdx) | Provides a Gherkin linter for PHP |
-| [Git artifact](git-artifact.mdx) | Package and push files to remote repositories |
-| [PHPCS](phpcs.mdx) | Check that code adheres to coding standards |
-| [PHPMD](phpmd.mdx) | Detect code smells and possible errors |
-| [PHPStan](phpstan.mdx) | PHP Static Analysis Tool |
-| [PHPUnit](phpunit.mdx) | The PHP Testing Framework |
-| [Pygmy](pygmy.mdx) | A local reverse-proxy and email catcher |
-| [Rector](rector.mdx) | Instant Upgrades and Automated Refactoring |
-| [Renovate](renovate.mdx) | A bot for automated dependency updates |
-| [Twig CS Fixer](twig-cs-fixer.mdx) | Checkstyle for Twig |
-| [Xdebug](xdebug.mdx) | Debugger and Profiler Tool for PHP |
+| Name | Description |
+|------------------------------------|---------------------------------------------------------------------------------|
+| [Ahoy](ahoy.mdx) | CLI command wrapper |
+| [Behat](behat.mdx) | Testing framework for for auto-testing your business expectations |
+| [Docker](docker.mdx) | A platform for containerizing and running applications |
+| [Doctor](doctor.mdx) | Check **Vortex** project requirements or retrieve environment information |
+| [Drush](drush.mdx) | Command line shell and Unix scripting interface for Drupal |
+| [Gherkin Lint](gherkin-lint.mdx) | Provides a Gherkin linter for PHP |
+| [Git artifact](git-artifact.mdx) | Package and push files to remote repositories |
+| [Hadolint](hadolint.mdx) | A smarter Dockerfile linter that helps you build best practice container images |
+| [PHPCS](phpcs.mdx) | Check that code adheres to coding standards |
+| [PHPMD](phpmd.mdx) | Detect code smells and possible errors |
+| [PHPStan](phpstan.mdx) | PHP Static Analysis Tool |
+| [PHPUnit](phpunit.mdx) | The PHP Testing Framework |
+| [Pygmy](pygmy.mdx) | A local reverse-proxy and email catcher |
+| [Rector](rector.mdx) | Instant Upgrades and Automated Refactoring |
+| [Renovate](renovate.mdx) | A bot for automated dependency updates |
+| [Twig CS Fixer](twig-cs-fixer.mdx) | Checkstyle for Twig |
+| [Xdebug](xdebug.mdx) | Debugger and Profiler Tool for PHP |
diff --git a/.vortex/docs/content/tools/docker.mdx b/.vortex/docs/content/tools/docker.mdx
index 5f2012015..70df9bcc5 100644
--- a/.vortex/docs/content/tools/docker.mdx
+++ b/.vortex/docs/content/tools/docker.mdx
@@ -208,9 +208,9 @@ Vortex:
different from the `cli` container in that it does not have certain
development dependencies installed, has a smaller size and is optimised for
scalability.
-- `mariadb` - a container that runs a database server. This container is used to
+- `database` - a container that runs a database server. This container is used to
store the application data. It can be accessed from the host via a randomly
- assigned port - run `docker compose port mariadb 3306` to get the port number.
+ assigned port - run `docker compose port database 3306` to get the port number.
- `redis` - an optional container that runs a Redis server. This container is
used to store the application cache.
- `solr` - an optional container that runs a Solr server. This container is used
diff --git a/.vortex/docs/content/tools/phpunit.mdx b/.vortex/docs/content/tools/phpunit.mdx
index 6ca43b782..69e5515ad 100644
--- a/.vortex/docs/content/tools/phpunit.mdx
+++ b/.vortex/docs/content/tools/phpunit.mdx
@@ -88,12 +88,6 @@ automated coverage assessment, and in `.logs/coverage/phpunit/.coverage-html` as
HTML coverage report, useful for visual report assessment during test
development.
-The tests need to run with `XDEBUG_MODE=coverage` environment variable set.
-
-```shell
-XDEBUG_MODE=coverage cli vendor/bin/phpunit
-```
-
CI runs tests with coverage by default and stores the reports as artifacts.
### Ignoring lines from coverage
diff --git a/.vortex/docs/content/tools/twig-cs-fixer.mdx b/.vortex/docs/content/tools/twig-cs-fixer.mdx
index 311dc10da..35021575c 100644
--- a/.vortex/docs/content/tools/twig-cs-fixer.mdx
+++ b/.vortex/docs/content/tools/twig-cs-fixer.mdx
@@ -28,18 +28,9 @@ Targets include custom modules and themes.
Adding or removing targets:
```php
-$ruleset = new TwigCsFixer\Ruleset\Ruleset();
-$ruleset->addStandard(new TwigCsFixer\Standard\Twig());
-
$finder = new TwigCsFixer\File\Finder();
$finder->in(__DIR__ . '/web/modules/custom');
$finder->in(__DIR__ . '/web/themes/custom');
-
-$config = new TwigCsFixer\Config\Config();
-$config->setRuleset($ruleset);
-$config->setFinder($finder);
-
-return $config;
```
## Ignoring
diff --git a/.vortex/docs/content/workflows/testing.mdx b/.vortex/docs/content/workflows/testing.mdx
index 7e9055162..7c175a710 100644
--- a/.vortex/docs/content/workflows/testing.mdx
+++ b/.vortex/docs/content/workflows/testing.mdx
@@ -4,21 +4,42 @@ sidebar_position: 2
# Testing
-**Vortex** supports running Unit (PHPUnit) and BDD (Behat) tests.
+**Vortex** supports running Unit, Kernel, Functional, and BDD tests.
-For local development, the tests can be run using handy Ahoy commands:
+For local development, the tests can be run directly or using handy Ahoy
+commands:
-```bash
-ahoy test # Run all tests
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
-ahoy test-unit # Run Unit tests
+
+
+ ```shell
+ vendor/bin/phpunit # Run Unit, Kernel and Functional tests
-ahoy test-kernel # Run Kernel tests
+ vendor/bin/phpunit --testsuite=unit # Run Unit tests
-ahoy test-functional # Run Functional tests
+ vendor/bin/phpunit --testsuite=kernel # Run Kernel tests
-ahoy test-bdd # Run BDD tests
-```
+ vendor/bin/phpunit --testsuite=functional # Run Functional tests
+
+ vendor/bin/behat # Run BDD tests
+ ```
+
+
+ ```shell
+ ahoy test # Run Unit, Kernel and Functional tests
+
+ ahoy test-unit # Run Unit tests
+
+ ahoy test-kernel # Run Kernel tests
+
+ ahoy test-functional # Run Functional tests
+
+ ahoy test-bdd # Run BDD tests
+ ```
+
+
In CI, tests are run by calling the test binaries directly.
@@ -27,78 +48,82 @@ In CI, tests are run by calling the test binaries directly.
**Vortex** uses PHPUnit as a framework for Unit testing.
It is configured to use a copy of Drupal core's `core/phpunit.xml.dist`
-configuration file. This is done to allow per-project customisations.
+configuration file to allow customizing the test suite per project.
### Reporting
-Test reports are stored in `$VORTEX_TEST_RESULTS_DIR/phpunit` directory
+Test reports are stored in `.logs/phpunit` directory
separated into multiple files and named after the suite name.
These reports are usually used in CI to track tests performance and stability.
-### Vortex
+### Boilerplate
-**Vortex** provides a Unit test scaffold for custom [modules](https://github.com/drevops/vortex/blob/develop/web/modules/custom/ys_core/tests/src),
+**Vortex** provides Unit, Kernel and Functional tests boilerplate for custom [modules](https://github.com/drevops/vortex/blob/develop/web/modules/custom/ys_core/tests/src),
[themes](https://github.com/drevops/vortex/blob/develop/web/themes/custom/your_site_theme/tests/src) and
[scripts](https://github.com/drevops/vortex/blob/develop/tests/phpunit).
-These tests already run in CI when you install **Vortex** and can be used as a
-starting point for writing your own.
+These boilerplate tests run in CI when you install **Vortex** and can be used as
+a starting point for writing your own.
#### Drupal settings tests
-**Vortex** provides a [Drupal settings tests](https://github.com/drevops/vortex/blob/develop/tests/phpunit/DrupalSettingsTest.php)
-to test that Drupal settings are correct based on the environment type the site
+**Vortex** provides [Drupal settings tests](https://github.com/drevops/vortex/blob/develop/tests/phpunit/Drupal/DrupalSettingsTest.php)
+to check that Drupal settings are correct based on the environment type the site
is running: with the number of custom modules multiplied by the number of
environment types, it is easy to miss certain settings which may lead to
-unexpected issues with deployments.
+unexpected issues when deploying a project to a different environment.
-It is intended to be used in a consumer site and kept up-to-date with the
-changes to the `settings.php` file.
+It is intended to be used in your site and kept up-to-date with the
+changes made to the `settings.php` file.
#### CI configuration tests
**Vortex** provides a [CI configuration tests](https://github.com/drevops/vortex/blob/develop/tests/phpunit/CircleCiConfigTest.php)
-to assert that CI configuration is correct. It is intended to be used in a consumer
+to check that the CI configuration is correct. It is intended to be used in your
site and kept up-to-date with the CI configurations.
-For example, there are tests for the regular expressions that control for which
-branches the deployment job runs. Such test makes sure that there are no
-unexpected surprises during the consumer site release to production.
+For example, there are tests for regular expressions used to filter the branches
+and tags before they are deployed to the hosting environment.
## BDD testing
-**Vortex** uses Behat for Behavior-Driven Development (BDD) testing.
+**Vortex** uses [Behat](https://behat.org) for Behavior-Driven Development (BDD)
+testing. Behat allows to write human-readable stories that describe the behavior
+of the application. Behat tests primarily focus on critical user journeys,
+serving as comprehensive end-to-end validations.
-It provides full Behat support, including configuration in [behat.yml](https://github.com/drevops/vortex/blob/develop/behat.yml)
-and a [browser container](https://github.com/drevops/vortex/blob/develop/docker-compose.yml) to run interactive tests.
+**Vortex** provides full Behat support, including configuration in [`behat.yml`](https://github.com/drevops/vortex/blob/develop/behat.yml)
+and a [browser container](https://github.com/drevops/vortex/blob/develop/docker-compose.yml) to run tests interactively in a real browser with
+a VNC viewer.
-It also provides additional features:
+Additional features include:
1. [Behat Drupal Extension](https://github.com/drupalprojects/drupalextension) - an extension to work with Drupal.
2. [Behat steps](https://github.com/drevops/behat-steps) - a library of re-usable Behat steps.
2. [Behat Screenshot](https://github.com/drevops/behat-screenshot) - extension to capture screenshots on-demand and on failure.
-3. [Behat Progress formatter](https://github.com/drevops/behat-format-progress-fail) - extension to show progress as TAP and fails inline.
+3. [Behat Progress formatter](https://github.com/drevops/behat-format-progress-fail) - extension to show progress as TAP and failures inline.
4. Parallel profiles - configuration to allow running tests in parallel.
### FeatureContext
-The [FeatureContext.php](https://github.com/drevops/vortex/blob/develop/tests/behat/bootstrap/FeatureContext.php) file comes with
+The [`FeatureContext.php`](https://github.com/drevops/vortex/blob/develop/tests/behat/bootstrap/FeatureContext.php) file comes with
included steps from [Behat steps](https://github.com/drevops/behat-steps) package.
-Custom steps can be added into this file.
+You can add your custom steps into this file.
### Profiles
-Behat `default` profile configured with sensible defaults to allow running Behat
+Behat's `default` profile configured with sensible defaults to allow running it
with provided extensions.
-In CI, the profile can be overridden using `$VORTEX_CI_BEHAT_PROFILE` environment
-variable.
+In CI, the profile can be overridden using `$VORTEX_CI_BEHAT_PROFILE`
+environment variable.
### Parallel runs
-In CI, Behat tests can be tagged to be split between multiple runners. The tags
-are then used by profiles with the identical names to run them.
+In CI, Behat tests can run within multiple runners to increase the speed of the
+test suite. To achieve this, Behat tags are used to mark features and scenarios
+with `@p*` tags.
Out of the box, **Vortex** provides support for unlimited parallel
runners, but only 2 parallel profiles `p0` and `p1`: a feature can be tagged by
@@ -122,25 +147,29 @@ Add `@skipped` tag to a feature or scenario to exclude it from the test run.
Test screenshots are stored into `.logs/screenshots` location by default,
which can be overwritten using `$BEHAT_SCREENSHOT_DIR` variable (courtesy of
-[Behat Screenshot](https://github.com/drevops/behat-screenshot) package). In CI, screenshots are stored as artifacts
-and are accessible in the Artifacts tab.
+[Behat Screenshot](https://github.com/drevops/behat-screenshot) package).
+
+In CI, screenshots are stored as build artifacts. In GitHub Actions, they can be
+downloaded from the `Summary` tab. In CircleCI they are accessible in
+the `Artifacts` tab.
### Format
Out of the box, **Vortex** comes with [Behat Progress formatter](https://github.com/drevops/behat-format-progress-fail)
-Behat output formatter to show progress as TAP and fails inline. This allows to
-continue test runs after failures while maintaining a minimal output.
+output formatter to show progress as TAP and failures inline. This allows to
+continue test runs after a failure while maintaining a minimal output.
### Reporting
-Test reports produced if `$VORTEX_TEST_RESULTS_DIR` is set. They are stored in
-`$VORTEX_TEST_RESULTS_DIR/behat` directory. These reports are usually used in
-CI to track tests performance and stability.
+Test reports are stored in `.logs/behat` directory.
+
+CI usually uses them to to track test performance and stability.
-### Examples
+### Boilerplate
-**Vortex** provides BDD test examples for custom [modules](https://github.com/drevops/vortex/blob/develop/web/modules/custom/ys_core/tests/src)
-and [themes](https://github.com/drevops/vortex/blob/develop/web/themes/custom/your_site_theme/tests/src).
+**Vortex** provides BDD tests boilerplate for [homepage](https://github.com/drevops/vortex/blob/develop/tests/behat/features/homepage.feature)
+and [login](https://github.com/drevops/vortex/blob/develop/tests/behat/features/login.feature)
+user journeys.
-These tests already run in CI when you install **Vortex** and can be used
-as a starting point for writing your own.
+These boilerplate tests run in CI when you install **Vortex** and can be used as
+a starting point for writing your own.
diff --git a/.vortex/docs/content/workflows/updating-vortex.mdx b/.vortex/docs/content/workflows/updating-vortex.mdx
index 352074242..d193ecb99 100644
--- a/.vortex/docs/content/workflows/updating-vortex.mdx
+++ b/.vortex/docs/content/workflows/updating-vortex.mdx
@@ -4,18 +4,14 @@ sidebar_position: 9
# Updating Vortex
-**Vortex** differs from other project templates in that it is not only used
-for a one-time project setup but also for ongoing maintenance. This means that
-you can update **Vortex** to the latest version at any time.
+**Vortex** differs from other project templates: after the initial setup, you can
+update it to get the latest features and bug fixes.
-Once you the update process finishes, you will need to review the changes and
-commit them to your project.
-
-If you have modified any of the files that are provided by **Vortex**,
-the update process will override them, because **Vortex** is not aware of the
-changes you have made. You would need to manually accept or reject the new
-changes.
+After the update, review and commit the changes to your project.
+If you’ve modified files provided by Vortex, the update will overwrite them
+since **Vortex** doesn’t track your changes. You’ll need to manually accept or
+reject the updates.
```shell title="Update to the latest version"
ahoy update-vortex
diff --git a/.vortex/docs/content/workflows/variables.mdx b/.vortex/docs/content/workflows/variables.mdx
index 4c5c91dbd..dfffd05a4 100644
--- a/.vortex/docs/content/workflows/variables.mdx
+++ b/.vortex/docs/content/workflows/variables.mdx
@@ -26,7 +26,7 @@ Defined in: `.env.local.example`
### `AHOY_CONFIRM_WAIT_SKIP`
-When Ahoy prompts are suppressed ([`$AHOY_CONFIRM_RESPONSE`](#ahoy_confirm_response) is `1`), the command will wait for `3` seconds before proceeding. Set this variable to "`1`" to skip the wait.
+When Ahoy prompts are suppressed ([`$AHOY_CONFIRM_RESPONSE`](#ahoy_confirm_response) is `1`), the command will wait for `3` seconds before proceeding. Set this variable to "`1`" to skip the wait.
Default value: `1`
@@ -36,7 +36,7 @@ Defined in: `.env.local.example`
Docker Compose project name.
-Sets the project name for a Docker Compose project. Influences container and network names.
+Sets the project name for a Docker Compose project. Influences container and network names.
Defaults to the name of the project directory.
@@ -44,6 +44,46 @@ Default value: `UNDEFINED`
Defined in: `ENVIRONMENT`
+### `DATABASE_HOST`
+
+Local database host.
+
+Default value: `database`
+
+Defined in: `docker-compose.yml`, `scripts/vortex/info.sh`
+
+### `DATABASE_NAME`
+
+Local database name.
+
+Default value: `drupal`
+
+Defined in: `docker-compose.yml`
+
+### `DATABASE_PASSWORD`
+
+Local database password.
+
+Default value: `drupal`
+
+Defined in: `docker-compose.yml`, `scripts/vortex/info.sh`
+
+### `DATABASE_PORT`
+
+Local database port.
+
+Default value: `3306`
+
+Defined in: `docker-compose.yml`, `scripts/vortex/info.sh`
+
+### `DATABASE_USERNAME`
+
+Local database user.
+
+Default value: `drupal`
+
+Defined in: `docker-compose.yml`, `scripts/vortex/info.sh`
+
### `DRUPAL_ADMIN_EMAIL`
Drupal admin email. May need to be reset if database was sanitized.
@@ -64,7 +104,7 @@ Defined in: `.env`
ClamAV mode.
-Run ClamAV in either daemon mode by setting it to `0` (or 'daemon') or in executable mode by setting it to `1`.
+Run ClamAV in either daemon mode by setting it to `0` (or 'daemon') or in executable mode by setting it to `1`.
Default value: `daemon`
@@ -72,7 +112,7 @@ Defined in: `.env`
### `DRUPAL_CONFIG_PATH`
-Path to configuration directory relative to the project root. Auto-discovered from site's `settings.php` file if not set.
+Path to configuration directory relative to the project root. Auto-discovered from site's `settings.php` file if not set.
Default value: `UNDEFINED`
@@ -94,7 +134,7 @@ Path to private files.
Default value: `${DRUPAL_PUBLIC_FILES}/private`
-Defined in: `.env`
+Defined in: `docker-compose.yml`
### `DRUPAL_PROFILE`
@@ -110,11 +150,11 @@ Path to public files.
Default value: `./${VORTEX_WEBROOT}/sites/default/files`
-Defined in: `.env`
+Defined in: `docker-compose.yml`
### `DRUPAL_REDIS_ENABLED`
-Enable Redis integration. See settings.redis.php for details.
+Enable Redis integration. See settings.redis.php for details.
Default value: `UNDEFINED`
@@ -130,7 +170,7 @@ Defined in: `.env`
### `DRUPAL_SITE_EMAIL`
-Drupal site email. Used only when installing from profile.
+Drupal site email. Used only when installing from profile.
Default value: `webmaster@your-site-url.example`
@@ -138,7 +178,7 @@ Defined in: `.env`, `scripts/vortex/provision.sh`
### `DRUPAL_SITE_NAME`
-Drupal site name. Used only when installing from profile.
+Drupal site name. Used only when installing from profile.
Default value: `${VORTEX_PROJECT}`
@@ -146,7 +186,7 @@ Defined in: `.env`, `scripts/vortex/provision.sh`
### `DRUPAL_STAGE_FILE_PROXY_ORIGIN`
-Stage file proxy origin. Note that HTTP Auth provided by Shield will be automatically added to the origin URL.
+Stage file proxy origin. Note that HTTP Auth provided by Shield will be automatically added to the origin URL.
Default value: `https://www.your-site-url.example/`
@@ -158,7 +198,7 @@ Path to temporary files.
Default value: `${DRUPAL_PRIVATE_FILES}/tmp`
-Defined in: `.env`
+Defined in: `docker-compose.yml`
### `DRUPAL_THEME`
@@ -178,7 +218,7 @@ Defined in: `.env`, `scripts/vortex/login.sh`, `scripts/vortex/logout.sh`
### `GITHUB_TOKEN`
-GitHub token used to overcome API rate limits or access private repositories. @see https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token
+GitHub token used to overcome API rate limits or access private repositories. @see https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token
Default value: `UNDEFINED`
@@ -238,7 +278,7 @@ Defined in: `CI config`
### `RENOVATE_TOKEN`
-Self-hosted Renovate bot token. Create a GitHub token with a permission to write to a repository.
+Self-hosted Renovate bot token. Create a GitHub token with a permission to write to a repository.
Default value: `UNDEFINED`
@@ -448,7 +488,7 @@ Defined in: `.env`, `scripts/vortex/download-db-acquia.sh`
### `VORTEX_DB_DOWNLOAD_CURL_URL`
-Database dump file sourced from CURL, with optional HTTP Basic Authentication credentials embedded into the value.
+Database dump file sourced from CURL, with optional HTTP Basic Authentication credentials embedded into the value.
Default value: `UNDEFINED`
@@ -530,7 +570,7 @@ Defined in: `scripts/vortex/download-db-lagoon.sh`
Wildcard file name to cleanup previously created dump files.
-Cleanup runs only if the variable is set and [`$VORTEX_DB_DOWNLOAD_LAGOON_REMOTE_FILE`](#vortex_db_download_lagoon_remote_file) does not exist.
+Cleanup runs only if the variable is set and [`$VORTEX_DB_DOWNLOAD_LAGOON_REMOTE_FILE`](#vortex_db_download_lagoon_remote_file) does not exist.
Default value: `db_*.sql`
@@ -578,7 +618,7 @@ Defined in: `scripts/vortex/download-db-lagoon.sh`
### `VORTEX_DB_DOWNLOAD_SOURCE`
-Note that "container_registry" works only for database-in-image database storage (when [`$VORTEX_DB_IMAGE`](#vortex_db_image) variable has a value).
+Note that "container_registry" works only for database-in-image database storage (when [`$VORTEX_DB_IMAGE`](#vortex_db_image) variable has a value).
Default value: `curl`
@@ -644,7 +684,7 @@ Defined in: `scripts/vortex/export-db-image.sh`
The service name to capture.
-Default value: `mariadb`
+Default value: `database`
Defined in: `scripts/vortex/export-db-image.sh`
@@ -670,7 +710,7 @@ Defined in: `.env`, `scripts/vortex/download-db-container-registry.sh`, `scripts
Name of the database fall-back container image to use.
-If the image specified in [`$VORTEX_DB_IMAGE`](#vortex_db_image) does not exist and base image was provided - it will be used as a "clean slate" for the database.
+If the image specified in [`$VORTEX_DB_IMAGE`](#vortex_db_image) does not exist and base image was provided - it will be used as a "clean slate" for the database.
Default value: `UNDEFINED`
@@ -707,7 +747,7 @@ Defined in: `scripts/vortex/deploy.sh`
### `VORTEX_DEPLOY_ARTIFACT_DST_BRANCH`
-Remote repository branch. Can be a specific branch or a token. @see https://github.com/drevops/git-artifact#token-support
+Remote repository branch. Can be a specific branch or a token. @see https://github.com/drevops/git-artifact#token-support
Default value: `[branch]`
@@ -747,7 +787,7 @@ Defined in: `scripts/vortex/deploy-artifact.sh`
### `VORTEX_DEPLOY_ARTIFACT_ROOT`
-The root directory where the deployment script should run from. Defaults to the current directory.
+The root directory where the deployment script should run from. Defaults to the current directory.
Default value: `$(pwd)`
@@ -789,7 +829,7 @@ Defined in: `scripts/vortex/deploy-container-registry.sh`
### `VORTEX_DEPLOY_CONTAINER_REGISTRY_MAP`
-Comma-separated map of container services and images to use for deployment in format "service1=org/image1,service2=org/image2".
+Comma-separated map of container services and images to use for deployment in format "service1=org/image1,service2=org/image2".
Default value: `UNDEFINED`
@@ -897,7 +937,7 @@ Defined in: `scripts/vortex/deploy-artifact.sh`, `scripts/vortex/deploy-lagoon.s
The type of deployment.
-Combination of comma-separated values to support multiple deployment targets: `artifact`,`container_registry`, `webhook`, `lagoon`.
+Combination of comma-separated values to support multiple deployment targets: `artifact`,`container_registry`, `webhook`, `lagoon`.
See https://vortex.drevops.com/workflows/deploy
@@ -1087,7 +1127,7 @@ Defined in: `.env`
### `VORTEX_LOCALDEV_URL`
-Local development URL. Override only if you need to use a different URL than the default.
+Local development URL. Override only if you need to use a different URL than the default.
Default value: `.docker.amazee.io`
@@ -1203,7 +1243,7 @@ Defined in: `scripts/vortex/notify-email.sh`
Email address(es) to send notifications to.
-Multiple names can be specified as a comma-separated list of email addresses with optional names in the format "email|name". Example: "to1@example.com|Jane Doe, to2@example.com|John Doe"
+Multiple names can be specified as a comma-separated list of email addresses with optional names in the format "email|name". Example: "to1@example.com|Jane Doe, to2@example.com|John Doe"
Default value: `webmaster@your-site-url.example`
@@ -1419,7 +1459,7 @@ Defined in: `scripts/vortex/notify.sh`
### `VORTEX_NOTIFY_WEBHOOK_HEADERS`
-Webhook headers. Separate multiple headers with a pipe `|`. Example: `Content-type: application/json|Authorization: Bearer API_KEY`.
+Webhook headers. Separate multiple headers with a pipe `|`. Example: `Content-type: application/json|Authorization: Bearer API_KEY`.
Default value: `Content-type: application/json`
@@ -1469,7 +1509,7 @@ Defined in: `.env`
Project name.
-Drives internal naming within the codebase. Does not affect the names of containers and development URL - those depend on the project directory and can be overridden with [`$COMPOSE_PROJECT_NAME`](#compose_project_name).
+Drives internal naming within the codebase. Does not affect the names of containers and development URL - those depend on the project directory and can be overridden with [`$COMPOSE_PROJECT_NAME`](#compose_project_name).
Default value: `your_site`
@@ -1495,7 +1535,7 @@ Defined in: `scripts/vortex/provision.sh`
Overwrite existing database if it exists.
-Usually set to `0` in deployed environments and can be temporary set to `1` for a specific deployment. Set this to `1` in .env.local to override when developing locally.
+Usually set to `0` in deployed environments and can be temporary set to `1` for a specific deployment. Set this to `1` in .env.local to override when developing locally.
Default value: `UNDEFINED`
@@ -1503,7 +1543,7 @@ Defined in: `.env`, `.env.local.example`, `scripts/vortex/provision.sh`
### `VORTEX_PROVISION_POST_OPERATIONS_SKIP`
-Flag to skip running of operations after site provision is complete. Useful to only import the database from file (or install from profile) and not perform any additional operations. For example, when need to capture database state before any updates ran (for example, DB caching in CI).
+Flag to skip running of operations after site provision is complete. Useful to only import the database from file (or install from profile) and not perform any additional operations. For example, when need to capture database state before any updates ran (for example, DB caching in CI).
Default value: `0`
@@ -1513,7 +1553,7 @@ Defined in: `scripts/vortex/provision.sh`
Path to file with custom sanitization SQL queries.
-To skip custom sanitization, remove the file defined in VORTEX_PROVISION_SANITIZE_DB_ADDITIONAL_FILE variable from the codebase.
+To skip custom sanitization, remove the file defined in VORTEX_PROVISION_SANITIZE_DB_ADDITIONAL_FILE variable from the codebase.
Default value: `./scripts/sanitize.sql`
@@ -1521,7 +1561,7 @@ Defined in: `scripts/vortex/provision-sanitize-db.sh`
### `VORTEX_PROVISION_SANITIZE_DB_EMAIL`
-Sanitization email pattern. Sanitization is enabled by default in all non-production environments. @see https://vortex.drevops.com/workflows/build#sanitization
+Sanitization email pattern. Sanitization is enabled by default in all non-production environments. @see https://vortex.drevops.com/workflows/build#sanitization
Default value: `user_%uid@your-site-url.example`
@@ -1537,7 +1577,7 @@ Defined in: `.env`, `scripts/vortex/provision-sanitize-db.sh`
### `VORTEX_PROVISION_SANITIZE_DB_REPLACE_USERNAME_WITH_EMAIL`
-Replace username with email after database sanitization. Useful when email is used as username.
+Replace username with email after database sanitization. Useful when email is used as username.
Default value: `UNDEFINED`
@@ -1547,7 +1587,7 @@ Defined in: `.env`, `scripts/vortex/provision-sanitize-db.sh`
Skip database sanitization.
-Database sanitization is enabled by default in all non-production environments and is always skipped in the production environment.
+Database sanitization is enabled by default in all non-production environments and is always skipped in the production environment.
Default value: `UNDEFINED`
@@ -1604,7 +1644,7 @@ Defined in: `scripts/vortex/doctor.sh`
### `VORTEX_SSH_PREFIX`
Prefix used to load SSH key from prefixes environment variables:
-- VORTEX_[`${VORTEX_SSH_PREFIX}`](#vortex_ssh_prefix)_SSH_FINGERPRINT - the variable name with the SSH key fingerprint value.
+- VORTEX_[`${VORTEX_SSH_PREFIX}`](#vortex_ssh_prefix)_SSH_FINGERPRINT - the variable name with the SSH key fingerprint value.
- VORTEX_[`${VORTEX_SSH_PREFIX}`](#vortex_ssh_prefix)_SSH_FILE - the variable name with the SSH
key file path.
@@ -1654,7 +1694,7 @@ Defined in: `scripts/vortex/task-copy-db-acquia.sh`
### `VORTEX_TASK_COPY_DB_ACQUIA_STATUS_RETRIES`
-Number of status retrieval retries. If this limit reached and task has not yet finished, the task is considered failed.
+Number of status retrieval retries. If this limit reached and task has not yet finished, the task is considered failed.
Default value: `600`
@@ -1694,7 +1734,7 @@ Defined in: `scripts/vortex/task-copy-files-acquia.sh`
### `VORTEX_TASK_COPY_FILES_ACQUIA_STATUS_RETRIES`
-Number of status retrieval retries. If this limit reached and task has not yet finished, the task is considered failed.
+Number of status retrieval retries. If this limit reached and task has not yet finished, the task is considered failed.
Default value: `300`
@@ -1790,7 +1830,7 @@ Defined in: `scripts/vortex/task-purge-cache-acquia.sh`
### `VORTEX_TASK_PURGE_CACHE_ACQUIA_STATUS_RETRIES`
-Number of status retrieval retries. If this limit reached and task has not yet finished, the task is considered failed.
+Number of status retrieval retries. If this limit reached and task has not yet finished, the task is considered failed.
Default value: `300`
diff --git a/.vortex/docs/static/img/diagram-dark.png b/.vortex/docs/static/img/diagram-dark.png
index 0a580931d..64424a07a 100644
Binary files a/.vortex/docs/static/img/diagram-dark.png and b/.vortex/docs/static/img/diagram-dark.png differ
diff --git a/.vortex/docs/static/img/diagram-light.png b/.vortex/docs/static/img/diagram-light.png
index 184db9b85..0d40be6c1 100644
Binary files a/.vortex/docs/static/img/diagram-light.png and b/.vortex/docs/static/img/diagram-light.png differ
diff --git a/.vortex/docs/static/img/logo-vortex-dark.png b/.vortex/docs/static/img/logo-vortex-dark.png
index 00fa43e61..fbb779a01 100644
Binary files a/.vortex/docs/static/img/logo-vortex-dark.png and b/.vortex/docs/static/img/logo-vortex-dark.png differ
diff --git a/.vortex/docs/static/img/logo-vortex-dark.svg b/.vortex/docs/static/img/logo-vortex-dark.svg
index dd881cd93..5160e747f 100644
--- a/.vortex/docs/static/img/logo-vortex-dark.svg
+++ b/.vortex/docs/static/img/logo-vortex-dark.svg
@@ -1,9 +1,10 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
diff --git a/.vortex/docs/static/img/logo-vortex-light.png b/.vortex/docs/static/img/logo-vortex-light.png
index 549198930..87bee0751 100644
Binary files a/.vortex/docs/static/img/logo-vortex-light.png and b/.vortex/docs/static/img/logo-vortex-light.png differ
diff --git a/.vortex/docs/static/img/logo-vortex-light.svg b/.vortex/docs/static/img/logo-vortex-light.svg
index e7a40ed82..b93677e1e 100644
--- a/.vortex/docs/static/img/logo-vortex-light.svg
+++ b/.vortex/docs/static/img/logo-vortex-light.svg
@@ -1,9 +1,9 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
diff --git a/.vortex/installer/.gitignore b/.vortex/installer/.gitignore
index 26b0e2065..f2501fe42 100644
--- a/.vortex/installer/.gitignore
+++ b/.vortex/installer/.gitignore
@@ -2,6 +2,6 @@
/.coverage-html
/.phpunit.cache
/cobertura.xml
-/composer.lock
/vendor
/vendor-bin
+!/composer.lock
diff --git a/.vortex/installer/composer.json b/.vortex/installer/composer.json
index 03c292c13..472178270 100644
--- a/.vortex/installer/composer.json
+++ b/.vortex/installer/composer.json
@@ -1,8 +1,8 @@
{
"name": "drevops/vortex-installer",
- "type": "library",
"description": "Installer for Vortex.",
"license": "GPL-2.0-or-later",
+ "type": "library",
"authors": [
{
"name": "Alex Skrypnyk",
@@ -17,16 +17,17 @@
"source": "https://github.com/drevops/vortex-installer"
},
"require": {
- "php": ">=8.1",
- "symfony/console": "^6.3 || ^7"
+ "php": ">=8.2",
+ "symfony/console": "^7.2",
+ "symfony/filesystem": "^7.2"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8",
"dealerdirect/phpcodesniffer-composer-installer": "^1",
"drupal/coder": "^8.3",
+ "ergebnis/composer-normalize": "^2.45",
"mikey179/vfsstream": "^1.6",
"opis/closure": "^3.6",
- "phpmd/phpmd": "^2.13",
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^10",
"rector/rector": "^1.0.0"
@@ -44,14 +45,23 @@
"tests/phpunit"
]
},
+ "bin": [
+ "install"
+ ],
"config": {
- "sort-packages": true,
"allow-plugins": {
"bamarni/composer-bin-plugin": true,
- "dealerdirect/phpcodesniffer-composer-installer": true
- }
+ "dealerdirect/phpcodesniffer-composer-installer": true,
+ "ergebnis/composer-normalize": true
+ },
+ "sort-packages": true
},
"scripts": {
+ "build": [
+ "@composer bin box require --dev humbug/box",
+ "box validate",
+ "box compile"
+ ],
"lint": [
"phpcs",
"phpstan",
@@ -61,14 +71,7 @@
"rector --clear-cache",
"phpcbf"
],
- "test": "if [ \"${XDEBUG_MODE}\" = 'coverage' ]; then phpunit; else phpunit --no-coverage; fi",
- "build": [
- "@composer bin box require --dev humbug/box",
- "box validate",
- "box compile"
- ]
- },
- "bin": [
- "install"
- ]
+ "reset": "rm -Rf vendor vendor-bin composer.lock",
+ "test": "phpunit"
+ }
}
diff --git a/.vortex/installer/composer.lock b/.vortex/installer/composer.lock
new file mode 100644
index 000000000..79fe7d405
--- /dev/null
+++ b/.vortex/installer/composer.lock
@@ -0,0 +1,3917 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "5a995c729a9bc0de421a808833578ca6",
+ "packages": [
+ {
+ "name": "psr/container",
+ "version": "2.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/container.git",
+ "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+ "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.4.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Container\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common Container Interface (PHP FIG PSR-11)",
+ "homepage": "https://github.com/php-fig/container",
+ "keywords": [
+ "PSR-11",
+ "container",
+ "container-interface",
+ "container-interop",
+ "psr"
+ ],
+ "support": {
+ "issues": "https://github.com/php-fig/container/issues",
+ "source": "https://github.com/php-fig/container/tree/2.0.2"
+ },
+ "time": "2021-11-05T16:47:00+00:00"
+ },
+ {
+ "name": "symfony/console",
+ "version": "v7.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/console.git",
+ "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/console/zipball/fefcc18c0f5d0efe3ab3152f15857298868dc2c3",
+ "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "symfony/polyfill-mbstring": "~1.0",
+ "symfony/service-contracts": "^2.5|^3",
+ "symfony/string": "^6.4|^7.0"
+ },
+ "conflict": {
+ "symfony/dependency-injection": "<6.4",
+ "symfony/dotenv": "<6.4",
+ "symfony/event-dispatcher": "<6.4",
+ "symfony/lock": "<6.4",
+ "symfony/process": "<6.4"
+ },
+ "provide": {
+ "psr/log-implementation": "1.0|2.0|3.0"
+ },
+ "require-dev": {
+ "psr/log": "^1|^2|^3",
+ "symfony/config": "^6.4|^7.0",
+ "symfony/dependency-injection": "^6.4|^7.0",
+ "symfony/event-dispatcher": "^6.4|^7.0",
+ "symfony/http-foundation": "^6.4|^7.0",
+ "symfony/http-kernel": "^6.4|^7.0",
+ "symfony/lock": "^6.4|^7.0",
+ "symfony/messenger": "^6.4|^7.0",
+ "symfony/process": "^6.4|^7.0",
+ "symfony/stopwatch": "^6.4|^7.0",
+ "symfony/var-dumper": "^6.4|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Console\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Eases the creation of beautiful and testable command line interfaces",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "cli",
+ "command-line",
+ "console",
+ "terminal"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/console/tree/v7.2.1"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-12-11T03:49:26+00:00"
+ },
+ {
+ "name": "symfony/deprecation-contracts",
+ "version": "v3.5.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/deprecation-contracts.git",
+ "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6",
+ "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/contracts",
+ "name": "symfony/contracts"
+ },
+ "branch-alias": {
+ "dev-main": "3.5-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "function.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "A generic function and convention to trigger deprecation notices",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-25T14:20:29+00:00"
+ },
+ {
+ "name": "symfony/filesystem",
+ "version": "v7.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/filesystem.git",
+ "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/filesystem/zipball/b8dce482de9d7c9fe2891155035a7248ab5c7fdb",
+ "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "symfony/polyfill-ctype": "~1.8",
+ "symfony/polyfill-mbstring": "~1.8"
+ },
+ "require-dev": {
+ "symfony/process": "^6.4|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Filesystem\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Provides basic utilities for the filesystem",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/filesystem/tree/v7.2.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-10-25T15:15:23+00:00"
+ },
+ {
+ "name": "symfony/polyfill-ctype",
+ "version": "v1.31.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-ctype.git",
+ "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638",
+ "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "provide": {
+ "ext-ctype": "*"
+ },
+ "suggest": {
+ "ext-ctype": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Ctype\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Gert de Pagter",
+ "email": "BackEndTea@gmail.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for ctype functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "ctype",
+ "polyfill",
+ "portable"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-09T11:45:10+00:00"
+ },
+ {
+ "name": "symfony/polyfill-intl-grapheme",
+ "version": "v1.31.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-intl-grapheme.git",
+ "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe",
+ "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "suggest": {
+ "ext-intl": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Intl\\Grapheme\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for intl's grapheme_* functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "grapheme",
+ "intl",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-09T11:45:10+00:00"
+ },
+ {
+ "name": "symfony/polyfill-intl-normalizer",
+ "version": "v1.31.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-intl-normalizer.git",
+ "reference": "3833d7255cc303546435cb650316bff708a1c75c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c",
+ "reference": "3833d7255cc303546435cb650316bff708a1c75c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "suggest": {
+ "ext-intl": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Intl\\Normalizer\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for intl's Normalizer class and related functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "intl",
+ "normalizer",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-09T11:45:10+00:00"
+ },
+ {
+ "name": "symfony/polyfill-mbstring",
+ "version": "v1.31.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-mbstring.git",
+ "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341",
+ "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "provide": {
+ "ext-mbstring": "*"
+ },
+ "suggest": {
+ "ext-mbstring": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Mbstring\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for the Mbstring extension",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "mbstring",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-09T11:45:10+00:00"
+ },
+ {
+ "name": "symfony/service-contracts",
+ "version": "v3.5.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/service-contracts.git",
+ "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0",
+ "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "psr/container": "^1.1|^2.0",
+ "symfony/deprecation-contracts": "^2.5|^3"
+ },
+ "conflict": {
+ "ext-psr": "<1.1|>=2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.5-dev"
+ },
+ "thanks": {
+ "name": "symfony/contracts",
+ "url": "https://github.com/symfony/contracts"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\Service\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Test/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Generic abstractions related to writing services",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "abstractions",
+ "contracts",
+ "decoupling",
+ "interfaces",
+ "interoperability",
+ "standards"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/service-contracts/tree/v3.5.1"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-25T14:20:29+00:00"
+ },
+ {
+ "name": "symfony/string",
+ "version": "v7.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/string.git",
+ "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/string/zipball/446e0d146f991dde3e73f45f2c97a9faad773c82",
+ "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "symfony/polyfill-ctype": "~1.8",
+ "symfony/polyfill-intl-grapheme": "~1.0",
+ "symfony/polyfill-intl-normalizer": "~1.0",
+ "symfony/polyfill-mbstring": "~1.0"
+ },
+ "conflict": {
+ "symfony/translation-contracts": "<2.5"
+ },
+ "require-dev": {
+ "symfony/emoji": "^7.1",
+ "symfony/error-handler": "^6.4|^7.0",
+ "symfony/http-client": "^6.4|^7.0",
+ "symfony/intl": "^6.4|^7.0",
+ "symfony/translation-contracts": "^2.5|^3.0",
+ "symfony/var-exporter": "^6.4|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "Resources/functions.php"
+ ],
+ "psr-4": {
+ "Symfony\\Component\\String\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "grapheme",
+ "i18n",
+ "string",
+ "unicode",
+ "utf-8",
+ "utf8"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/string/tree/v7.2.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-11-13T13:31:26+00:00"
+ }
+ ],
+ "packages-dev": [
+ {
+ "name": "bamarni/composer-bin-plugin",
+ "version": "1.8.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/bamarni/composer-bin-plugin.git",
+ "reference": "92fd7b1e6e9cdae19b0d57369d8ad31a37b6a880"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/bamarni/composer-bin-plugin/zipball/92fd7b1e6e9cdae19b0d57369d8ad31a37b6a880",
+ "reference": "92fd7b1e6e9cdae19b0d57369d8ad31a37b6a880",
+ "shasum": ""
+ },
+ "require": {
+ "composer-plugin-api": "^2.0",
+ "php": "^7.2.5 || ^8.0"
+ },
+ "require-dev": {
+ "composer/composer": "^2.0",
+ "ext-json": "*",
+ "phpstan/extension-installer": "^1.1",
+ "phpstan/phpstan": "^1.8",
+ "phpstan/phpstan-phpunit": "^1.1",
+ "phpunit/phpunit": "^8.5 || ^9.5",
+ "symfony/console": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0",
+ "symfony/finder": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0",
+ "symfony/process": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0"
+ },
+ "type": "composer-plugin",
+ "extra": {
+ "class": "Bamarni\\Composer\\Bin\\BamarniBinPlugin"
+ },
+ "autoload": {
+ "psr-4": {
+ "Bamarni\\Composer\\Bin\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "No conflicts for your bin dependencies",
+ "keywords": [
+ "composer",
+ "conflict",
+ "dependency",
+ "executable",
+ "isolation",
+ "tool"
+ ],
+ "support": {
+ "issues": "https://github.com/bamarni/composer-bin-plugin/issues",
+ "source": "https://github.com/bamarni/composer-bin-plugin/tree/1.8.2"
+ },
+ "time": "2022-10-31T08:38:03+00:00"
+ },
+ {
+ "name": "dealerdirect/phpcodesniffer-composer-installer",
+ "version": "v1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHPCSStandards/composer-installer.git",
+ "reference": "4be43904336affa5c2f70744a348312336afd0da"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/4be43904336affa5c2f70744a348312336afd0da",
+ "reference": "4be43904336affa5c2f70744a348312336afd0da",
+ "shasum": ""
+ },
+ "require": {
+ "composer-plugin-api": "^1.0 || ^2.0",
+ "php": ">=5.4",
+ "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0"
+ },
+ "require-dev": {
+ "composer/composer": "*",
+ "ext-json": "*",
+ "ext-zip": "*",
+ "php-parallel-lint/php-parallel-lint": "^1.3.1",
+ "phpcompatibility/php-compatibility": "^9.0",
+ "yoast/phpunit-polyfills": "^1.0"
+ },
+ "type": "composer-plugin",
+ "extra": {
+ "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin"
+ },
+ "autoload": {
+ "psr-4": {
+ "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Franck Nijhof",
+ "email": "franck.nijhof@dealerdirect.com",
+ "homepage": "http://www.frenck.nl",
+ "role": "Developer / IT Manager"
+ },
+ {
+ "name": "Contributors",
+ "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors"
+ }
+ ],
+ "description": "PHP_CodeSniffer Standards Composer Installer Plugin",
+ "homepage": "http://www.dealerdirect.com",
+ "keywords": [
+ "PHPCodeSniffer",
+ "PHP_CodeSniffer",
+ "code quality",
+ "codesniffer",
+ "composer",
+ "installer",
+ "phpcbf",
+ "phpcs",
+ "plugin",
+ "qa",
+ "quality",
+ "standard",
+ "standards",
+ "style guide",
+ "stylecheck",
+ "tests"
+ ],
+ "support": {
+ "issues": "https://github.com/PHPCSStandards/composer-installer/issues",
+ "source": "https://github.com/PHPCSStandards/composer-installer"
+ },
+ "time": "2023-01-05T11:28:13+00:00"
+ },
+ {
+ "name": "drupal/coder",
+ "version": "8.3.27",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/pfrenssen/coder.git",
+ "reference": "04a4563b82c419e43cc58393a78b21c44fcc29e2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/pfrenssen/coder/zipball/04a4563b82c419e43cc58393a78b21c44fcc29e2",
+ "reference": "04a4563b82c419e43cc58393a78b21c44fcc29e2",
+ "shasum": ""
+ },
+ "require": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.7.1 || ^1.0.0",
+ "ext-mbstring": "*",
+ "php": ">=7.2",
+ "sirbrillig/phpcs-variable-analysis": "^2.11.7",
+ "slevomat/coding-standard": "^8.11",
+ "squizlabs/php_codesniffer": "^3.11.2",
+ "symfony/yaml": ">=3.4.0"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "^1.7.12",
+ "phpunit/phpunit": "^8.0"
+ },
+ "type": "phpcodesniffer-standard",
+ "autoload": {
+ "psr-4": {
+ "Drupal\\": "coder_sniffer/Drupal/",
+ "DrupalPractice\\": "coder_sniffer/DrupalPractice/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "GPL-2.0-or-later"
+ ],
+ "description": "Coder is a library to review Drupal code.",
+ "homepage": "https://www.drupal.org/project/coder",
+ "keywords": [
+ "code review",
+ "phpcs",
+ "standards"
+ ],
+ "support": {
+ "issues": "https://www.drupal.org/project/issues/coder",
+ "source": "https://www.drupal.org/project/coder"
+ },
+ "time": "2025-01-06T09:46:24+00:00"
+ },
+ {
+ "name": "ergebnis/composer-normalize",
+ "version": "2.45.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ergebnis/composer-normalize.git",
+ "reference": "bb82b484bed2556da6311b9eff779fa7e73ce937"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ergebnis/composer-normalize/zipball/bb82b484bed2556da6311b9eff779fa7e73ce937",
+ "reference": "bb82b484bed2556da6311b9eff779fa7e73ce937",
+ "shasum": ""
+ },
+ "require": {
+ "composer-plugin-api": "^2.0.0",
+ "ergebnis/json": "^1.4.0",
+ "ergebnis/json-normalizer": "^4.8.0",
+ "ergebnis/json-printer": "^3.7.0",
+ "ext-json": "*",
+ "justinrainbow/json-schema": "^5.2.12 || ^6.0.0",
+ "localheinz/diff": "^1.2.0",
+ "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0"
+ },
+ "require-dev": {
+ "composer/composer": "^2.8.3",
+ "ergebnis/license": "^2.6.0",
+ "ergebnis/php-cs-fixer-config": "^6.39.0",
+ "ergebnis/phpunit-slow-test-detector": "^2.17.0",
+ "fakerphp/faker": "^1.24.1",
+ "infection/infection": "~0.26.6",
+ "phpstan/extension-installer": "^1.4.3",
+ "phpstan/phpstan": "^1.12.12",
+ "phpstan/phpstan-deprecation-rules": "^1.2.1",
+ "phpstan/phpstan-phpunit": "^1.4.1",
+ "phpstan/phpstan-strict-rules": "^1.6.1",
+ "phpunit/phpunit": "^9.6.20",
+ "rector/rector": "^1.2.10",
+ "symfony/filesystem": "^5.4.41"
+ },
+ "type": "composer-plugin",
+ "extra": {
+ "class": "Ergebnis\\Composer\\Normalize\\NormalizePlugin",
+ "branch-alias": {
+ "dev-main": "2.44-dev"
+ },
+ "plugin-optional": true,
+ "composer-normalize": {
+ "indent-size": 2,
+ "indent-style": "space"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Ergebnis\\Composer\\Normalize\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Andreas Möller",
+ "email": "am@localheinz.com",
+ "homepage": "https://localheinz.com"
+ }
+ ],
+ "description": "Provides a composer plugin for normalizing composer.json.",
+ "homepage": "https://github.com/ergebnis/composer-normalize",
+ "keywords": [
+ "composer",
+ "normalize",
+ "normalizer",
+ "plugin"
+ ],
+ "support": {
+ "issues": "https://github.com/ergebnis/composer-normalize/issues",
+ "security": "https://github.com/ergebnis/composer-normalize/blob/main/.github/SECURITY.md",
+ "source": "https://github.com/ergebnis/composer-normalize"
+ },
+ "time": "2024-12-04T18:36:37+00:00"
+ },
+ {
+ "name": "ergebnis/json",
+ "version": "1.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ergebnis/json.git",
+ "reference": "7656ac2aa6c2ca4408f96f599e9a17a22c464f69"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ergebnis/json/zipball/7656ac2aa6c2ca4408f96f599e9a17a22c464f69",
+ "reference": "7656ac2aa6c2ca4408f96f599e9a17a22c464f69",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0"
+ },
+ "require-dev": {
+ "ergebnis/data-provider": "^3.3.0",
+ "ergebnis/license": "^2.5.0",
+ "ergebnis/php-cs-fixer-config": "^6.37.0",
+ "ergebnis/phpunit-slow-test-detector": "^2.16.1",
+ "fakerphp/faker": "^1.24.0",
+ "infection/infection": "~0.26.6",
+ "phpstan/extension-installer": "^1.4.3",
+ "phpstan/phpstan": "^1.12.10",
+ "phpstan/phpstan-deprecation-rules": "^1.2.1",
+ "phpstan/phpstan-phpunit": "^1.4.0",
+ "phpstan/phpstan-strict-rules": "^1.6.1",
+ "phpunit/phpunit": "^9.6.18",
+ "rector/rector": "^1.2.10"
+ },
+ "type": "library",
+ "extra": {
+ "composer-normalize": {
+ "indent-size": 2,
+ "indent-style": "space"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Ergebnis\\Json\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Andreas Möller",
+ "email": "am@localheinz.com",
+ "homepage": "https://localheinz.com"
+ }
+ ],
+ "description": "Provides a Json value object for representing a valid JSON string.",
+ "homepage": "https://github.com/ergebnis/json",
+ "keywords": [
+ "json"
+ ],
+ "support": {
+ "issues": "https://github.com/ergebnis/json/issues",
+ "security": "https://github.com/ergebnis/json/blob/main/.github/SECURITY.md",
+ "source": "https://github.com/ergebnis/json"
+ },
+ "time": "2024-11-17T11:51:22+00:00"
+ },
+ {
+ "name": "ergebnis/json-normalizer",
+ "version": "4.8.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ergebnis/json-normalizer.git",
+ "reference": "e3a477b62808f377f4fc69a50f9eb66ec102747b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ergebnis/json-normalizer/zipball/e3a477b62808f377f4fc69a50f9eb66ec102747b",
+ "reference": "e3a477b62808f377f4fc69a50f9eb66ec102747b",
+ "shasum": ""
+ },
+ "require": {
+ "ergebnis/json": "^1.2.0",
+ "ergebnis/json-pointer": "^3.4.0",
+ "ergebnis/json-printer": "^3.5.0",
+ "ergebnis/json-schema-validator": "^4.2.0",
+ "ext-json": "*",
+ "justinrainbow/json-schema": "^5.2.12 || ^6.0.0",
+ "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0"
+ },
+ "require-dev": {
+ "composer/semver": "^3.4.3",
+ "ergebnis/composer-normalize": "^2.44.0",
+ "ergebnis/data-provider": "^3.3.0",
+ "ergebnis/license": "^2.5.0",
+ "ergebnis/php-cs-fixer-config": "^6.37.0",
+ "ergebnis/phpunit-slow-test-detector": "^2.16.1",
+ "fakerphp/faker": "^1.24.0",
+ "infection/infection": "~0.26.6",
+ "phpstan/extension-installer": "^1.4.3",
+ "phpstan/phpstan": "^1.12.10",
+ "phpstan/phpstan-deprecation-rules": "^1.2.1",
+ "phpstan/phpstan-phpunit": "^1.4.0",
+ "phpstan/phpstan-strict-rules": "^1.6.1",
+ "phpunit/phpunit": "^9.6.19",
+ "rector/rector": "^1.2.10"
+ },
+ "suggest": {
+ "composer/semver": "If you want to use ComposerJsonNormalizer or VersionConstraintNormalizer"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "4.8-dev"
+ },
+ "composer-normalize": {
+ "indent-size": 2,
+ "indent-style": "space"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Ergebnis\\Json\\Normalizer\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Andreas Möller",
+ "email": "am@localheinz.com",
+ "homepage": "https://localheinz.com"
+ }
+ ],
+ "description": "Provides generic and vendor-specific normalizers for normalizing JSON documents.",
+ "homepage": "https://github.com/ergebnis/json-normalizer",
+ "keywords": [
+ "json",
+ "normalizer"
+ ],
+ "support": {
+ "issues": "https://github.com/ergebnis/json-normalizer/issues",
+ "security": "https://github.com/ergebnis/json-normalizer/blob/main/.github/SECURITY.md",
+ "source": "https://github.com/ergebnis/json-normalizer"
+ },
+ "time": "2024-12-04T16:48:55+00:00"
+ },
+ {
+ "name": "ergebnis/json-pointer",
+ "version": "3.6.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ergebnis/json-pointer.git",
+ "reference": "4fc85d8edb74466d282119d8d9541ec7cffc0798"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ergebnis/json-pointer/zipball/4fc85d8edb74466d282119d8d9541ec7cffc0798",
+ "reference": "4fc85d8edb74466d282119d8d9541ec7cffc0798",
+ "shasum": ""
+ },
+ "require": {
+ "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0"
+ },
+ "require-dev": {
+ "ergebnis/composer-normalize": "^2.43.0",
+ "ergebnis/data-provider": "^3.2.0",
+ "ergebnis/license": "^2.4.0",
+ "ergebnis/php-cs-fixer-config": "^6.32.0",
+ "ergebnis/phpunit-slow-test-detector": "^2.15.0",
+ "fakerphp/faker": "^1.23.1",
+ "infection/infection": "~0.26.6",
+ "phpstan/extension-installer": "^1.4.3",
+ "phpstan/phpstan": "^1.12.10",
+ "phpstan/phpstan-deprecation-rules": "^1.2.1",
+ "phpstan/phpstan-phpunit": "^1.4.0",
+ "phpstan/phpstan-strict-rules": "^1.6.1",
+ "phpunit/phpunit": "^9.6.19",
+ "rector/rector": "^1.2.10"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.6-dev"
+ },
+ "composer-normalize": {
+ "indent-size": 2,
+ "indent-style": "space"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Ergebnis\\Json\\Pointer\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Andreas Möller",
+ "email": "am@localheinz.com",
+ "homepage": "https://localheinz.com"
+ }
+ ],
+ "description": "Provides an abstraction of a JSON pointer.",
+ "homepage": "https://github.com/ergebnis/json-pointer",
+ "keywords": [
+ "RFC6901",
+ "json",
+ "pointer"
+ ],
+ "support": {
+ "issues": "https://github.com/ergebnis/json-pointer/issues",
+ "security": "https://github.com/ergebnis/json-pointer/blob/main/.github/SECURITY.md",
+ "source": "https://github.com/ergebnis/json-pointer"
+ },
+ "time": "2024-11-17T12:37:06+00:00"
+ },
+ {
+ "name": "ergebnis/json-printer",
+ "version": "3.7.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ergebnis/json-printer.git",
+ "reference": "ced41fce7854152f0e8f38793c2ffe59513cdd82"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ergebnis/json-printer/zipball/ced41fce7854152f0e8f38793c2ffe59513cdd82",
+ "reference": "ced41fce7854152f0e8f38793c2ffe59513cdd82",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "ext-mbstring": "*",
+ "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0"
+ },
+ "require-dev": {
+ "ergebnis/data-provider": "^3.3.0",
+ "ergebnis/license": "^2.5.0",
+ "ergebnis/php-cs-fixer-config": "^6.37.0",
+ "ergebnis/phpunit-slow-test-detector": "^2.16.1",
+ "fakerphp/faker": "^1.24.0",
+ "infection/infection": "~0.26.6",
+ "phpstan/extension-installer": "^1.4.3",
+ "phpstan/phpstan": "^1.12.10",
+ "phpstan/phpstan-deprecation-rules": "^1.2.1",
+ "phpstan/phpstan-phpunit": "^1.4.1",
+ "phpstan/phpstan-strict-rules": "^1.6.1",
+ "phpunit/phpunit": "^9.6.21",
+ "rector/rector": "^1.2.10"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Ergebnis\\Json\\Printer\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Andreas Möller",
+ "email": "am@localheinz.com",
+ "homepage": "https://localheinz.com"
+ }
+ ],
+ "description": "Provides a JSON printer, allowing for flexible indentation.",
+ "homepage": "https://github.com/ergebnis/json-printer",
+ "keywords": [
+ "formatter",
+ "json",
+ "printer"
+ ],
+ "support": {
+ "issues": "https://github.com/ergebnis/json-printer/issues",
+ "security": "https://github.com/ergebnis/json-printer/blob/main/.github/SECURITY.md",
+ "source": "https://github.com/ergebnis/json-printer"
+ },
+ "time": "2024-11-17T11:20:51+00:00"
+ },
+ {
+ "name": "ergebnis/json-schema-validator",
+ "version": "4.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ergebnis/json-schema-validator.git",
+ "reference": "85f90c81f718aebba1d738800af83eeb447dc7ec"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ergebnis/json-schema-validator/zipball/85f90c81f718aebba1d738800af83eeb447dc7ec",
+ "reference": "85f90c81f718aebba1d738800af83eeb447dc7ec",
+ "shasum": ""
+ },
+ "require": {
+ "ergebnis/json": "^1.2.0",
+ "ergebnis/json-pointer": "^3.4.0",
+ "ext-json": "*",
+ "justinrainbow/json-schema": "^5.2.12 || ^6.0.0",
+ "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0"
+ },
+ "require-dev": {
+ "ergebnis/composer-normalize": "^2.44.0",
+ "ergebnis/data-provider": "^3.3.0",
+ "ergebnis/license": "^2.5.0",
+ "ergebnis/php-cs-fixer-config": "^6.37.0",
+ "ergebnis/phpunit-slow-test-detector": "^2.16.1",
+ "fakerphp/faker": "^1.24.0",
+ "infection/infection": "~0.26.6",
+ "phpstan/extension-installer": "^1.4.3",
+ "phpstan/phpstan": "^1.12.10",
+ "phpstan/phpstan-deprecation-rules": "^1.2.1",
+ "phpstan/phpstan-phpunit": "^1.4.0",
+ "phpstan/phpstan-strict-rules": "^1.6.1",
+ "phpunit/phpunit": "^9.6.20",
+ "rector/rector": "^1.2.10"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "4.4-dev"
+ },
+ "composer-normalize": {
+ "indent-size": 2,
+ "indent-style": "space"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Ergebnis\\Json\\SchemaValidator\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Andreas Möller",
+ "email": "am@localheinz.com",
+ "homepage": "https://localheinz.com"
+ }
+ ],
+ "description": "Provides a JSON schema validator, building on top of justinrainbow/json-schema.",
+ "homepage": "https://github.com/ergebnis/json-schema-validator",
+ "keywords": [
+ "json",
+ "schema",
+ "validator"
+ ],
+ "support": {
+ "issues": "https://github.com/ergebnis/json-schema-validator/issues",
+ "security": "https://github.com/ergebnis/json-schema-validator/blob/main/.github/SECURITY.md",
+ "source": "https://github.com/ergebnis/json-schema-validator"
+ },
+ "time": "2024-11-18T06:32:28+00:00"
+ },
+ {
+ "name": "icecave/parity",
+ "version": "1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/icecave/parity.git",
+ "reference": "0109fef58b3230d23b20b2ac52ecdf477218d300"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/icecave/parity/zipball/0109fef58b3230d23b20b2ac52ecdf477218d300",
+ "reference": "0109fef58b3230d23b20b2ac52ecdf477218d300",
+ "shasum": ""
+ },
+ "require": {
+ "icecave/repr": "~1",
+ "php": ">=5.3"
+ },
+ "require-dev": {
+ "eloquent/liberator": "~1",
+ "icecave/archer": "~1"
+ },
+ "suggest": {
+ "eloquent/asplode": "Drop-in exception-based error handling."
+ },
+ "type": "library",
+ "autoload": {
+ "psr-0": {
+ "Icecave\\Parity": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "James Harris",
+ "email": "james.harris@icecave.com.au",
+ "homepage": "https://github.com/jmalloc"
+ }
+ ],
+ "description": "A customizable deep comparison library.",
+ "homepage": "https://github.com/IcecaveStudios/parity",
+ "keywords": [
+ "compare",
+ "comparison",
+ "equal",
+ "equality",
+ "greater",
+ "less",
+ "sort",
+ "sorting"
+ ],
+ "support": {
+ "issues": "https://github.com/icecave/parity/issues",
+ "source": "https://github.com/icecave/parity/tree/1.0.0"
+ },
+ "time": "2014-01-17T05:56:27+00:00"
+ },
+ {
+ "name": "icecave/repr",
+ "version": "1.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/icecave/repr.git",
+ "reference": "8a3d2953adf5f464a06e3e2587aeacc97e2bed07"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/icecave/repr/zipball/8a3d2953adf5f464a06e3e2587aeacc97e2bed07",
+ "reference": "8a3d2953adf5f464a06e3e2587aeacc97e2bed07",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3"
+ },
+ "require-dev": {
+ "icecave/archer": "~1"
+ },
+ "suggest": {
+ "eloquent/asplode": "Drop-in exception-based error handling."
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Icecave\\Repr\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "James Harris",
+ "email": "james.harris@icecave.com.au",
+ "homepage": "https://github.com/jmalloc"
+ }
+ ],
+ "description": "A library for generating string representations of any value, inspired by Python's reprlib library.",
+ "homepage": "https://github.com/IcecaveStudios/repr",
+ "keywords": [
+ "human",
+ "readable",
+ "repr",
+ "representation",
+ "string"
+ ],
+ "support": {
+ "issues": "https://github.com/icecave/repr/issues",
+ "source": "https://github.com/icecave/repr/tree/1.0.1"
+ },
+ "time": "2014-07-25T05:44:41+00:00"
+ },
+ {
+ "name": "justinrainbow/json-schema",
+ "version": "6.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/jsonrainbow/json-schema.git",
+ "reference": "a38c6198d53b09c0702f440585a4f4a5d9137bd9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/a38c6198d53b09c0702f440585a4f4a5d9137bd9",
+ "reference": "a38c6198d53b09c0702f440585a4f4a5d9137bd9",
+ "shasum": ""
+ },
+ "require": {
+ "icecave/parity": "1.0.0",
+ "marc-mabe/php-enum": "^2.0 || ^3.0 || ^4.0",
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "~2.2.20 || ~2.19.0",
+ "json-schema/json-schema-test-suite": "1.2.0",
+ "phpunit/phpunit": "^4.8.35"
+ },
+ "bin": [
+ "bin/validate-json"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "6.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "JsonSchema\\": "src/JsonSchema/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Bruno Prieto Reis",
+ "email": "bruno.p.reis@gmail.com"
+ },
+ {
+ "name": "Justin Rainbow",
+ "email": "justin.rainbow@gmail.com"
+ },
+ {
+ "name": "Igor Wiedler",
+ "email": "igor@wiedler.ch"
+ },
+ {
+ "name": "Robert Schönthal",
+ "email": "seroscho@googlemail.com"
+ }
+ ],
+ "description": "A library to validate a json schema.",
+ "homepage": "https://github.com/jsonrainbow/json-schema",
+ "keywords": [
+ "json",
+ "schema"
+ ],
+ "support": {
+ "issues": "https://github.com/jsonrainbow/json-schema/issues",
+ "source": "https://github.com/jsonrainbow/json-schema/tree/6.0.0"
+ },
+ "time": "2024-07-30T17:49:21+00:00"
+ },
+ {
+ "name": "localheinz/diff",
+ "version": "1.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/localheinz/diff.git",
+ "reference": "ec413943c2b518464865673fd5b38f7df867a010"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/localheinz/diff/zipball/ec413943c2b518464865673fd5b38f7df867a010",
+ "reference": "ec413943c2b518464865673fd5b38f7df867a010",
+ "shasum": ""
+ },
+ "require": {
+ "php": "~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^7.5.0 || ^8.5.23",
+ "symfony/process": "^4.2 || ^5"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Kore Nordmann",
+ "email": "mail@kore-nordmann.de"
+ }
+ ],
+ "description": "Fork of sebastian/diff for use with ergebnis/composer-normalize",
+ "homepage": "https://github.com/localheinz/diff",
+ "keywords": [
+ "diff",
+ "udiff",
+ "unidiff",
+ "unified diff"
+ ],
+ "support": {
+ "issues": "https://github.com/localheinz/diff/issues",
+ "source": "https://github.com/localheinz/diff/tree/1.2.0"
+ },
+ "time": "2024-12-04T14:16:01+00:00"
+ },
+ {
+ "name": "marc-mabe/php-enum",
+ "version": "v4.7.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/marc-mabe/php-enum.git",
+ "reference": "7159809e5cfa041dca28e61f7f7ae58063aae8ed"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/marc-mabe/php-enum/zipball/7159809e5cfa041dca28e61f7f7ae58063aae8ed",
+ "reference": "7159809e5cfa041dca28e61f7f7ae58063aae8ed",
+ "shasum": ""
+ },
+ "require": {
+ "ext-reflection": "*",
+ "php": "^7.1 | ^8.0"
+ },
+ "require-dev": {
+ "phpbench/phpbench": "^0.16.10 || ^1.0.4",
+ "phpstan/phpstan": "^1.3.1",
+ "phpunit/phpunit": "^7.5.20 | ^8.5.22 | ^9.5.11",
+ "vimeo/psalm": "^4.17.0 | ^5.26.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-3.x": "3.2-dev",
+ "dev-master": "4.7-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "MabeEnum\\": "src/"
+ },
+ "classmap": [
+ "stubs/Stringable.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Marc Bennewitz",
+ "email": "dev@mabe.berlin",
+ "homepage": "https://mabe.berlin/",
+ "role": "Lead"
+ }
+ ],
+ "description": "Simple and fast implementation of enumerations with native PHP",
+ "homepage": "https://github.com/marc-mabe/php-enum",
+ "keywords": [
+ "enum",
+ "enum-map",
+ "enum-set",
+ "enumeration",
+ "enumerator",
+ "enummap",
+ "enumset",
+ "map",
+ "set",
+ "type",
+ "type-hint",
+ "typehint"
+ ],
+ "support": {
+ "issues": "https://github.com/marc-mabe/php-enum/issues",
+ "source": "https://github.com/marc-mabe/php-enum/tree/v4.7.1"
+ },
+ "time": "2024-11-28T04:54:44+00:00"
+ },
+ {
+ "name": "mikey179/vfsstream",
+ "version": "v1.6.12",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/bovigo/vfsStream.git",
+ "reference": "fe695ec993e0a55c3abdda10a9364eb31c6f1bf0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/bovigo/vfsStream/zipball/fe695ec993e0a55c3abdda10a9364eb31c6f1bf0",
+ "reference": "fe695ec993e0a55c3abdda10a9364eb31c6f1bf0",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^7.5||^8.5||^9.6",
+ "yoast/phpunit-polyfills": "^2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.6.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "org\\bovigo\\vfs\\": "src/main/php"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Frank Kleine",
+ "homepage": "http://frankkleine.de/",
+ "role": "Developer"
+ }
+ ],
+ "description": "Virtual file system to mock the real file system in unit tests.",
+ "homepage": "http://vfs.bovigo.org/",
+ "support": {
+ "issues": "https://github.com/bovigo/vfsStream/issues",
+ "source": "https://github.com/bovigo/vfsStream/tree/master",
+ "wiki": "https://github.com/bovigo/vfsStream/wiki"
+ },
+ "time": "2024-08-29T18:43:31+00:00"
+ },
+ {
+ "name": "myclabs/deep-copy",
+ "version": "1.12.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/myclabs/DeepCopy.git",
+ "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845",
+ "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1 || ^8.0"
+ },
+ "conflict": {
+ "doctrine/collections": "<1.6.8",
+ "doctrine/common": "<2.13.3 || >=3 <3.2.2"
+ },
+ "require-dev": {
+ "doctrine/collections": "^1.6.8",
+ "doctrine/common": "^2.13.3 || ^3.2.2",
+ "phpspec/prophecy": "^1.10",
+ "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/DeepCopy/deep_copy.php"
+ ],
+ "psr-4": {
+ "DeepCopy\\": "src/DeepCopy/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Create deep copies (clones) of your objects",
+ "keywords": [
+ "clone",
+ "copy",
+ "duplicate",
+ "object",
+ "object graph"
+ ],
+ "support": {
+ "issues": "https://github.com/myclabs/DeepCopy/issues",
+ "source": "https://github.com/myclabs/DeepCopy/tree/1.12.1"
+ },
+ "funding": [
+ {
+ "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-11-08T17:47:46+00:00"
+ },
+ {
+ "name": "nikic/php-parser",
+ "version": "v5.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nikic/PHP-Parser.git",
+ "reference": "447a020a1f875a434d62f2a401f53b82a396e494"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494",
+ "reference": "447a020a1f875a434d62f2a401f53b82a396e494",
+ "shasum": ""
+ },
+ "require": {
+ "ext-ctype": "*",
+ "ext-json": "*",
+ "ext-tokenizer": "*",
+ "php": ">=7.4"
+ },
+ "require-dev": {
+ "ircmaxell/php-yacc": "^0.0.7",
+ "phpunit/phpunit": "^9.0"
+ },
+ "bin": [
+ "bin/php-parse"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PhpParser\\": "lib/PhpParser"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Nikita Popov"
+ }
+ ],
+ "description": "A PHP parser written in PHP",
+ "keywords": [
+ "parser",
+ "php"
+ ],
+ "support": {
+ "issues": "https://github.com/nikic/PHP-Parser/issues",
+ "source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0"
+ },
+ "time": "2024-12-30T11:07:19+00:00"
+ },
+ {
+ "name": "opis/closure",
+ "version": "3.6.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/opis/closure.git",
+ "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad",
+ "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.4 || ^7.0 || ^8.0"
+ },
+ "require-dev": {
+ "jeremeamia/superclosure": "^2.0",
+ "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.6.x-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "functions.php"
+ ],
+ "psr-4": {
+ "Opis\\Closure\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Marius Sarca",
+ "email": "marius.sarca@gmail.com"
+ },
+ {
+ "name": "Sorin Sarca",
+ "email": "sarca_sorin@hotmail.com"
+ }
+ ],
+ "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.",
+ "homepage": "https://opis.io/closure",
+ "keywords": [
+ "anonymous functions",
+ "closure",
+ "function",
+ "serializable",
+ "serialization",
+ "serialize"
+ ],
+ "support": {
+ "issues": "https://github.com/opis/closure/issues",
+ "source": "https://github.com/opis/closure/tree/3.6.3"
+ },
+ "time": "2022-01-27T09:35:39+00:00"
+ },
+ {
+ "name": "phar-io/manifest",
+ "version": "2.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/manifest.git",
+ "reference": "54750ef60c58e43759730615a392c31c80e23176"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176",
+ "reference": "54750ef60c58e43759730615a392c31c80e23176",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-libxml": "*",
+ "ext-phar": "*",
+ "ext-xmlwriter": "*",
+ "phar-io/version": "^3.0.1",
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
+ "support": {
+ "issues": "https://github.com/phar-io/manifest/issues",
+ "source": "https://github.com/phar-io/manifest/tree/2.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/theseer",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-03T12:33:53+00:00"
+ },
+ {
+ "name": "phar-io/version",
+ "version": "3.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/version.git",
+ "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
+ "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Library for handling version information and constraints",
+ "support": {
+ "issues": "https://github.com/phar-io/version/issues",
+ "source": "https://github.com/phar-io/version/tree/3.2.1"
+ },
+ "time": "2022-02-21T01:04:05+00:00"
+ },
+ {
+ "name": "phpstan/phpdoc-parser",
+ "version": "1.33.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpstan/phpdoc-parser.git",
+ "reference": "82a311fd3690fb2bf7b64d5c98f912b3dd746140"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/82a311fd3690fb2bf7b64d5c98f912b3dd746140",
+ "reference": "82a311fd3690fb2bf7b64d5c98f912b3dd746140",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0"
+ },
+ "require-dev": {
+ "doctrine/annotations": "^2.0",
+ "nikic/php-parser": "^4.15",
+ "php-parallel-lint/php-parallel-lint": "^1.2",
+ "phpstan/extension-installer": "^1.0",
+ "phpstan/phpstan": "^1.5",
+ "phpstan/phpstan-phpunit": "^1.1",
+ "phpstan/phpstan-strict-rules": "^1.0",
+ "phpunit/phpunit": "^9.5",
+ "symfony/process": "^5.2"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "PHPStan\\PhpDocParser\\": [
+ "src/"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "PHPDoc parser with support for nullable, intersection and generic types",
+ "support": {
+ "issues": "https://github.com/phpstan/phpdoc-parser/issues",
+ "source": "https://github.com/phpstan/phpdoc-parser/tree/1.33.0"
+ },
+ "time": "2024-10-13T11:25:22+00:00"
+ },
+ {
+ "name": "phpstan/phpstan",
+ "version": "1.12.15",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpstan/phpstan.git",
+ "reference": "c91d4e8bc056f46cf653656e6f71004b254574d1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/c91d4e8bc056f46cf653656e6f71004b254574d1",
+ "reference": "c91d4e8bc056f46cf653656e6f71004b254574d1",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2|^8.0"
+ },
+ "conflict": {
+ "phpstan/phpstan-shim": "*"
+ },
+ "bin": [
+ "phpstan",
+ "phpstan.phar"
+ ],
+ "type": "library",
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "PHPStan - PHP Static Analysis Tool",
+ "keywords": [
+ "dev",
+ "static analysis"
+ ],
+ "support": {
+ "docs": "https://phpstan.org/user-guide/getting-started",
+ "forum": "https://github.com/phpstan/phpstan/discussions",
+ "issues": "https://github.com/phpstan/phpstan/issues",
+ "security": "https://github.com/phpstan/phpstan/security/policy",
+ "source": "https://github.com/phpstan/phpstan-src"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/ondrejmirtes",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/phpstan",
+ "type": "github"
+ }
+ ],
+ "time": "2025-01-05T16:40:22+00:00"
+ },
+ {
+ "name": "phpunit/php-code-coverage",
+ "version": "10.1.16",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+ "reference": "7e308268858ed6baedc8704a304727d20bc07c77"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77",
+ "reference": "7e308268858ed6baedc8704a304727d20bc07c77",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-libxml": "*",
+ "ext-xmlwriter": "*",
+ "nikic/php-parser": "^4.19.1 || ^5.1.0",
+ "php": ">=8.1",
+ "phpunit/php-file-iterator": "^4.1.0",
+ "phpunit/php-text-template": "^3.0.1",
+ "sebastian/code-unit-reverse-lookup": "^3.0.0",
+ "sebastian/complexity": "^3.2.0",
+ "sebastian/environment": "^6.1.0",
+ "sebastian/lines-of-code": "^2.0.2",
+ "sebastian/version": "^4.0.1",
+ "theseer/tokenizer": "^1.2.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.1"
+ },
+ "suggest": {
+ "ext-pcov": "PHP extension that provides line coverage",
+ "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "10.1.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
+ "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
+ "keywords": [
+ "coverage",
+ "testing",
+ "xunit"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
+ "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.16"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-08-22T04:31:57+00:00"
+ },
+ {
+ "name": "phpunit/php-file-iterator",
+ "version": "4.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
+ "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c",
+ "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "FilterIterator implementation that filters files based on a list of suffixes.",
+ "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
+ "keywords": [
+ "filesystem",
+ "iterator"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
+ "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.1.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-08-31T06:24:48+00:00"
+ },
+ {
+ "name": "phpunit/php-invoker",
+ "version": "4.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-invoker.git",
+ "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7",
+ "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "ext-pcntl": "*",
+ "phpunit/phpunit": "^10.0"
+ },
+ "suggest": {
+ "ext-pcntl": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Invoke callables with a timeout",
+ "homepage": "https://github.com/sebastianbergmann/php-invoker/",
+ "keywords": [
+ "process"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-invoker/issues",
+ "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-02-03T06:56:09+00:00"
+ },
+ {
+ "name": "phpunit/php-text-template",
+ "version": "3.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-text-template.git",
+ "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748",
+ "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Simple template engine.",
+ "homepage": "https://github.com/sebastianbergmann/php-text-template/",
+ "keywords": [
+ "template"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-text-template/issues",
+ "security": "https://github.com/sebastianbergmann/php-text-template/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-08-31T14:07:24+00:00"
+ },
+ {
+ "name": "phpunit/php-timer",
+ "version": "6.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-timer.git",
+ "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d",
+ "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Utility class for timing",
+ "homepage": "https://github.com/sebastianbergmann/php-timer/",
+ "keywords": [
+ "timer"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-timer/issues",
+ "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-02-03T06:57:52+00:00"
+ },
+ {
+ "name": "phpunit/phpunit",
+ "version": "10.5.41",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/phpunit.git",
+ "reference": "e76586fa3d49714f230221734b44892e384109d7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e76586fa3d49714f230221734b44892e384109d7",
+ "reference": "e76586fa3d49714f230221734b44892e384109d7",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-json": "*",
+ "ext-libxml": "*",
+ "ext-mbstring": "*",
+ "ext-xml": "*",
+ "ext-xmlwriter": "*",
+ "myclabs/deep-copy": "^1.12.1",
+ "phar-io/manifest": "^2.0.4",
+ "phar-io/version": "^3.2.1",
+ "php": ">=8.1",
+ "phpunit/php-code-coverage": "^10.1.16",
+ "phpunit/php-file-iterator": "^4.1.0",
+ "phpunit/php-invoker": "^4.0.0",
+ "phpunit/php-text-template": "^3.0.1",
+ "phpunit/php-timer": "^6.0.0",
+ "sebastian/cli-parser": "^2.0.1",
+ "sebastian/code-unit": "^2.0.0",
+ "sebastian/comparator": "^5.0.3",
+ "sebastian/diff": "^5.1.1",
+ "sebastian/environment": "^6.1.0",
+ "sebastian/exporter": "^5.1.2",
+ "sebastian/global-state": "^6.0.2",
+ "sebastian/object-enumerator": "^5.0.0",
+ "sebastian/recursion-context": "^5.0.0",
+ "sebastian/type": "^4.0.0",
+ "sebastian/version": "^4.0.1"
+ },
+ "suggest": {
+ "ext-soap": "To be able to generate mocks based on WSDL files"
+ },
+ "bin": [
+ "phpunit"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "10.5-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/Framework/Assert/Functions.php"
+ ],
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "The PHP Unit Testing framework.",
+ "homepage": "https://phpunit.de/",
+ "keywords": [
+ "phpunit",
+ "testing",
+ "xunit"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/phpunit/issues",
+ "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.41"
+ },
+ "funding": [
+ {
+ "url": "https://phpunit.de/sponsors.html",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-01-13T09:33:05+00:00"
+ },
+ {
+ "name": "rector/rector",
+ "version": "1.2.10",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/rectorphp/rector.git",
+ "reference": "40f9cf38c05296bd32f444121336a521a293fa61"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/rectorphp/rector/zipball/40f9cf38c05296bd32f444121336a521a293fa61",
+ "reference": "40f9cf38c05296bd32f444121336a521a293fa61",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2|^8.0",
+ "phpstan/phpstan": "^1.12.5"
+ },
+ "conflict": {
+ "rector/rector-doctrine": "*",
+ "rector/rector-downgrade-php": "*",
+ "rector/rector-phpunit": "*",
+ "rector/rector-symfony": "*"
+ },
+ "suggest": {
+ "ext-dom": "To manipulate phpunit.xml via the custom-rule command"
+ },
+ "bin": [
+ "bin/rector"
+ ],
+ "type": "library",
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Instant Upgrade and Automated Refactoring of any PHP code",
+ "keywords": [
+ "automation",
+ "dev",
+ "migration",
+ "refactoring"
+ ],
+ "support": {
+ "issues": "https://github.com/rectorphp/rector/issues",
+ "source": "https://github.com/rectorphp/rector/tree/1.2.10"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/tomasvotruba",
+ "type": "github"
+ }
+ ],
+ "time": "2024-11-08T13:59:10+00:00"
+ },
+ {
+ "name": "sebastian/cli-parser",
+ "version": "2.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/cli-parser.git",
+ "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084",
+ "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for parsing CLI options",
+ "homepage": "https://github.com/sebastianbergmann/cli-parser",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/cli-parser/issues",
+ "security": "https://github.com/sebastianbergmann/cli-parser/security/policy",
+ "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-02T07:12:49+00:00"
+ },
+ {
+ "name": "sebastian/code-unit",
+ "version": "2.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/code-unit.git",
+ "reference": "a81fee9eef0b7a76af11d121767abc44c104e503"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503",
+ "reference": "a81fee9eef0b7a76af11d121767abc44c104e503",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Collection of value objects that represent the PHP code units",
+ "homepage": "https://github.com/sebastianbergmann/code-unit",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/code-unit/issues",
+ "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-02-03T06:58:43+00:00"
+ },
+ {
+ "name": "sebastian/code-unit-reverse-lookup",
+ "version": "3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
+ "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d",
+ "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Looks up which function or method a line of code belongs to",
+ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues",
+ "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-02-03T06:59:15+00:00"
+ },
+ {
+ "name": "sebastian/comparator",
+ "version": "5.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/comparator.git",
+ "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e",
+ "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-mbstring": "*",
+ "php": ">=8.1",
+ "sebastian/diff": "^5.0",
+ "sebastian/exporter": "^5.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@2bepublished.at"
+ }
+ ],
+ "description": "Provides the functionality to compare PHP values for equality",
+ "homepage": "https://github.com/sebastianbergmann/comparator",
+ "keywords": [
+ "comparator",
+ "compare",
+ "equality"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/comparator/issues",
+ "security": "https://github.com/sebastianbergmann/comparator/security/policy",
+ "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-10-18T14:56:07+00:00"
+ },
+ {
+ "name": "sebastian/complexity",
+ "version": "3.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/complexity.git",
+ "reference": "68ff824baeae169ec9f2137158ee529584553799"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799",
+ "reference": "68ff824baeae169ec9f2137158ee529584553799",
+ "shasum": ""
+ },
+ "require": {
+ "nikic/php-parser": "^4.18 || ^5.0",
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.2-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for calculating the complexity of PHP code units",
+ "homepage": "https://github.com/sebastianbergmann/complexity",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/complexity/issues",
+ "security": "https://github.com/sebastianbergmann/complexity/security/policy",
+ "source": "https://github.com/sebastianbergmann/complexity/tree/3.2.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-12-21T08:37:17+00:00"
+ },
+ {
+ "name": "sebastian/diff",
+ "version": "5.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/diff.git",
+ "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e",
+ "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0",
+ "symfony/process": "^6.4"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "5.1-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Kore Nordmann",
+ "email": "mail@kore-nordmann.de"
+ }
+ ],
+ "description": "Diff implementation",
+ "homepage": "https://github.com/sebastianbergmann/diff",
+ "keywords": [
+ "diff",
+ "udiff",
+ "unidiff",
+ "unified diff"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/diff/issues",
+ "security": "https://github.com/sebastianbergmann/diff/security/policy",
+ "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-02T07:15:17+00:00"
+ },
+ {
+ "name": "sebastian/environment",
+ "version": "6.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/environment.git",
+ "reference": "8074dbcd93529b357029f5cc5058fd3e43666984"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984",
+ "reference": "8074dbcd93529b357029f5cc5058fd3e43666984",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0"
+ },
+ "suggest": {
+ "ext-posix": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.1-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides functionality to handle HHVM/PHP environments",
+ "homepage": "https://github.com/sebastianbergmann/environment",
+ "keywords": [
+ "Xdebug",
+ "environment",
+ "hhvm"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/environment/issues",
+ "security": "https://github.com/sebastianbergmann/environment/security/policy",
+ "source": "https://github.com/sebastianbergmann/environment/tree/6.1.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-23T08:47:14+00:00"
+ },
+ {
+ "name": "sebastian/exporter",
+ "version": "5.1.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/exporter.git",
+ "reference": "955288482d97c19a372d3f31006ab3f37da47adf"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/955288482d97c19a372d3f31006ab3f37da47adf",
+ "reference": "955288482d97c19a372d3f31006ab3f37da47adf",
+ "shasum": ""
+ },
+ "require": {
+ "ext-mbstring": "*",
+ "php": ">=8.1",
+ "sebastian/recursion-context": "^5.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "5.1-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@gmail.com"
+ }
+ ],
+ "description": "Provides the functionality to export PHP variables for visualization",
+ "homepage": "https://www.github.com/sebastianbergmann/exporter",
+ "keywords": [
+ "export",
+ "exporter"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/exporter/issues",
+ "security": "https://github.com/sebastianbergmann/exporter/security/policy",
+ "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-02T07:17:12+00:00"
+ },
+ {
+ "name": "sebastian/global-state",
+ "version": "6.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/global-state.git",
+ "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9",
+ "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "sebastian/object-reflector": "^3.0",
+ "sebastian/recursion-context": "^5.0"
+ },
+ "require-dev": {
+ "ext-dom": "*",
+ "phpunit/phpunit": "^10.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Snapshotting of global state",
+ "homepage": "https://www.github.com/sebastianbergmann/global-state",
+ "keywords": [
+ "global state"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/global-state/issues",
+ "security": "https://github.com/sebastianbergmann/global-state/security/policy",
+ "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-02T07:19:19+00:00"
+ },
+ {
+ "name": "sebastian/lines-of-code",
+ "version": "2.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/lines-of-code.git",
+ "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0",
+ "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0",
+ "shasum": ""
+ },
+ "require": {
+ "nikic/php-parser": "^4.18 || ^5.0",
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for counting the lines of code in PHP source code",
+ "homepage": "https://github.com/sebastianbergmann/lines-of-code",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/lines-of-code/issues",
+ "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy",
+ "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-12-21T08:38:20+00:00"
+ },
+ {
+ "name": "sebastian/object-enumerator",
+ "version": "5.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-enumerator.git",
+ "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906",
+ "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "sebastian/object-reflector": "^3.0",
+ "sebastian/recursion-context": "^5.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Traverses array structures and object graphs to enumerate all referenced objects",
+ "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/object-enumerator/issues",
+ "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-02-03T07:08:32+00:00"
+ },
+ {
+ "name": "sebastian/object-reflector",
+ "version": "3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-reflector.git",
+ "reference": "24ed13d98130f0e7122df55d06c5c4942a577957"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957",
+ "reference": "24ed13d98130f0e7122df55d06c5c4942a577957",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Allows reflection of object attributes, including inherited and non-public ones",
+ "homepage": "https://github.com/sebastianbergmann/object-reflector/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/object-reflector/issues",
+ "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-02-03T07:06:18+00:00"
+ },
+ {
+ "name": "sebastian/recursion-context",
+ "version": "5.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/recursion-context.git",
+ "reference": "05909fb5bc7df4c52992396d0116aed689f93712"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/05909fb5bc7df4c52992396d0116aed689f93712",
+ "reference": "05909fb5bc7df4c52992396d0116aed689f93712",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ }
+ ],
+ "description": "Provides functionality to recursively process PHP variables",
+ "homepage": "https://github.com/sebastianbergmann/recursion-context",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/recursion-context/issues",
+ "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-02-03T07:05:40+00:00"
+ },
+ {
+ "name": "sebastian/type",
+ "version": "4.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/type.git",
+ "reference": "462699a16464c3944eefc02ebdd77882bd3925bf"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf",
+ "reference": "462699a16464c3944eefc02ebdd77882bd3925bf",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Collection of value objects that represent the types of the PHP type system",
+ "homepage": "https://github.com/sebastianbergmann/type",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/type/issues",
+ "source": "https://github.com/sebastianbergmann/type/tree/4.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-02-03T07:10:45+00:00"
+ },
+ {
+ "name": "sebastian/version",
+ "version": "4.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/version.git",
+ "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17",
+ "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that helps with managing the version number of Git-hosted PHP projects",
+ "homepage": "https://github.com/sebastianbergmann/version",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/version/issues",
+ "source": "https://github.com/sebastianbergmann/version/tree/4.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-02-07T11:34:05+00:00"
+ },
+ {
+ "name": "sirbrillig/phpcs-variable-analysis",
+ "version": "v2.11.22",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sirbrillig/phpcs-variable-analysis.git",
+ "reference": "ffb6f16c6033ec61ed84446b479a31d6529f0eb7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sirbrillig/phpcs-variable-analysis/zipball/ffb6f16c6033ec61ed84446b479a31d6529f0eb7",
+ "reference": "ffb6f16c6033ec61ed84446b479a31d6529f0eb7",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4.0",
+ "squizlabs/php_codesniffer": "^3.5.6"
+ },
+ "require-dev": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || ^1.0",
+ "phpcsstandards/phpcsdevcs": "^1.1",
+ "phpstan/phpstan": "^1.7",
+ "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.5 || ^7.0 || ^8.0 || ^9.0 || ^10.5.32 || ^11.3.3",
+ "vimeo/psalm": "^0.2 || ^0.3 || ^1.1 || ^4.24 || ^5.0"
+ },
+ "type": "phpcodesniffer-standard",
+ "autoload": {
+ "psr-4": {
+ "VariableAnalysis\\": "VariableAnalysis/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-2-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sam Graham",
+ "email": "php-codesniffer-variableanalysis@illusori.co.uk"
+ },
+ {
+ "name": "Payton Swick",
+ "email": "payton@foolord.com"
+ }
+ ],
+ "description": "A PHPCS sniff to detect problems with variables.",
+ "keywords": [
+ "phpcs",
+ "static analysis"
+ ],
+ "support": {
+ "issues": "https://github.com/sirbrillig/phpcs-variable-analysis/issues",
+ "source": "https://github.com/sirbrillig/phpcs-variable-analysis",
+ "wiki": "https://github.com/sirbrillig/phpcs-variable-analysis/wiki"
+ },
+ "time": "2025-01-06T17:54:24+00:00"
+ },
+ {
+ "name": "slevomat/coding-standard",
+ "version": "8.15.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/slevomat/coding-standard.git",
+ "reference": "7d1d957421618a3803b593ec31ace470177d7817"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/7d1d957421618a3803b593ec31ace470177d7817",
+ "reference": "7d1d957421618a3803b593ec31ace470177d7817",
+ "shasum": ""
+ },
+ "require": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7 || ^1.0",
+ "php": "^7.2 || ^8.0",
+ "phpstan/phpdoc-parser": "^1.23.1",
+ "squizlabs/php_codesniffer": "^3.9.0"
+ },
+ "require-dev": {
+ "phing/phing": "2.17.4",
+ "php-parallel-lint/php-parallel-lint": "1.3.2",
+ "phpstan/phpstan": "1.10.60",
+ "phpstan/phpstan-deprecation-rules": "1.1.4",
+ "phpstan/phpstan-phpunit": "1.3.16",
+ "phpstan/phpstan-strict-rules": "1.5.2",
+ "phpunit/phpunit": "8.5.21|9.6.8|10.5.11"
+ },
+ "type": "phpcodesniffer-standard",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "8.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "SlevomatCodingStandard\\": "SlevomatCodingStandard/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.",
+ "keywords": [
+ "dev",
+ "phpcs"
+ ],
+ "support": {
+ "issues": "https://github.com/slevomat/coding-standard/issues",
+ "source": "https://github.com/slevomat/coding-standard/tree/8.15.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/kukulich",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-03-09T15:20:58+00:00"
+ },
+ {
+ "name": "squizlabs/php_codesniffer",
+ "version": "3.11.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git",
+ "reference": "1368f4a58c3c52114b86b1abe8f4098869cb0079"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/1368f4a58c3c52114b86b1abe8f4098869cb0079",
+ "reference": "1368f4a58c3c52114b86b1abe8f4098869cb0079",
+ "shasum": ""
+ },
+ "require": {
+ "ext-simplexml": "*",
+ "ext-tokenizer": "*",
+ "ext-xmlwriter": "*",
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4"
+ },
+ "bin": [
+ "bin/phpcbf",
+ "bin/phpcs"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.x-dev"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Greg Sherwood",
+ "role": "Former lead"
+ },
+ {
+ "name": "Juliette Reinders Folmer",
+ "role": "Current lead"
+ },
+ {
+ "name": "Contributors",
+ "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors"
+ }
+ ],
+ "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
+ "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer",
+ "keywords": [
+ "phpcs",
+ "standards",
+ "static analysis"
+ ],
+ "support": {
+ "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues",
+ "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy",
+ "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer",
+ "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/PHPCSStandards",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/jrfnl",
+ "type": "github"
+ },
+ {
+ "url": "https://opencollective.com/php_codesniffer",
+ "type": "open_collective"
+ }
+ ],
+ "time": "2024-12-11T16:04:26+00:00"
+ },
+ {
+ "name": "symfony/yaml",
+ "version": "v7.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/yaml.git",
+ "reference": "099581e99f557e9f16b43c5916c26380b54abb22"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/099581e99f557e9f16b43c5916c26380b54abb22",
+ "reference": "099581e99f557e9f16b43c5916c26380b54abb22",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "symfony/deprecation-contracts": "^2.5|^3.0",
+ "symfony/polyfill-ctype": "^1.8"
+ },
+ "conflict": {
+ "symfony/console": "<6.4"
+ },
+ "require-dev": {
+ "symfony/console": "^6.4|^7.0"
+ },
+ "bin": [
+ "Resources/bin/yaml-lint"
+ ],
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Yaml\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Loads and dumps YAML files",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/yaml/tree/v7.2.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-10-23T06:56:12+00:00"
+ },
+ {
+ "name": "theseer/tokenizer",
+ "version": "1.2.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/theseer/tokenizer.git",
+ "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2",
+ "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-tokenizer": "*",
+ "ext-xmlwriter": "*",
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
+ "support": {
+ "issues": "https://github.com/theseer/tokenizer/issues",
+ "source": "https://github.com/theseer/tokenizer/tree/1.2.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/theseer",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-03T12:36:25+00:00"
+ }
+ ],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": {},
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": {
+ "php": ">=8.2"
+ },
+ "platform-dev": {},
+ "plugin-api-version": "2.6.0"
+}
diff --git a/.vortex/installer/phpcs.xml b/.vortex/installer/phpcs.xml
index 88d613c61..a46acf690 100644
--- a/.vortex/installer/phpcs.xml
+++ b/.vortex/installer/phpcs.xml
@@ -3,7 +3,9 @@
Custom PHPCS standard.
-
+
+
+
diff --git a/.vortex/installer/phpmd.xml b/.vortex/installer/phpmd.xml
deleted file mode 100644
index 7d3cedb37..000000000
--- a/.vortex/installer/phpmd.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/.vortex/installer/phpstan.neon b/.vortex/installer/phpstan.neon
index aeb474883..049d59e25 100644
--- a/.vortex/installer/phpstan.neon
+++ b/.vortex/installer/phpstan.neon
@@ -4,7 +4,7 @@
parameters:
- level: 1
+ level: 7
paths:
- src
diff --git a/.vortex/installer/rector.php b/.vortex/installer/rector.php
index 27de71c0a..d18c2c219 100644
--- a/.vortex/installer/rector.php
+++ b/.vortex/installer/rector.php
@@ -21,6 +21,7 @@
use Rector\DeadCode\Rector\If_\RemoveAlwaysTrueIfConditionRector;
use Rector\Set\ValueObject\SetList;
use Rector\Strict\Rector\Empty_\DisallowedEmptyRuleFixerRector;
+use Rector\TypeDeclaration\Rector\StmtsAwareInterface\DeclareStrictTypesRector;
return static function (RectorConfig $rectorConfig): void {
$rectorConfig->paths([
@@ -28,8 +29,7 @@
]);
$rectorConfig->sets([
- SetList::PHP_80,
- SetList::PHP_81,
+ SetList::PHP_82,
SetList::CODE_QUALITY,
SetList::CODING_STYLE,
SetList::DEAD_CODE,
@@ -37,6 +37,8 @@
SetList::TYPE_DECLARATION,
]);
+ $rectorConfig->rule(DeclareStrictTypesRector::class);
+
$rectorConfig->skip([
// Rules added by Rector's rule sets.
CountArrayToEmptyArrayComparisonRector::class,
diff --git a/.vortex/installer/src/Command/InstallCommand.php b/.vortex/installer/src/Command/InstallCommand.php
index 1857f2c2f..2c8d21f43 100644
--- a/.vortex/installer/src/Command/InstallCommand.php
+++ b/.vortex/installer/src/Command/InstallCommand.php
@@ -1,11 +1,24 @@
fs = is_null($fs) ? new Filesystem() : $fs;
+ }
/**
* {@inheritdoc}
*/
- protected static $defaultName = 'install';
-
- /**
- * Configures the current command.
- */
protected function configure(): void {
- $this
- ->setName('Vortex CLI installer')
- ->addArgument('path', InputArgument::OPTIONAL, 'Destination directory. Optional. Defaults to the current directory.')
- ->setHelp($this->printHelp());
+ $this->setName('Vortex CLI installer');
+ $this->setDescription('Install Vortex CLI from remote or local repository.');
+ $this->setHelp(<<addArgument('path', InputArgument::OPTIONAL, 'Destination directory. Optional. Defaults to the current directory.');
+
+ $this->addOption('root', NULL, InputOption::VALUE_REQUIRED, 'Path to the root for file path resolution. If not specified, current directory is used.');
+
+ $this->config = new Config();
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
- return $this->main($input, $output);
+ $this->output = $output;
+
+ try {
+ $this->checkRequirements();
+
+ $this->resolveOptions($input->getOptions(), $input->getArgument('path'));
+
+ $this->doExecute();
+ }
+ catch (\Exception $exception) {
+ $this->output->writeln([
+ 'Installation failed with an error: ',
+ '' . $exception->getMessage() . ' ',
+ ]);
+
+ return Command::FAILURE;
+ }
+
+ $this->printFooter();
+
+ return Command::SUCCESS;
+ }
+
+ protected function checkRequirements(): void {
+ $this->commandExists('git');
+ $this->commandExists('tar');
+ $this->commandExists('composer');
}
/**
- * Main functionality.
+ * Instantiate configuration from CLI option and environment variables.
+ *
+ * Installer configuration is a set of internal installer script variables
+ * prefixed with "VORTEX_INSTALL_" and used to control the installation. They
+ * are read from the environment variables with $this->config->get().
+ *
+ * For simplicity of naming, internal installer config variables used in
+ * $this->config->get() are matching environment variables names.
+ *
+ * @param array $options
+ * Array of CLI options.
+ * @param string|null $path
+ * Destination directory. Optional. Defaults to the current directory.
*/
- protected function main(InputInterface $input, OutputInterface $output): int {
- self::$currentDir = getcwd();
+ protected function resolveOptions(array $options, ?string $path): void {
+ if (!empty($options['quiet'])) {
+ $this->config->set('quiet', TRUE);
+ }
+
+ if (!empty($options['no-ansi'])) {
+ $this->config->set('ANSI', FALSE);
+ }
+ else {
+ // On Windows, default to no ANSI, except in ANSICON and ConEmu.
+ // Everywhere else, default to ANSI if stdout is a terminal.
+ $is_ansi = (DIRECTORY_SEPARATOR === '\\')
+ ? (FALSE !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI'))
+ : (function_exists('posix_isatty') && posix_isatty(1));
+ $this->config->set('ANSI', $is_ansi);
+ }
- $this->initConfig($input);
+ // Set root directory to use it for path resolution.
+ $this->fsSetRootDir(!empty($options['root']) && is_scalar($options['root']) ? strval($options['root']) : NULL);
- if ($this->getConfig('help')) {
- $output->write($this->printHelp());
+ // Set destination directory.
+ if (!empty($path)) {
+ $path = $this->fsGetAbsolutePath($path);
+ if (!is_readable($path) || !is_dir($path)) {
+ throw new \RuntimeException(sprintf('Destination directory "%s" is not readable or does not exist.', $path));
+ }
+ }
+ $this->config->set('VORTEX_INSTALL_DST_DIR', $path ?: static::getenvOrDefault('VORTEX_INSTALL_DST_DIR', $this->fsGetRootDir()));
- return self::EXIT_SUCCESS;
+ // Load .env file from the destination directory, if it exists.
+ if ($this->fs->exists($this->config->getDstDir() . '/.env')) {
+ static::loadDotenv($this->config->getDstDir() . '/.env');
}
- $this->checkRequirements();
+ // Internal version of Vortex.
+ // @todo Convert to option and remove from the environment variables.
+ $this->config->set('VORTEX_VERSION', static::getenvOrDefault('VORTEX_VERSION', 'develop'));
+ // Flag to display install debug information.
+ // @todo Convert to option and remove from the environment variables.
+ $this->config->set('VORTEX_INSTALL_DEBUG', (bool) static::getenvOrDefault('VORTEX_INSTALL_DEBUG', FALSE));
+ // Flag to proceed with installation. If FALSE - the installation will only
+ // print resolved values and will not proceed.
+ // @todo Convert to option and remove from the environment variables.
+ $this->config->set('VORTEX_INSTALL_PROCEED', (bool) static::getenvOrDefault('VORTEX_INSTALL_PROCEED', TRUE));
+ // Temporary directory to download and expand files to.
+ // @todo Convert to option and remove from the environment variables.
+ $this->config->set('VORTEX_INSTALL_TMP_DIR', static::getenvOrDefault('VORTEX_INSTALL_TMP_DIR', File::createTempdir()));
+ // Path to local Vortex repository. If not provided - remote will be used.
+ // @todo Convert to option and remove from the environment variables.
+ $this->config->set('VORTEX_INSTALL_LOCAL_REPO', static::getenvOrDefault('VORTEX_INSTALL_LOCAL_REPO'));
+ // Optional commit to download. If not provided, latest release will be
+ // downloaded.
+ // @todo Convert to option and remove from the environment variables.
+ $this->config->set('VORTEX_INSTALL_COMMIT', static::getenvOrDefault('VORTEX_INSTALL_COMMIT', 'HEAD'));
+
+ // Internal flag to enforce DEMO mode. If not set, the demo mode will be
+ // discovered automatically.
+ if (!is_null(static::getenvOrDefault('VORTEX_INSTALL_DEMO'))) {
+ $this->config->set('VORTEX_INSTALL_DEMO', (bool) static::getenvOrDefault('VORTEX_INSTALL_DEMO'));
+ }
+ // Internal flag to skip processing of the demo mode.
+ $this->config->set('VORTEX_INSTALL_DEMO_SKIP', (bool) static::getenvOrDefault('VORTEX_INSTALL_DEMO_SKIP', FALSE));
+ }
+ protected function doExecute(): void {
$this->printHeader();
$this->collectAnswers();
- if ($this->askShouldProceed()) {
- $this->install();
+ if (!$this->askShouldProceed()) {
+ $this->printAbort();
+
+ return;
+ }
+
+ $this->downloadScaffold();
+
+ $this->prepareDestination();
+
+ $this->replaceTokens();
+
+ $this->copyFiles();
+
+ $this->handleDemo();
+
+ $this->printFooter();
+ }
+
+ protected function collectAnswers(): void {
+ // Set answers that may be used in other answers' discoveries.
+ $this->setAnswer('webroot', $this->discoverValue('webroot'));
+
+ // @formatter:off
+ // phpcs:disable Generic.Functions.FunctionCallArgumentSpacing.TooMuchSpaceAfterComma
+ // phpcs:disable Drupal.WhiteSpace.Comma.TooManySpaces
+ $this->askForAnswer('name', 'What is your site name?');
+ $this->askForAnswer('machine_name', 'What is your site machine name?');
+ $this->askForAnswer('org', 'What is your organization name');
+ $this->askForAnswer('org_machine_name', 'What is your organization machine name?');
+ $this->askForAnswer('module_prefix', 'What is your project-specific module prefix?');
+ $this->askForAnswer('profile', 'What is your custom profile machine name (leave empty to use "standard" profile)?');
+ $this->askForAnswer('theme', 'What is your theme machine name?');
+ $this->askForAnswer('url', 'What is your site public URL?');
+ $this->askForAnswer('webroot', 'Web root (web, docroot)?');
+
+ $this->askForAnswer('provision_use_profile', 'Do you want to install from profile (leave empty or "n" for using database?');
- $this->printFooter();
+ if ($this->getAnswer('provision_use_profile') === self::ANSWER_YES) {
+ $this->setAnswer('database_download_source', 'none');
+ $this->setAnswer('database_image', '');
}
else {
- $this->printAbort();
+ $this->askForAnswer('database_download_source', "Where does the database dump come from into every environment:\n - [u]rl\n - [f]tp\n - [a]cquia backup\n - [d]ocker registry?");
+
+ if ($this->getAnswer('database_download_source') !== 'container_registry') {
+ // Note that "database_store_type" is a pseudo-answer - it is only used
+ // to improve UX and is not exposed as a variable (although has default,
+ // discovery and normalisation callbacks).
+ $this->askForAnswer('database_store_type', ' When developing locally, do you want to import the database dump from the [f]ile or store it imported in the [d]ocker image for faster builds?');
+ }
+
+ if ($this->getAnswer('database_store_type') === 'file') {
+ $this->setAnswer('database_image', '');
+ }
+ else {
+ $this->askForAnswer('database_image', ' What is your database image name and a tag (e.g. drevops/mariadb-drupal-data:latest)?');
+ }
}
+ // @formatter:on
+ // phpcs:enable Generic.Functions.FunctionCallArgumentSpacing.TooMuchSpaceAfterComma
+ // phpcs:enable Drupal.WhiteSpace.Comma.TooManySpaces
- return self::EXIT_SUCCESS;
- }
+ $this->askForAnswer('override_existing_db', 'Do you want to override existing database in the environment?');
- protected function checkRequirements() {
- $this->commandExists('git');
- $this->commandExists('tar');
- $this->commandExists('composer');
- }
+ $this->askForAnswer('ci_provider', 'Which provider do you want to use for CI ([c]ircleci, [g]ithub actions, [n]one)?');
- protected function install() {
- $this->download();
+ $this->askForAnswer('deploy_type', 'How do you deploy your code to the hosting ([w]ebhook call, [c]ode artifact, [d]ocker image, [l]agoon, [n]one as a comma-separated list)?');
- $this->prepareDestination();
+ if ($this->getAnswer('database_download_source') !== 'ftp') {
+ $this->askForAnswer('preserve_ftp', 'Do you want to keep FTP integration?');
+ }
+ else {
+ $this->setAnswer('preserve_ftp', self::ANSWER_YES);
+ }
- $this->replaceTokens();
+ if ($this->getAnswer('database_download_source') !== 'acquia') {
+ $this->askForAnswer('preserve_acquia', 'Do you want to keep Acquia Cloud integration?');
+ }
+ else {
+ $this->setAnswer('preserve_acquia', self::ANSWER_YES);
+ }
- $this->copyFiles();
+ $this->askForAnswer('preserve_lagoon', 'Do you want to keep Amazee.io Lagoon integration?');
+
+ $this->askForAnswer('preserve_renovatebot', 'Do you want to keep RenovateBot integration?');
+
+ $this->askForAnswer('preserve_doc_comments', 'Do you want to keep detailed documentation in comments?');
+ $this->askForAnswer('preserve_vortex_info', 'Do you want to keep all Vortex information?');
+
+ $this->printSummary();
+
+ if ($this->config->isInstallDebug()) {
+ $this->printBox($this->formatValuesList($this->getAnswers(), '', 80 - 6), 'DEBUG RESOLVED ANSWERS');
+ }
+ }
- $this->processDemo();
+ protected function downloadScaffold(): void {
+ if ($this->config->get('VORTEX_INSTALL_LOCAL_REPO')) {
+ $this->downloadScaffoldLocal();
+ }
+ else {
+ $this->downloadScaffoldRemote();
+ }
}
- protected function prepareDestination() {
- $dst = $this->getDstDir();
+ protected function prepareDestination(): void {
+ $dst = $this->config->getDstDir();
if (!is_dir($dst)) {
$this->status(sprintf('Creating destination directory "%s".', $dst), self::INSTALLER_STATUS_MESSAGE, FALSE);
mkdir($dst);
+
if (!is_writable($dst)) {
throw new \RuntimeException(sprintf('Destination directory "%s" is not writable.', $dst));
}
- print ' ';
+
$this->status('Done', self::INSTALLER_STATUS_SUCCESS);
}
@@ -144,19 +333,18 @@ protected function prepareDestination() {
else {
$this->status(sprintf('Initialising Git repository in directory "%s".', $dst), self::INSTALLER_STATUS_MESSAGE, FALSE);
$this->doExec(sprintf('git --work-tree="%s" --git-dir="%s/.git" init > /dev/null', $dst, $dst));
- if (!is_readable($dst . '/.git')) {
+
+ if (!file_exists($dst . '/.git')) {
throw new \RuntimeException(sprintf('Unable to init git project in directory "%s".', $dst));
}
}
+
print ' ';
$this->status('Done', self::INSTALLER_STATUS_SUCCESS);
}
- /**
- * Replace tokens.
- */
- protected function replaceTokens() {
- $dir = $this->getConfig('VORTEX_INSTALL_TMP_DIR');
+ protected function replaceTokens(): void {
+ $dir = $this->config->get('VORTEX_INSTALL_TMP_DIR');
$this->status('Replacing tokens ', self::INSTALLER_STATUS_MESSAGE, FALSE);
@@ -190,17 +378,17 @@ protected function replaceTokens() {
$this->status('Done', self::INSTALLER_STATUS_SUCCESS);
}
- protected function copyFiles() {
- $src = $this->getConfig('VORTEX_INSTALL_TMP_DIR');
- $dst = $this->getDstDir();
+ protected function copyFiles(): void {
+ $src = $this->config->get('VORTEX_INSTALL_TMP_DIR');
+ $dst = $this->config->getDstDir();
// Due to the way symlinks can be ordered, we cannot copy files one-by-one
// into destination directory. Instead, we are removing all ignored files
// and empty directories, making the src directory "clean", and then
// recursively copying the whole directory.
- $all = static::scandirRecursive($src, static::ignorePaths(), TRUE);
- $files = static::scandirRecursive($src);
- $valid_files = static::scandirRecursive($src, static::ignorePaths());
+ $all = File::scandirRecursive($src, File::ignorePaths(), TRUE);
+ $files = File::scandirRecursive($src);
+ $valid_files = File::scandirRecursive($src, File::ignorePaths());
$dirs = array_diff($all, $valid_files);
$ignored_files = array_diff($files, $valid_files);
@@ -209,10 +397,9 @@ protected function copyFiles() {
foreach ($valid_files as $filename) {
$relative_file = str_replace($src . DIRECTORY_SEPARATOR, '.' . DIRECTORY_SEPARATOR, (string) $filename);
- if (static::isInternalPath($relative_file)) {
+ if (File::isInternalPath($relative_file)) {
$this->status(sprintf('Skipped file %s as an internal Vortex file.', $relative_file), self::INSTALLER_STATUS_DEBUG);
unlink($filename);
- continue;
}
}
@@ -225,34 +412,34 @@ protected function copyFiles() {
// Remove empty directories.
foreach ($dirs as $dir) {
- static::rmdirRecursiveEmpty($dir);
+ File::rmdirRecursiveEmpty($dir);
}
// Src directory is now "clean" - copy it to dst directory.
- if (is_dir($src) && !static::dirIsEmpty($src)) {
- static::copyRecursive($src, $dst, 0755, FALSE);
+ if (is_dir($src) && !File::dirIsEmpty($src)) {
+ File::copyRecursive($src, $dst, 0755, FALSE);
}
// Special case for .env.local as it may exist.
if (!file_exists($dst . '/.env.local')) {
- static::copyRecursive($dst . '/.env.local.example', $dst . '/.env.local', 0755, FALSE);
+ File::copyRecursive($dst . '/.env.local.example', $dst . '/.env.local', 0755, FALSE);
}
}
- protected function processDemo() {
- if (empty($this->getConfig('VORTEX_INSTALL_DEMO')) || !empty($this->getConfig('VORTEX_INSTALL_DEMO_SKIP'))) {
+ protected function handleDemo(): void {
+ if (empty($this->config->get('VORTEX_INSTALL_DEMO')) || !empty($this->config->get('VORTEX_INSTALL_DEMO_SKIP'))) {
return;
}
// Reload variables from destination's .env.
- static::loadDotenv($this->getDstDir() . '/.env');
+ static::loadDotenv($this->config->getDstDir() . '/.env');
$url = static::getenvOrDefault('VORTEX_DB_DOWNLOAD_CURL_URL');
if (empty($url)) {
return;
}
- $data_dir = $this->getDstDir() . DIRECTORY_SEPARATOR . static::getenvOrDefault('VORTEX_DB_DIR', './.data');
+ $data_dir = $this->config->getDstDir() . DIRECTORY_SEPARATOR . static::getenvOrDefault('VORTEX_DB_DIR', './.data');
$file = static::getenvOrDefault('VORTEX_DB_FILE', 'db.sql');
$this->status(sprintf('No database dump file found in "%s" directory. Downloading DEMO database from %s.', $data_dir, $url), self::INSTALLER_STATUS_MESSAGE, FALSE);
@@ -271,2156 +458,4 @@ protected function processDemo() {
$this->status('Done', self::INSTALLER_STATUS_SUCCESS);
}
- protected static function copyRecursive($source, $dest, $permissions = 0755, $copy_empty_dirs = FALSE): bool {
- $parent = dirname((string) $dest);
-
- if (!is_dir($parent)) {
- mkdir($parent, $permissions, TRUE);
- }
-
- // Note that symlink target must exist.
- if (is_link($source)) {
- // Changing dir symlink will be relevant to the current destination's file
- // directory.
- $cur_dir = getcwd();
- chdir($parent);
- $ret = TRUE;
- if (!is_readable(basename((string) $dest))) {
- $ret = symlink(readlink($source), basename((string) $dest));
- }
- chdir($cur_dir);
-
- return $ret;
- }
-
- if (is_file($source)) {
- $ret = copy($source, $dest);
- if ($ret) {
- chmod($dest, fileperms($source));
- }
-
- return $ret;
- }
-
- if (!is_dir($dest) && $copy_empty_dirs) {
- mkdir($dest, $permissions, TRUE);
- }
-
- $dir = dir($source);
- while ($dir && FALSE !== $entry = $dir->read()) {
- if ($entry == '.' || $entry == '..') {
- continue;
- }
- static::copyRecursive(sprintf('%s/%s', $source, $entry), sprintf('%s/%s', $dest, $entry), $permissions, FALSE);
- }
-
- $dir && $dir->close();
-
- return TRUE;
- }
-
- protected function gitFileIsTracked($path, string $dir): bool {
- if (is_dir($dir . DIRECTORY_SEPARATOR . '.git')) {
- $cwd = getcwd();
- chdir($dir);
- $this->doExec(sprintf('git ls-files --error-unmatch "%s" 2>&1 >/dev/null', $path), $output, $code);
- chdir($cwd);
-
- return $code === 0;
- }
-
- return FALSE;
- }
-
- protected function drupalCoreProfiles(): array {
- return [
- 'standard',
- 'minimal',
- 'testing',
- 'demo_umami',
- ];
- }
-
- /**
- * Process answers.
- */
- protected function processAnswer($name, $dir) {
- return $this->executeCallback('process', $name, $dir);
- }
-
- protected function processProfile(string $dir) {
- $webroot = $this->getAnswer('webroot');
- // For core profiles - remove custom profile and direct links to it.
- if (in_array($this->getAnswer('profile'), $this->drupalCoreProfiles())) {
- static::rmdirRecursive(sprintf('%s/%s/profiles/your_site_profile', $dir, $webroot));
- static::rmdirRecursive(sprintf('%s/%s/profiles/custom/your_site_profile', $dir, $webroot));
- static::dirReplaceContent($webroot . '/profiles/your_site_profile,', '', $dir);
- static::dirReplaceContent($webroot . '/profiles/custom/your_site_profile,', '', $dir);
- }
- static::dirReplaceContent('your_site_profile', $this->getAnswer('profile'), $dir);
- }
-
- protected function processProvisionUseProfile(string $dir) {
- if ($this->getAnswer('provision_use_profile') == self::ANSWER_YES) {
- static::fileReplaceContent('/VORTEX_PROVISION_USE_PROFILE=.*/', "VORTEX_PROVISION_USE_PROFILE=1", $dir . '/.env');
- $this->removeTokenWithContent('!PROVISION_USE_PROFILE', $dir);
- }
- else {
- static::fileReplaceContent('/VORTEX_PROVISION_USE_PROFILE=.*/', "VORTEX_PROVISION_USE_PROFILE=0", $dir . '/.env');
- $this->removeTokenWithContent('PROVISION_USE_PROFILE', $dir);
- }
- }
-
- protected function processDatabaseDownloadSource(string $dir) {
- $type = $this->getAnswer('database_download_source');
- static::fileReplaceContent('/VORTEX_DB_DOWNLOAD_SOURCE=.*/', 'VORTEX_DB_DOWNLOAD_SOURCE=' . $type, $dir . '/.env');
-
- $types = [
- 'curl',
- 'ftp',
- 'acquia',
- 'lagoon',
- 'container_registry',
- 'none',
- ];
-
- foreach ($types as $t) {
- $token = 'VORTEX_DB_DOWNLOAD_SOURCE_' . strtoupper($t);
- if ($t == $type) {
- $this->removeTokenWithContent('!' . $token, $dir);
- }
- else {
- $this->removeTokenWithContent($token, $dir);
- }
- }
- }
-
- protected function processDatabaseImage(string $dir) {
- $image = $this->getAnswer('database_image');
- static::fileReplaceContent('/VORTEX_DB_IMAGE=.*/', 'VORTEX_DB_IMAGE=' . $image, $dir . '/.env');
-
- if ($image) {
- $this->removeTokenWithContent('!VORTEX_DB_IMAGE', $dir);
- }
- else {
- $this->removeTokenWithContent('VORTEX_DB_IMAGE', $dir);
- }
- }
-
- protected function processOverrideExistingDb(string $dir) {
- if ($this->getAnswer('override_existing_db') == self::ANSWER_YES) {
- static::fileReplaceContent('/VORTEX_PROVISION_OVERRIDE_DB=.*/', "VORTEX_PROVISION_OVERRIDE_DB=1", $dir . '/.env');
- }
- else {
- static::fileReplaceContent('/VORTEX_PROVISION_OVERRIDE_DB=.*/', "VORTEX_PROVISION_OVERRIDE_DB=0", $dir . '/.env');
- }
- }
-
- protected function processCiProvider(string $dir) {
- $type = $this->getAnswer('ci_provider');
-
- $remove_gha = FALSE;
- $remove_circleci = FALSE;
-
- switch ($type) {
- case 'CircleCI':
- $remove_gha = TRUE;
- break;
-
- case 'GitHub Actions':
- $remove_circleci = TRUE;
- break;
-
- default:
- $remove_circleci = TRUE;
- $remove_gha = TRUE;
- }
-
- if ($remove_gha) {
- @unlink($dir . '/.github/workflows/build-test-deploy.yml');
- $this->removeTokenWithContent('CI_PROVIDER_GHA', $dir);
- }
-
- if ($remove_circleci) {
- static::rmdirRecursive($dir . '/.circleci');
- @unlink($dir . '/tests/phpunit/CircleCiConfigTest.php');
- $this->removeTokenWithContent('CI_PROVIDER_CIRCLECI', $dir);
- }
-
- if ($remove_gha && $remove_circleci) {
- @unlink($dir . '/docs/ci.md');
- $this->removeTokenWithContent('CI_PROVIDER_ANY', $dir);
- }
- else {
- $this->removeTokenWithContent('!CI_PROVIDER_ANY', $dir);
- }
- }
-
- protected function processDeployType(string $dir) {
- $type = $this->getAnswer('deploy_type');
- if ($type != 'none') {
- static::fileReplaceContent('/VORTEX_DEPLOY_TYPES=.*/', 'VORTEX_DEPLOY_TYPES=' . $type, $dir . '/.env');
-
- if (!str_contains((string) $type, 'artifact')) {
- @unlink($dir . '/.gitignore.deployment');
- @unlink($dir . '/.gitignore.artifact');
- }
-
- $this->removeTokenWithContent('!DEPLOYMENT', $dir);
- }
- else {
- @unlink($dir . '/docs/deployment.md');
- @unlink($dir . '/.gitignore.deployment');
- @unlink($dir . '/.gitignore.artifact');
- $this->removeTokenWithContent('DEPLOYMENT', $dir);
- }
- }
-
- protected function processPreserveAcquia(string $dir) {
- if ($this->getAnswer('preserve_acquia') == self::ANSWER_YES) {
- $this->removeTokenWithContent('!ACQUIA', $dir);
- }
- else {
- static::rmdirRecursive($dir . '/hooks');
- $webroot = $this->getAnswer('webroot');
- @unlink(sprintf('%s/%s/sites/default/includes/providers/settings.acquia.php', $dir, $webroot));
- $this->removeTokenWithContent('ACQUIA', $dir);
- }
- }
-
- protected function processPreserveLagoon(string $dir) {
- if ($this->getAnswer('preserve_lagoon') == self::ANSWER_YES) {
- $this->removeTokenWithContent('!LAGOON', $dir);
- }
- else {
- @unlink($dir . '/drush/sites/lagoon.site.yml');
- @unlink($dir . '/.lagoon.yml');
- @unlink($dir . '/.github/workflows/close-pull-request.yml');
- $webroot = $this->getAnswer('webroot');
- @unlink(sprintf('%s/%s/sites/default/includes/providers/settings.lagoon.php', $dir, $webroot));
- $this->removeTokenWithContent('LAGOON', $dir);
- }
- }
-
- protected function processPreserveFtp(string $dir) {
- if ($this->getAnswer('preserve_ftp') == self::ANSWER_YES) {
- $this->removeTokenWithContent('!FTP', $dir);
- }
- else {
- $this->removeTokenWithContent('FTP', $dir);
- }
- }
-
- protected function processPreserveRenovatebot(string $dir) {
- if ($this->getAnswer('preserve_renovatebot') == self::ANSWER_YES) {
- $this->removeTokenWithContent('!RENOVATEBOT', $dir);
- }
- else {
- @unlink($dir . '/renovate.json');
- $this->removeTokenWithContent('RENOVATEBOT', $dir);
- }
- }
-
- protected function processStringTokens(string $dir) {
- $machine_name_hyphenated = str_replace('_', '-', (string) $this->getAnswer('machine_name'));
- $machine_name_camel_cased = static::toCamelCase($this->getAnswer('machine_name'), TRUE);
- $module_prefix_camel_cased = static::toCamelCase($this->getAnswer('module_prefix'), TRUE);
- $module_prefix_uppercase = strtoupper((string) $module_prefix_camel_cased);
- $theme_camel_cased = static::toCamelCase($this->getAnswer('theme'), TRUE);
- $vortex_version_urlencoded = str_replace('-', '--', (string) $this->getConfig('VORTEX_VERSION'));
- $url = $this->getAnswer('url');
- $host = parse_url((string) $url, PHP_URL_HOST);
- $domain = ($host) ? $host : $url;
- $domain_non_www = str_starts_with((string) $domain, "www.") ? substr((string) $domain, 4) : $domain;
- $webroot = $this->getAnswer('webroot');
-
- // @formatter:off
- // phpcs:disable Generic.Functions.FunctionCallArgumentSpacing.TooMuchSpaceAfterComma
- // phpcs:disable Drupal.WhiteSpace.Comma.TooManySpaces
- static::dirReplaceContent('your_site_theme', $this->getAnswer('theme'), $dir);
- static::dirReplaceContent('YourSiteTheme', $theme_camel_cased, $dir);
- static::dirReplaceContent('your_org', $this->getAnswer('org_machine_name'), $dir);
- static::dirReplaceContent('YOURORG', $this->getAnswer('org'), $dir);
- static::dirReplaceContent('www.your-site-url.example', $domain, $dir);
- static::dirReplaceContent('your-site-url.example', $domain_non_www, $dir);
- static::dirReplaceContent('ys_core', $this->getAnswer('module_prefix') . '_core', $dir . sprintf('/%s/modules/custom', $webroot));
- static::dirReplaceContent('ys_core', $this->getAnswer('module_prefix') . '_core', $dir . sprintf('/%s/themes/custom', $webroot));
- static::dirReplaceContent('ys_core', $this->getAnswer('module_prefix') . '_core', $dir . '/scripts/custom');
- static::dirReplaceContent('YsCore', $module_prefix_camel_cased . 'Core', $dir . sprintf('/%s/modules/custom', $webroot));
- static::dirReplaceContent('YSCODE', $module_prefix_uppercase, $dir);
- static::dirReplaceContent('your-site', $machine_name_hyphenated, $dir);
- static::dirReplaceContent('your_site', $this->getAnswer('machine_name'), $dir);
- static::dirReplaceContent('YOURSITE', $this->getAnswer('name'), $dir);
- static::dirReplaceContent('YourSite', $machine_name_camel_cased, $dir);
-
- static::replaceStringFilename('YourSiteTheme', $theme_camel_cased, $dir);
- static::replaceStringFilename('your_site_theme', $this->getAnswer('theme'), $dir);
- static::replaceStringFilename('YourSite', $machine_name_camel_cased, $dir);
- static::replaceStringFilename('ys_core', $this->getAnswer('module_prefix') . '_core', $dir . sprintf('/%s/modules/custom', $webroot));
- static::replaceStringFilename('YsCore', $module_prefix_camel_cased . 'Core', $dir . sprintf('/%s/modules/custom', $webroot));
- static::replaceStringFilename('your_org', $this->getAnswer('org_machine_name'), $dir);
- static::replaceStringFilename('your_site', $this->getAnswer('machine_name'), $dir);
-
- static::dirReplaceContent('VORTEX_VERSION_URLENCODED', $vortex_version_urlencoded, $dir);
- static::dirReplaceContent('VORTEX_VERSION', $this->getConfig('VORTEX_VERSION'), $dir);
- // @formatter:on
- // phpcs:enable Generic.Functions.FunctionCallArgumentSpacing.TooMuchSpaceAfterComma
- // phpcs:enable Drupal.WhiteSpace.Comma.TooManySpaces
- }
-
- protected function processPreserveDocComments(string $dir) {
- if ($this->getAnswer('preserve_doc_comments') == self::ANSWER_YES) {
- // Replace special "#: " comments with normal "#" comments.
- static::dirReplaceContent('#:', '#', $dir);
- }
- else {
- $this->removeTokenLine('#:', $dir);
- }
- }
-
- protected function processDemoMode(string $dir) {
- // Only discover demo mode if not explicitly set.
- if (is_null($this->getConfig('VORTEX_INSTALL_DEMO'))) {
- if ($this->getAnswer('provision_use_profile') == self::ANSWER_NO) {
- $download_source = $this->getAnswer('database_download_source');
- $db_file = static::getenvOrDefault('VORTEX_DB_DIR', './.data') . DIRECTORY_SEPARATOR . static::getenvOrDefault('VORTEX_DB_FILE', 'db.sql');
- $has_comment = static::fileContains('to allow to demonstrate how Vortex works without', $this->getDstDir() . '/.env');
-
- // Enable Vortex demo mode if download source is file AND
- // there is no downloaded file present OR if there is a demo comment in
- // destination .env file.
- if ($download_source != 'container_registry') {
- if ($has_comment || !file_exists($db_file)) {
- $this->setConfig('VORTEX_INSTALL_DEMO', TRUE);
- }
- else {
- $this->setConfig('VORTEX_INSTALL_DEMO', FALSE);
- }
- }
- elseif ($has_comment || $download_source == 'container_registry') {
- $this->setConfig('VORTEX_INSTALL_DEMO', TRUE);
- }
- else {
- $this->setConfig('VORTEX_INSTALL_DEMO', FALSE);
- }
- }
- else {
- $this->setConfig('VORTEX_INSTALL_DEMO', FALSE);
- }
- }
-
- if (!$this->getConfig('VORTEX_INSTALL_DEMO')) {
- $this->removeTokenWithContent('DEMO', $dir);
- }
- }
-
- protected function processPreserveVortexInfo(string $dir) {
- if ($this->getAnswer('preserve_vortex_info') == self::ANSWER_NO) {
- // Remove code required for Vortex maintenance.
- $this->removeTokenWithContent('VORTEX_DEV', $dir);
-
- // Remove all other comments.
- $this->removeTokenLine('#;', $dir);
- }
- }
-
- protected function processVortexInternal(string $dir) {
- if (file_exists($dir . DIRECTORY_SEPARATOR . 'README.dist.md')) {
- rename($dir . DIRECTORY_SEPARATOR . 'README.dist.md', $dir . DIRECTORY_SEPARATOR . 'README.md');
- }
-
- // Remove Vortex internal files.
- static::rmdirRecursive($dir . DIRECTORY_SEPARATOR . '.vortex');
-
- @unlink($dir . '/.github/FUNDING.yml');
- @unlink($dir . 'CODE_OF_CONDUCT.md');
- @unlink($dir . 'CONTRIBUTING.md');
- @unlink($dir . 'LICENSE');
- @unlink($dir . 'SECURITY.md');
-
- // Remove Vortex internal GHAs.
- foreach (glob($dir . '/.github/workflows/vortex-*.yml') as $file) {
- @unlink($file);
- }
-
- // Remove other unhandled tokenized comments.
- $this->removeTokenLine('#;<', $dir);
- $this->removeTokenLine('#;>', $dir);
- }
-
- protected function processEnableCommentedCode(string $dir) {
- // Enable_commented_code.
- static::dirReplaceContent('##### ', '', $dir);
- }
-
- protected function processWebroot(string $dir) {
- $new_name = $this->getAnswer('webroot', 'web');
-
- if ($new_name != 'web') {
- static::dirReplaceContent('web/', $new_name . '/', $dir);
- static::dirReplaceContent('web\/', $new_name . '\/', $dir);
- static::dirReplaceContent(': web', ': ' . $new_name, $dir);
- static::dirReplaceContent('=web', '=' . $new_name, $dir);
- static::dirReplaceContent('!web', '!' . $new_name, $dir);
- static::dirReplaceContent('/\/web\//', '/' . $new_name . '/', $dir);
- rename($dir . DIRECTORY_SEPARATOR . 'web', $dir . DIRECTORY_SEPARATOR . $new_name);
- }
- }
-
- /**
- * Download Vortex source files.
- */
- protected function download() {
- if ($this->getConfig('VORTEX_INSTALL_LOCAL_REPO')) {
- $this->downloadLocal();
- }
- else {
- $this->downloadRemote();
- }
- }
-
- protected function downloadLocal() {
- $dst = $this->getConfig('VORTEX_INSTALL_TMP_DIR');
- $repo = $this->getConfig('VORTEX_INSTALL_LOCAL_REPO');
- $ref = $this->getConfig('VORTEX_INSTALL_COMMIT');
-
- $this->status(sprintf('Downloading Vortex from the local repository "%s" at ref "%s".', $repo, $ref), self::INSTALLER_STATUS_MESSAGE, FALSE);
-
- $command = sprintf('git --git-dir="%s/.git" --work-tree="%s" archive --format=tar "%s" | tar xf - -C "%s"', $repo, $repo, $ref, $dst);
- $this->doExec($command, $output, $code);
-
- $this->status(implode(PHP_EOL, $output), self::INSTALLER_STATUS_DEBUG);
-
- if ($code != 0) {
- throw new \RuntimeException(implode(PHP_EOL, $output));
- }
-
- $this->status(sprintf('Downloaded to "%s".', $dst), self::INSTALLER_STATUS_DEBUG);
-
- print ' ';
- $this->status('Done', self::INSTALLER_STATUS_SUCCESS);
- }
-
- protected function downloadRemote() {
- $dst = $this->getConfig('VORTEX_INSTALL_TMP_DIR');
- $org = 'drevops';
- $project = 'vortex';
- $ref = $this->getConfig('VORTEX_INSTALL_COMMIT');
- $release_prefix = $this->getConfig('VORTEX_VERSION');
-
- if ($ref == 'HEAD') {
- $release_prefix = $release_prefix == 'develop' ? NULL : $release_prefix;
- $ref = $this->findLatestVortexRelease($org, $project, $release_prefix);
- $this->setConfig('VORTEX_VERSION', $ref);
- }
-
- $url = sprintf('https://github.com/%s/%s/archive/%s.tar.gz', $org, $project, $ref);
- $this->status(sprintf('Downloading Vortex from the remote repository "%s" at ref "%s".', $url, $ref), self::INSTALLER_STATUS_MESSAGE, FALSE);
- $this->doExec(sprintf('curl -sS -L "%s" | tar xzf - -C "%s" --strip 1', $url, $dst), $output, $code);
-
- if ($code != 0) {
- throw new \RuntimeException(implode(PHP_EOL, $output));
- }
-
- $this->status(sprintf('Downloaded to "%s".', $dst), self::INSTALLER_STATUS_DEBUG);
-
- $this->status('Done', self::INSTALLER_STATUS_SUCCESS);
- }
-
- protected function findLatestVortexRelease($org, $project, $release_prefix) {
- $release_url = sprintf('https://api.github.com/repos/%s/%s/releases', $org, $project);
- $release_contents = file_get_contents($release_url, FALSE, stream_context_create([
- 'http' => ['method' => 'GET', 'header' => ['User-Agent: PHP']],
- ]));
-
- if (!$release_contents) {
- throw new \RuntimeException(sprintf('Unable to download release information from "%s".', $release_url));
- }
-
- $records = json_decode($release_contents, TRUE);
- foreach ($records as $record) {
- if (isset($record['tag_name']) && ($release_prefix && str_contains((string) $record['tag_name'], (string) $release_prefix) || !$release_prefix)) {
- return $record['tag_name'];
- }
- }
-
- return NULL;
- }
-
- /**
- * Gather answers.
- *
- * This is how the values pipeline works for a variable:
- * 1. Read from .env
- * 2. Read from environment
- * 3. Read from user: default->discovered->answer->normalisation->save answer
- * 4. Use answers for processing, including writing values into correct
- * variables in .env.
- */
- protected function collectAnswers() {
- // Set answers that may be used in other answers' discoveries.
- $this->setAnswer('webroot', $this->discoverValue('webroot'));
-
- // @formatter:off
- // phpcs:disable Generic.Functions.FunctionCallArgumentSpacing.TooMuchSpaceAfterComma
- // phpcs:disable Drupal.WhiteSpace.Comma.TooManySpaces
- $this->askForAnswer('name', 'What is your site name?');
- $this->askForAnswer('machine_name', 'What is your site machine name?');
- $this->askForAnswer('org', 'What is your organization name');
- $this->askForAnswer('org_machine_name', 'What is your organization machine name?');
- $this->askForAnswer('module_prefix', 'What is your project-specific module prefix?');
- $this->askForAnswer('profile', 'What is your custom profile machine name (leave empty to use "standard" profile)?');
- $this->askForAnswer('theme', 'What is your theme machine name?');
- $this->askForAnswer('url', 'What is your site public URL?');
- $this->askForAnswer('webroot', 'Web root (web, docroot)?');
-
- $this->askForAnswer('provision_use_profile', 'Do you want to install from profile (leave empty or "n" for using database?');
-
- if ($this->getAnswer('provision_use_profile') == self::ANSWER_YES) {
- $this->setAnswer('database_download_source', 'none');
- $this->setAnswer('database_image', '');
- }
- else {
- $this->askForAnswer('database_download_source', "Where does the database dump come from into every environment:\n - [u]rl\n - [f]tp\n - [a]cquia backup\n - [d]ocker registry?");
-
- if ($this->getAnswer('database_download_source') != 'container_registry') {
- // Note that "database_store_type" is a pseudo-answer - it is only used
- // to improve UX and is not exposed as a variable (although has default,
- // discovery and normalisation callbacks).
- $this->askForAnswer('database_store_type', ' When developing locally, do you want to import the database dump from the [f]ile or store it imported in the [d]ocker image for faster builds?');
- }
-
- if ($this->getAnswer('database_store_type') == 'file') {
- $this->setAnswer('database_image', '');
- }
- else {
- $this->askForAnswer('database_image', ' What is your database image name and a tag (e.g. drevops/mariadb-drupal-data:latest)?');
- }
- }
- // @formatter:on
- // phpcs:enable Generic.Functions.FunctionCallArgumentSpacing.TooMuchSpaceAfterComma
- // phpcs:enable Drupal.WhiteSpace.Comma.TooManySpaces
-
- $this->askForAnswer('override_existing_db', 'Do you want to override existing database in the environment?');
-
- $this->askForAnswer('ci_provider', 'Which provider do you want to use for CI ([c]ircleci, [g]ithub actions, [n]one)?');
-
- $this->askForAnswer('deploy_type', 'How do you deploy your code to the hosting ([w]ebhook call, [c]ode artifact, [d]ocker image, [l]agoon, [n]one as a comma-separated list)?');
-
- if ($this->getAnswer('database_download_source') != 'ftp') {
- $this->askForAnswer('preserve_ftp', 'Do you want to keep FTP integration?');
- }
- else {
- $this->setAnswer('preserve_ftp', self::ANSWER_YES);
- }
-
- if ($this->getAnswer('database_download_source') != 'acquia') {
- $this->askForAnswer('preserve_acquia', 'Do you want to keep Acquia Cloud integration?');
- }
- else {
- $this->setAnswer('preserve_acquia', self::ANSWER_YES);
- }
-
- $this->askForAnswer('preserve_lagoon', 'Do you want to keep Amazee.io Lagoon integration?');
-
- $this->askForAnswer('preserve_renovatebot', 'Do you want to keep RenovateBot integration?');
-
- $this->askForAnswer('preserve_doc_comments', 'Do you want to keep detailed documentation in comments?');
- $this->askForAnswer('preserve_vortex_info', 'Do you want to keep all Vortex information?');
-
- $this->printSummary();
-
- if ($this->isInstallDebug()) {
- $this->printBox($this->formatValuesList($this->getAnswers(), '', 80 - 6), 'DEBUG RESOLVED ANSWERS');
- }
- }
-
- protected function askShouldProceed(): bool {
- $proceed = self::ANSWER_YES;
-
- if (!$this->isQuiet()) {
- $proceed = $this->ask(sprintf('Proceed with installing Vortex into your project\'s directory "%s"? (Y,n)', $this->getDstDir()), $proceed, TRUE);
- }
-
- // Kill-switch to not proceed with install. If false, the install will not
- // proceed despite the answer received above.
- if (!$this->getConfig('VORTEX_INSTALL_PROCEED')) {
- $proceed = self::ANSWER_NO;
- }
-
- return strtolower((string) $proceed) === self::ANSWER_YES;
- }
-
- protected function askForAnswer($name, $question) {
- $discovered = $this->discoverValue($name);
- $answer = $this->ask($question, $discovered);
- $answer = $this->normaliseAnswer($name, $answer);
-
- $this->setAnswer($name, $answer);
- }
-
- protected function ask($question, $default, $close_handle = FALSE) {
- if ($this->isQuiet()) {
- return $default;
- }
-
- $question = sprintf('> %s [%s] ', $question, $default);
-
- $this->out($question, 'question', FALSE);
- $handle = $this->getStdinHandle();
- $answer = trim(fgets($handle));
-
- if ($close_handle) {
- $this->closeStdinHandle();
- }
-
- return empty($answer) ? $default : $answer;
- }
-
- /**
- * Get installer configuration.
- *
- * Installer config is a config of this installer script. For configs of the
- * project being installed, @see get_answer().
- *
- * @see init_config()
- */
- protected function getConfig($name, $default = NULL) {
- global $_config;
-
- return $_config[$name] ?? $default;
- }
-
- /**
- * Set installer configuration.
- *
- * Installer config is a config of this installer script. For configs of the
- * project being installed, @see set_answer().
- *
- * @see init_config()
- */
- protected function setConfig($name, $value) {
- global $_config;
-
- if (!is_null($value)) {
- $_config[$name] = $value;
- }
- }
-
- /**
- * Get a named option from discovered answers for the project bing installed.
- */
- protected function getAnswer($name, $default = NULL) {
- global $_answers;
-
- return $_answers[$name] ?? $default;
- }
-
- /**
- * Set a named option for discovered answers for the project bing installed.
- */
- protected function setAnswer($name, $value) {
- global $_answers;
- $_answers[$name] = $value;
- }
-
- /**
- * Get all options from discovered answers for the project bing installed.
- */
- protected function getAnswers() {
- global $_answers;
-
- return $_answers;
- }
-
- /**
- * Init all config.
- */
- protected function initConfig($input) {
- $this->initCliArgsAndOptions($input);
-
- static::loadDotenv($this->getDstDir() . '/.env');
-
- $this->initInstallerConfig();
- }
-
- /**
- * Initialise CLI options.
- */
- protected function initCliArgsAndOptions($input) {
- $arg = $input->getArguments();
- $options = $input->getOptions();
-
- if (!empty($options['help'])) {
- $this->setConfig('help', TRUE);
- }
-
- if (!empty($options['quiet'])) {
- $this->setConfig('quiet', TRUE);
- }
-
- if (!empty($options['no-ansi'])) {
- $this->setConfig('ANSI', FALSE);
- }
- else {
- // On Windows, default to no ANSI, except in ANSICON and ConEmu.
- // Everywhere else, default to ANSI if stdout is a terminal.
- $is_ansi = (DIRECTORY_SEPARATOR === '\\')
- ? (FALSE !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI'))
- : (function_exists('posix_isatty') && posix_isatty(1));
- $this->setConfig('ANSI', $is_ansi);
- }
-
- if (!empty($arg['path'])) {
- $this->setConfig('VORTEX_INSTALL_DST_DIR', $arg['path']);
- }
- else {
- $this->setConfig('VORTEX_INSTALL_DST_DIR', static::getenvOrDefault('VORTEX_INSTALL_DST_DIR', self::$currentDir));
- }
- }
-
- /**
- * Instantiate installer configuration from environment variables.
- *
- * Installer configuration is a set of internal installer script variables,
- * read from the environment variables. These environment variables are not
- * read directly in any operations of this installer script. Instead, these
- * environment variables are accessible with get_installer_config().
- *
- * For simplicity of naming, internal installer config variables are matching
- * environment variables names.
- */
- protected function initInstallerConfig() {
- // Internal version of Vortex.
- $this->setConfig('VORTEX_VERSION', static::getenvOrDefault('VORTEX_VERSION', 'develop'));
- // Flag to display install debug information.
- $this->setConfig('VORTEX_INSTALL_DEBUG', (bool) static::getenvOrDefault('VORTEX_INSTALL_DEBUG', FALSE));
- // Flag to proceed with installation. If FALSE - the installation will only
- // print resolved values and will not proceed.
- $this->setConfig('VORTEX_INSTALL_PROCEED', (bool) static::getenvOrDefault('VORTEX_INSTALL_PROCEED', TRUE));
- // Temporary directory to download and expand files to.
- $this->setConfig('VORTEX_INSTALL_TMP_DIR', static::getenvOrDefault('VORTEX_INSTALL_TMP_DIR', static::tempdir()));
- // Path to local Vortex repository. If not provided - remote will be used.
- $this->setConfig('VORTEX_INSTALL_LOCAL_REPO', static::getenvOrDefault('VORTEX_INSTALL_LOCAL_REPO'));
- // Optional commit to download. If not provided, latest release will be
- // downloaded.
- $this->setConfig('VORTEX_INSTALL_COMMIT', static::getenvOrDefault('VORTEX_INSTALL_COMMIT', 'HEAD'));
-
- // Internal flag to enforce DEMO mode. If not set, the demo mode will be
- // discovered automatically.
- if (!is_null(static::getenvOrDefault('VORTEX_INSTALL_DEMO'))) {
- $this->setConfig('VORTEX_INSTALL_DEMO', (bool) static::getenvOrDefault('VORTEX_INSTALL_DEMO'));
- }
- // Internal flag to skip processing of the demo mode.
- $this->setConfig('VORTEX_INSTALL_DEMO_SKIP', (bool) static::getenvOrDefault('VORTEX_INSTALL_DEMO_SKIP', FALSE));
- }
-
- protected function getDstDir() {
- return $this->getConfig('VORTEX_INSTALL_DST_DIR');
- }
-
- /**
- * Shorthand to get the value of whether install should be quiet.
- */
- protected function isQuiet() {
- return $this->getConfig('quiet');
- }
-
- /**
- * Shorthand to get the value of VORTEX_INSTALL_DEBUG.
- */
- protected function isInstallDebug() {
- return $this->getConfig('VORTEX_INSTALL_DEBUG');
- }
-
- /**
- * Get default value router.
- */
- protected function getDefaultValue($name) {
- // Allow to override default values from config variables.
- $config_name = strtoupper((string) $name);
-
- return $this->getConfig($config_name, $this->executeCallback('getDefaultValue', $name));
- }
-
- protected function getDefaultValueName(): ?string {
- return static::toHumanName(static::getenvOrDefault('VORTEX_PROJECT', basename((string) $this->getDstDir())));
- }
-
- protected function getDefaultValueMachineName(): string {
- return static::toMachineName($this->getAnswer('name'));
- }
-
- protected function getDefaultValueOrg(): string {
- return $this->getAnswer('name') . ' Org';
- }
-
- protected function getDefaultValueOrgMachineName(): string {
- return static::toMachineName($this->getAnswer('org'));
- }
-
- protected function getDefaultValueModulePrefix(): string|array {
- return $this->toAbbreviation($this->getAnswer('machine_name'));
- }
-
- protected function getDefaultValueProfile(): string {
- return self::ANSWER_NO;
- }
-
- protected function getDefaultValueTheme() {
- return $this->getAnswer('machine_name');
- }
-
- protected function getDefaultValueUrl(): string {
- $value = $this->getAnswer('machine_name');
- $value = str_replace('_', '-', (string) $value);
-
- return $value . '.com';
- }
-
- protected function getDefaultValueWebroot(): string {
- return 'web';
- }
-
- protected function getDefaultValueProvisionUseProfile(): string {
- return self::ANSWER_NO;
- }
-
- protected function getDefaultValueDatabaseDownloadSource(): string {
- return 'curl';
- }
-
- protected function getDefaultValueDatabaseStoreType(): string {
- return 'file';
- }
-
- protected function getDefaultValueDatabaseImage(): string {
- return 'drevops/mariadb-drupal-data:latest';
- }
-
- protected function getDefaultValueOverrideExistingDb(): string {
- return self::ANSWER_NO;
- }
-
- protected function getDefaultValueCiProvider(): string {
- return 'GitHub Actions';
- }
-
- protected function getDefaultValueDeployType(): string {
- return 'artifact';
- }
-
- protected function getDefaultValuePreserveAcquia(): string {
- return self::ANSWER_NO;
- }
-
- protected function getDefaultValuePreserveLagoon(): string {
- return self::ANSWER_NO;
- }
-
- protected function getDefaultValuePreserveFtp(): string {
- return self::ANSWER_NO;
- }
-
- protected function getDefaultValuePreserveRenovatebot(): string {
- return self::ANSWER_YES;
- }
-
- protected function getDefaultValuePreserveDocComments(): string {
- return self::ANSWER_YES;
- }
-
- protected function getDefaultValuePreserveVortexInfo(): string {
- return self::ANSWER_NO;
- }
-
- /**
- * Discover value router.
- *
- * Value discoveries should return NULL if they don't have the resources to
- * discover a value. This means that if the value is expected to come from a
- * file but the file is not available, the function should return NULL instead
- * of a falsy value like FALSE or 0.
- */
- protected function discoverValue($name) {
- $value = $this->executeCallback('discoverValue', $name);
-
- return is_null($value) ? $this->getDefaultValue($name) : $value;
- }
-
- protected function discoverValueName(): ?string {
- $value = $this->getComposerJsonValue('description');
- if ($value && preg_match('/Drupal \d+ .* of ([0-9a-zA-Z\- ]+) for ([0-9a-zA-Z\- ]+)/', (string) $value, $matches) && !empty($matches[1])) {
- return $matches[1];
- }
-
- return NULL;
- }
-
- protected function discoverValueMachineName(): ?string {
- $value = $this->getComposerJsonValue('name');
- if ($value && preg_match('/([^\/]+)\/(.+)/', (string) $value, $matches) && !empty($matches[2])) {
- return $matches[2];
- }
-
- return NULL;
- }
-
- protected function discoverValueOrg(): ?string {
- $value = $this->getComposerJsonValue('description');
- if ($value && preg_match('/Drupal \d+ .* of ([0-9a-zA-Z\- ]+) for ([0-9a-zA-Z\- ]+)/', (string) $value, $matches) && !empty($matches[2])) {
- return $matches[2];
- }
-
- return NULL;
- }
-
- protected function discoverValueOrgMachineName(): ?string {
- $value = $this->getComposerJsonValue('name');
- if ($value && preg_match('/([^\/]+)\/(.+)/', (string) $value, $matches) && !empty($matches[1])) {
- return $matches[1];
- }
-
- return NULL;
- }
-
- protected function discoverValueModulePrefix(): null|string|array {
- $webroot = $this->getAnswer('webroot');
-
- $locations = [
- $this->getDstDir() . sprintf('/%s/modules/custom/*_core', $webroot),
- $this->getDstDir() . sprintf('/%s/sites/all/modules/custom/*_core', $webroot),
- $this->getDstDir() . sprintf('/%s/profiles/*/modules/*_core', $webroot),
- $this->getDstDir() . sprintf('/%s/profiles/*/modules/custom/*_core', $webroot),
- $this->getDstDir() . sprintf('/%s/profiles/custom/*/modules/*_core', $webroot),
- $this->getDstDir() . sprintf('/%s/profiles/custom/*/modules/custom/*_core', $webroot),
- ];
-
- $name = $this->findMatchingPath($locations);
-
- if (empty($name)) {
- return NULL;
- }
-
- if ($name) {
- $name = basename((string) $name);
- $name = str_replace('_core', '', $name);
- }
-
- return $name;
- }
-
- protected function discoverValueProfile() {
- $webroot = $this->getAnswer('webroot');
-
- if ($this->isInstalled()) {
- $name = $this->getValueFromDstDotenv('DRUPAL_PROFILE');
- if (!empty($name)) {
- return $name;
- }
- }
-
- $locations = [
- $this->getDstDir() . sprintf('/%s/profiles/*/*.info', $webroot),
- $this->getDstDir() . sprintf('/%s/profiles/*/*.info.yml', $webroot),
- $this->getDstDir() . sprintf('/%s/profiles/custom/*/*.info', $webroot),
- $this->getDstDir() . sprintf('/%s/profiles/custom/*/*.info.yml', $webroot),
- ];
-
- $name = $this->findMatchingPath($locations, 'Drupal 10 profile implementation of');
-
- if (empty($name)) {
- return NULL;
- }
-
- if ($name) {
- $name = basename((string) $name);
- $name = str_replace(['.info.yml', '.info'], '', $name);
- }
-
- return $name;
- }
-
- protected function discoverValueTheme() {
- $webroot = $this->getAnswer('webroot');
-
- if ($this->isInstalled()) {
- $name = $this->getValueFromDstDotenv('DRUPAL_THEME');
- if (!empty($name)) {
- return $name;
- }
- }
-
- $locations = [
- $this->getDstDir() . sprintf('/%s/themes/custom/*/*.info', $webroot),
- $this->getDstDir() . sprintf('/%s/themes/custom/*/*.info.yml', $webroot),
- $this->getDstDir() . sprintf('/%s/sites/all/themes/custom/*/*.info', $webroot),
- $this->getDstDir() . sprintf('/%s/sites/all/themes/custom/*/*.info.yml', $webroot),
- $this->getDstDir() . sprintf('/%s/profiles/*/themes/custom/*/*.info', $webroot),
- $this->getDstDir() . sprintf('/%s/profiles/*/themes/custom/*/*.info.yml', $webroot),
- $this->getDstDir() . sprintf('/%s/profiles/custom/*/themes/custom/*/*.info', $webroot),
- $this->getDstDir() . sprintf('/%s/profiles/custom/*/themes/custom/*/*.info.yml', $webroot),
- ];
-
- $name = $this->findMatchingPath($locations);
-
- if (empty($name)) {
- return NULL;
- }
-
- if ($name) {
- $name = basename((string) $name);
- $name = str_replace(['.info.yml', '.info'], '', $name);
- }
-
- return $name;
- }
-
- protected function discoverValueUrl() {
- $webroot = $this->getAnswer('webroot');
-
- $origin = NULL;
- $path = $this->getDstDir() . sprintf('/%s/sites/default/settings.php', $webroot);
-
- if (!is_readable($path)) {
- return NULL;
- }
-
- $contents = file_get_contents($path);
-
- // Drupal 8 and 9.
- if (preg_match('/\$config\s*\[\'stage_file_proxy.settings\'\]\s*\[\'origin\'\]\s*=\s*[\'"]([^\'"]+)[\'"];/', $contents, $matches)) {
- if (!empty($matches[1])) {
- $origin = $matches[1];
- }
- }
- // Drupal 7.
- elseif (preg_match('/\$conf\s*\[\'stage_file_proxy_origin\'\]\s*=\s*[\'"]([^\'"]+)[\'"];/', $contents, $matches)) {
- if (!empty($matches[1])) {
- $origin = $matches[1];
- }
- }
- if ($origin) {
- $origin = parse_url($origin, PHP_URL_HOST);
- }
-
- return empty($origin) ? NULL : $origin;
- }
-
- protected function discoverValueWebroot() {
- $webroot = $this->getValueFromDstDotenv('VORTEX_WEBROOT');
-
- if (empty($webroot) && $this->isInstalled()) {
- // Try from composer.json.
- $extra = $this->getComposerJsonValue('extra');
- if (!empty($extra)) {
- $webroot = $extra['drupal-scaffold']['drupal-scaffold']['locations']['web-root'] ?? NULL;
- }
- }
-
- return $webroot;
- }
-
- protected function discoverValueProvisionUseProfile(): string {
- return $this->getValueFromDstDotenv('VORTEX_PROVISION_USE_PROFILE') ? self::ANSWER_YES : self::ANSWER_NO;
- }
-
- protected function discoverValueDatabaseDownloadSource() {
- return $this->getValueFromDstDotenv('VORTEX_DB_DOWNLOAD_SOURCE');
- }
-
- protected function discoverValueDatabaseStoreType(): string {
- return $this->discoverValueDatabaseImage() ? 'container_image' : 'file';
- }
-
- protected function discoverValueDatabaseImage() {
- return $this->getValueFromDstDotenv('VORTEX_DB_IMAGE');
- }
-
- protected function discoverValueOverrideExistingDb(): string {
- return $this->getValueFromDstDotenv('VORTEX_PROVISION_OVERRIDE_DB') ? self::ANSWER_YES : self::ANSWER_NO;
- }
-
- protected function discoverValueCiProvider() {
- if (is_readable($this->getDstDir() . '/.github/workflows/build-test-deploy.yml')) {
- return 'GitHub Actions';
- }
-
- if (is_readable($this->getDstDir() . '/.circleci/config.yml')) {
- return 'CircleCI';
- }
-
- return $this->isInstalled() ? 'none' : NULL;
- }
-
- protected function discoverValueDeployType() {
- return $this->getValueFromDstDotenv('VORTEX_DEPLOY_TYPES');
- }
-
- protected function discoverValuePreserveAcquia(): ?string {
- if (is_readable($this->getDstDir() . '/hooks')) {
- return self::ANSWER_YES;
- }
- $value = $this->getValueFromDstDotenv('VORTEX_DB_DOWNLOAD_SOURCE');
-
- if (is_null($value)) {
- return NULL;
- }
-
- return $value == 'acquia' ? self::ANSWER_YES : self::ANSWER_NO;
- }
-
- protected function discoverValuePreserveLagoon(): ?string {
- if (is_readable($this->getDstDir() . '/.lagoon.yml')) {
- return self::ANSWER_YES;
- }
-
- if ($this->getAnswer('deploy_type') == 'lagoon') {
- return self::ANSWER_YES;
- }
-
- $value = $this->getValueFromDstDotenv('LAGOON_PROJECT');
-
- // Special case - only work with non-empty value as 'LAGOON_PROJECT'
- // may not exist in installed site's .env file.
- if (empty($value)) {
- return NULL;
- }
-
- return self::ANSWER_YES;
- }
-
- protected function discoverValuePreserveFtp(): ?string {
- $value = $this->getValueFromDstDotenv('VORTEX_DB_DOWNLOAD_SOURCE');
- if (is_null($value)) {
- return NULL;
- }
-
- return $value == 'ftp' ? self::ANSWER_YES : self::ANSWER_NO;
- }
-
- protected function discoverValuePreserveRenovatebot(): ?string {
- if (!$this->isInstalled()) {
- return NULL;
- }
-
- return is_readable($this->getDstDir() . '/renovate.json') ? self::ANSWER_YES : self::ANSWER_NO;
- }
-
- protected function discoverValuePreserveDocComments(): ?string {
- $file = $this->getDstDir() . '/.ahoy.yml';
- if (!is_readable($file)) {
- return NULL;
- }
-
- return static::fileContains('Ahoy configuration file', $file) ? self::ANSWER_YES : self::ANSWER_NO;
- }
-
- protected function discoverValuePreserveVortexInfo(): ?string {
- $file = $this->getDstDir() . '/.ahoy.yml';
- if (!is_readable($file)) {
- return NULL;
- }
-
- return static::fileContains('Comments starting with', $file) ? self::ANSWER_YES : self::ANSWER_NO;
- }
-
- protected function getValueFromDstDotenv($name, $default = NULL) {
- // Environment variables always take precedence.
- $env_value = static::getenvOrDefault($name, NULL);
- if (!is_null($env_value)) {
- return $env_value;
- }
-
- $file = $this->getDstDir() . '/.env';
- if (!is_readable($file)) {
- return $default;
- }
- $parsed = static::parseDotenv($file);
-
- return $parsed ? $parsed[$name] ?? $default : $default;
- }
-
- protected function findMatchingPath($paths, $text = NULL) {
- $paths = is_array($paths) ? $paths : [$paths];
-
- foreach ($paths as $path) {
- $files = glob($path);
- if (empty($files)) {
- continue;
- }
-
- if (count($files)) {
- if (!empty($text)) {
- foreach ($files as $file) {
- if (static::fileContains($text, $file)) {
- return $file;
- }
- }
- }
- else {
- return reset($files);
- }
- }
- }
-
- return NULL;
- }
-
- /**
- * Check that Vortex is installed for this project.
- */
- protected function isInstalled(): bool {
- $path = $this->getDstDir() . DIRECTORY_SEPARATOR . 'README.md';
-
- return file_exists($path) && preg_match('/badge\/Vortex\-/', file_get_contents($path));
- }
-
- /**
- * Normalisation router.
- */
- protected function normaliseAnswer($name, $value) {
- $normalised = $this->executeCallback('normaliseAnswer', $name, $value);
-
- return $normalised ?? $value;
- }
-
- protected function normaliseAnswerName($value): string {
- return ucfirst((string) static::toHumanName($value));
- }
-
- protected function normaliseAnswerMachineName($value): string {
- return static::toMachineName($value);
- }
-
- protected function normaliseAnswerOrgMachineName($value): string {
- return static::toMachineName($value);
- }
-
- protected function normaliseAnswerModulePrefix($value): string {
- return static::toMachineName($value);
- }
-
- protected function normaliseAnswerProfile($value): string {
- $profile = static::toMachineName($value);
- if (empty($profile) || strtolower($profile) === self::ANSWER_NO) {
- $profile = 'standard';
- }
-
- return $profile;
- }
-
- protected function normaliseAnswerTheme($value): string {
- return static::toMachineName($value);
- }
-
- protected function normaliseAnswerUrl($url): string|array {
- $url = trim((string) $url);
-
- return str_replace([' ', '_'], '-', $url);
- }
-
- protected function normaliseAnswerWebroot($value): string {
- return strtolower(trim((string) $value, '/'));
- }
-
- protected function normaliseAnswerProvisionUseProfile($value): string {
- return strtolower((string) $value) !== self::ANSWER_YES ? self::ANSWER_NO : self::ANSWER_YES;
- }
-
- protected function normaliseAnswerDatabaseDownloadSource($value): string {
- $value = strtolower((string) $value);
-
- return match ($value) {
- 'f', 'ftp' => 'ftp',
- 'a', 'acquia' => 'acquia',
- 'i', 'image', 'container_image', 'container_registry' => 'container_registry',
- 'c', 'curl' => 'curl',
- default => $this->getDefaultValueDatabaseDownloadSource(),
- };
- }
-
- protected function normaliseAnswerDatabaseStoreType($value): string {
- $value = strtolower((string) $value);
-
- return match ($value) {
- 'i', 'image', 'container_image', => 'container_image',
- 'f', 'file' => 'file',
- default => $this->getDefaultValueDatabaseStoreType(),
- };
- }
-
- protected function normaliseAnswerDatabaseImage($value): string {
- $value = static::toMachineName($value, ['-', '/', ':', '.']);
-
- return str_contains($value, ':') ? $value : $value . ':latest';
- }
-
- protected function normaliseAnswerOverrideExistingDb($value): string {
- return strtolower((string) $value) !== self::ANSWER_YES ? self::ANSWER_NO : self::ANSWER_YES;
- }
-
- protected function normaliseAnswerCiProvider($value): string {
- $value = trim(strtolower((string) $value));
-
- switch ($value) {
- case 'c':
- case 'circleci':
- return 'CircleCI';
-
- case 'g':
- case 'gha':
- case 'github actions':
- return 'GitHub Actions';
- }
-
- return 'none';
- }
-
- protected function normaliseAnswerDeployType($value): ?string {
- $types = explode(',', (string) $value);
-
- $normalised = [];
- foreach ($types as $type) {
- $type = trim($type);
- switch ($type) {
- case 'w':
- case 'webhook':
- $normalised[] = 'webhook';
- break;
-
- case 'c':
- case 'code':
- case 'a':
- case 'artifact':
- $normalised[] = 'artifact';
- break;
-
- case 'r':
- case 'container_registry':
- $normalised[] = 'container_registry';
- break;
-
- case 'l':
- case 'lagoon':
- $normalised[] = 'lagoon';
- break;
-
- case 'n':
- case 'none':
- $normalised[] = 'none';
- break;
- }
- }
-
- if (in_array('none', $normalised)) {
- return NULL;
- }
-
- $normalised = array_unique($normalised);
-
- return implode(',', $normalised);
- }
-
- protected function normaliseAnswerPreserveAcquia($value): string {
- return strtolower((string) $value) !== self::ANSWER_YES ? self::ANSWER_NO : self::ANSWER_YES;
- }
-
- protected function normaliseAnswerPreserveLagoon($value): string {
- return strtolower((string) $value) !== self::ANSWER_YES ? self::ANSWER_NO : self::ANSWER_YES;
- }
-
- protected function normaliseAnswerPreserveFtp($value): string {
- return strtolower((string) $value) !== self::ANSWER_YES ? self::ANSWER_NO : self::ANSWER_YES;
- }
-
- protected function normaliseAnswerPreserveRenovatebot($value): string {
- return strtolower((string) $value) !== self::ANSWER_YES ? self::ANSWER_NO : self::ANSWER_YES;
- }
-
- protected function normaliseAnswerPreserveDocComments($value): string {
- return strtolower((string) $value) !== self::ANSWER_YES ? self::ANSWER_NO : self::ANSWER_YES;
- }
-
- protected function normaliseAnswerPreserveVortexInfo($value): string {
- return strtolower((string) $value) !== self::ANSWER_YES ? self::ANSWER_NO : self::ANSWER_YES;
- }
-
- /**
- * Print help.
- */
- protected function printHelp(): string {
- return <<isQuiet()) {
- $this->printHeaderQuiet();
- }
- else {
- $this->printHeaderInteractive();
- }
- print PHP_EOL;
- }
-
- protected function printHeaderInteractive() {
- $commit = $this->getConfig('VORTEX_INSTALL_COMMIT');
-
- $content = '';
- if ($commit == 'HEAD') {
- $content .= 'This will install the latest version of Vortex into your project.' . PHP_EOL;
- }
- else {
- $content .= sprintf('This will install Vortex into your project at commit "%s".', $commit) . PHP_EOL;
- }
- $content .= PHP_EOL;
- if ($this->isInstalled()) {
- $content .= 'It looks like Vortex is already installed into this project.' . PHP_EOL;
- $content .= PHP_EOL;
- }
- $content .= 'Please answer the questions below to install configuration relevant to your site.' . PHP_EOL;
- $content .= 'No changes will be applied until the last confirmation step.' . PHP_EOL;
- $content .= PHP_EOL;
- $content .= 'Existing committed files will be modified. You will need to resolve changes manually.' . PHP_EOL;
- $content .= PHP_EOL;
- $content .= 'Press Ctrl+C at any time to exit this installer.' . PHP_EOL;
-
- $this->printBox($content, 'WELCOME TO VORTEX INTERACTIVE INSTALLER');
- }
-
- protected function printHeaderQuiet() {
- $commit = $this->getConfig('VORTEX_INSTALL_COMMIT');
-
- $content = '';
- if ($commit == 'HEAD') {
- $content .= 'This will install the latest version of Vortex into your project.' . PHP_EOL;
- }
- else {
- $content .= sprintf('This will install Vortex into your project at commit "%s".', $commit) . PHP_EOL;
- }
- $content .= PHP_EOL;
- if ($this->isInstalled()) {
- $content .= 'It looks like Vortex is already installed into this project.' . PHP_EOL;
- $content .= PHP_EOL;
- }
- $content .= 'Vortex installer will try to discover the settings from the environment and will install configuration relevant to your site.' . PHP_EOL;
- $content .= PHP_EOL;
- $content .= 'Existing committed files will be modified. You will need to resolve changes manually.' . PHP_EOL;
-
- $this->printBox($content, 'WELCOME TO VORTEX QUIET INSTALLER');
- }
-
- protected function printSummary() {
- $values['Current directory'] = self::$currentDir;
- $values['Destination directory'] = $this->getDstDir();
- $values['Vortex version'] = $this->getConfig('VORTEX_VERSION');
- $values['Vortex commit'] = $this->formatNotEmpty($this->getConfig('VORTEX_INSTALL_COMMIT'), 'Latest');
-
- $values[] = '';
- $values[] = str_repeat('─', 80 - 2 - 2 * 2);
- $values[] = '';
-
- $values['Name'] = $this->getAnswer('name');
- $values['Machine name'] = $this->getAnswer('machine_name');
- $values['Organisation'] = $this->getAnswer('org');
- $values['Organisation machine name'] = $this->getAnswer('org_machine_name');
- $values['Module prefix'] = $this->getAnswer('module_prefix');
- $values['Profile'] = $this->getAnswer('profile');
- $values['Theme name'] = $this->getAnswer('theme');
- $values['URL'] = $this->getAnswer('url');
- $values['Web root'] = $this->getAnswer('webroot');
-
- $values['Install from profile'] = $this->formatYesNo($this->getAnswer('provision_use_profile'));
-
- $values['Database download source'] = $this->getAnswer('database_download_source');
- $image = $this->getAnswer('database_image');
- $values['Database store type'] = empty($image) ? 'file' : 'container_image';
- if ($image) {
- $values['Database image name'] = $image;
- }
-
- $values['Override existing database'] = $this->formatYesNo($this->getAnswer('override_existing_db'));
- $values['CI provider'] = $this->formatNotEmpty($this->getAnswer('ci_provider'), 'None');
- $values['Deployment'] = $this->formatNotEmpty($this->getAnswer('deploy_type'), 'Disabled');
- $values['FTP integration'] = $this->formatEnabled($this->getAnswer('preserve_ftp'));
- $values['Acquia integration'] = $this->formatEnabled($this->getAnswer('preserve_acquia'));
- $values['Lagoon integration'] = $this->formatEnabled($this->getAnswer('preserve_lagoon'));
- $values['RenovateBot integration'] = $this->formatEnabled($this->getAnswer('preserve_renovatebot'));
- $values['Preserve docs in comments'] = $this->formatYesNo($this->getAnswer('preserve_doc_comments'));
- $values['Preserve Vortex comments'] = $this->formatYesNo($this->getAnswer('preserve_vortex_info'));
-
- $content = $this->formatValuesList($values, '', 80 - 2 - 2 * 2);
-
- $this->printBox($content, 'INSTALLATION SUMMARY');
- }
-
- protected function printAbort() {
- $this->printBox('Aborting project installation. No files were changed.');
- }
-
- protected function printFooter() {
- print PHP_EOL;
-
- if ($this->isInstalled()) {
- $this->printBox('Finished updating Vortex. Review changes and commit required files.');
- }
- else {
- $this->printBox('Finished installing Vortex.');
-
- $output = '';
- $output .= PHP_EOL;
- $output .= 'Next steps:' . PHP_EOL;
- $output .= ' cd ' . $this->getDstDir() . PHP_EOL;
- $output .= ' git add -A # Add all files.' . PHP_EOL;
- $output .= ' git commit -m "Initial commit." # Commit all files.' . PHP_EOL;
- $output .= ' ahoy build # Build site.' . PHP_EOL;
- $output .= PHP_EOL;
- $output .= ' See https://vortex.drevops.com/quickstart';
- $this->status($output, self::INSTALLER_STATUS_SUCCESS, TRUE, FALSE);
- }
- }
-
- protected function printTitle($text, $fill = '-', $width = 80, string $cols = '|', $has_content = FALSE) {
- $this->printDivider($fill, $width, 'down');
- $lines = explode(PHP_EOL, wordwrap((string) $text, $width - 4, PHP_EOL));
- foreach ($lines as $line) {
- $line = ' ' . $line . ' ';
- print $cols . str_pad($line, $width - 2, ' ', STR_PAD_BOTH) . $cols . PHP_EOL;
- }
- $this->printDivider($fill, $width, $has_content ? 'up' : 'both');
- }
-
- protected function printSubtitle($text, $fill = '=', $width = 80) {
- $is_multiline = strlen((string) $text) + 4 >= $width;
- if ($is_multiline) {
- $this->printTitle($text, $fill, $width, 'both');
- }
- else {
- $text = ' ' . $text . ' ';
- print str_pad($text, $width, $fill, STR_PAD_BOTH) . PHP_EOL;
- }
- }
-
- protected function printDivider($fill = '-', $width = 80, $direction = 'none') {
- $start = $fill;
- $finish = $fill;
- switch ($direction) {
- case 'up':
- $start = '╰';
- $finish = '╯';
- break;
-
- case 'down':
- $start = '╭';
- $finish = '╮';
- break;
-
- case 'both':
- $start = '├';
- $finish = '┤';
- break;
- }
-
- print $start . str_repeat((string) $fill, $width - 2) . $finish . PHP_EOL;
- }
-
- protected function printBox($content, $title = '', $fill = '─', $padding = 2, $width = 80) {
- $cols = '│';
-
- $max_width = $width - 2 - $padding * 2;
- $lines = explode(PHP_EOL, wordwrap(rtrim((string) $content, PHP_EOL), $max_width, PHP_EOL));
- $pad = str_pad(' ', $padding);
- $mask = sprintf('%s%s%%-%ss%s%s', $cols, $pad, $max_width, $pad, $cols) . PHP_EOL;
-
- print PHP_EOL;
- if (!empty($title)) {
- $this->printTitle($title, $fill, $width);
- }
- else {
- $this->printDivider($fill, $width, 'down');
- }
-
- array_unshift($lines, '');
- $lines[] = '';
- foreach ($lines as $line) {
- printf($mask, $line);
- }
-
- $this->printDivider($fill, $width, 'up');
- print PHP_EOL;
- }
-
- protected function printTick($text = NULL) {
- if (!empty($text) && $this->isInstallDebug()) {
- print PHP_EOL;
- $this->status($text, self::INSTALLER_STATUS_DEBUG, FALSE);
- }
- else {
- $this->status('.', self::INSTALLER_STATUS_MESSAGE, FALSE, FALSE);
- }
- }
-
- protected function formatValuesList($values, $delim = '', $width = 80): string {
- // Line width - length of delimiters * 2 - 2 spacers.
- $line_width = $width - strlen((string) $delim) * 2 - 2;
-
- // Max name length + spaced on the sides + colon.
- $max_name_width = max(array_map('strlen', array_keys($values))) + 2 + 1;
-
- // Whole width - (name width + 2 delimiters on the sides + 1 delimiter in
- // the middle + 2 spaces on the sides + 2 spaces for the center delimiter).
- $value_width = $width - ($max_name_width + strlen((string) $delim) * 2 + strlen((string) $delim) + 2 + 2);
-
- $mask1 = sprintf('%s %%%ds %s %%-%s.%ss %s', $delim, $max_name_width, $delim, $value_width, $value_width, $delim) . PHP_EOL;
- $mask2 = sprintf('%s%%2$%ss%s', $delim, $line_width, $delim) . PHP_EOL;
-
- $output = [];
- foreach ($values as $name => $value) {
- $is_multiline_value = strlen((string) $value) > $value_width;
-
- if (is_numeric($name)) {
- $name = '';
- $mask = $mask2;
- $is_multiline_value = FALSE;
- }
- else {
- $name .= ':';
- $mask = $mask1;
- }
-
- if ($is_multiline_value) {
- $lines = array_filter(explode(PHP_EOL, chunk_split((string) $value, $value_width, PHP_EOL)));
- $first_line = array_shift($lines);
- $output[] = sprintf($mask, $name, $first_line);
- foreach ($lines as $line) {
- $output[] = sprintf($mask, '', $line);
- }
- }
- else {
- $output[] = sprintf($mask, $name, $value);
- }
- }
-
- return implode('', $output);
- }
-
- protected function formatEnabled($value): string {
- return $value && strtolower((string) $value) !== 'n' ? 'Enabled' : 'Disabled';
- }
-
- protected function formatYesNo($value): string {
- return $value == self::ANSWER_YES ? 'Yes' : 'No';
- }
-
- protected function formatNotEmpty($value, $default) {
- return empty($value) ? $default : $value;
- }
-
- public static function fileContains($needle, $file): int|bool {
- if (!is_readable($file)) {
- return FALSE;
- }
-
- $content = file_get_contents($file);
-
- if (static::isRegex($needle)) {
- return preg_match($needle, $content);
- }
-
- return str_contains($content, (string) $needle);
- }
-
- protected static function dirContains($needle, string $dir): bool {
- $files = static::scandirRecursive($dir, static::ignorePaths());
- foreach ($files as $filename) {
- if (static::fileContains($needle, $filename)) {
- return TRUE;
- }
- }
-
- return FALSE;
- }
-
- protected static function isRegex($str): bool {
- if ($str === '' || strlen((string) $str) < 3) {
- return FALSE;
- }
-
- return @preg_match($str, '') !== FALSE;
- }
-
- protected static function fileReplaceContent($needle, $replacement, $filename): ?bool {
- if (!is_readable($filename) || static::fileIsExcludedFromProcessing($filename)) {
- return FALSE;
- }
-
- $content = file_get_contents($filename);
-
- if (static::isRegex($needle)) {
- $replaced = preg_replace($needle, (string) $replacement, $content);
- }
- else {
- $replaced = str_replace($needle, $replacement, $content);
- }
- if ($replaced != $content) {
- file_put_contents($filename, $replaced);
- }
-
- return NULL;
- }
-
- protected static function dirReplaceContent($needle, $replacement, string $dir) {
- $files = static::scandirRecursive($dir, static::ignorePaths());
- foreach ($files as $filename) {
- static::fileReplaceContent($needle, $replacement, $filename);
- }
- }
-
- protected function removeTokenWithContent(string $token, string $dir) {
- $files = static::scandirRecursive($dir, static::ignorePaths());
- foreach ($files as $filename) {
- static::removeTokenFromFile($filename, '#;< ' . $token, '#;> ' . $token, TRUE);
- }
- }
-
- protected function removeTokenLine($token, string $dir) {
- if (!empty($token)) {
- $files = static::scandirRecursive($dir, static::ignorePaths());
- foreach ($files as $filename) {
- static::removeTokenFromFile($filename, $token, NULL);
- }
- }
- }
-
- public static function removeTokenFromFile($filename, $token_begin, $token_end = NULL, $with_content = FALSE): void {
- if (self::fileIsExcludedFromProcessing($filename)) {
- return;
- }
-
- $token_end = $token_end ?? $token_begin;
-
- $content = file_get_contents($filename);
-
- if ($token_begin != $token_end) {
- $token_begin_count = preg_match_all('/' . preg_quote((string) $token_begin) . '/', $content);
- $token_end_count = preg_match_all('/' . preg_quote((string) $token_end) . '/', $content);
- if ($token_begin_count !== $token_end_count) {
- throw new \RuntimeException(sprintf('Invalid begin and end token count in file %s: begin is %s(%s), end is %s(%s).', $filename, $token_begin, $token_begin_count, $token_end, $token_end_count));
- }
- }
-
- $out = [];
- $within_token = FALSE;
-
- $lines = file($filename);
- foreach ($lines as $line) {
- if (str_contains($line, (string) $token_begin)) {
- if ($with_content) {
- $within_token = TRUE;
- }
- continue;
- }
- elseif (str_contains($line, (string) $token_end)) {
- if ($with_content) {
- $within_token = FALSE;
- }
- continue;
- }
-
- if ($with_content && $within_token) {
- // Skip content as contents of the token.
- continue;
- }
-
- $out[] = $line;
- }
-
- file_put_contents($filename, implode('', $out));
- }
-
- protected static function replaceStringFilename($search, $replace, string $dir) {
- $files = static::scandirRecursive($dir, static::ignorePaths());
- foreach ($files as $filename) {
- $new_filename = str_replace($search, $replace, (string) $filename);
- if ($filename != $new_filename) {
- $new_dir = dirname($new_filename);
- if (!is_dir($new_dir)) {
- mkdir($new_dir, 0777, TRUE);
- }
- rename($filename, $new_filename);
- }
- }
- }
-
- /**
- * Recursively scan directory for files.
- */
- protected static function scandirRecursive(string $dir, $ignore_paths = [], $include_dirs = FALSE): array {
- $discovered = [];
-
- if (is_dir($dir)) {
- $paths = array_diff(scandir($dir), ['.', '..']);
- foreach ($paths as $path) {
- $path = $dir . '/' . $path;
- foreach ($ignore_paths as $ignore_path) {
- // Exlude based on sub-path match.
- if (str_contains($path, (string) $ignore_path)) {
- continue(2);
- }
- }
- if (is_dir($path)) {
- if ($include_dirs) {
- $discovered[] = $path;
- }
- $discovered = array_merge($discovered, static::scandirRecursive($path, $ignore_paths, $include_dirs));
- }
- else {
- $discovered[] = $path;
- }
- }
- }
-
- return $discovered;
- }
-
- protected function globRecursive($pattern, $flags = 0): array|false {
- $files = glob($pattern, $flags | GLOB_BRACE);
- foreach (glob(dirname((string) $pattern) . '/{,.}*[!.]', GLOB_BRACE | GLOB_ONLYDIR | GLOB_NOSORT) as $dir) {
- $files = array_merge($files, $this->globRecursive($dir . '/' . basename((string) $pattern), $flags));
- }
-
- return $files;
- }
-
- protected static function ignorePaths(): array {
- return array_merge([
- '/.git/',
- '/.idea/',
- '/vendor/',
- '/node_modules/',
- '/.data/',
- ], static::internalPaths());
- }
-
- protected static function internalPaths(): array {
- return [
- '/LICENSE',
- '/CODE_OF_CONDUCT.md',
- '/CONTRIBUTING.md',
- '/LICENSE',
- '/SECURITY.md',
- '/.vortex/docs',
- '/.vortex/tests',
- ];
- }
-
- protected static function isInternalPath($relative_path): bool {
- $relative_path = '/' . ltrim((string) $relative_path, './');
-
- return in_array($relative_path, static::internalPaths());
- }
-
- protected static function fileIsExcludedFromProcessing($filename): int|false {
- $excluded_patterns = [
- '.+\.png',
- '.+\.jpg',
- '.+\.jpeg',
- '.+\.bpm',
- '.+\.tiff',
- ];
-
- return preg_match('/^(' . implode('|', $excluded_patterns) . ')$/', (string) $filename);
- }
-
- /**
- * Execute command wrapper.
- */
- protected function doExec($command, array &$output = NULL, &$return_var = NULL): string|false {
- if ($this->isInstallDebug()) {
- $this->status(sprintf('COMMAND: %s', $command), self::INSTALLER_STATUS_DEBUG);
- }
- $result = exec($command, $output, $return_var);
- if ($this->isInstallDebug()) {
- $this->status(sprintf(' OUTPUT: %s', implode('', $output)), self::INSTALLER_STATUS_DEBUG);
- $this->status(sprintf(' CODE : %s', $return_var), self::INSTALLER_STATUS_DEBUG);
- $this->status(sprintf(' RESULT: %s', $result), self::INSTALLER_STATUS_DEBUG);
- }
-
- return $result;
- }
-
- protected static function rmdirRecursive($directory, array $options = []) {
- if (!isset($options['traverseSymlinks'])) {
- $options['traverseSymlinks'] = FALSE;
- }
- $items = glob($directory . DIRECTORY_SEPARATOR . '{,.}*', GLOB_MARK | GLOB_BRACE);
- foreach ($items as $item) {
- if (basename($item) === '.' || basename($item) === '..') {
- continue;
- }
- if (substr($item, -1) === DIRECTORY_SEPARATOR) {
- if (!$options['traverseSymlinks'] && is_link(rtrim($item, DIRECTORY_SEPARATOR))) {
- unlink(rtrim($item, DIRECTORY_SEPARATOR));
- }
- else {
- static::rmdirRecursive($item, $options);
- }
- }
- else {
- unlink($item);
- }
- }
- if (is_dir($directory = rtrim((string) $directory, '\\/'))) {
- if (is_link($directory)) {
- unlink($directory);
- }
- else {
- rmdir($directory);
- }
- }
- }
-
- protected static function rmdirRecursiveEmpty($directory, $options = []) {
- if (static::dirIsEmpty($directory)) {
- static::rmdirRecursive($directory, $options);
- static::rmdirRecursiveEmpty(dirname((string) $directory), $options);
- }
- }
-
- protected static function dirIsEmpty($directory): bool {
- return is_dir($directory) && count(scandir($directory)) === 2;
- }
-
- protected function status(string $message, $level = self::INSTALLER_STATUS_MESSAGE, $eol = TRUE, $use_prefix = TRUE) {
- $prefix = '';
- $color = NULL;
-
- switch ($level) {
- case self::INSTALLER_STATUS_SUCCESS:
- $prefix = '✓️';
- $color = 'success';
- break;
-
- case self::INSTALLER_STATUS_ERROR:
- $prefix = '✗';
- $color = 'error';
- break;
-
- case self::INSTALLER_STATUS_MESSAGE:
- $prefix = 'i️';
- $color = 'info';
- break;
-
- case self::INSTALLER_STATUS_DEBUG:
- $prefix = ' [D]';
- break;
- }
-
- if ($level != self::INSTALLER_STATUS_DEBUG || $this->isInstallDebug()) {
- $this->out(($use_prefix ? $prefix . ' ' : '') . $message, $color, $eol);
- }
- }
-
- protected static function parseDotenv($filename = '.env'): false|array {
- if (!is_readable($filename)) {
- return FALSE;
- }
-
- $contents = file_get_contents($filename);
- // Replace all # not inside quotes.
- $contents = preg_replace('/#(?=(?:(?:[^"]*"){2})*[^"]*$)/', ';', $contents);
-
- return parse_ini_string((string) $contents);
- }
-
- protected static function loadDotenv($filename = '.env', $override_existing = FALSE) {
- $parsed = static::parseDotenv($filename);
-
- if ($parsed === FALSE) {
- return;
- }
-
- foreach ($parsed as $var => $value) {
- if (!static::getenvOrDefault($var) || $override_existing) {
- putenv($var . '=' . $value);
- }
- }
-
- $GLOBALS['_ENV'] = $GLOBALS['_ENV'] ?? [];
- $GLOBALS['_SERVER'] = $GLOBALS['_SERVER'] ?? [];
-
- if ($override_existing) {
- $GLOBALS['_ENV'] = $parsed + $GLOBALS['_ENV'];
- $GLOBALS['_SERVER'] = $parsed + $GLOBALS['_SERVER'];
- }
- else {
- $GLOBALS['_ENV'] += $parsed;
- $GLOBALS['_SERVER'] += $parsed;
- }
- }
-
- /**
- * Reliable wrapper to work with environment values.
- */
- protected static function getenvOrDefault($name, $default = NULL) {
- $vars = getenv();
-
- if (!isset($vars[$name]) || $vars[$name] === '') {
- return $default;
- }
-
- return $vars[$name];
- }
-
- public static function tempdir($dir = NULL, $prefix = 'tmp_', $mode = 0700, $max_attempts = 1000): false|string {
- if (is_null($dir)) {
- $dir = sys_get_temp_dir();
- }
-
- $dir = rtrim((string) $dir, DIRECTORY_SEPARATOR);
-
- if (!is_dir($dir) || !is_writable($dir)) {
- return FALSE;
- }
-
- if (strpbrk((string) $prefix, '\\/:*?"<>|') !== FALSE) {
- return FALSE;
- }
- $attempts = 0;
-
- do {
- $path = sprintf('%s%s%s%s', $dir, DIRECTORY_SEPARATOR, $prefix, mt_rand(100000, mt_getrandmax()));
- } while (!mkdir($path, $mode) && $attempts++ < $max_attempts);
-
- if (!is_dir($path) || !is_writable($path)) {
- throw new \RuntimeException(sprintf('Unable to create temporary directory "%s".', $path));
- }
-
- return $path;
- }
-
- protected function commandExists(string $command) {
- $this->doExec('command -v ' . $command, $lines, $ret);
- if ($ret === 1) {
- throw new \RuntimeException(sprintf('Command "%s" does not exist in the current environment.', $command));
- }
- }
-
- protected static function toHumanName($value): ?string {
- $value = preg_replace('/[^a-zA-Z0-9]/', ' ', (string) $value);
- $value = trim((string) $value);
-
- return preg_replace('/\s{2,}/', ' ', $value);
- }
-
- protected static function toMachineName($value, $preserve_chars = []): string {
- $preserve = '';
- foreach ($preserve_chars as $char) {
- $preserve .= preg_quote((string) $char, '/');
- }
- $pattern = '/[^a-zA-Z0-9' . $preserve . ']/';
-
- $value = preg_replace($pattern, '_', (string) $value);
-
- return strtolower((string) $value);
- }
-
- protected static function toCamelCase($value, $capitalise_first = FALSE): string|array {
- $value = str_replace(' ', '', ucwords((string) preg_replace('/[^a-zA-Z0-9]/', ' ', (string) $value)));
-
- return $capitalise_first ? $value : lcfirst($value);
- }
-
- protected function toAbbreviation($value, $length = 2, $word_delim = '_'): string|array {
- $value = trim((string) $value);
- $value = str_replace(' ', '_', $value);
- $parts = explode($word_delim, $value);
- if (count($parts) == 1) {
- return strlen($parts[0]) > $length ? substr($parts[0], 0, $length) : $value;
- }
-
- $value = implode('', array_map(static function ($word): string {
- return substr($word, 0, 1);
- }, $parts));
-
- return substr($value, 0, $length);
- }
-
- protected function executeCallback(string $prefix, $name) {
- $args = func_get_args();
- $args = array_slice($args, 2);
-
- $name = $this->snakeToPascal($name);
-
- $callback = [static::class, $prefix . $name];
- if (method_exists($callback[0], $callback[1])) {
- return call_user_func_array($callback, $args);
- }
-
- return NULL;
- }
-
- protected function snakeToPascal($string): string {
- return str_replace(' ', '', ucwords(str_replace('_', ' ', (string) $string)));
- }
-
- protected function getComposerJsonValue($name) {
- $composer_json = $this->getDstDir() . DIRECTORY_SEPARATOR . 'composer.json';
- if (is_readable($composer_json)) {
- $json = json_decode(file_get_contents($composer_json), TRUE);
- if (isset($json[$name])) {
- return $json[$name];
- }
- }
-
- return NULL;
- }
-
- protected function getStdinHandle() {
- global $_stdin_handle;
- if (!$_stdin_handle) {
- $h = fopen('php://stdin', 'r');
- $_stdin_handle = stream_isatty($h) || static::getenvOrDefault('VORTEX_INSTALLER_FORCE_TTY') ? $h : fopen('/dev/tty', 'r+');
- }
-
- return $_stdin_handle;
- }
-
- protected function closeStdinHandle() {
- $_stdin_handle = $this->getStdinHandle();
- fclose($_stdin_handle);
- }
-
- protected function out($text, $color = NULL, $new_line = TRUE) {
- $styles = [
- 'success' => "\033[0;32m%s\033[0m",
- 'error' => "\033[31;31m%s\033[0m",
- ];
-
- $format = '%s';
-
- if (isset($styles[$color]) && $this->getConfig('ANSI')) {
- $format = $styles[$color];
- }
-
- if ($new_line) {
- $format .= PHP_EOL;
- }
-
- printf($format, $text);
- }
-
- protected function debug($value, string $name = '') {
- print PHP_EOL;
- print trim($name . ' DEBUG START') . PHP_EOL;
- print print_r($value, TRUE) . PHP_EOL;
- print trim($name . ' DEBUG FINISH') . PHP_EOL;
- print PHP_EOL;
- }
-
}
diff --git a/.vortex/installer/src/Config.php b/.vortex/installer/src/Config.php
new file mode 100644
index 000000000..8e0034978
--- /dev/null
+++ b/.vortex/installer/src/Config.php
@@ -0,0 +1,58 @@
+
+ */
+ protected array $config = [];
+
+ /**
+ * Get a configuration value or default.
+ */
+ public function get(string $name, mixed $default = NULL): mixed {
+ return $this->config[$name] ?? $default;
+ }
+
+ /**
+ * Set a configuration value.
+ */
+ public function set(string $name, mixed $value): void {
+ if (!is_null($value)) {
+ $this->config[$name] = $value;
+ }
+ }
+
+ public function getDstDir(): ?string {
+ return $this->get('VORTEX_INSTALL_DST_DIR');
+ }
+
+ /**
+ * Shorthand to get the value of whether install should be quiet.
+ */
+ public function isQuiet(): bool {
+ return (bool) $this->get('quiet', FALSE);
+ }
+
+ /**
+ * Shorthand to get the value of VORTEX_INSTALL_DEBUG.
+ */
+ public function isInstallDebug(): bool {
+ return (bool) $this->get('VORTEX_INSTALL_DEBUG', FALSE);
+ }
+
+}
diff --git a/.vortex/installer/src/Converter.php b/.vortex/installer/src/Converter.php
new file mode 100644
index 000000000..1c12447d4
--- /dev/null
+++ b/.vortex/installer/src/Converter.php
@@ -0,0 +1,72 @@
+ $preserve_chars
+ * Array of characters to preserve.
+ *
+ * @return string
+ * Converted value.
+ */
+ public static function toMachineName(string $value, array $preserve_chars = []): string {
+ $preserve = '';
+ foreach ($preserve_chars as $char) {
+ $preserve .= preg_quote(strval($char), '/');
+ }
+ $pattern = '/[^a-zA-Z0-9' . $preserve . ']/';
+
+ $value = preg_replace($pattern, '_', $value);
+
+ return strtolower($value);
+ }
+
+ public static function toAbbreviation(string $value, int $length = 2, string $word_delim = '_'): string {
+ $value = trim($value);
+ $value = str_replace(' ', '_', $value);
+ $parts = empty($word_delim) ? [$value] : explode($word_delim, $value);
+
+ if (count($parts) == 1) {
+ return strlen($parts[0]) > $length ? substr($parts[0], 0, $length) : $value;
+ }
+
+ $value = implode('', array_map(static function (string $word): string {
+ return substr($word, 0, 1);
+ }, $parts));
+
+ return substr($value, 0, $length);
+ }
+
+ public static function snakeToPascal(string $string): string {
+ return str_replace(' ', '', ucwords(str_replace('_', ' ', $string)));
+ }
+
+}
diff --git a/.vortex/installer/src/File.php b/.vortex/installer/src/File.php
new file mode 100644
index 000000000..d9d0c9e98
--- /dev/null
+++ b/.vortex/installer/src/File.php
@@ -0,0 +1,509 @@
+ $ignore_paths
+ * Array of paths to ignore.
+ * @param bool $include_dirs
+ * Include directories in the result.
+ *
+ * @return array
+ * Array of discovered files.
+ */
+ public static function scandirRecursive(string $dir, array $ignore_paths = [], bool $include_dirs = FALSE): array {
+ $discovered = [];
+
+ if (is_dir($dir)) {
+ $files = scandir($dir);
+ if (empty($files)) {
+ return [];
+ }
+
+ $paths = array_diff($files, ['.', '..']);
+
+ foreach ($paths as $path) {
+ $path = $dir . '/' . $path;
+
+ foreach ($ignore_paths as $ignore_path) {
+ // Exlude based on sub-path match.
+ if (str_contains($path, (string) $ignore_path)) {
+ continue(2);
+ }
+ }
+
+ if (is_dir($path)) {
+ if ($include_dirs) {
+ $discovered[] = $path;
+ }
+ $discovered = array_merge($discovered, File::scandirRecursive($path, $ignore_paths, $include_dirs));
+ }
+ else {
+ $discovered[] = $path;
+ }
+ }
+ }
+
+ return $discovered;
+ }
+
+ /**
+ * Get list of paths to ignore.
+ *
+ * @return array
+ * Array of paths to ignore.
+ */
+ public static function ignorePaths(): array {
+ return array_merge([
+ '/.git/',
+ '/.idea/',
+ '/vendor/',
+ '/node_modules/',
+ '/.data/',
+ ], File::internalPaths());
+ }
+
+ /**
+ * Remove directory recursively.
+ *
+ * @param string $directory
+ * Directory to remove.
+ * @param array $options
+ * Options to pass.
+ */
+ public static function rmdirRecursive(string $directory, array $options = []): void {
+ if (!isset($options['traverseSymlinks'])) {
+ $options['traverseSymlinks'] = FALSE;
+ }
+
+ $files = glob($directory . DIRECTORY_SEPARATOR . '{,.}*', GLOB_MARK | GLOB_BRACE);
+ if (!empty($files)) {
+
+ foreach ($files as $file) {
+ if (basename($file) === '.' || basename($file) === '..') {
+ continue;
+ }
+
+ if (substr($file, -1) === DIRECTORY_SEPARATOR) {
+ if (!$options['traverseSymlinks'] && is_link(rtrim($file, DIRECTORY_SEPARATOR))) {
+ unlink(rtrim($file, DIRECTORY_SEPARATOR));
+ }
+ else {
+ File::rmdirRecursive($file, $options);
+ }
+ }
+ else {
+ unlink($file);
+ }
+ }
+ }
+
+ if (is_dir($directory = rtrim($directory, '\\/'))) {
+ if (is_link($directory)) {
+ unlink($directory);
+ }
+ else {
+ rmdir($directory);
+ }
+ }
+ }
+
+ public static function replaceStringFilename(string $search, string $replace, string $dir): void {
+ $files = File::scandirRecursive($dir, File::ignorePaths());
+
+ foreach ($files as $filename) {
+ $new_filename = str_replace($search, $replace, (string) $filename);
+
+ if ($filename != $new_filename) {
+ $new_dir = dirname($new_filename);
+
+ if (!is_dir($new_dir)) {
+ mkdir($new_dir, 0777, TRUE);
+ }
+
+ rename($filename, $new_filename);
+ }
+ }
+ }
+
+ public static function dirReplaceContent(string $needle, string $replacement, string $dir): void {
+ $files = File::scandirRecursive($dir, File::ignorePaths());
+ foreach ($files as $filename) {
+ File::fileReplaceContent($needle, $replacement, $filename);
+ }
+ }
+
+ /**
+ * Check if path is internal.
+ *
+ * @param string $path
+ * Path to check.
+ *
+ * @return bool
+ * TRUE if path is internal, FALSE otherwise.
+ */
+ public static function isInternalPath(string $path): bool {
+ $path = '/' . ltrim($path, './');
+
+ return in_array($path, File::internalPaths());
+ }
+
+ public static function fileReplaceContent(string $needle, string $replacement, string $filename): void {
+ if (!is_readable($filename) || File::fileIsExcludedFromProcessing($filename)) {
+ return;
+ }
+
+ $content = file_get_contents($filename);
+ if (!$content) {
+ return;
+ }
+
+ if (File::isRegex($needle)) {
+ $replaced = preg_replace($needle, $replacement, $content);
+ }
+ else {
+ $replaced = str_replace($needle, $replacement, $content);
+ }
+ if ($replaced != $content) {
+ file_put_contents($filename, $replaced);
+ }
+ }
+
+ public static function isRegex(string $str): bool {
+ if ($str === '' || strlen($str) < 3) {
+ return FALSE;
+ }
+
+ return @preg_match($str, '') !== FALSE;
+ }
+
+ /**
+ * Get list of internal paths.
+ *
+ * @return array
+ * Array of internal paths.
+ */
+ public static function internalPaths(): array {
+ return [
+ '/LICENSE',
+ '/CODE_OF_CONDUCT.md',
+ '/CONTRIBUTING.md',
+ '/LICENSE',
+ '/SECURITY.md',
+ '/.vortex/docs',
+ '/.vortex/tests',
+ ];
+ }
+
+ public static function copyRecursive(string $source, string $dest, int $permissions = 0755, bool $copy_empty_dirs = FALSE): bool {
+ $parent = dirname($dest);
+
+ if (!is_dir($parent)) {
+ mkdir($parent, $permissions, TRUE);
+ }
+
+ // Note that symlink target must exist.
+ if (is_link($source)) {
+ // Changing dir symlink will be relevant to the current destination's file
+ // directory.
+ $cur_dir = getcwd();
+
+ if (!$cur_dir) {
+ throw new \RuntimeException('Unable to determine current working directory.');
+ }
+
+ chdir($parent);
+ $ret = TRUE;
+
+ if (!is_readable(basename($dest))) {
+ $link = readlink($source);
+ if ($link) {
+ $ret = symlink($link, basename($dest));
+ }
+ }
+
+ chdir($cur_dir);
+
+ return $ret;
+ }
+
+ if (is_file($source)) {
+ $ret = copy($source, $dest);
+ if ($ret) {
+ $perms = fileperms($source);
+ if ($perms !== FALSE) {
+ chmod($dest, $perms);
+ }
+ }
+
+ return $ret;
+ }
+
+ if (!is_dir($dest) && $copy_empty_dirs) {
+ mkdir($dest, $permissions, TRUE);
+ }
+
+ $dir = dir($source);
+ while ($dir && FALSE !== $entry = $dir->read()) {
+ if ($entry == '.' || $entry == '..') {
+ continue;
+ }
+ File::copyRecursive(sprintf('%s/%s', $source, $entry), sprintf('%s/%s', $dest, $entry), $permissions, FALSE);
+ }
+
+ $dir && $dir->close();
+
+ return TRUE;
+ }
+
+ /**
+ * Check if file is excluded from processing.
+ *
+ * @param string $filename
+ * Filename to check.
+ *
+ * @return bool
+ * TRUE if file is excluded, FALSE otherwise.
+ */
+ public static function fileIsExcludedFromProcessing(string $filename): bool {
+ $excluded_patterns = [
+ '.+\.png',
+ '.+\.jpg',
+ '.+\.jpeg',
+ '.+\.bpm',
+ '.+\.tiff',
+ ];
+
+ return (bool) preg_match('/^(' . implode('|', $excluded_patterns) . ')$/', $filename);
+ }
+
+ public static function dirContains(string $needle, string $dir): bool {
+ $files = File::scandirRecursive($dir, File::ignorePaths());
+ foreach ($files as $filename) {
+ if (File::fileContains($needle, $filename)) {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+ }
+
+ public static function fileContains(string $needle, string $filename): bool {
+ if (!is_readable($filename)) {
+ return FALSE;
+ }
+
+ $content = file_get_contents($filename);
+ if (!$content) {
+ return FALSE;
+ }
+
+ if (File::isRegex($needle)) {
+ return (bool) preg_match($needle, $content);
+ }
+
+ return str_contains($content, $needle);
+ }
+
+ /**
+ * Remove directory recursively if empty.
+ *
+ * @param string $directory
+ * Directory to remove.
+ * @param array $options
+ * Options to pass.
+ */
+ public static function rmdirRecursiveEmpty(string $directory, array $options = []): void {
+ if (File::dirIsEmpty($directory)) {
+ File::rmdirRecursive($directory, $options);
+ File::rmdirRecursiveEmpty(dirname($directory), $options);
+ }
+ }
+
+ /**
+ * Check if directory is empty.
+ *
+ * @param string $directory
+ * Directory to check.
+ *
+ * @return bool
+ * TRUE if directory is empty, FALSE otherwise.
+ */
+ public static function dirIsEmpty(string $directory): bool {
+ return is_dir($directory) && count(scandir($directory) ?: []) === 2;
+ }
+
+ public static function createTempdir(?string $dir = NULL, string $prefix = 'tmp_', int $mode = 0700, int $max_attempts = 1000): string {
+ if (is_null($dir)) {
+ $dir = sys_get_temp_dir();
+ }
+
+ $dir = rtrim($dir, DIRECTORY_SEPARATOR);
+
+ if (!is_dir($dir) || !is_writable($dir)) {
+ throw new \RuntimeException(sprintf('Temporary directory "%s" does not exist or is not writable.', $dir));
+ }
+
+ if (strpbrk($prefix, '\\/:*?"<>|') !== FALSE) {
+ throw new \InvalidArgumentException('Invalid prefix.');
+ }
+ $attempts = 0;
+
+ do {
+ $path = sprintf('%s%s%s%s', $dir, DIRECTORY_SEPARATOR, $prefix, mt_rand(100000, mt_getrandmax()));
+ } while (!mkdir($path, $mode) && $attempts++ < $max_attempts);
+
+ if (!is_dir($path) || !is_writable($path)) {
+ throw new \RuntimeException(sprintf('Unable to create temporary directory "%s".', $path));
+ }
+
+ return $path;
+ }
+
+ public static function removeTokenFromFile(string $filename, string $token_begin, ?string $token_end = NULL, bool $with_content = FALSE): void {
+ if (File::fileIsExcludedFromProcessing($filename)) {
+ return;
+ }
+
+ $token_end = $token_end ?? $token_begin;
+
+ $content = file_get_contents($filename);
+ if (!$content) {
+ return;
+ }
+
+ if ($token_begin !== $token_end) {
+ $token_begin_count = preg_match_all('/' . preg_quote($token_begin) . '/', $content);
+ $token_end_count = preg_match_all('/' . preg_quote($token_end) . '/', $content);
+ if ($token_begin_count !== $token_end_count) {
+ throw new \RuntimeException(sprintf('Invalid begin and end token count in file %s: begin is %s(%s), end is %s(%s).', $filename, $token_begin, $token_begin_count, $token_end, $token_end_count));
+ }
+ }
+
+ $out = [];
+ $within_token = FALSE;
+
+ $lines = file($filename);
+ if (!$lines) {
+ return;
+ }
+
+ foreach ($lines as $line) {
+ if (str_contains($line, $token_begin)) {
+ if ($with_content) {
+ $within_token = TRUE;
+ }
+ continue;
+ }
+ elseif (str_contains($line, $token_end)) {
+ if ($with_content) {
+ $within_token = FALSE;
+ }
+ continue;
+ }
+
+ if ($with_content && $within_token) {
+ // Skip content as contents of the token.
+ continue;
+ }
+
+ $out[] = $line;
+ }
+
+ file_put_contents($filename, implode('', $out));
+ }
+
+ /**
+ * Find a matching path using glob.
+ *
+ * @param array|string $paths
+ * Array of paths wildcards to search.
+ * @param string|null $text
+ * Optional text to search in the files.
+ *
+ * @return string|null
+ * Path to the file or NULL if not found.
+ */
+ public static function findMatchingPath(array|string $paths, ?string $text = NULL): ?string {
+ $paths = is_array($paths) ? $paths : [$paths];
+
+ foreach ($paths as $path) {
+ $files = glob($path);
+
+ if (empty($files)) {
+ continue;
+ }
+
+ if (!empty($text)) {
+ foreach ($files as $file) {
+ if (File::fileContains($text, $file)) {
+ return $file;
+ }
+ }
+ }
+ else {
+ return reset($files);
+ }
+ }
+
+ return NULL;
+ }
+
+ /**
+ * Recursively scan directory for files.
+ *
+ * @param string $pattern
+ * Pattern to search.
+ * @param int $flags
+ * Flags to pass to glob.
+ *
+ * @return array
+ * Array of discovered files.
+ */
+ public static function globRecursive(string $pattern, int $flags = 0): array {
+ $files = glob($pattern, $flags | GLOB_BRACE);
+
+ if ($files) {
+ $dirs = glob(dirname($pattern) . '/{,.}*[!.]', GLOB_BRACE | GLOB_ONLYDIR | GLOB_NOSORT);
+ if ($dirs) {
+ foreach ($dirs as $dir) {
+ $files = array_merge($files, File::globRecursive($dir . '/' . basename($pattern), $flags));
+ }
+ }
+ }
+
+ return $files ?: [];
+ }
+
+ public static function removeTokenWithContent(string $token, string $dir): void {
+ $files = File::scandirRecursive($dir, File::ignorePaths());
+ foreach ($files as $filename) {
+ File::removeTokenFromFile($filename, '#;< ' . $token, '#;> ' . $token, TRUE);
+ }
+ }
+
+ public static function removeTokenLine(string $token, string $dir): void {
+ if (!empty($token)) {
+ $files = File::scandirRecursive($dir, File::ignorePaths());
+ foreach ($files as $filename) {
+ File::removeTokenFromFile($filename, $token, NULL);
+ }
+ }
+ }
+
+}
diff --git a/.vortex/installer/src/Traits/DownloadTrait.php b/.vortex/installer/src/Traits/DownloadTrait.php
new file mode 100644
index 000000000..604c816f6
--- /dev/null
+++ b/.vortex/installer/src/Traits/DownloadTrait.php
@@ -0,0 +1,80 @@
+config->get('VORTEX_INSTALL_TMP_DIR');
+ $repo = $this->config->get('VORTEX_INSTALL_LOCAL_REPO');
+ $ref = $this->config->get('VORTEX_INSTALL_COMMIT');
+
+ $this->status(sprintf('Downloading Vortex from the local repository "%s" at ref "%s".', $repo, $ref), self::INSTALLER_STATUS_MESSAGE, FALSE);
+
+ $command = sprintf('git --git-dir="%s/.git" --work-tree="%s" archive --format=tar "%s" | tar xf - -C "%s"', $repo, $repo, $ref, $dst);
+ $this->doExec($command, $output, $code);
+
+ $this->status(implode(PHP_EOL, $output), self::INSTALLER_STATUS_DEBUG);
+
+ if ($code != 0) {
+ throw new \RuntimeException(implode(PHP_EOL, $output));
+ }
+
+ $this->status(sprintf('Downloaded to "%s".', $dst), self::INSTALLER_STATUS_DEBUG);
+
+ print ' ';
+ $this->status('Done', self::INSTALLER_STATUS_SUCCESS);
+ }
+
+ protected function downloadScaffoldRemote(): void {
+ $dst = $this->config->get('VORTEX_INSTALL_TMP_DIR');
+ $org = 'drevops';
+ $project = 'vortex';
+ $ref = $this->config->get('VORTEX_INSTALL_COMMIT');
+ $release_prefix = $this->config->get('VORTEX_VERSION');
+
+ if ($ref == 'HEAD') {
+ $release_prefix = $release_prefix == 'develop' ? NULL : $release_prefix;
+ $ref = $this->findLatestVortexRelease($org, $project, $release_prefix);
+ $this->config->set('VORTEX_VERSION', $ref);
+ }
+
+ $url = sprintf('https://github.com/%s/%s/archive/%s.tar.gz', $org, $project, $ref);
+ $this->status(sprintf('Downloading Vortex from the remote repository "%s" at ref "%s".', $url, $ref), self::INSTALLER_STATUS_MESSAGE, FALSE);
+ $this->doExec(sprintf('curl -sS -L "%s" | tar xzf - -C "%s" --strip 1', $url, $dst), $output, $code);
+
+ if ($code != 0) {
+ throw new \RuntimeException(implode(PHP_EOL, $output));
+ }
+
+ $this->status(sprintf('Downloaded to "%s".', $dst), self::INSTALLER_STATUS_DEBUG);
+
+ $this->status('Done', self::INSTALLER_STATUS_SUCCESS);
+ }
+
+ protected function findLatestVortexRelease(string $org, string $project, string $release_prefix): ?string {
+ $release_url = sprintf('https://api.github.com/repos/%s/%s/releases', $org, $project);
+ $release_contents = file_get_contents($release_url, FALSE, stream_context_create([
+ 'http' => ['method' => 'GET', 'header' => ['User-Agent: PHP']],
+ ]));
+
+ if (!$release_contents) {
+ throw new \RuntimeException(sprintf('Unable to download release information from "%s".', $release_url));
+ }
+
+ $records = json_decode($release_contents, TRUE);
+ foreach ($records as $record) {
+ if (isset($record['tag_name']) && ($release_prefix && str_contains((string) $record['tag_name'], $release_prefix) || !$release_prefix)) {
+ return $record['tag_name'];
+ }
+ }
+
+ return NULL;
+ }
+
+}
diff --git a/.vortex/installer/src/Traits/EnvTrait.php b/.vortex/installer/src/Traits/EnvTrait.php
new file mode 100644
index 000000000..f70ca5c4b
--- /dev/null
+++ b/.vortex/installer/src/Traits/EnvTrait.php
@@ -0,0 +1,97 @@
+config->getDstDir() . '/.env';
+ if (!is_readable($file)) {
+ return $default;
+ }
+
+ $parsed = static::parseDotenv($file);
+
+ return $parsed !== [] ? $parsed[$name] ?? $default : $default;
+ }
+
+ /**
+ * Parse .env file.
+ *
+ * @param string $filename
+ * Filename to parse.
+ *
+ * @return array
+ * Array of parsed values, key is the variable name.
+ */
+ protected static function parseDotenv(string $filename = '.env'): array {
+ if (!is_readable($filename)) {
+ return [];
+ }
+
+ $contents = file_get_contents($filename);
+ if ($contents === FALSE) {
+ return [];
+ }
+
+ // Replace all # not inside quotes.
+ $contents = preg_replace('/#(?=(?:(?:[^"]*"){2})*[^"]*$)/', ';', $contents);
+
+ return parse_ini_string($contents) ?: [];
+ }
+
+ /**
+ * Load .env file.
+ *
+ * @param string $filename
+ * Filename to load.
+ * @param bool $override_existing
+ * Override existing values.
+ */
+ protected static function loadDotenv(string $filename = '.env', bool $override_existing = FALSE): void {
+ $values = static::parseDotenv($filename);
+
+ foreach ($values as $var => $value) {
+ if (!static::getenvOrDefault($var) || $override_existing) {
+ putenv($var . '=' . $value);
+ }
+ }
+
+ $GLOBALS['_ENV'] = $GLOBALS['_ENV'] ?? [];
+ $GLOBALS['_SERVER'] = $GLOBALS['_SERVER'] ?? [];
+
+ if ($override_existing) {
+ $GLOBALS['_ENV'] = $values + $GLOBALS['_ENV'];
+ $GLOBALS['_SERVER'] = $values + $GLOBALS['_SERVER'];
+ }
+ else {
+ $GLOBALS['_ENV'] += $values;
+ $GLOBALS['_SERVER'] += $values;
+ }
+ }
+
+ /**
+ * Reliable wrapper to work with environment values.
+ */
+ protected static function getenvOrDefault(string $name, mixed $default = NULL): mixed {
+ $vars = getenv();
+
+ if (!isset($vars[$name]) || $vars[$name] === '') {
+ return $default;
+ }
+
+ return $vars[$name];
+ }
+
+}
diff --git a/.vortex/installer/src/Traits/FilesystemTrait.php b/.vortex/installer/src/Traits/FilesystemTrait.php
new file mode 100644
index 000000000..0d5d74ca7
--- /dev/null
+++ b/.vortex/installer/src/Traits/FilesystemTrait.php
@@ -0,0 +1,239 @@
+
+ */
+ protected array $fsOriginalCwdStack = [];
+
+ /**
+ * Set root directory path.
+ *
+ * @param string|null $path
+ * The path of the root directory.
+ *
+ * @return static
+ * The called object.
+ */
+ protected function fsSetRootDir(?string $path = NULL): static {
+ $path = empty($path) ? $this->fsGetRootDir() : $this->fsGetAbsolutePath($path);
+ $this->fsAssertPathsExist($path);
+ $this->fsRootDir = $path;
+
+ return $this;
+ }
+
+ /**
+ * Get root directory.
+ *
+ * @return string
+ * Get value of the root directory, the directory where the
+ * script was started from or current working directory.
+ */
+ protected function fsGetRootDir(): string {
+ if (isset($this->fsRootDir)) {
+ return $this->fsRootDir;
+ }
+
+ if (isset($_SERVER['PWD'])) {
+ return $_SERVER['PWD'];
+ }
+
+ return (string) getcwd();
+ }
+
+ /**
+ * Set current working directory.
+ *
+ * It is important to note that this should be called in pair with
+ * cwdRestore().
+ *
+ * @param string $dir
+ * Path to the current directory.
+ *
+ * @return static
+ * The called object.
+ */
+ protected function fsSetCwd(string $dir): static {
+ chdir($dir);
+ $this->fsOriginalCwdStack[] = $dir;
+
+ return $this;
+ }
+
+ /**
+ * Set current working directory to a previously saved path.
+ *
+ * It is important to note that this should be called in pair with cwdSet().
+ */
+ protected function fsCwdRestore(): void {
+ $dir = array_shift($this->fsOriginalCwdStack);
+ if ($dir) {
+ chdir($dir);
+ }
+ }
+
+ /**
+ * Get current working directory.
+ *
+ * @return string
+ * Full path of current working directory.
+ */
+ protected function fsCwdGet(): string {
+ return (string) getcwd();
+ }
+
+ /**
+ * Get absolute path for provided file.
+ *
+ * @param string $file
+ * File to resolve. If absolute, no resolution will be performed.
+ * @param string|null $root
+ * Optional path to root dir. If not provided, internal root path is used.
+ *
+ * @return string
+ * Absolute path for provided file.
+ */
+ protected function fsGetAbsolutePath(string $file, ?string $root = NULL): string {
+ if ($this->fs->isAbsolutePath($file)) {
+ return $this->fsRealpath($file);
+ }
+
+ $root = $root ? $root : $this->fsGetRootDir();
+ $root = $this->fsRealpath($root);
+ $file = $root . DIRECTORY_SEPARATOR . $file;
+
+ return $this->fsRealpath($file);
+ }
+
+ /**
+ * Check that path exists.
+ *
+ * @param string|array $paths
+ * File name or array of file names to check.
+ * @param bool $strict
+ * If TRUE and the file does not exist, an exception will be thrown.
+ * Defaults to TRUE.
+ *
+ * @return bool
+ * TRUE if file exists and FALSE if not, but only if $strict is FALSE.
+ *
+ * @throws \Exception
+ * If at least one file does not exist.
+ */
+ protected function fsAssertPathsExist($paths, bool $strict = TRUE): bool {
+ $paths = is_array($paths) ? $paths : [$paths];
+
+ if (!$this->fs->exists($paths)) {
+ if ($strict) {
+ throw new \Exception(sprintf('One of the files or directories does not exist: %s', implode(', ', $paths)));
+ }
+
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ /**
+ * Replacement for PHP's `realpath` resolves non-existing paths.
+ *
+ * The main deference is that it does not return FALSE on non-existing
+ * paths.
+ *
+ * @param string $path
+ * Path that needs to be resolved.
+ *
+ * @return string
+ * Resolved path.
+ *
+ * @see https://stackoverflow.com/a/29372360/712666
+ *
+ * @SuppressWarnings(PHPMD.CyclomaticComplexity)
+ * @SuppressWarnings(PHPMD.NPathComplexity)
+ */
+ protected function fsRealpath(string $path): string {
+ // Whether $path is unix or not.
+ $unipath = $path === '' || $path[0] !== '/';
+ $unc = str_starts_with($path, '\\\\');
+
+ // Attempt to detect if path is relative in which case, add cwd.
+ if (!str_contains($path, ':') && $unipath && !$unc) {
+ $path = getcwd() . DIRECTORY_SEPARATOR . $path;
+ if ($path[0] === '/') {
+ $unipath = FALSE;
+ }
+ }
+
+ // Resolve path parts (single dot, double dot and double delimiters).
+ $path = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $path);
+ $parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), static function ($part): bool {
+ return strlen($part) > 0;
+ });
+
+ $absolutes = [];
+ foreach ($parts as $part) {
+ if ('.' === $part) {
+ continue;
+ }
+ if ('..' === $part) {
+ array_pop($absolutes);
+ }
+ else {
+ $absolutes[] = $part;
+ }
+ }
+
+ $path = implode(DIRECTORY_SEPARATOR, $absolutes);
+
+ // Resolve any symlinks.
+ if (function_exists('readlink') && file_exists($path) && linkinfo($path) > 0) {
+ $path = readlink($path);
+
+ if (!$path) {
+ throw new \Exception(sprintf('Could not resolve symlink for path: %s', $path));
+ }
+ }
+
+ // Put initial separator that could have been lost.
+ $path = $unipath ? $path : '/' . $path;
+
+ $path = $unc ? '\\\\' . $path : $path;
+
+ if (str_starts_with($path, sys_get_temp_dir())) {
+ $tmp_realpath = realpath(sys_get_temp_dir());
+ if ($tmp_realpath) {
+ $path = str_replace(sys_get_temp_dir(), $tmp_realpath, $path);
+ }
+ }
+
+ return $path;
+ }
+
+}
diff --git a/.vortex/installer/src/Traits/GitTrait.php b/.vortex/installer/src/Traits/GitTrait.php
new file mode 100644
index 000000000..fda6c5a9c
--- /dev/null
+++ b/.vortex/installer/src/Traits/GitTrait.php
@@ -0,0 +1,29 @@
+doExec(sprintf('git ls-files --error-unmatch "%s" 2>&1 >/dev/null', $path), $output, $code);
+ chdir($cwd);
+
+ return $code === 0;
+ }
+
+ return FALSE;
+ }
+
+}
diff --git a/.vortex/installer/src/Traits/PrinterTrait.php b/.vortex/installer/src/Traits/PrinterTrait.php
new file mode 100644
index 000000000..9de6a4a25
--- /dev/null
+++ b/.vortex/installer/src/Traits/PrinterTrait.php
@@ -0,0 +1,221 @@
+ "\033[0;32m%s\033[0m",
+ 'error' => "\033[31;31m%s\033[0m",
+ ];
+
+ $format = '%s';
+
+ if (isset($styles[$color]) && $this->config->get('ANSI')) {
+ $format = $styles[$color];
+ }
+
+ if ($new_line) {
+ $format .= PHP_EOL;
+ }
+
+ printf($format, $text);
+ }
+
+ protected function debug(mixed $value, string $name = ''): void {
+ print PHP_EOL;
+ print trim($name . ' DEBUG START') . PHP_EOL;
+ print print_r($value, TRUE) . PHP_EOL;
+ print trim($name . ' DEBUG FINISH') . PHP_EOL;
+ print PHP_EOL;
+ }
+
+ protected function printTitle(string $text, string $fill = '-', int $width = 80, string $cols_delim = '|', bool $has_content = FALSE): void {
+ $this->printDivider($fill, $width, 'down');
+ $lines = explode(PHP_EOL, wordwrap($text, $width - 4, PHP_EOL));
+ foreach ($lines as $line) {
+ $line = ' ' . $line . ' ';
+ print $cols_delim . str_pad($line, $width - 2, ' ', STR_PAD_BOTH) . $cols_delim . PHP_EOL;
+ }
+ $this->printDivider($fill, $width, $has_content ? 'up' : 'both');
+ }
+
+ protected function printSubtitle(string $text, string $fill = '=', int $width = 80): void {
+ $is_multiline = strlen($text) + 4 >= $width;
+ if ($is_multiline) {
+ $this->printTitle($text, $fill, $width, 'both');
+ }
+ else {
+ $text = ' ' . $text . ' ';
+ print str_pad($text, $width, $fill, STR_PAD_BOTH) . PHP_EOL;
+ }
+ }
+
+ protected function printDivider(string $fill = '-', int $width = 80, string $direction = 'none'): void {
+ $start = $fill;
+ $finish = $fill;
+ switch ($direction) {
+ case 'up':
+ $start = '╰';
+ $finish = '╯';
+ break;
+
+ case 'down':
+ $start = '╭';
+ $finish = '╮';
+ break;
+
+ case 'both':
+ $start = '├';
+ $finish = '┤';
+ break;
+ }
+
+ print $start . str_repeat($fill, $width - 2) . $finish . PHP_EOL;
+ }
+
+ protected function printBox(string $content, string $title = '', string $fill = '─', int $padding = 2, int $width = 80): void {
+ $cols = '│';
+
+ $max_width = $width - 2 - $padding * 2;
+ $lines = explode(PHP_EOL, wordwrap(rtrim($content, PHP_EOL), $max_width, PHP_EOL));
+ $pad = str_pad(' ', $padding);
+ $mask = sprintf('%s%s%%-%ss%s%s', $cols, $pad, $max_width, $pad, $cols) . PHP_EOL;
+
+ print PHP_EOL;
+ if (!empty($title)) {
+ $this->printTitle($title, $fill, $width);
+ }
+ else {
+ $this->printDivider($fill, $width, 'down');
+ }
+
+ array_unshift($lines, '');
+ $lines[] = '';
+ foreach ($lines as $line) {
+ printf($mask, $line);
+ }
+
+ $this->printDivider($fill, $width, 'up');
+ print PHP_EOL;
+ }
+
+ protected function printTick(?string $text = NULL): void {
+ if (!empty($text) && $this->config->isInstallDebug()) {
+ print PHP_EOL;
+ $this->status($text, self::INSTALLER_STATUS_DEBUG, FALSE);
+ }
+ else {
+ $this->status('.', self::INSTALLER_STATUS_MESSAGE, FALSE, FALSE);
+ }
+ }
+
+ protected function status(string $message, int $level = self::INSTALLER_STATUS_MESSAGE, bool $use_eol = TRUE, bool $use_prefix = TRUE): void {
+ $prefix = '';
+ $color = NULL;
+
+ switch ($level) {
+ case self::INSTALLER_STATUS_SUCCESS:
+ $prefix = '✓️';
+ $color = 'success';
+ break;
+
+ case self::INSTALLER_STATUS_ERROR:
+ $prefix = '✗';
+ $color = 'error';
+ break;
+
+ case self::INSTALLER_STATUS_MESSAGE:
+ $prefix = 'i️';
+ $color = 'info';
+ break;
+
+ case self::INSTALLER_STATUS_DEBUG:
+ $prefix = ' [D]';
+ break;
+ }
+
+ if ($level != self::INSTALLER_STATUS_DEBUG || $this->config->isInstallDebug()) {
+ $this->out(($use_prefix ? $prefix . ' ' : '') . $message, $color, $use_eol);
+ }
+ }
+
+ /**
+ * Format values list.
+ *
+ * @param array $values
+ * Array of values to format.
+ * @param string $delim
+ * Delimiter to use.
+ * @param int $width
+ * Width of the line.
+ *
+ * @return string
+ * Formatted values list.
+ */
+ protected function formatValuesList(array $values, string $delim = '', int $width = 80): string {
+ // Only keep the keys that are not numeric.
+ $keys = array_filter(array_keys($values), static fn($key): bool => !is_numeric($key));
+
+ // Line width - length of delimiters * 2 - 2 spacers.
+ $line_width = $width - strlen($delim) * 2 - 2;
+
+ // Max name length + spaced on the sides + colon.
+ $max_name_width = max(array_map(static fn(string $key): int => strlen($key), $keys)) + 2 + 1;
+
+ // Whole width - (name width + 2 delimiters on the sides + 1 delimiter in
+ // the middle + 2 spaces on the sides + 2 spaces for the center delimiter).
+ $value_width = max($width - ($max_name_width + strlen($delim) * 2 + strlen($delim) + 2 + 2), 1);
+
+ $mask1 = sprintf('%s %%%ds %s %%-%s.%ss %s', $delim, $max_name_width, $delim, $value_width, $value_width, $delim) . PHP_EOL;
+ $mask2 = sprintf('%s%%2$%ss%s', $delim, $line_width, $delim) . PHP_EOL;
+
+ $output = [];
+ foreach ($values as $name => $value) {
+ $is_multiline_value = strlen((string) $value) > $value_width;
+
+ if (is_numeric($name)) {
+ $name = '';
+ $mask = $mask2;
+ $is_multiline_value = FALSE;
+ }
+ else {
+ $name .= ':';
+ $mask = $mask1;
+ }
+
+ if ($is_multiline_value) {
+ $lines = array_filter(explode(PHP_EOL, chunk_split(strval($value), $value_width, PHP_EOL)));
+ $first_line = array_shift($lines);
+ $output[] = sprintf($mask, $name, $first_line);
+ foreach ($lines as $line) {
+ $output[] = sprintf($mask, '', $line);
+ }
+ }
+ else {
+ $output[] = sprintf($mask, $name, $value);
+ }
+ }
+
+ return implode('', $output);
+ }
+
+ protected function formatEnabled(mixed $value): string {
+ return $value && strtolower((string) $value) !== 'n' ? 'Enabled' : 'Disabled';
+ }
+
+ protected function formatYesNo(string $value): string {
+ return $value === self::ANSWER_YES ? 'Yes' : 'No';
+ }
+
+ protected function formatNotEmpty(mixed $value, mixed $default): mixed {
+ return empty($value) ? $default : $value;
+ }
+
+}
diff --git a/.vortex/installer/src/Traits/PromptsTrait.php b/.vortex/installer/src/Traits/PromptsTrait.php
new file mode 100644
index 000000000..108a80321
--- /dev/null
+++ b/.vortex/installer/src/Traits/PromptsTrait.php
@@ -0,0 +1,977 @@
+config->get($config_name, $this->executeCallback('getDefaultValue', $name));
+ }
+
+ protected function getDefaultValueName(): ?string {
+ return Converter::toHumanName(static::getenvOrDefault('VORTEX_PROJECT', basename((string) $this->config->getDstDir())));
+ }
+
+ protected function getDefaultValueMachineName(): ?string {
+ return Converter::toMachineName($this->getAnswer('name', 'your_site'));
+ }
+
+ protected function getDefaultValueOrg(): string {
+ return $this->getAnswer('name', 'Your Site') . ' Org';
+ }
+
+ protected function getDefaultValueOrgMachineName(): string {
+ return Converter::toMachineName($this->getAnswer('org'));
+ }
+
+ protected function getDefaultValueModulePrefix(): string {
+ return Converter::toAbbreviation($this->getAnswer('machine_name'));
+ }
+
+ protected function getDefaultValueProfile(): string {
+ return self::ANSWER_NO;
+ }
+
+ protected function getDefaultValueTheme(): mixed {
+ return $this->getAnswer('machine_name');
+ }
+
+ protected function getDefaultValueUrl(): string {
+ $value = $this->getAnswer('machine_name');
+ $value = str_replace('_', '-', $value);
+
+ return $value . '.com';
+ }
+
+ protected function getDefaultValueWebroot(): string {
+ return 'web';
+ }
+
+ protected function getDefaultValueProvisionUseProfile(): string {
+ return self::ANSWER_NO;
+ }
+
+ protected function getDefaultValueDatabaseDownloadSource(): string {
+ return 'curl';
+ }
+
+ protected function getDefaultValueDatabaseStoreType(): string {
+ return 'file';
+ }
+
+ protected function getDefaultValueDatabaseImage(): string {
+ return 'drevops/mariadb-drupal-data:latest';
+ }
+
+ protected function getDefaultValueOverrideExistingDb(): string {
+ return self::ANSWER_NO;
+ }
+
+ protected function getDefaultValueCiProvider(): string {
+ return 'GitHub Actions';
+ }
+
+ protected function getDefaultValueDeployType(): string {
+ return 'artifact';
+ }
+
+ protected function getDefaultValuePreserveAcquia(): string {
+ return self::ANSWER_NO;
+ }
+
+ protected function getDefaultValuePreserveLagoon(): string {
+ return self::ANSWER_NO;
+ }
+
+ protected function getDefaultValuePreserveFtp(): string {
+ return self::ANSWER_NO;
+ }
+
+ protected function getDefaultValuePreserveRenovatebot(): string {
+ return self::ANSWER_YES;
+ }
+
+ protected function getDefaultValuePreserveDocComments(): string {
+ return self::ANSWER_YES;
+ }
+
+ protected function getDefaultValuePreserveVortexInfo(): string {
+ return self::ANSWER_NO;
+ }
+
+ protected function processProfile(string $dir): void {
+ $webroot = $this->getAnswer('webroot');
+ // For core profiles - remove custom profile and direct links to it.
+ if (in_array($this->getAnswer('profile'), $this->drupalCoreProfiles())) {
+ File::rmdirRecursive(sprintf('%s/%s/profiles/your_site_profile', $dir, $webroot));
+ File::rmdirRecursive(sprintf('%s/%s/profiles/custom/your_site_profile', $dir, $webroot));
+ File::dirReplaceContent($webroot . '/profiles/your_site_profile,', '', $dir);
+ File::dirReplaceContent($webroot . '/profiles/custom/your_site_profile,', '', $dir);
+ }
+ File::dirReplaceContent('your_site_profile', $this->getAnswer('profile'), $dir);
+ }
+
+ /**
+ * Get core profiles names.
+ *
+ * @return array
+ * Array of core profiles names.
+ */
+ protected function drupalCoreProfiles(): array {
+ return [
+ 'standard',
+ 'minimal',
+ 'testing',
+ 'demo_umami',
+ ];
+ }
+
+ protected function processProvisionUseProfile(string $dir): void {
+ if ($this->getAnswer('provision_use_profile') === self::ANSWER_YES) {
+ File::fileReplaceContent('/VORTEX_PROVISION_USE_PROFILE=.*/', "VORTEX_PROVISION_USE_PROFILE=1", $dir . '/.env');
+ File::removeTokenWithContent('!PROVISION_USE_PROFILE', $dir);
+ }
+ else {
+ File::fileReplaceContent('/VORTEX_PROVISION_USE_PROFILE=.*/', "VORTEX_PROVISION_USE_PROFILE=0", $dir . '/.env');
+ File::removeTokenWithContent('PROVISION_USE_PROFILE', $dir);
+ }
+ }
+
+ protected function processDatabaseDownloadSource(string $dir): void {
+ $type = $this->getAnswer('database_download_source');
+ File::fileReplaceContent('/VORTEX_DB_DOWNLOAD_SOURCE=.*/', 'VORTEX_DB_DOWNLOAD_SOURCE=' . $type, $dir . '/.env');
+
+ $types = [
+ 'curl',
+ 'ftp',
+ 'acquia',
+ 'lagoon',
+ 'container_registry',
+ 'none',
+ ];
+
+ foreach ($types as $t) {
+ $token = 'VORTEX_DB_DOWNLOAD_SOURCE_' . strtoupper($t);
+ if ($t === $type) {
+ File::removeTokenWithContent('!' . $token, $dir);
+ }
+ else {
+ File::removeTokenWithContent($token, $dir);
+ }
+ }
+ }
+
+ protected function processDatabaseImage(string $dir): void {
+ $image = $this->getAnswer('database_image');
+ File::fileReplaceContent('/VORTEX_DB_IMAGE=.*/', 'VORTEX_DB_IMAGE=' . $image, $dir . '/.env');
+
+ if ($image !== '' && $image !== '0') {
+ File::removeTokenWithContent('!VORTEX_DB_IMAGE', $dir);
+ }
+ else {
+ File::removeTokenWithContent('VORTEX_DB_IMAGE', $dir);
+ }
+ }
+
+ protected function processOverrideExistingDb(string $dir): void {
+ if ($this->getAnswer('override_existing_db') === self::ANSWER_YES) {
+ File::fileReplaceContent('/VORTEX_PROVISION_OVERRIDE_DB=.*/', "VORTEX_PROVISION_OVERRIDE_DB=1", $dir . '/.env');
+ }
+ else {
+ File::fileReplaceContent('/VORTEX_PROVISION_OVERRIDE_DB=.*/', "VORTEX_PROVISION_OVERRIDE_DB=0", $dir . '/.env');
+ }
+ }
+
+ protected function processCiProvider(string $dir): void {
+ $type = $this->getAnswer('ci_provider');
+
+ $remove_gha = FALSE;
+ $remove_circleci = FALSE;
+
+ switch ($type) {
+ case 'CircleCI':
+ $remove_gha = TRUE;
+ break;
+
+ case 'GitHub Actions':
+ $remove_circleci = TRUE;
+ break;
+
+ default:
+ $remove_circleci = TRUE;
+ $remove_gha = TRUE;
+ }
+
+ if ($remove_gha) {
+ @unlink($dir . '/.github/workflows/build-test-deploy.yml');
+ File::removeTokenWithContent('CI_PROVIDER_GHA', $dir);
+ }
+
+ if ($remove_circleci) {
+ File::rmdirRecursive($dir . '/.circleci');
+ @unlink($dir . '/tests/phpunit/CircleCiConfigTest.php');
+ File::removeTokenWithContent('CI_PROVIDER_CIRCLECI', $dir);
+ }
+
+ if ($remove_gha && $remove_circleci) {
+ @unlink($dir . '/docs/ci.md');
+ File::removeTokenWithContent('CI_PROVIDER_ANY', $dir);
+ }
+ else {
+ File::removeTokenWithContent('!CI_PROVIDER_ANY', $dir);
+ }
+ }
+
+ protected function processDeployType(string $dir): void {
+ $type = $this->getAnswer('deploy_type');
+ if ($type !== 'none') {
+ File::fileReplaceContent('/VORTEX_DEPLOY_TYPES=.*/', 'VORTEX_DEPLOY_TYPES=' . $type, $dir . '/.env');
+
+ if (!str_contains($type, 'artifact')) {
+ @unlink($dir . '/.gitignore.deployment');
+ @unlink($dir . '/.gitignore.artifact');
+ }
+
+ File::removeTokenWithContent('!DEPLOYMENT', $dir);
+ }
+ else {
+ @unlink($dir . '/docs/deployment.md');
+ @unlink($dir . '/.gitignore.deployment');
+ @unlink($dir . '/.gitignore.artifact');
+ File::removeTokenWithContent('DEPLOYMENT', $dir);
+ }
+ }
+
+ protected function processPreserveAcquia(string $dir): void {
+ if ($this->getAnswer('preserve_acquia') === self::ANSWER_YES) {
+ File::removeTokenWithContent('!ACQUIA', $dir);
+ }
+ else {
+ File::rmdirRecursive($dir . '/hooks');
+ $webroot = $this->getAnswer('webroot');
+ @unlink(sprintf('%s/%s/sites/default/includes/providers/settings.acquia.php', $dir, $webroot));
+ File::removeTokenWithContent('ACQUIA', $dir);
+ }
+ }
+
+ protected function processPreserveLagoon(string $dir): void {
+ if ($this->getAnswer('preserve_lagoon') === self::ANSWER_YES) {
+ File::removeTokenWithContent('!LAGOON', $dir);
+ }
+ else {
+ @unlink($dir . '/drush/sites/lagoon.site.yml');
+ @unlink($dir . '/.lagoon.yml');
+ @unlink($dir . '/.github/workflows/close-pull-request.yml');
+ $webroot = $this->getAnswer('webroot');
+ @unlink(sprintf('%s/%s/sites/default/includes/providers/settings.lagoon.php', $dir, $webroot));
+ File::removeTokenWithContent('LAGOON', $dir);
+ }
+ }
+
+ protected function processPreserveFtp(string $dir): void {
+ if ($this->getAnswer('preserve_ftp') === self::ANSWER_YES) {
+ File::removeTokenWithContent('!FTP', $dir);
+ }
+ else {
+ File::removeTokenWithContent('FTP', $dir);
+ }
+ }
+
+ protected function processPreserveRenovatebot(string $dir): void {
+ if ($this->getAnswer('preserve_renovatebot') === self::ANSWER_YES) {
+ File::removeTokenWithContent('!RENOVATEBOT', $dir);
+ }
+ else {
+ @unlink($dir . '/renovate.json');
+ File::removeTokenWithContent('RENOVATEBOT', $dir);
+ }
+ }
+
+ protected function processDemoMode(string $dir): void {
+ // Only discover demo mode if not explicitly set.
+ if (is_null($this->config->get('VORTEX_INSTALL_DEMO'))) {
+ if ($this->getAnswer('provision_use_profile') === self::ANSWER_NO) {
+ $download_source = $this->getAnswer('database_download_source');
+ $db_file = static::getenvOrDefault('VORTEX_DB_DIR', './.data') . DIRECTORY_SEPARATOR . static::getenvOrDefault('VORTEX_DB_FILE', 'db.sql');
+ $has_comment = File::fileContains('to allow to demonstrate how Vortex works without', $this->config->getDstDir() . '/.env');
+
+ // Enable Vortex demo mode if download source is file AND
+ // there is no downloaded file present OR if there is a demo comment in
+ // destination .env file.
+ if ($download_source !== 'container_registry') {
+ if ($has_comment || !file_exists($db_file)) {
+ $this->config->set('VORTEX_INSTALL_DEMO', TRUE);
+ }
+ else {
+ $this->config->set('VORTEX_INSTALL_DEMO', FALSE);
+ }
+ }
+ elseif ($has_comment) {
+ $this->config->set('VORTEX_INSTALL_DEMO', TRUE);
+ }
+ else {
+ $this->config->set('VORTEX_INSTALL_DEMO', FALSE);
+ }
+ }
+ else {
+ $this->config->set('VORTEX_INSTALL_DEMO', FALSE);
+ }
+ }
+
+ if (!$this->config->get('VORTEX_INSTALL_DEMO')) {
+ File::removeTokenWithContent('DEMO', $dir);
+ }
+ }
+
+ protected function processPreserveVortexInfo(string $dir): void {
+ if ($this->getAnswer('preserve_vortex_info') === self::ANSWER_NO) {
+ // Remove code required for Vortex maintenance.
+ File::removeTokenWithContent('VORTEX_DEV', $dir);
+
+ // Remove all other comments.
+ File::removeTokenLine('#;', $dir);
+ }
+ }
+
+ protected function processVortexInternal(string $dir): void {
+ if (file_exists($dir . DIRECTORY_SEPARATOR . 'README.dist.md')) {
+ rename($dir . DIRECTORY_SEPARATOR . 'README.dist.md', $dir . DIRECTORY_SEPARATOR . 'README.md');
+ }
+
+ // Remove Vortex internal files.
+ File::rmdirRecursive($dir . DIRECTORY_SEPARATOR . '.vortex');
+
+ @unlink($dir . '/.github/FUNDING.yml');
+ @unlink($dir . 'CODE_OF_CONDUCT.md');
+ @unlink($dir . 'CONTRIBUTING.md');
+ @unlink($dir . 'LICENSE');
+ @unlink($dir . 'SECURITY.md');
+
+ // Remove Vortex internal GHAs.
+ $files = glob($dir . '/.github/workflows/vortex-*.yml');
+ if ($files) {
+ foreach ($files as $file) {
+ @unlink($file);
+ }
+ }
+
+ // Remove other unhandled tokenized comments.
+ File::removeTokenLine('#;<', $dir);
+ File::removeTokenLine('#;>', $dir);
+ }
+
+ protected function processEnableCommentedCode(string $dir): void {
+ // Enable_commented_code.
+ File::dirReplaceContent('##### ', '', $dir);
+ }
+
+ protected function processWebroot(string $dir): void {
+ $new_name = $this->getAnswer('webroot', 'web');
+
+ if ($new_name !== 'web') {
+ File::dirReplaceContent('web/', $new_name . '/', $dir);
+ File::dirReplaceContent('web\/', $new_name . '\/', $dir);
+ File::dirReplaceContent(': web', ': ' . $new_name, $dir);
+ File::dirReplaceContent('=web', '=' . $new_name, $dir);
+ File::dirReplaceContent('!web', '!' . $new_name, $dir);
+ File::dirReplaceContent('/\/web\//', '/' . $new_name . '/', $dir);
+ File::dirReplaceContent('/\'\/web\'/', "'/" . $new_name . "'", $dir);
+ rename($dir . DIRECTORY_SEPARATOR . 'web', $dir . DIRECTORY_SEPARATOR . $new_name);
+ }
+ }
+
+ protected function processPreserveDocComments(string $dir): void {
+ if ($this->getAnswer('preserve_doc_comments') === self::ANSWER_YES) {
+ // Replace special "#: " comments with normal "#" comments.
+ File::dirReplaceContent('#:', '#', $dir);
+ }
+ else {
+ File::removeTokenLine('#:', $dir);
+ }
+ }
+
+ /**
+ * Discover value router.
+ *
+ * Value discoveries should return NULL if they don't have the resources to
+ * discover a value. This means that if the value is expected to come from a
+ * file but the file is not available, the function should return NULL instead
+ * of a falsy value like FALSE or 0.
+ */
+ protected function discoverValue(string $name): mixed {
+ $value = $this->executeCallback('discoverValue', $name);
+
+ return is_null($value) ? $this->getDefaultValue($name) : $value;
+ }
+
+ protected function discoverValueName(): ?string {
+ $value = $this->getComposerJsonValue('description');
+ if ($value && preg_match('/Drupal \d+ .* of ([0-9a-zA-Z\- ]+) for ([0-9a-zA-Z\- ]+)/', (string) $value, $matches) && !empty($matches[1])) {
+ return $matches[1];
+ }
+
+ return NULL;
+ }
+
+ protected function discoverValueMachineName(): ?string {
+ $value = $this->getComposerJsonValue('name');
+ if ($value && preg_match('/([^\/]+)\/(.+)/', (string) $value, $matches) && !empty($matches[2])) {
+ return $matches[2];
+ }
+
+ return NULL;
+ }
+
+ protected function discoverValueOrg(): ?string {
+ $value = $this->getComposerJsonValue('description');
+ if ($value && preg_match('/Drupal \d+ .* of ([0-9a-zA-Z\- ]+) for ([0-9a-zA-Z\- ]+)/', (string) $value, $matches) && !empty($matches[2])) {
+ return $matches[2];
+ }
+
+ return NULL;
+ }
+
+ protected function discoverValueOrgMachineName(): ?string {
+ $value = $this->getComposerJsonValue('name');
+ if ($value && preg_match('/([^\/]+)\/(.+)/', (string) $value, $matches) && !empty($matches[1])) {
+ return $matches[1];
+ }
+
+ return NULL;
+ }
+
+ protected function discoverValueModulePrefix(): ?string {
+ $webroot = $this->getAnswer('webroot');
+
+ $locations = [
+ $this->config->getDstDir() . sprintf('/%s/modules/custom/*_core', $webroot),
+ $this->config->getDstDir() . sprintf('/%s/sites/all/modules/custom/*_core', $webroot),
+ $this->config->getDstDir() . sprintf('/%s/profiles/*/modules/*_core', $webroot),
+ $this->config->getDstDir() . sprintf('/%s/profiles/*/modules/custom/*_core', $webroot),
+ $this->config->getDstDir() . sprintf('/%s/profiles/custom/*/modules/*_core', $webroot),
+ $this->config->getDstDir() . sprintf('/%s/profiles/custom/*/modules/custom/*_core', $webroot),
+ ];
+
+ $path = File::findMatchingPath($locations);
+
+ if (empty($path)) {
+ return NULL;
+ }
+
+ $path = basename($path);
+
+ return str_replace('_core', '', $path);
+ }
+
+ protected function discoverValueProfile(): ?string {
+ $webroot = $this->getAnswer('webroot');
+
+ if ($this->isInstalled()) {
+ $name = $this->getValueFromDstDotenv('DRUPAL_PROFILE');
+ if (!empty($name)) {
+ return $name;
+ }
+ }
+
+ $locations = [
+ $this->config->getDstDir() . sprintf('/%s/profiles/*/*.info', $webroot),
+ $this->config->getDstDir() . sprintf('/%s/profiles/*/*.info.yml', $webroot),
+ $this->config->getDstDir() . sprintf('/%s/profiles/custom/*/*.info', $webroot),
+ $this->config->getDstDir() . sprintf('/%s/profiles/custom/*/*.info.yml', $webroot),
+ ];
+
+ $name = File::findMatchingPath($locations, 'Drupal 10 profile implementation of');
+
+ if (empty($name)) {
+ return NULL;
+ }
+
+ $name = basename($name);
+
+ return str_replace(['.info.yml', '.info'], '', $name);
+ }
+
+ protected function discoverValueTheme(): ?string {
+ $webroot = $this->getAnswer('webroot');
+
+ if ($this->isInstalled()) {
+ $name = $this->getValueFromDstDotenv('DRUPAL_THEME');
+ if (!empty($name)) {
+ return $name;
+ }
+ }
+
+ $locations = [
+ $this->config->getDstDir() . sprintf('/%s/themes/custom/*/*.info', $webroot),
+ $this->config->getDstDir() . sprintf('/%s/themes/custom/*/*.info.yml', $webroot),
+ $this->config->getDstDir() . sprintf('/%s/sites/all/themes/custom/*/*.info', $webroot),
+ $this->config->getDstDir() . sprintf('/%s/sites/all/themes/custom/*/*.info.yml', $webroot),
+ $this->config->getDstDir() . sprintf('/%s/profiles/*/themes/custom/*/*.info', $webroot),
+ $this->config->getDstDir() . sprintf('/%s/profiles/*/themes/custom/*/*.info.yml', $webroot),
+ $this->config->getDstDir() . sprintf('/%s/profiles/custom/*/themes/custom/*/*.info', $webroot),
+ $this->config->getDstDir() . sprintf('/%s/profiles/custom/*/themes/custom/*/*.info.yml', $webroot),
+ ];
+
+ $name = File::findMatchingPath($locations);
+
+ if (empty($name)) {
+ return NULL;
+ }
+
+ $name = basename($name);
+
+ return str_replace(['.info.yml', '.info'], '', $name);
+ }
+
+ protected function discoverValueUrl(): ?string {
+ $webroot = $this->getAnswer('webroot');
+
+ $origin = NULL;
+ $path = $this->config->getDstDir() . sprintf('/%s/sites/default/settings.php', $webroot);
+
+ if (!is_readable($path)) {
+ return NULL;
+ }
+
+ $contents = file_get_contents($path);
+ if (!$contents) {
+ return NULL;
+ }
+
+ // Drupal 8 and 9.
+ if (preg_match('/\$config\s*\[\'stage_file_proxy.settings\'\]\s*\[\'origin\'\]\s*=\s*[\'"]([^\'"]+)[\'"];/', $contents, $matches)) {
+ $origin = $matches[1];
+ }
+ // Drupal 7.
+ elseif (preg_match('/\$conf\s*\[\'stage_file_proxy_origin\'\]\s*=\s*[\'"]([^\'"]+)[\'"];/', $contents, $matches)) {
+ $origin = $matches[1];
+ }
+
+ if ($origin) {
+ $origin = parse_url($origin, PHP_URL_HOST);
+ }
+
+ return empty($origin) ? NULL : $origin;
+ }
+
+ protected function discoverValueWebroot(): ?string {
+ $webroot = $this->getValueFromDstDotenv('VORTEX_WEBROOT');
+
+ if (empty($webroot) && $this->isInstalled()) {
+ // Try from composer.json.
+ $extra = $this->getComposerJsonValue('extra');
+ if (!empty($extra)) {
+ $webroot = $extra['drupal-scaffold']['drupal-scaffold']['locations']['web-root'] ?? NULL;
+ }
+ }
+
+ return $webroot;
+ }
+
+ protected function discoverValueProvisionUseProfile(): string {
+ return $this->getValueFromDstDotenv('VORTEX_PROVISION_USE_PROFILE') ? self::ANSWER_YES : self::ANSWER_NO;
+ }
+
+ protected function discoverValueDatabaseDownloadSource(): ?string {
+ return $this->getValueFromDstDotenv('VORTEX_DB_DOWNLOAD_SOURCE');
+ }
+
+ protected function discoverValueDatabaseStoreType(): string {
+ return $this->discoverValueDatabaseImage() ? 'container_image' : 'file';
+ }
+
+ protected function discoverValueDatabaseImage(): ?string {
+ return $this->getValueFromDstDotenv('VORTEX_DB_IMAGE');
+ }
+
+ protected function discoverValueOverrideExistingDb(): string {
+ return $this->getValueFromDstDotenv('VORTEX_PROVISION_OVERRIDE_DB') ? self::ANSWER_YES : self::ANSWER_NO;
+ }
+
+ protected function discoverValueCiProvider(): ?string {
+ if (is_readable($this->config->getDstDir() . '/.github/workflows/build-test-deploy.yml')) {
+ return 'GitHub Actions';
+ }
+
+ if (is_readable($this->config->getDstDir() . '/.circleci/config.yml')) {
+ return 'CircleCI';
+ }
+
+ return $this->isInstalled() ? 'none' : NULL;
+ }
+
+ protected function discoverValueDeployType(): ?string {
+ return $this->getValueFromDstDotenv('VORTEX_DEPLOY_TYPES');
+ }
+
+ protected function discoverValuePreserveAcquia(): ?string {
+ if (is_readable($this->config->getDstDir() . '/hooks')) {
+ return self::ANSWER_YES;
+ }
+
+ $value = $this->getValueFromDstDotenv('VORTEX_DB_DOWNLOAD_SOURCE');
+
+ if (is_null($value)) {
+ return NULL;
+ }
+
+ return $value == 'acquia' ? self::ANSWER_YES : self::ANSWER_NO;
+ }
+
+ protected function discoverValuePreserveLagoon(): ?string {
+ if (is_readable($this->config->getDstDir() . '/.lagoon.yml')) {
+ return self::ANSWER_YES;
+ }
+
+ if ($this->getAnswer('deploy_type') === 'lagoon') {
+ return self::ANSWER_YES;
+ }
+
+ $value = $this->getValueFromDstDotenv('LAGOON_PROJECT');
+
+ // Special case - only work with non-empty value as 'LAGOON_PROJECT'
+ // may not exist in installed site's .env file.
+ if (empty($value)) {
+ return NULL;
+ }
+
+ return self::ANSWER_YES;
+ }
+
+ protected function discoverValuePreserveFtp(): ?string {
+ $value = $this->getValueFromDstDotenv('VORTEX_DB_DOWNLOAD_SOURCE');
+ if (is_null($value)) {
+ return NULL;
+ }
+
+ return $value == 'ftp' ? self::ANSWER_YES : self::ANSWER_NO;
+ }
+
+ protected function discoverValuePreserveRenovatebot(): ?string {
+ if (!$this->isInstalled()) {
+ return NULL;
+ }
+
+ return is_readable($this->config->getDstDir() . '/renovate.json') ? self::ANSWER_YES : self::ANSWER_NO;
+ }
+
+ protected function discoverValuePreserveDocComments(): ?string {
+ $file = $this->config->getDstDir() . '/.ahoy.yml';
+
+ if (!is_readable($file)) {
+ return NULL;
+ }
+
+ return File::fileContains('Ahoy configuration file', $file) ? self::ANSWER_YES : self::ANSWER_NO;
+ }
+
+ protected function discoverValuePreserveVortexInfo(): ?string {
+ $file = $this->config->getDstDir() . '/.ahoy.yml';
+ if (!is_readable($file)) {
+ return NULL;
+ }
+
+ return File::fileContains('Comments starting with', $file) ? self::ANSWER_YES : self::ANSWER_NO;
+ }
+
+ protected function normaliseAnswerName(string $value): string {
+ return ucfirst((string) Converter::toHumanName($value));
+ }
+
+ protected function normaliseAnswerMachineName(string $value): string {
+ return Converter::toMachineName($value);
+ }
+
+ protected function normaliseAnswerOrgMachineName(string $value): string {
+ return Converter::toMachineName($value);
+ }
+
+ protected function normaliseAnswerModulePrefix(string $value): string {
+ return Converter::toMachineName($value);
+ }
+
+ protected function normaliseAnswerProfile(string $value): string {
+ $profile = Converter::toMachineName($value);
+
+ if (empty($profile) || strtolower($profile) === self::ANSWER_NO) {
+ $profile = 'standard';
+ }
+
+ return $profile;
+ }
+
+ protected function normaliseAnswerTheme(string $value): string {
+ return Converter::toMachineName($value);
+ }
+
+ protected function normaliseAnswerUrl(string $url): string {
+ $url = trim($url);
+
+ return str_replace([' ', '_'], '-', $url);
+ }
+
+ protected function normaliseAnswerWebroot(string $value): string {
+ return strtolower(trim($value, '/'));
+ }
+
+ protected function normaliseAnswerProvisionUseProfile(string $value): string {
+ return strtolower($value) !== self::ANSWER_YES ? self::ANSWER_NO : self::ANSWER_YES;
+ }
+
+ protected function normaliseAnswerDatabaseDownloadSource(string $value): string {
+ $value = strtolower($value);
+
+ return match ($value) {
+ 'f', 'ftp' => 'ftp',
+ 'a', 'acquia' => 'acquia',
+ 'i', 'image', 'container_image', 'container_registry' => 'container_registry',
+ 'c', 'curl' => 'curl',
+ default => $this->getDefaultValueDatabaseDownloadSource(),
+ };
+ }
+
+ protected function normaliseAnswerDatabaseStoreType(string $value): string {
+ $value = strtolower($value);
+
+ return match ($value) {
+ 'i', 'image', 'container_image', => 'container_image',
+ 'f', 'file' => 'file',
+ default => $this->getDefaultValueDatabaseStoreType(),
+ };
+ }
+
+ protected function normaliseAnswerDatabaseImage(string $value): string {
+ $value = Converter::toMachineName($value, ['-', '/', ':', '.']);
+
+ return str_contains($value, ':') ? $value : $value . ':latest';
+ }
+
+ protected function normaliseAnswerOverrideExistingDb(string $value): string {
+ return strtolower($value) !== self::ANSWER_YES ? self::ANSWER_NO : self::ANSWER_YES;
+ }
+
+ protected function normaliseAnswerCiProvider(string $value): string {
+ $value = trim(strtolower($value));
+
+ return match ($value) {
+ 'c', 'circleci' => 'CircleCI',
+ 'g', 'gha', 'github actions' => 'GitHub Actions',
+ default => 'none',
+ };
+ }
+
+ protected function normaliseAnswerDeployType(string $value): ?string {
+ $types = explode(',', $value);
+
+ $normalised = [];
+ foreach ($types as $type) {
+ $type = trim($type);
+ switch ($type) {
+ case 'w':
+ case 'webhook':
+ $normalised[] = 'webhook';
+ break;
+
+ case 'c':
+ case 'code':
+ case 'a':
+ case 'artifact':
+ $normalised[] = 'artifact';
+ break;
+
+ case 'r':
+ case 'container_registry':
+ $normalised[] = 'container_registry';
+ break;
+
+ case 'l':
+ case 'lagoon':
+ $normalised[] = 'lagoon';
+ break;
+
+ case 'n':
+ case 'none':
+ $normalised[] = 'none';
+ break;
+ }
+ }
+
+ // @todo Should we return `none` instead of `NULL`?
+ if (in_array('none', $normalised)) {
+ return NULL;
+ }
+
+ $normalised = array_unique($normalised);
+
+ return implode(',', $normalised);
+ }
+
+ protected function normaliseAnswerPreserveAcquia(string $value): string {
+ return strtolower($value) !== self::ANSWER_YES ? self::ANSWER_NO : self::ANSWER_YES;
+ }
+
+ protected function normaliseAnswerPreserveLagoon(string $value): string {
+ return strtolower($value) !== self::ANSWER_YES ? self::ANSWER_NO : self::ANSWER_YES;
+ }
+
+ protected function normaliseAnswerPreserveFtp(string $value): string {
+ return strtolower($value) !== self::ANSWER_YES ? self::ANSWER_NO : self::ANSWER_YES;
+ }
+
+ protected function normaliseAnswerPreserveRenovatebot(string $value): string {
+ return strtolower($value) !== self::ANSWER_YES ? self::ANSWER_NO : self::ANSWER_YES;
+ }
+
+ protected function normaliseAnswerPreserveDocComments(string $value): string {
+ return strtolower($value) !== self::ANSWER_YES ? self::ANSWER_NO : self::ANSWER_YES;
+ }
+
+ protected function normaliseAnswerPreserveVortexInfo(string $value): string {
+ return strtolower($value) !== self::ANSWER_YES ? self::ANSWER_NO : self::ANSWER_YES;
+ }
+
+ /**
+ * Normalisation router.
+ */
+ protected function normaliseAnswer(string $name, mixed $value): mixed {
+ $normalised = $this->executeCallback('normaliseAnswer', $name, strval($value));
+
+ return $normalised ?? $value;
+ }
+
+ /**
+ * Check that Vortex is installed for this project.
+ */
+ protected function isInstalled(): bool {
+ $path = $this->config->getDstDir() . DIRECTORY_SEPARATOR . 'README.md';
+
+ if (!file_exists($path)) {
+ return FALSE;
+ }
+
+ $content = file_get_contents($path);
+ if (!$content) {
+ return FALSE;
+ }
+
+ return (bool) preg_match('/badge\/Vortex\-/', $content);
+ }
+
+ /**
+ * Get the value of a composer.json key.
+ *
+ * @param string $name
+ * Name of the key.
+ *
+ * @return mixed|null
+ * Value of the key or NULL if not found.
+ */
+ protected function getComposerJsonValue(string $name): mixed {
+ $composer_json = $this->config->getDstDir() . DIRECTORY_SEPARATOR . 'composer.json';
+ if (is_readable($composer_json)) {
+ $contents = file_get_contents($composer_json);
+ if ($contents === FALSE) {
+ return NULL;
+ }
+
+ $json = json_decode($contents, TRUE);
+ if (isset($json[$name])) {
+ return $json[$name];
+ }
+ }
+
+ return NULL;
+ }
+
+ protected function processStringTokens(string $dir): void {
+ $machine_name_hyphenated = str_replace('_', '-', $this->getAnswer('machine_name'));
+ $machine_name_camel_cased = Converter::toCamelCase($this->getAnswer('machine_name'), TRUE);
+ $module_prefix_camel_cased = Converter::toCamelCase($this->getAnswer('module_prefix'), TRUE);
+ $module_prefix_uppercase = strtoupper($module_prefix_camel_cased);
+ $theme_camel_cased = Converter::toCamelCase($this->getAnswer('theme'), TRUE);
+ $vortex_version_urlencoded = str_replace('-', '--', (string) $this->config->get('VORTEX_VERSION'));
+ $url = $this->getAnswer('url');
+ $host = parse_url($url, PHP_URL_HOST);
+ $domain = $host ?: $url;
+ $domain_non_www = str_starts_with((string) $domain, "www.") ? substr((string) $domain, 4) : $domain;
+ $webroot = $this->getAnswer('webroot');
+
+ // @formatter:off
+ // phpcs:disable Generic.Functions.FunctionCallArgumentSpacing.TooMuchSpaceAfterComma
+ // phpcs:disable Drupal.WhiteSpace.Comma.TooManySpaces
+ File::dirReplaceContent('your_site_theme', $this->getAnswer('theme'), $dir);
+ File::dirReplaceContent('YourSiteTheme', $theme_camel_cased, $dir);
+ File::dirReplaceContent('your_org', $this->getAnswer('org_machine_name'), $dir);
+ File::dirReplaceContent('YOURORG', $this->getAnswer('org'), $dir);
+ File::dirReplaceContent('www.your-site-url.example', $domain, $dir);
+ File::dirReplaceContent('your-site-url.example', $domain_non_www, $dir);
+ File::dirReplaceContent('ys_core', $this->getAnswer('module_prefix') . '_core', $dir . sprintf('/%s/modules/custom', $webroot));
+ File::dirReplaceContent('ys_search', $this->getAnswer('module_prefix') . '_search', $dir . sprintf('/%s/modules/custom', $webroot));
+ File::dirReplaceContent('ys_core', $this->getAnswer('module_prefix') . '_core', $dir . sprintf('/%s/themes/custom', $webroot));
+ File::dirReplaceContent('ys_core', $this->getAnswer('module_prefix') . '_core', $dir . '/scripts/custom');
+ File::dirReplaceContent('ys_search', $this->getAnswer('module_prefix') . '_search', $dir . '/scripts/custom');
+ File::dirReplaceContent('YsCore', $module_prefix_camel_cased . 'Core', $dir . sprintf('/%s/modules/custom', $webroot));
+ File::dirReplaceContent('YsSearch', $module_prefix_camel_cased . 'Search', $dir . sprintf('/%s/modules/custom', $webroot));
+ File::dirReplaceContent('YSCODE', $module_prefix_uppercase, $dir);
+ File::dirReplaceContent('YSSEARCH', $module_prefix_uppercase, $dir);
+ File::dirReplaceContent('your-site', $machine_name_hyphenated, $dir);
+ File::dirReplaceContent('your_site', $this->getAnswer('machine_name'), $dir);
+ File::dirReplaceContent('YOURSITE', $this->getAnswer('name'), $dir);
+ File::dirReplaceContent('YourSite', $machine_name_camel_cased, $dir);
+
+ File::replaceStringFilename('YourSiteTheme', $theme_camel_cased, $dir);
+ File::replaceStringFilename('your_site_theme', $this->getAnswer('theme'), $dir);
+ File::replaceStringFilename('YourSite', $machine_name_camel_cased, $dir);
+ File::replaceStringFilename('ys_core', $this->getAnswer('module_prefix') . '_core', $dir . sprintf('/%s/modules/custom', $webroot));
+ File::replaceStringFilename('ys_search', $this->getAnswer('module_prefix') . '_search', $dir . sprintf('/%s/modules/custom', $webroot));
+ File::replaceStringFilename('YsCore', $module_prefix_camel_cased . 'Core', $dir . sprintf('/%s/modules/custom', $webroot));
+ File::replaceStringFilename('your_org', $this->getAnswer('org_machine_name'), $dir);
+ File::replaceStringFilename('your_site', $this->getAnswer('machine_name'), $dir);
+
+ File::dirReplaceContent('VORTEX_VERSION_URLENCODED', $vortex_version_urlencoded, $dir);
+ File::dirReplaceContent('VORTEX_VERSION', $this->config->get('VORTEX_VERSION'), $dir);
+ // @formatter:on
+ // phpcs:enable Generic.Functions.FunctionCallArgumentSpacing.TooMuchSpaceAfterComma
+ // phpcs:enable Drupal.WhiteSpace.Comma.TooManySpaces
+ }
+
+ /**
+ * Execute this class's callback.
+ *
+ * @param string $prefix
+ * Prefix of the callback.
+ * @param string $name
+ * Name of the callback.
+ *
+ * @return mixed
+ * Result of the callback.
+ */
+ protected function executeCallback(string $prefix, string $name): mixed {
+ $args = func_get_args();
+ $args = array_slice($args, 2);
+
+ $name = Converter::snakeToPascal($name);
+
+ $callback = [static::class, $prefix . $name];
+ if (method_exists($callback[0], $callback[1]) && is_callable($callback)) {
+ return call_user_func_array($callback, $args);
+ }
+
+ return NULL;
+ }
+
+}
diff --git a/.vortex/installer/src/Traits/TuiTrait.php b/.vortex/installer/src/Traits/TuiTrait.php
new file mode 100644
index 000000000..2e82c965a
--- /dev/null
+++ b/.vortex/installer/src/Traits/TuiTrait.php
@@ -0,0 +1,296 @@
+config->isQuiet()) {
+ return $default;
+ }
+
+ $question = sprintf('> %s [%s] ', $question, $default);
+
+ $this->out($question, 'question', FALSE);
+ $handle = $this->getStdinHandle();
+ $answer = fgets($handle);
+ if ($answer !== FALSE) {
+ $answer = trim($answer);
+ }
+
+ if ($close_handle) {
+ $this->closeStdinHandle();
+ }
+
+ return empty($answer) ? $default : $answer;
+ }
+
+ protected function getStdinHandle(): mixed {
+ global $_stdin_handle;
+
+ if (!$_stdin_handle) {
+ $h = fopen('php://stdin', 'r');
+ if (!$h) {
+ throw new \RuntimeException('Unable to open stdin handle.');
+ }
+ $_stdin_handle = stream_isatty($h) || static::getenvOrDefault('VORTEX_INSTALLER_FORCE_TTY') ? $h : fopen('/dev/tty', 'r+');
+ }
+
+ return $_stdin_handle;
+ }
+
+ protected function closeStdinHandle(): void {
+ $_stdin_handle = $this->getStdinHandle();
+ fclose($_stdin_handle);
+ }
+
+ protected function printHeader(): void {
+ if ($this->config->isQuiet()) {
+ $this->printHeaderQuiet();
+ }
+ else {
+ $this->printHeaderInteractive();
+ }
+ print PHP_EOL;
+ }
+
+ protected function printHeaderInteractive(): void {
+ $commit = $this->config->get('VORTEX_INSTALL_COMMIT');
+
+ $content = '';
+ if ($commit == 'HEAD') {
+ $content .= 'This will install the latest version of Vortex into your project.' . PHP_EOL;
+ }
+ else {
+ $content .= sprintf('This will install Vortex into your project at commit "%s".', $commit) . PHP_EOL;
+ }
+ $content .= PHP_EOL;
+ if ($this->isInstalled()) {
+ $content .= 'It looks like Vortex is already installed into this project.' . PHP_EOL;
+ $content .= PHP_EOL;
+ }
+ $content .= 'Please answer the questions below to install configuration relevant to your site.' . PHP_EOL;
+ $content .= 'No changes will be applied until the last confirmation step.' . PHP_EOL;
+ $content .= PHP_EOL;
+ $content .= 'Existing committed files will be modified. You will need to resolve changes manually.' . PHP_EOL;
+ $content .= PHP_EOL;
+ $content .= 'Press Ctrl+C at any time to exit this installer.' . PHP_EOL;
+
+ $this->printBox($content, 'WELCOME TO VORTEX INTERACTIVE INSTALLER');
+ }
+
+ protected function printHeaderQuiet(): void {
+ $commit = $this->config->get('VORTEX_INSTALL_COMMIT');
+
+ $content = '';
+ if ($commit == 'HEAD') {
+ $content .= 'This will install the latest version of Vortex into your project.' . PHP_EOL;
+ }
+ else {
+ $content .= sprintf('This will install Vortex into your project at commit "%s".', $commit) . PHP_EOL;
+ }
+
+ $content .= PHP_EOL;
+ if ($this->isInstalled()) {
+ $content .= 'It looks like Vortex is already installed into this project.' . PHP_EOL;
+ $content .= PHP_EOL;
+ }
+
+ $content .= 'Vortex installer will try to discover the settings from the environment and will install configuration relevant to your site.' . PHP_EOL;
+ $content .= PHP_EOL;
+ $content .= 'Existing committed files will be modified. You will need to resolve changes manually.' . PHP_EOL;
+
+ $this->printBox($content, 'WELCOME TO VORTEX QUIET INSTALLER');
+ }
+
+ protected function printSummary(): void {
+ $values['Current directory'] = $this->fsGetRootDir();
+ $values['Destination directory'] = $this->config->getDstDir();
+ $values['Vortex version'] = $this->config->get('VORTEX_VERSION');
+ $values['Vortex commit'] = $this->formatNotEmpty($this->config->get('VORTEX_INSTALL_COMMIT'), 'Latest');
+
+ $values[] = '';
+ $values[] = str_repeat('─', 80 - 2 - 2 * 2);
+ $values[] = '';
+
+ $values['Name'] = $this->getAnswer('name');
+ $values['Machine name'] = $this->getAnswer('machine_name');
+ $values['Organisation'] = $this->getAnswer('org');
+ $values['Organisation machine name'] = $this->getAnswer('org_machine_name');
+ $values['Module prefix'] = $this->getAnswer('module_prefix');
+ $values['Profile'] = $this->getAnswer('profile');
+ $values['Theme name'] = $this->getAnswer('theme');
+ $values['URL'] = $this->getAnswer('url');
+ $values['Web root'] = $this->getAnswer('webroot');
+
+ $values['Install from profile'] = $this->formatYesNo($this->getAnswer('provision_use_profile'));
+
+ $values['Database download source'] = $this->getAnswer('database_download_source');
+ $image = $this->getAnswer('database_image');
+ $values['Database store type'] = empty($image) ? 'file' : 'container_image';
+
+ if ($image !== '' && $image !== '0') {
+ $values['Database image name'] = $image;
+ }
+
+ $values['Override existing database'] = $this->formatYesNo($this->getAnswer('override_existing_db'));
+ $values['CI provider'] = $this->formatNotEmpty($this->getAnswer('ci_provider'), 'None');
+ $values['Deployment'] = $this->formatNotEmpty($this->getAnswer('deploy_type'), 'Disabled');
+ $values['FTP integration'] = $this->formatEnabled($this->getAnswer('preserve_ftp'));
+ $values['Acquia integration'] = $this->formatEnabled($this->getAnswer('preserve_acquia'));
+ $values['Lagoon integration'] = $this->formatEnabled($this->getAnswer('preserve_lagoon'));
+ $values['RenovateBot integration'] = $this->formatEnabled($this->getAnswer('preserve_renovatebot'));
+ $values['Preserve docs in comments'] = $this->formatYesNo($this->getAnswer('preserve_doc_comments'));
+ $values['Preserve Vortex comments'] = $this->formatYesNo($this->getAnswer('preserve_vortex_info'));
+
+ $content = $this->formatValuesList($values, '', 80 - 2 - 2 * 2);
+
+ $this->printBox($content, 'INSTALLATION SUMMARY');
+ }
+
+ protected function printAbort(): void {
+ $this->printBox('Aborting project installation. No files were changed.');
+ }
+
+ protected function printFooter(): void {
+ print PHP_EOL;
+
+ if ($this->isInstalled()) {
+ $this->printBox('Finished updating Vortex. Review changes and commit required files.');
+ }
+ else {
+ $this->printBox('Finished installing Vortex.');
+
+ $output = '';
+ $output .= PHP_EOL;
+ $output .= 'Next steps:' . PHP_EOL;
+ $output .= ' cd ' . $this->config->getDstDir() . PHP_EOL;
+ $output .= ' git add -A # Add all files.' . PHP_EOL;
+ $output .= ' git commit -m "Initial commit." # Commit all files.' . PHP_EOL;
+ $output .= ' ahoy build # Build site.' . PHP_EOL;
+ $output .= PHP_EOL;
+ $output .= ' See https://vortex.drevops.com/quickstart';
+ $this->status($output, self::INSTALLER_STATUS_SUCCESS, TRUE, FALSE);
+ }
+ }
+
+ protected function commandExists(string $command): void {
+ $this->doExec('command -v ' . $command, $lines, $ret);
+ if ($ret === 1) {
+ throw new \RuntimeException(sprintf('Command "%s" does not exist in the current environment.', $command));
+ }
+ }
+
+ /**
+ * Execute command.
+ *
+ * @param string $command
+ * Command to execute.
+ * @param array|null $output
+ * Output of the command.
+ * @param int $return_var
+ * Return code of the command.
+ *
+ * @return string|false
+ * Result of the command.
+ */
+ protected function doExec(string $command, ?array &$output = NULL, ?int &$return_var = NULL): string|false {
+ if ($this->config->isInstallDebug()) {
+ $this->status(sprintf('COMMAND: %s', $command), self::INSTALLER_STATUS_DEBUG);
+ }
+
+ $result = exec($command, $output, $return_var);
+
+ if ($this->config->isInstallDebug()) {
+ $this->status(sprintf(' OUTPUT: %s', implode('', $output)), self::INSTALLER_STATUS_DEBUG);
+ $this->status(sprintf(' CODE : %s', $return_var), self::INSTALLER_STATUS_DEBUG);
+ $this->status(sprintf(' RESULT: %s', $result), self::INSTALLER_STATUS_DEBUG);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get a named option from discovered answers for the project bing installed.
+ */
+ protected function getAnswer(string $name, mixed $default = NULL): ?string {
+ global $_answers;
+
+ return $_answers[$name] ?? $default;
+ }
+
+ /**
+ * Set a named option for discovered answers for the project bing installed.
+ */
+ protected function setAnswer(string $name, mixed $value): void {
+ global $_answers;
+ $_answers[$name] = $value;
+ }
+
+ /**
+ * Get all options from discovered answers for the project bing installed.
+ *
+ * @return array
+ * Array of all discovered answers.
+ */
+ protected function getAnswers(): array {
+ global $_answers;
+
+ return $_answers;
+ }
+
+ protected function askShouldProceed(): bool {
+ $proceed = self::ANSWER_YES;
+
+ if (!$this->config->isQuiet()) {
+ $proceed = $this->ask(sprintf('Proceed with installing Vortex into your project\'s directory "%s"? (Y,n)', $this->config->getDstDir()), $proceed, TRUE);
+ }
+
+ // Kill-switch to not proceed with install. If false, the install will not
+ // proceed despite the answer received above.
+ if (!$this->config->get('VORTEX_INSTALL_PROCEED')) {
+ $proceed = self::ANSWER_NO;
+ }
+
+ return strtolower((string) $proceed) === self::ANSWER_YES;
+ }
+
+ protected function askForAnswer(string $name, string $question): void {
+ $discovered = $this->discoverValue($name);
+ $answer = $this->ask($question, $discovered);
+ $answer = $this->normaliseAnswer($name, $answer);
+
+ $this->setAnswer($name, $answer);
+ }
+
+ /**
+ * Process answers.
+ */
+ protected function processAnswer(string $name, string $dir): mixed {
+ return $this->executeCallback('process', $name, $dir);
+ }
+
+}
diff --git a/.vortex/installer/src/app.php b/.vortex/installer/src/app.php
index 8946edd06..ad34c47e8 100644
--- a/.vortex/installer/src/app.php
+++ b/.vortex/installer/src/app.php
@@ -5,6 +5,8 @@
* Main entry point for the application.
*/
+declare(strict_types=1);
+
use DrevOps\Installer\Command\InstallCommand;
use Symfony\Component\Console\Application;
diff --git a/.vortex/installer/tests/phpunit/Traits/ReflectionTrait.php b/.vortex/installer/tests/phpunit/Traits/ReflectionTrait.php
index f74b451a9..6433aeb3c 100644
--- a/.vortex/installer/tests/phpunit/Traits/ReflectionTrait.php
+++ b/.vortex/installer/tests/phpunit/Traits/ReflectionTrait.php
@@ -1,5 +1,7 @@
getProperty($property);
$property->setAccessible(TRUE);
diff --git a/.vortex/installer/tests/phpunit/Unit/Command/InstallCommandTest.php b/.vortex/installer/tests/phpunit/Unit/Command/InstallCommandTest.php
index b4746a0cc..8c1e8254b 100644
--- a/.vortex/installer/tests/phpunit/Unit/Command/InstallCommandTest.php
+++ b/.vortex/installer/tests/phpunit/Unit/Command/InstallCommandTest.php
@@ -1,5 +1,7 @@
callProtectedMethod(InstallCommand::class, 'toHumanName', [$value]);
- $this->assertEquals($expected, $actual);
+ $this->assertEquals($expected, Converter::toHumanName($value));
}
public static function dataProviderToHumanName(): array {
@@ -48,8 +47,7 @@ public static function dataProviderToHumanName(): array {
* @covers ::toMachineName
*/
public function testToMachineName(string $value, array $preserve, mixed $expected): void {
- $actual = $this->callProtectedMethod(InstallCommand::class, 'toMachineName', [$value, $preserve]);
- $this->assertEquals($expected, $actual);
+ $this->assertEquals($expected, Converter::toMachineName($value, $preserve));
}
public static function dataProviderToMachineName(): array {
@@ -89,8 +87,7 @@ public static function dataProviderToMachineName(): array {
* @covers ::toCamelCase
*/
public function testToCamelCase(string $value, bool $capitalise_first, mixed $expected): void {
- $actual = $this->callProtectedMethod(InstallCommand::class, 'toCamelCase', [$value, $capitalise_first]);
- $this->assertEquals($expected, $actual);
+ $this->assertEquals($expected, Converter::toCamelCase($value, $capitalise_first));
}
public static function dataProviderToCamelCase(): array {
@@ -112,48 +109,4 @@ public static function dataProviderToCamelCase(): array {
];
}
- /**
- * @dataProvider dataProviderIsRegex
- * @covers ::isRegex
- */
- public function testIsRegex(string $value, mixed $expected): void {
- $actual = $this->callProtectedMethod(InstallCommand::class, 'isRegex', [$value]);
- $this->assertEquals($expected, $actual);
- }
-
- public static function dataProviderIsRegex(): array {
- return [
- ['', FALSE],
-
- // Valid regular expressions.
- ["/^[a-z]$/", TRUE],
- ["#[a-z]*#i", TRUE],
- ["{\\d+}", TRUE],
- ["(\\d+)", TRUE],
- ["<[A-Z]{3,6}>", TRUE],
-
- // Invalid regular expressions (wrong delimiters or syntax).
- ["^[a-z]$", FALSE],
- ["/[a-z", FALSE],
- ["[a-z]+/", FALSE],
- ["{[a-z]*", FALSE],
- ["(a-z]", FALSE],
-
- // Edge cases.
- // Valid, but '*' as delimiter would be invalid.
- ["/a*/", TRUE],
- // Empty string.
- ["", FALSE],
- // Just delimiters, no pattern.
- ["//", FALSE],
-
- ['web/', FALSE],
- ['web\/', FALSE],
- [': web', FALSE],
- ['=web', FALSE],
- ['!web', FALSE],
- ['/web', FALSE],
- ];
- }
-
}
diff --git a/.vortex/installer/tests/phpunit/Unit/CopyRecursiveTest.php b/.vortex/installer/tests/phpunit/Unit/CopyRecursiveTest.php
deleted file mode 100644
index 578f3b440..000000000
--- a/.vortex/installer/tests/phpunit/Unit/CopyRecursiveTest.php
+++ /dev/null
@@ -1,64 +0,0 @@
-prepareFixtureDir();
- }
-
- protected function tearDown(): void {
- parent::tearDown();
- $this->cleanupFixtureDir();
- }
-
- /**
- * @covers ::copyRecursive
- */
- public function testCopyRecursive(): void {
- $files_dir = $this->getFixtureDir('copyfiles');
-
- $this->callProtectedMethod(InstallCommand::class, 'copyRecursive', [$files_dir, $this->fixtureDir]);
-
- $dir = $this->fixtureDir . DIRECTORY_SEPARATOR;
-
- $this->assertTrue(is_file($dir . 'file.txt'));
- $this->assertTrue((fileperms($dir . 'file.txt') & 0777) === 0755);
- $this->assertTrue(is_dir($dir . 'dir'));
- $this->assertTrue(is_file($dir . 'dir/file_in_dir.txt'));
- $this->assertTrue(is_dir($dir . 'dir/subdir'));
- $this->assertTrue(is_file($dir . 'dir/subdir/file_in_subdir.txt'));
-
- $this->assertTrue(is_link($dir . 'file_link.txt'));
-
- $this->assertTrue(is_link($dir . 'dir_link'));
- $this->assertTrue(is_dir($dir . 'dir_link/subdir'));
- $this->assertTrue(is_file($dir . 'dir_link/subdir/file_in_subdir.txt'));
- $this->assertTrue(is_link($dir . 'dir_link/subdir/file_link_from_subdir.txt'));
-
- $this->assertTrue(is_link($dir . 'subdir_link_root'));
- $this->assertTrue(is_link($dir . 'subdir_link_root/file_link_from_subdir.txt'));
- $this->assertTrue((fileperms($dir . 'subdir_link_root/file_link_from_subdir.txt') & 0777) === 0755);
- $this->assertTrue(is_file($dir . 'subdir_link_root/file_in_subdir.txt'));
-
- $this->assertTrue(is_link($dir . 'dir/subdir_link'));
- $this->assertTrue(is_dir($dir . 'dir/subdir_link'));
-
- $this->assertDirectoryDoesNotExist($dir . 'emptydir');
- }
-
-}
diff --git a/.vortex/installer/tests/phpunit/Unit/DotEnvTest.php b/.vortex/installer/tests/phpunit/Unit/DotEnvTest.php
index 7b4235ff0..e201e007e 100644
--- a/.vortex/installer/tests/phpunit/Unit/DotEnvTest.php
+++ b/.vortex/installer/tests/phpunit/Unit/DotEnvTest.php
@@ -1,5 +1,7 @@
createFixtureEnvFile($content);
- $this->assertEmpty(getenv('var1'), getenv('var1'));
+ $this->assertEmpty(getenv('var1'));
$this->callProtectedMethod(InstallCommand::class, 'loadDotenv', [$filename]);
$this->assertEquals('val1', getenv('var1'));
@@ -216,7 +218,7 @@ public static function dataProviderGlobals(): array {
];
}
- protected function createFixtureEnvFile($content): string|false {
+ protected function createFixtureEnvFile(string $content): string|false {
$filename = tempnam(sys_get_temp_dir(), '.env');
file_put_contents($filename, $content);
diff --git a/.vortex/installer/tests/phpunit/Unit/TokenTest.php b/.vortex/installer/tests/phpunit/Unit/FileTest.php
similarity index 66%
rename from .vortex/installer/tests/phpunit/Unit/TokenTest.php
rename to .vortex/installer/tests/phpunit/Unit/FileTest.php
index 17315b811..ae07aba05 100644
--- a/.vortex/installer/tests/phpunit/Unit/TokenTest.php
+++ b/.vortex/installer/tests/phpunit/Unit/FileTest.php
@@ -1,20 +1,22 @@
cleanupFixtureDir();
}
+ /**
+ * @covers ::copyRecursive
+ */
+ public function testCopyRecursive(): void {
+ $files_dir = $this->getFixtureDir('copyfiles');
+
+ File::copyRecursive($files_dir, $this->fixtureDir);
+
+ $dir = $this->fixtureDir . DIRECTORY_SEPARATOR;
+
+ $this->assertTrue(is_file($dir . 'file.txt'));
+ $this->assertTrue((fileperms($dir . 'file.txt') & 0777) === 0755);
+ $this->assertTrue(is_dir($dir . 'dir'));
+ $this->assertTrue(is_file($dir . 'dir/file_in_dir.txt'));
+ $this->assertTrue(is_dir($dir . 'dir/subdir'));
+ $this->assertTrue(is_file($dir . 'dir/subdir/file_in_subdir.txt'));
+
+ $this->assertTrue(is_link($dir . 'file_link.txt'));
+
+ $this->assertTrue(is_link($dir . 'dir_link'));
+ $this->assertTrue(is_dir($dir . 'dir_link/subdir'));
+ $this->assertTrue(is_file($dir . 'dir_link/subdir/file_in_subdir.txt'));
+ $this->assertTrue(is_link($dir . 'dir_link/subdir/file_link_from_subdir.txt'));
+
+ $this->assertTrue(is_link($dir . 'subdir_link_root'));
+ $this->assertTrue(is_link($dir . 'subdir_link_root/file_link_from_subdir.txt'));
+ $this->assertTrue((fileperms($dir . 'subdir_link_root/file_link_from_subdir.txt') & 0777) === 0755);
+ $this->assertTrue(is_file($dir . 'subdir_link_root/file_in_subdir.txt'));
+
+ $this->assertTrue(is_link($dir . 'dir/subdir_link'));
+ $this->assertTrue(is_dir($dir . 'dir/subdir_link'));
+
+ $this->assertDirectoryDoesNotExist($dir . 'emptydir');
+ }
+
+ /**
+ * @dataProvider dataProviderIsRegex
+ * @covers ::isRegex
+ */
+ public function testIsRegex(string $value, mixed $expected): void {
+ $this->assertEquals($expected, File::isRegex($value));
+ }
+
+ public static function dataProviderIsRegex(): array {
+ return [
+ ['', FALSE],
+
+ // Valid regular expressions.
+ ["/^[a-z]$/", TRUE],
+ ["#[a-z]*#i", TRUE],
+ ["{\\d+}", TRUE],
+ ["(\\d+)", TRUE],
+ ["<[A-Z]{3,6}>", TRUE],
+
+ // Invalid regular expressions (wrong delimiters or syntax).
+ ["^[a-z]$", FALSE],
+ ["/[a-z", FALSE],
+ ["[a-z]+/", FALSE],
+ ["{[a-z]*", FALSE],
+ ["(a-z]", FALSE],
+
+ // Edge cases.
+ // Valid, but '*' as delimiter would be invalid.
+ ["/a*/", TRUE],
+ // Empty string.
+ ["", FALSE],
+ // Just delimiters, no pattern.
+ ["//", FALSE],
+
+ ['web/', FALSE],
+ ['web\/', FALSE],
+ [': web', FALSE],
+ ['=web', FALSE],
+ ['!web', FALSE],
+ ['/web', FALSE],
+ ];
+ }
+
/**
* Flatten file tree.
+ *
+ * @param array $tree
+ * File tree.
+ * @param string $parent
+ * Parent directory.
+ *
+ * @return array
+ * Flattened file tree.
*/
- protected function flattenFileTree($tree, string $parent = '.'): array {
+ protected function flattenFileTree(array $tree, string $parent = '.'): array {
$flatten = [];
+
foreach ($tree as $dir => $file) {
if (is_array($file)) {
$flatten = array_merge($flatten, $this->flattenFileTree($file, $parent . DIRECTORY_SEPARATOR . $dir));
@@ -53,7 +142,11 @@ public function testFileContains(string $string, string $file, mixed $expected):
$created_files = $this->createFixtureFiles($files, $tokens_dir);
$created_file = reset($created_files);
- $actual = InstallCommand::fileContains($string, $created_file);
+ if (empty($created_file) || !file_exists($created_file)) {
+ throw new \RuntimeException('File does not exist.');
+ }
+
+ $actual = File::fileContains($string, $created_file);
$this->assertEquals($expected, $actual);
}
@@ -80,7 +173,7 @@ public function testDirContains(string $string, array $files, mixed $expected):
$files = $this->flattenFileTree($files, $tokens_dir);
$this->createFixtureFiles($files, $tokens_dir);
- $actual = $this->callProtectedMethod(InstallCommand::class, 'dirContains', [$string, $this->fixtureDir]);
+ $actual = File::dirContains($string, $this->fixtureDir);
$this->assertEquals($expected, $actual);
}
@@ -111,11 +204,15 @@ public function testRemoveTokenFromFile(string $file, string $begin, string $end
$expected_files = $this->flattenFileTree([$expected_file], $tokens_dir);
$expected_file = reset($expected_files);
+ if (empty($created_file) || !file_exists($created_file)) {
+ throw new \RuntimeException('File does not exist.');
+ }
+
if ($expect_exception) {
$this->expectException(\RuntimeException::class);
}
- InstallCommand::removeTokenFromFile($created_file, $begin, $end, $with_content);
+ File::removeTokenFromFile($created_file, $begin, $end, $with_content);
$this->assertFileEquals($expected_file, $created_file);
}
@@ -170,7 +267,7 @@ public function testDirReplaceContent(array $files, array $expected_files): void
throw new \RuntimeException('Provided files number is not equal to expected files number.');
}
- $this->callProtectedMethod(InstallCommand::class, 'dirReplaceContent', ['BAR', 'FOO', $this->fixtureDir]);
+ File::dirReplaceContent('BAR', 'FOO', $this->fixtureDir);
foreach (array_keys($created_files) as $k) {
$this->assertFileEquals($expected_files[$k], $created_files[$k]);
@@ -208,7 +305,7 @@ public function testReplaceStringFilename(array $files, array $expected_files):
throw new \RuntimeException('Provided files number is not equal to expected files number.');
}
- $this->callProtectedMethod(InstallCommand::class, 'replaceStringFilename', ['foo', 'bar', $this->fixtureDir]);
+ File::replaceStringFilename('foo', 'bar', $this->fixtureDir);
foreach (array_keys($expected_files) as $k) {
$this->assertFileExists($expected_files[$k]);
diff --git a/.vortex/installer/tests/phpunit/Unit/UnitTestBase.php b/.vortex/installer/tests/phpunit/Unit/UnitTestBase.php
index 4ac64dcba..d65299992 100644
--- a/.vortex/installer/tests/phpunit/Unit/UnitTestBase.php
+++ b/.vortex/installer/tests/phpunit/Unit/UnitTestBase.php
@@ -1,8 +1,10 @@
fixtureDir = InstallCommand::tempdir();
+ // Using createTempdir() from the install file itself.
+ $this->fixtureDir = File::createTempdir();
}
/**
@@ -46,10 +48,17 @@ public function cleanupFixtureDir(): void {
/**
* Create fixture files.
*
+ * @param array $files
+ * Files to create.
+ * @param string|null $basedir
+ * Base directory.
+ * @param bool $append_rand
+ * Append random number to the file name.
+ *
* @return string[]
* Created file names.
*/
- protected function createFixtureFiles($files, $basedir = NULL, $append_rand = TRUE): array {
+ protected function createFixtureFiles(array $files, ?string $basedir = NULL, bool $append_rand = TRUE): array {
$fs = new Filesystem();
$created = [];
diff --git a/.vortex/tests/bats/_helper.bash b/.vortex/tests/bats/_helper.bash
index e97bb7d0e..45502d67e 100644
--- a/.vortex/tests/bats/_helper.bash
+++ b/.vortex/tests/bats/_helper.bash
@@ -144,7 +144,7 @@ setup() {
# Demo DB is what is being downloaded when the installer runs for the first
# time do demonstrate downloading from CURL and importing from the DB dump
# functionality.
- export VORTEX_INSTALL_DEMO_DB_TEST=https://github.com/drevops/vortex/releases/download/1.18.0/db_d10.test.sql
+ export VORTEX_INSTALL_DEMO_DB_TEST=https://github.com/drevops/vortex/releases/download/24.11.0/db_d11.test.sql
##
## Phase 5: SUT files setup.
@@ -271,6 +271,7 @@ assert_files_not_present_common() {
assert_dir_not_exists "${webroot}/themes/custom/your_site_theme"
assert_dir_not_exists "${webroot}/profiles/custom/${suffix}_profile"
assert_dir_not_exists "${webroot}/modules/custom/${suffix_abbreviated}_core"
+ assert_dir_not_exists "${webroot}/modules/custom/${suffix_abbreviated}_search"
assert_dir_not_exists "${webroot}/themes/custom/${suffix}"
assert_file_not_exists "${webroot}/sites/default/default.settings.local.php"
assert_file_not_exists "${webroot}/sites/default/default.services.local.yml"
@@ -309,7 +310,7 @@ assert_files_present_vortex() {
pushd "${dir}" >/dev/null || exit 1
assert_file_exists ".docker/cli.dockerfile"
- assert_file_exists ".docker/mariadb.dockerfile"
+ assert_file_exists ".docker/database.dockerfile"
assert_file_exists ".docker/nginx-drupal.dockerfile"
assert_file_exists ".docker/php.dockerfile"
assert_file_exists ".docker/solr.dockerfile"
@@ -474,6 +475,10 @@ assert_files_present_drupal() {
assert_file_exists "${webroot}/modules/custom/${suffix_abbreviated}_core/tests/src/Functional/${suffix_abbreviated_camel_cased}CoreFunctionalTestBase.php"
assert_file_exists "${webroot}/modules/custom/${suffix_abbreviated}_core/tests/src/Functional/ExampleTest.php"
+ # Site search module created.
+ assert_dir_exists "${webroot}/modules/custom/${suffix_abbreviated}_search"
+ assert_file_exists "${webroot}/modules/custom/${suffix_abbreviated}_search/${suffix_abbreviated}_search.info.yml"
+
# Site theme created.
assert_dir_exists "${webroot}/themes/custom/${suffix}"
assert_file_exists "${webroot}/themes/custom/${suffix}/js/${suffix}.js"
@@ -840,7 +845,7 @@ assert_files_present_integration_acquia() {
assert_symlink_not_exists "hooks/prod/post-db-copy"
assert_file_exists "${webroot}/sites/default/includes/providers/settings.acquia.php"
- assert_file_contains "${webroot}/.htaccess" "RewriteCond %{ENV:AH_SITE_ENVIRONMENT} prod [NC]"
+ assert_file_contains "${webroot}/.htaccess" "RewriteCond %{HTTP_HOST} !\.acquia-sites\.com [NC]"
if [ "${include_scripts:-}" -eq 1 ]; then
assert_dir_exists "scripts"
@@ -862,7 +867,7 @@ assert_files_present_no_integration_acquia() {
assert_dir_not_exists "hooks"
assert_dir_not_exists "hooks/library"
assert_file_not_exists "${webroot}sites/default/includes/providers/settings.acquia.php"
- assert_file_not_contains "${webroot}/.htaccess" "RewriteCond %{ENV:AH_SITE_ENVIRONMENT} prod [NC]"
+ assert_file_not_contains "${webroot}/.htaccess" "RewriteCond %{HTTP_HOST} !\.acquia-sites\.com [NC]"
assert_file_not_contains ".env" "VORTEX_ACQUIA_APP_NAME="
assert_file_not_contains ".env" "VORTEX_DB_DOWNLOAD_ACQUIA_DB_NAME="
assert_file_not_contains ".ahoy.yml" "VORTEX_ACQUIA_APP_NAME="
diff --git a/.vortex/tests/bats/_helper.workflow.bash b/.vortex/tests/bats/_helper.workflow.bash
index a91cf3abf..331c1d382 100644
--- a/.vortex/tests/bats/_helper.workflow.bash
+++ b/.vortex/tests/bats/_helper.workflow.bash
@@ -94,6 +94,7 @@ assert_ahoy_build() {
process_ahoyyml
run ahoy build
+ assert_success
run sync_to_host
# Assert that lock files were created.
@@ -239,7 +240,7 @@ assert_timezone() {
assert_output_contains "AE"
run docker compose exec nginx date
assert_output_contains "AE"
- run docker compose exec mariadb date
+ run docker compose exec database date
assert_output_contains "AE"
# Add variable to the .env file and apply the change to container.
@@ -253,7 +254,7 @@ assert_timezone() {
assert_output_contains "AWST"
run docker compose exec nginx date
assert_output_contains "AWST"
- run docker compose exec mariadb date
+ run docker compose exec database date
assert_output_contains "AWST"
# Restore file, apply changes and assert that original behaviour has been restored.
@@ -292,7 +293,7 @@ assert_ahoy_info() {
assert_output_contains "Docker Compose project name : star_wars"
assert_output_contains "Site local URL : http://star_wars.docker.amazee.io"
assert_output_contains "Path to web root : /app/${webroot}"
- assert_output_contains "DB host : mariadb"
+ assert_output_contains "DB host : database"
assert_output_contains "DB username : drupal"
assert_output_contains "DB password : drupal"
assert_output_contains "DB port : 3306"
@@ -434,7 +435,8 @@ assert_ahoy_test_unit() {
step "Run Drupal Unit tests"
substep "Run all Unit tests"
- run ahoy test-unit
+ # @todo: Add coverage check.
+ run ahoy test-unit --no-coverage
assert_success
assert_output_contains "OK ("
sync_to_host
@@ -459,7 +461,8 @@ assert_ahoy_test_kernel() {
step "Run Drupal Kernel tests"
substep "Run all Kernel tests"
- run ahoy test-kernel
+ # @todo: Add coverage check.
+ run ahoy test-kernel --no-coverage
assert_success
assert_output_contains "OK ("
sync_to_host
@@ -484,7 +487,8 @@ assert_ahoy_test_functional() {
step "Run Drupal Functional tests"
substep "Run all Functional tests"
- run ahoy test-functional
+ # @todo: Add coverage check.
+ run ahoy test-functional --no-coverage
assert_success
assert_output_contains "OK ("
sync_to_host
diff --git a/.vortex/tests/bats/circleci.bats b/.vortex/tests/bats/circleci.bats
index 06a285035..5e0116d27 100644
--- a/.vortex/tests/bats/circleci.bats
+++ b/.vortex/tests/bats/circleci.bats
@@ -28,7 +28,8 @@ load _helper.circleci.bash
assert_contains "homepage.feature" "${artifact_path_runner_0}"
assert_contains "login.feature" "${artifact_path_runner_0}"
- assert_contains "clamav.feature" "${artifact_path_runner_0}"
+ # @see https://github.com/drevops/vortex/issues/1461
+ # assert_contains "clamav.feature" "${artifact_path_runner_0}"
assert_not_contains "search.feature" "${artifact_path_runner_0}"
artifact_path_runner_1="$(echo "${artifacts_data}" | jq -r '.items | map(select(.node_index == 1).path) | join("\n")')"
@@ -37,7 +38,8 @@ load _helper.circleci.bash
assert_contains "homepage.feature" "${artifact_path_runner_1}"
assert_contains "login.feature" "${artifact_path_runner_1}"
- assert_not_contains "clamav.feature" "${artifact_path_runner_1}"
+ # @see https://github.com/drevops/vortex/issues/1461
+ # assert_not_contains "clamav.feature" "${artifact_path_runner_1}"
assert_contains "search.feature" "${artifact_path_runner_1}"
done
}
@@ -66,7 +68,8 @@ load _helper.circleci.bash
assert_contains "homepage.feature" "${tests_data}"
assert_contains "login.feature" "${tests_data}"
- assert_contains "clamav.feature" "${tests_data}"
+ # @see https://github.com/drevops/vortex/issues/1461
+ # assert_contains "clamav.feature" "${tests_data}"
assert_contains "search.feature" "${tests_data}"
done
}
diff --git a/.vortex/tests/bats/fixtures/docker-compose.env.json b/.vortex/tests/bats/fixtures/docker-compose.env.json
index 5c1c6d68d..892915571 100644
--- a/.vortex/tests/bats/fixtures/docker-compose.env.json
+++ b/.vortex/tests/bats/fixtures/docker-compose.env.json
@@ -18,6 +18,11 @@
"entrypoint": null,
"environment": {
"CI": "true",
+ "DATABASE_HOST": "database",
+ "DATABASE_NAME": "drupal",
+ "DATABASE_PASSWORD": "drupal",
+ "DATABASE_PORT": "3306",
+ "DATABASE_USERNAME": "drupal",
"DRUPAL_CONFIG_PATH": "/app/config/default",
"DRUPAL_PRIVATE_FILES": "/app/web/sites/default/files/private",
"DRUPAL_PUBLIC_FILES": "/app/web/sites/default/files",
@@ -26,11 +31,6 @@
"DRUPAL_SHIELD_USER": "",
"DRUPAL_TEMPORARY_FILES": "/tmp",
"LAGOON_ROUTE": "star_wars.docker.amazee.io",
- "MARIADB_DATABASE": "drupal",
- "MARIADB_HOST": "mariadb",
- "MARIADB_PASSWORD": "drupal",
- "MARIADB_PORT": "3306",
- "MARIADB_USERNAME": "drupal",
"TZ": "Australia/Melbourne",
"VORTEX_LOCALDEV_URL": "star_wars.docker.amazee.io",
"XDEBUG_ENABLE": ""
@@ -74,6 +74,11 @@
"entrypoint": null,
"environment": {
"CI": "true",
+ "DATABASE_HOST": "database",
+ "DATABASE_NAME": "drupal",
+ "DATABASE_PASSWORD": "drupal",
+ "DATABASE_PORT": "3306",
+ "DATABASE_USERNAME": "drupal",
"DRUPAL_CONFIG_PATH": "/app/config/default",
"DRUPAL_PRIVATE_FILES": "/app/web/sites/default/files/private",
"DRUPAL_PUBLIC_FILES": "/app/web/sites/default/files",
@@ -82,11 +87,6 @@
"DRUPAL_SHIELD_USER": "",
"DRUPAL_TEMPORARY_FILES": "/tmp",
"LAGOON_ROUTE": "star_wars.docker.amazee.io",
- "MARIADB_DATABASE": "drupal",
- "MARIADB_HOST": "mariadb",
- "MARIADB_PASSWORD": "drupal",
- "MARIADB_PORT": "3306",
- "MARIADB_USERNAME": "drupal",
"TZ": "Australia/Melbourne",
"VORTEX_LOCALDEV_URL": "star_wars.docker.amazee.io",
"XDEBUG_ENABLE": ""
@@ -121,6 +121,11 @@
"entrypoint": null,
"environment": {
"CI": "true",
+ "DATABASE_HOST": "database",
+ "DATABASE_NAME": "drupal",
+ "DATABASE_PASSWORD": "drupal",
+ "DATABASE_PORT": "3306",
+ "DATABASE_USERNAME": "drupal",
"DRUPAL_CONFIG_PATH": "/app/config/default",
"DRUPAL_PRIVATE_FILES": "/app/web/sites/default/files/private",
"DRUPAL_PUBLIC_FILES": "/app/web/sites/default/files",
@@ -129,11 +134,6 @@
"DRUPAL_SHIELD_USER": "",
"DRUPAL_TEMPORARY_FILES": "/tmp",
"LAGOON_ROUTE": "star_wars.docker.amazee.io",
- "MARIADB_DATABASE": "drupal",
- "MARIADB_HOST": "mariadb",
- "MARIADB_PASSWORD": "drupal",
- "MARIADB_PORT": "3306",
- "MARIADB_USERNAME": "drupal",
"TZ": "Australia/Melbourne",
"VORTEX_LOCALDEV_URL": "star_wars.docker.amazee.io",
"XDEBUG_ENABLE": ""
@@ -163,18 +163,23 @@
}
]
},
- "mariadb": {
+ "database": {
"build": {
"args": {
"IMAGE": "uselagoon/mariadb-10.11-drupal:VERSION"
},
"context": "FIXTURE_CUR_DIR",
- "dockerfile": ".docker/mariadb.dockerfile"
+ "dockerfile": ".docker/database.dockerfile"
},
"command": null,
"entrypoint": null,
"environment": {
"CI": "true",
+ "DATABASE_HOST": "database",
+ "DATABASE_NAME": "drupal",
+ "DATABASE_PASSWORD": "drupal",
+ "DATABASE_PORT": "3306",
+ "DATABASE_USERNAME": "drupal",
"DRUPAL_CONFIG_PATH": "/app/config/default",
"DRUPAL_PRIVATE_FILES": "/app/web/sites/default/files/private",
"DRUPAL_PUBLIC_FILES": "/app/web/sites/default/files",
@@ -183,11 +188,6 @@
"DRUPAL_SHIELD_USER": "",
"DRUPAL_TEMPORARY_FILES": "/tmp",
"LAGOON_ROUTE": "star_wars.docker.amazee.io",
- "MARIADB_DATABASE": "drupal",
- "MARIADB_HOST": "mariadb",
- "MARIADB_PASSWORD": "drupal",
- "MARIADB_PORT": "3306",
- "MARIADB_USERNAME": "drupal",
"TZ": "Australia/Melbourne",
"VORTEX_LOCALDEV_URL": "star_wars.docker.amazee.io",
"XDEBUG_ENABLE": ""
@@ -230,6 +230,11 @@
"entrypoint": null,
"environment": {
"CI": "true",
+ "DATABASE_HOST": "database",
+ "DATABASE_NAME": "drupal",
+ "DATABASE_PASSWORD": "drupal",
+ "DATABASE_PORT": "3306",
+ "DATABASE_USERNAME": "drupal",
"DRUPAL_CONFIG_PATH": "/app/config/default",
"DRUPAL_PRIVATE_FILES": "/app/web/sites/default/files/private",
"DRUPAL_PUBLIC_FILES": "/app/web/sites/default/files",
@@ -238,11 +243,6 @@
"DRUPAL_SHIELD_USER": "",
"DRUPAL_TEMPORARY_FILES": "/tmp",
"LAGOON_ROUTE": "star_wars.docker.amazee.io",
- "MARIADB_DATABASE": "drupal",
- "MARIADB_HOST": "mariadb",
- "MARIADB_PASSWORD": "drupal",
- "MARIADB_PORT": "3306",
- "MARIADB_USERNAME": "drupal",
"TZ": "Australia/Melbourne",
"VORTEX_LOCALDEV_URL": "star_wars.docker.amazee.io",
"XDEBUG_ENABLE": ""
@@ -290,6 +290,11 @@
"entrypoint": null,
"environment": {
"CI": "true",
+ "DATABASE_HOST": "database",
+ "DATABASE_NAME": "drupal",
+ "DATABASE_PASSWORD": "drupal",
+ "DATABASE_PORT": "3306",
+ "DATABASE_USERNAME": "drupal",
"DRUPAL_CONFIG_PATH": "/app/config/default",
"DRUPAL_PRIVATE_FILES": "/app/web/sites/default/files/private",
"DRUPAL_PUBLIC_FILES": "/app/web/sites/default/files",
@@ -298,11 +303,6 @@
"DRUPAL_SHIELD_USER": "",
"DRUPAL_TEMPORARY_FILES": "/tmp",
"LAGOON_ROUTE": "star_wars.docker.amazee.io",
- "MARIADB_DATABASE": "drupal",
- "MARIADB_HOST": "mariadb",
- "MARIADB_PASSWORD": "drupal",
- "MARIADB_PORT": "3306",
- "MARIADB_USERNAME": "drupal",
"TZ": "Australia/Melbourne",
"VORTEX_LOCALDEV_URL": "star_wars.docker.amazee.io",
"XDEBUG_ENABLE": ""
@@ -361,6 +361,11 @@
"entrypoint": null,
"environment": {
"CI": "true",
+ "DATABASE_HOST": "database",
+ "DATABASE_NAME": "drupal",
+ "DATABASE_PASSWORD": "drupal",
+ "DATABASE_PORT": "3306",
+ "DATABASE_USERNAME": "drupal",
"DRUPAL_CONFIG_PATH": "/app/config/default",
"DRUPAL_PRIVATE_FILES": "/app/web/sites/default/files/private",
"DRUPAL_PUBLIC_FILES": "/app/web/sites/default/files",
@@ -369,11 +374,6 @@
"DRUPAL_SHIELD_USER": "",
"DRUPAL_TEMPORARY_FILES": "/tmp",
"LAGOON_ROUTE": "star_wars.docker.amazee.io",
- "MARIADB_DATABASE": "drupal",
- "MARIADB_HOST": "mariadb",
- "MARIADB_PASSWORD": "drupal",
- "MARIADB_PORT": "3306",
- "MARIADB_USERNAME": "drupal",
"TZ": "Australia/Melbourne",
"VORTEX_LOCALDEV_URL": "star_wars.docker.amazee.io",
"XDEBUG_ENABLE": ""
@@ -402,7 +402,7 @@
},
"wait_dependencies": {
"command": [
- "mariadb:3306",
+ "database:3306",
"clamav:3310"
],
"depends_on": {
@@ -414,7 +414,7 @@
"condition": "service_started",
"required": true
},
- "mariadb": {
+ "database": {
"condition": "service_started",
"required": true
}
diff --git a/.vortex/tests/bats/fixtures/docker-compose.env_local.json b/.vortex/tests/bats/fixtures/docker-compose.env_local.json
index 5c1c6d68d..892915571 100644
--- a/.vortex/tests/bats/fixtures/docker-compose.env_local.json
+++ b/.vortex/tests/bats/fixtures/docker-compose.env_local.json
@@ -18,6 +18,11 @@
"entrypoint": null,
"environment": {
"CI": "true",
+ "DATABASE_HOST": "database",
+ "DATABASE_NAME": "drupal",
+ "DATABASE_PASSWORD": "drupal",
+ "DATABASE_PORT": "3306",
+ "DATABASE_USERNAME": "drupal",
"DRUPAL_CONFIG_PATH": "/app/config/default",
"DRUPAL_PRIVATE_FILES": "/app/web/sites/default/files/private",
"DRUPAL_PUBLIC_FILES": "/app/web/sites/default/files",
@@ -26,11 +31,6 @@
"DRUPAL_SHIELD_USER": "",
"DRUPAL_TEMPORARY_FILES": "/tmp",
"LAGOON_ROUTE": "star_wars.docker.amazee.io",
- "MARIADB_DATABASE": "drupal",
- "MARIADB_HOST": "mariadb",
- "MARIADB_PASSWORD": "drupal",
- "MARIADB_PORT": "3306",
- "MARIADB_USERNAME": "drupal",
"TZ": "Australia/Melbourne",
"VORTEX_LOCALDEV_URL": "star_wars.docker.amazee.io",
"XDEBUG_ENABLE": ""
@@ -74,6 +74,11 @@
"entrypoint": null,
"environment": {
"CI": "true",
+ "DATABASE_HOST": "database",
+ "DATABASE_NAME": "drupal",
+ "DATABASE_PASSWORD": "drupal",
+ "DATABASE_PORT": "3306",
+ "DATABASE_USERNAME": "drupal",
"DRUPAL_CONFIG_PATH": "/app/config/default",
"DRUPAL_PRIVATE_FILES": "/app/web/sites/default/files/private",
"DRUPAL_PUBLIC_FILES": "/app/web/sites/default/files",
@@ -82,11 +87,6 @@
"DRUPAL_SHIELD_USER": "",
"DRUPAL_TEMPORARY_FILES": "/tmp",
"LAGOON_ROUTE": "star_wars.docker.amazee.io",
- "MARIADB_DATABASE": "drupal",
- "MARIADB_HOST": "mariadb",
- "MARIADB_PASSWORD": "drupal",
- "MARIADB_PORT": "3306",
- "MARIADB_USERNAME": "drupal",
"TZ": "Australia/Melbourne",
"VORTEX_LOCALDEV_URL": "star_wars.docker.amazee.io",
"XDEBUG_ENABLE": ""
@@ -121,6 +121,11 @@
"entrypoint": null,
"environment": {
"CI": "true",
+ "DATABASE_HOST": "database",
+ "DATABASE_NAME": "drupal",
+ "DATABASE_PASSWORD": "drupal",
+ "DATABASE_PORT": "3306",
+ "DATABASE_USERNAME": "drupal",
"DRUPAL_CONFIG_PATH": "/app/config/default",
"DRUPAL_PRIVATE_FILES": "/app/web/sites/default/files/private",
"DRUPAL_PUBLIC_FILES": "/app/web/sites/default/files",
@@ -129,11 +134,6 @@
"DRUPAL_SHIELD_USER": "",
"DRUPAL_TEMPORARY_FILES": "/tmp",
"LAGOON_ROUTE": "star_wars.docker.amazee.io",
- "MARIADB_DATABASE": "drupal",
- "MARIADB_HOST": "mariadb",
- "MARIADB_PASSWORD": "drupal",
- "MARIADB_PORT": "3306",
- "MARIADB_USERNAME": "drupal",
"TZ": "Australia/Melbourne",
"VORTEX_LOCALDEV_URL": "star_wars.docker.amazee.io",
"XDEBUG_ENABLE": ""
@@ -163,18 +163,23 @@
}
]
},
- "mariadb": {
+ "database": {
"build": {
"args": {
"IMAGE": "uselagoon/mariadb-10.11-drupal:VERSION"
},
"context": "FIXTURE_CUR_DIR",
- "dockerfile": ".docker/mariadb.dockerfile"
+ "dockerfile": ".docker/database.dockerfile"
},
"command": null,
"entrypoint": null,
"environment": {
"CI": "true",
+ "DATABASE_HOST": "database",
+ "DATABASE_NAME": "drupal",
+ "DATABASE_PASSWORD": "drupal",
+ "DATABASE_PORT": "3306",
+ "DATABASE_USERNAME": "drupal",
"DRUPAL_CONFIG_PATH": "/app/config/default",
"DRUPAL_PRIVATE_FILES": "/app/web/sites/default/files/private",
"DRUPAL_PUBLIC_FILES": "/app/web/sites/default/files",
@@ -183,11 +188,6 @@
"DRUPAL_SHIELD_USER": "",
"DRUPAL_TEMPORARY_FILES": "/tmp",
"LAGOON_ROUTE": "star_wars.docker.amazee.io",
- "MARIADB_DATABASE": "drupal",
- "MARIADB_HOST": "mariadb",
- "MARIADB_PASSWORD": "drupal",
- "MARIADB_PORT": "3306",
- "MARIADB_USERNAME": "drupal",
"TZ": "Australia/Melbourne",
"VORTEX_LOCALDEV_URL": "star_wars.docker.amazee.io",
"XDEBUG_ENABLE": ""
@@ -230,6 +230,11 @@
"entrypoint": null,
"environment": {
"CI": "true",
+ "DATABASE_HOST": "database",
+ "DATABASE_NAME": "drupal",
+ "DATABASE_PASSWORD": "drupal",
+ "DATABASE_PORT": "3306",
+ "DATABASE_USERNAME": "drupal",
"DRUPAL_CONFIG_PATH": "/app/config/default",
"DRUPAL_PRIVATE_FILES": "/app/web/sites/default/files/private",
"DRUPAL_PUBLIC_FILES": "/app/web/sites/default/files",
@@ -238,11 +243,6 @@
"DRUPAL_SHIELD_USER": "",
"DRUPAL_TEMPORARY_FILES": "/tmp",
"LAGOON_ROUTE": "star_wars.docker.amazee.io",
- "MARIADB_DATABASE": "drupal",
- "MARIADB_HOST": "mariadb",
- "MARIADB_PASSWORD": "drupal",
- "MARIADB_PORT": "3306",
- "MARIADB_USERNAME": "drupal",
"TZ": "Australia/Melbourne",
"VORTEX_LOCALDEV_URL": "star_wars.docker.amazee.io",
"XDEBUG_ENABLE": ""
@@ -290,6 +290,11 @@
"entrypoint": null,
"environment": {
"CI": "true",
+ "DATABASE_HOST": "database",
+ "DATABASE_NAME": "drupal",
+ "DATABASE_PASSWORD": "drupal",
+ "DATABASE_PORT": "3306",
+ "DATABASE_USERNAME": "drupal",
"DRUPAL_CONFIG_PATH": "/app/config/default",
"DRUPAL_PRIVATE_FILES": "/app/web/sites/default/files/private",
"DRUPAL_PUBLIC_FILES": "/app/web/sites/default/files",
@@ -298,11 +303,6 @@
"DRUPAL_SHIELD_USER": "",
"DRUPAL_TEMPORARY_FILES": "/tmp",
"LAGOON_ROUTE": "star_wars.docker.amazee.io",
- "MARIADB_DATABASE": "drupal",
- "MARIADB_HOST": "mariadb",
- "MARIADB_PASSWORD": "drupal",
- "MARIADB_PORT": "3306",
- "MARIADB_USERNAME": "drupal",
"TZ": "Australia/Melbourne",
"VORTEX_LOCALDEV_URL": "star_wars.docker.amazee.io",
"XDEBUG_ENABLE": ""
@@ -361,6 +361,11 @@
"entrypoint": null,
"environment": {
"CI": "true",
+ "DATABASE_HOST": "database",
+ "DATABASE_NAME": "drupal",
+ "DATABASE_PASSWORD": "drupal",
+ "DATABASE_PORT": "3306",
+ "DATABASE_USERNAME": "drupal",
"DRUPAL_CONFIG_PATH": "/app/config/default",
"DRUPAL_PRIVATE_FILES": "/app/web/sites/default/files/private",
"DRUPAL_PUBLIC_FILES": "/app/web/sites/default/files",
@@ -369,11 +374,6 @@
"DRUPAL_SHIELD_USER": "",
"DRUPAL_TEMPORARY_FILES": "/tmp",
"LAGOON_ROUTE": "star_wars.docker.amazee.io",
- "MARIADB_DATABASE": "drupal",
- "MARIADB_HOST": "mariadb",
- "MARIADB_PASSWORD": "drupal",
- "MARIADB_PORT": "3306",
- "MARIADB_USERNAME": "drupal",
"TZ": "Australia/Melbourne",
"VORTEX_LOCALDEV_URL": "star_wars.docker.amazee.io",
"XDEBUG_ENABLE": ""
@@ -402,7 +402,7 @@
},
"wait_dependencies": {
"command": [
- "mariadb:3306",
+ "database:3306",
"clamav:3310"
],
"depends_on": {
@@ -414,7 +414,7 @@
"condition": "service_started",
"required": true
},
- "mariadb": {
+ "database": {
"condition": "service_started",
"required": true
}
diff --git a/.vortex/tests/bats/fixtures/docker-compose.env_mod.json b/.vortex/tests/bats/fixtures/docker-compose.env_mod.json
index 2faa6e947..e0a019da5 100644
--- a/.vortex/tests/bats/fixtures/docker-compose.env_mod.json
+++ b/.vortex/tests/bats/fixtures/docker-compose.env_mod.json
@@ -18,6 +18,11 @@
"entrypoint": null,
"environment": {
"CI": "true",
+ "DATABASE_HOST": "database",
+ "DATABASE_NAME": "drupal",
+ "DATABASE_PASSWORD": "drupal",
+ "DATABASE_PORT": "3306",
+ "DATABASE_USERNAME": "drupal",
"DRUPAL_CONFIG_PATH": "/app/config/default",
"DRUPAL_PRIVATE_FILES": "/app/web/sites/default/files/private",
"DRUPAL_PUBLIC_FILES": "/app/web/sites/default/files",
@@ -26,11 +31,6 @@
"DRUPAL_SHIELD_USER": "jane",
"DRUPAL_TEMPORARY_FILES": "/tmp",
"LAGOON_ROUTE": "the_matrix.docker.amazee.io",
- "MARIADB_DATABASE": "drupal",
- "MARIADB_HOST": "mariadb",
- "MARIADB_PASSWORD": "drupal",
- "MARIADB_PORT": "3306",
- "MARIADB_USERNAME": "drupal",
"TZ": "Australia/Melbourne",
"VORTEX_LOCALDEV_URL": "the_matrix.docker.amazee.io",
"XDEBUG_ENABLE": "1"
@@ -74,6 +74,11 @@
"entrypoint": null,
"environment": {
"CI": "true",
+ "DATABASE_HOST": "database",
+ "DATABASE_NAME": "drupal",
+ "DATABASE_PASSWORD": "drupal",
+ "DATABASE_PORT": "3306",
+ "DATABASE_USERNAME": "drupal",
"DRUPAL_CONFIG_PATH": "/app/config/default",
"DRUPAL_PRIVATE_FILES": "/app/web/sites/default/files/private",
"DRUPAL_PUBLIC_FILES": "/app/web/sites/default/files",
@@ -82,11 +87,6 @@
"DRUPAL_SHIELD_USER": "jane",
"DRUPAL_TEMPORARY_FILES": "/tmp",
"LAGOON_ROUTE": "the_matrix.docker.amazee.io",
- "MARIADB_DATABASE": "drupal",
- "MARIADB_HOST": "mariadb",
- "MARIADB_PASSWORD": "drupal",
- "MARIADB_PORT": "3306",
- "MARIADB_USERNAME": "drupal",
"TZ": "Australia/Melbourne",
"VORTEX_LOCALDEV_URL": "the_matrix.docker.amazee.io",
"XDEBUG_ENABLE": "1"
@@ -121,6 +121,11 @@
"entrypoint": null,
"environment": {
"CI": "true",
+ "DATABASE_HOST": "database",
+ "DATABASE_NAME": "drupal",
+ "DATABASE_PASSWORD": "drupal",
+ "DATABASE_PORT": "3306",
+ "DATABASE_USERNAME": "drupal",
"DRUPAL_CONFIG_PATH": "/app/config/default",
"DRUPAL_PRIVATE_FILES": "/app/web/sites/default/files/private",
"DRUPAL_PUBLIC_FILES": "/app/web/sites/default/files",
@@ -129,11 +134,6 @@
"DRUPAL_SHIELD_USER": "jane",
"DRUPAL_TEMPORARY_FILES": "/tmp",
"LAGOON_ROUTE": "the_matrix.docker.amazee.io",
- "MARIADB_DATABASE": "drupal",
- "MARIADB_HOST": "mariadb",
- "MARIADB_PASSWORD": "drupal",
- "MARIADB_PORT": "3306",
- "MARIADB_USERNAME": "drupal",
"TZ": "Australia/Melbourne",
"VORTEX_LOCALDEV_URL": "the_matrix.docker.amazee.io",
"XDEBUG_ENABLE": "1"
@@ -163,18 +163,23 @@
}
]
},
- "mariadb": {
+ "database": {
"build": {
"args": {
"IMAGE": "myorg/my_db_image"
},
"context": "FIXTURE_CUR_DIR",
- "dockerfile": ".docker/mariadb.dockerfile"
+ "dockerfile": ".docker/database.dockerfile"
},
"command": null,
"entrypoint": null,
"environment": {
"CI": "true",
+ "DATABASE_HOST": "database",
+ "DATABASE_NAME": "drupal",
+ "DATABASE_PASSWORD": "drupal",
+ "DATABASE_PORT": "3306",
+ "DATABASE_USERNAME": "drupal",
"DRUPAL_CONFIG_PATH": "/app/config/default",
"DRUPAL_PRIVATE_FILES": "/app/web/sites/default/files/private",
"DRUPAL_PUBLIC_FILES": "/app/web/sites/default/files",
@@ -183,11 +188,6 @@
"DRUPAL_SHIELD_USER": "jane",
"DRUPAL_TEMPORARY_FILES": "/tmp",
"LAGOON_ROUTE": "the_matrix.docker.amazee.io",
- "MARIADB_DATABASE": "drupal",
- "MARIADB_HOST": "mariadb",
- "MARIADB_PASSWORD": "drupal",
- "MARIADB_PORT": "3306",
- "MARIADB_USERNAME": "drupal",
"TZ": "Australia/Melbourne",
"VORTEX_LOCALDEV_URL": "the_matrix.docker.amazee.io",
"XDEBUG_ENABLE": "1"
@@ -230,6 +230,11 @@
"entrypoint": null,
"environment": {
"CI": "true",
+ "DATABASE_HOST": "database",
+ "DATABASE_NAME": "drupal",
+ "DATABASE_PASSWORD": "drupal",
+ "DATABASE_PORT": "3306",
+ "DATABASE_USERNAME": "drupal",
"DRUPAL_CONFIG_PATH": "/app/config/default",
"DRUPAL_PRIVATE_FILES": "/app/web/sites/default/files/private",
"DRUPAL_PUBLIC_FILES": "/app/web/sites/default/files",
@@ -238,11 +243,6 @@
"DRUPAL_SHIELD_USER": "jane",
"DRUPAL_TEMPORARY_FILES": "/tmp",
"LAGOON_ROUTE": "the_matrix.docker.amazee.io",
- "MARIADB_DATABASE": "drupal",
- "MARIADB_HOST": "mariadb",
- "MARIADB_PASSWORD": "drupal",
- "MARIADB_PORT": "3306",
- "MARIADB_USERNAME": "drupal",
"TZ": "Australia/Melbourne",
"VORTEX_LOCALDEV_URL": "the_matrix.docker.amazee.io",
"XDEBUG_ENABLE": "1"
@@ -290,6 +290,11 @@
"entrypoint": null,
"environment": {
"CI": "true",
+ "DATABASE_HOST": "database",
+ "DATABASE_NAME": "drupal",
+ "DATABASE_PASSWORD": "drupal",
+ "DATABASE_PORT": "3306",
+ "DATABASE_USERNAME": "drupal",
"DRUPAL_CONFIG_PATH": "/app/config/default",
"DRUPAL_PRIVATE_FILES": "/app/web/sites/default/files/private",
"DRUPAL_PUBLIC_FILES": "/app/web/sites/default/files",
@@ -298,11 +303,6 @@
"DRUPAL_SHIELD_USER": "jane",
"DRUPAL_TEMPORARY_FILES": "/tmp",
"LAGOON_ROUTE": "the_matrix.docker.amazee.io",
- "MARIADB_DATABASE": "drupal",
- "MARIADB_HOST": "mariadb",
- "MARIADB_PASSWORD": "drupal",
- "MARIADB_PORT": "3306",
- "MARIADB_USERNAME": "drupal",
"TZ": "Australia/Melbourne",
"VORTEX_LOCALDEV_URL": "the_matrix.docker.amazee.io",
"XDEBUG_ENABLE": "1"
@@ -361,6 +361,11 @@
"entrypoint": null,
"environment": {
"CI": "true",
+ "DATABASE_HOST": "database",
+ "DATABASE_NAME": "drupal",
+ "DATABASE_PASSWORD": "drupal",
+ "DATABASE_PORT": "3306",
+ "DATABASE_USERNAME": "drupal",
"DRUPAL_CONFIG_PATH": "/app/config/default",
"DRUPAL_PRIVATE_FILES": "/app/web/sites/default/files/private",
"DRUPAL_PUBLIC_FILES": "/app/web/sites/default/files",
@@ -369,11 +374,6 @@
"DRUPAL_SHIELD_USER": "jane",
"DRUPAL_TEMPORARY_FILES": "/tmp",
"LAGOON_ROUTE": "the_matrix.docker.amazee.io",
- "MARIADB_DATABASE": "drupal",
- "MARIADB_HOST": "mariadb",
- "MARIADB_PASSWORD": "drupal",
- "MARIADB_PORT": "3306",
- "MARIADB_USERNAME": "drupal",
"TZ": "Australia/Melbourne",
"VORTEX_LOCALDEV_URL": "the_matrix.docker.amazee.io",
"XDEBUG_ENABLE": "1"
@@ -402,7 +402,7 @@
},
"wait_dependencies": {
"command": [
- "mariadb:3306",
+ "database:3306",
"clamav:3310"
],
"depends_on": {
@@ -414,7 +414,7 @@
"condition": "service_started",
"required": true
},
- "mariadb": {
+ "database": {
"condition": "service_started",
"required": true
}
diff --git a/.vortex/tests/bats/fixtures/docker-compose.noenv.json b/.vortex/tests/bats/fixtures/docker-compose.noenv.json
index b420522b2..e9f8fa7c4 100644
--- a/.vortex/tests/bats/fixtures/docker-compose.noenv.json
+++ b/.vortex/tests/bats/fixtures/docker-compose.noenv.json
@@ -18,6 +18,11 @@
"entrypoint": null,
"environment": {
"CI": "true",
+ "DATABASE_HOST": "database",
+ "DATABASE_NAME": "drupal",
+ "DATABASE_PASSWORD": "drupal",
+ "DATABASE_PORT": "3306",
+ "DATABASE_USERNAME": "drupal",
"DRUPAL_CONFIG_PATH": "/app/config/default",
"DRUPAL_PRIVATE_FILES": "/app/web/sites/default/files/private",
"DRUPAL_PUBLIC_FILES": "/app/web/sites/default/files",
@@ -26,11 +31,6 @@
"DRUPAL_SHIELD_USER": "",
"DRUPAL_TEMPORARY_FILES": "/tmp",
"LAGOON_ROUTE": "star_wars.docker.amazee.io",
- "MARIADB_DATABASE": "drupal",
- "MARIADB_HOST": "mariadb",
- "MARIADB_PASSWORD": "drupal",
- "MARIADB_PORT": "3306",
- "MARIADB_USERNAME": "drupal",
"TZ": "Australia/Melbourne",
"VORTEX_LOCALDEV_URL": "star_wars.docker.amazee.io",
"XDEBUG_ENABLE": ""
@@ -74,6 +74,11 @@
"entrypoint": null,
"environment": {
"CI": "true",
+ "DATABASE_HOST": "database",
+ "DATABASE_NAME": "drupal",
+ "DATABASE_PASSWORD": "drupal",
+ "DATABASE_PORT": "3306",
+ "DATABASE_USERNAME": "drupal",
"DRUPAL_CONFIG_PATH": "/app/config/default",
"DRUPAL_PRIVATE_FILES": "/app/web/sites/default/files/private",
"DRUPAL_PUBLIC_FILES": "/app/web/sites/default/files",
@@ -82,11 +87,6 @@
"DRUPAL_SHIELD_USER": "",
"DRUPAL_TEMPORARY_FILES": "/tmp",
"LAGOON_ROUTE": "star_wars.docker.amazee.io",
- "MARIADB_DATABASE": "drupal",
- "MARIADB_HOST": "mariadb",
- "MARIADB_PASSWORD": "drupal",
- "MARIADB_PORT": "3306",
- "MARIADB_USERNAME": "drupal",
"TZ": "Australia/Melbourne",
"VORTEX_LOCALDEV_URL": "star_wars.docker.amazee.io",
"XDEBUG_ENABLE": ""
@@ -121,6 +121,11 @@
"entrypoint": null,
"environment": {
"CI": "true",
+ "DATABASE_HOST": "database",
+ "DATABASE_NAME": "drupal",
+ "DATABASE_PASSWORD": "drupal",
+ "DATABASE_PORT": "3306",
+ "DATABASE_USERNAME": "drupal",
"DRUPAL_CONFIG_PATH": "/app/config/default",
"DRUPAL_PRIVATE_FILES": "/app/web/sites/default/files/private",
"DRUPAL_PUBLIC_FILES": "/app/web/sites/default/files",
@@ -129,11 +134,6 @@
"DRUPAL_SHIELD_USER": "",
"DRUPAL_TEMPORARY_FILES": "/tmp",
"LAGOON_ROUTE": "star_wars.docker.amazee.io",
- "MARIADB_DATABASE": "drupal",
- "MARIADB_HOST": "mariadb",
- "MARIADB_PASSWORD": "drupal",
- "MARIADB_PORT": "3306",
- "MARIADB_USERNAME": "drupal",
"TZ": "Australia/Melbourne",
"VORTEX_LOCALDEV_URL": "star_wars.docker.amazee.io",
"XDEBUG_ENABLE": ""
@@ -163,18 +163,23 @@
}
]
},
- "mariadb": {
+ "database": {
"build": {
"args": {
"IMAGE": "uselagoon/mariadb-10.11-drupal:VERSION"
},
"context": "FIXTURE_CUR_DIR",
- "dockerfile": ".docker/mariadb.dockerfile"
+ "dockerfile": ".docker/database.dockerfile"
},
"command": null,
"entrypoint": null,
"environment": {
"CI": "true",
+ "DATABASE_HOST": "database",
+ "DATABASE_NAME": "drupal",
+ "DATABASE_PASSWORD": "drupal",
+ "DATABASE_PORT": "3306",
+ "DATABASE_USERNAME": "drupal",
"DRUPAL_CONFIG_PATH": "/app/config/default",
"DRUPAL_PRIVATE_FILES": "/app/web/sites/default/files/private",
"DRUPAL_PUBLIC_FILES": "/app/web/sites/default/files",
@@ -183,11 +188,6 @@
"DRUPAL_SHIELD_USER": "",
"DRUPAL_TEMPORARY_FILES": "/tmp",
"LAGOON_ROUTE": "star_wars.docker.amazee.io",
- "MARIADB_DATABASE": "drupal",
- "MARIADB_HOST": "mariadb",
- "MARIADB_PASSWORD": "drupal",
- "MARIADB_PORT": "3306",
- "MARIADB_USERNAME": "drupal",
"TZ": "Australia/Melbourne",
"VORTEX_LOCALDEV_URL": "star_wars.docker.amazee.io",
"XDEBUG_ENABLE": ""
@@ -230,6 +230,11 @@
"entrypoint": null,
"environment": {
"CI": "true",
+ "DATABASE_HOST": "database",
+ "DATABASE_NAME": "drupal",
+ "DATABASE_PASSWORD": "drupal",
+ "DATABASE_PORT": "3306",
+ "DATABASE_USERNAME": "drupal",
"DRUPAL_CONFIG_PATH": "/app/config/default",
"DRUPAL_PRIVATE_FILES": "/app/web/sites/default/files/private",
"DRUPAL_PUBLIC_FILES": "/app/web/sites/default/files",
@@ -238,11 +243,6 @@
"DRUPAL_SHIELD_USER": "",
"DRUPAL_TEMPORARY_FILES": "/tmp",
"LAGOON_ROUTE": "star_wars.docker.amazee.io",
- "MARIADB_DATABASE": "drupal",
- "MARIADB_HOST": "mariadb",
- "MARIADB_PASSWORD": "drupal",
- "MARIADB_PORT": "3306",
- "MARIADB_USERNAME": "drupal",
"TZ": "Australia/Melbourne",
"VORTEX_LOCALDEV_URL": "star_wars.docker.amazee.io",
"XDEBUG_ENABLE": ""
@@ -290,6 +290,11 @@
"entrypoint": null,
"environment": {
"CI": "true",
+ "DATABASE_HOST": "database",
+ "DATABASE_NAME": "drupal",
+ "DATABASE_PASSWORD": "drupal",
+ "DATABASE_PORT": "3306",
+ "DATABASE_USERNAME": "drupal",
"DRUPAL_CONFIG_PATH": "/app/config/default",
"DRUPAL_PRIVATE_FILES": "/app/web/sites/default/files/private",
"DRUPAL_PUBLIC_FILES": "/app/web/sites/default/files",
@@ -298,11 +303,6 @@
"DRUPAL_SHIELD_USER": "",
"DRUPAL_TEMPORARY_FILES": "/tmp",
"LAGOON_ROUTE": "star_wars.docker.amazee.io",
- "MARIADB_DATABASE": "drupal",
- "MARIADB_HOST": "mariadb",
- "MARIADB_PASSWORD": "drupal",
- "MARIADB_PORT": "3306",
- "MARIADB_USERNAME": "drupal",
"TZ": "Australia/Melbourne",
"VORTEX_LOCALDEV_URL": "star_wars.docker.amazee.io",
"XDEBUG_ENABLE": ""
@@ -361,6 +361,11 @@
"entrypoint": null,
"environment": {
"CI": "true",
+ "DATABASE_HOST": "database",
+ "DATABASE_NAME": "drupal",
+ "DATABASE_PASSWORD": "drupal",
+ "DATABASE_PORT": "3306",
+ "DATABASE_USERNAME": "drupal",
"DRUPAL_CONFIG_PATH": "/app/config/default",
"DRUPAL_PRIVATE_FILES": "/app/web/sites/default/files/private",
"DRUPAL_PUBLIC_FILES": "/app/web/sites/default/files",
@@ -369,11 +374,6 @@
"DRUPAL_SHIELD_USER": "",
"DRUPAL_TEMPORARY_FILES": "/tmp",
"LAGOON_ROUTE": "star_wars.docker.amazee.io",
- "MARIADB_DATABASE": "drupal",
- "MARIADB_HOST": "mariadb",
- "MARIADB_PASSWORD": "drupal",
- "MARIADB_PORT": "3306",
- "MARIADB_USERNAME": "drupal",
"TZ": "Australia/Melbourne",
"VORTEX_LOCALDEV_URL": "star_wars.docker.amazee.io",
"XDEBUG_ENABLE": ""
@@ -402,7 +402,7 @@
},
"wait_dependencies": {
"command": [
- "mariadb:3306",
+ "database:3306",
"clamav:3310"
],
"depends_on": {
@@ -414,7 +414,7 @@
"condition": "service_started",
"required": true
},
- "mariadb": {
+ "database": {
"condition": "service_started",
"required": true
}
diff --git a/.vortex/tests/bats/install.initial.bats b/.vortex/tests/bats/install.initial.bats
index 5264df039..beea14d6c 100644
--- a/.vortex/tests/bats/install.initial.bats
+++ b/.vortex/tests/bats/install.initial.bats
@@ -222,24 +222,24 @@ load _helper.bash
@test "Install into empty directory; db from curl; storage is container image" {
export VORTEX_DB_DOWNLOAD_SOURCE=curl
- export VORTEX_DB_IMAGE="drevops/vortex-dev-mariadb-drupal-data-demo-10.x:latest"
+ export VORTEX_DB_IMAGE="drevops/vortex-dev-mariadb-drupal-data-demo-11.x:latest"
run_installer_quiet
assert_file_contains ".env" "VORTEX_DB_DOWNLOAD_SOURCE=curl"
assert_file_contains ".env" "VORTEX_DB_DOWNLOAD_CURL_URL="
- assert_file_contains ".env" "VORTEX_DB_IMAGE=drevops/vortex-dev-mariadb-drupal-data-demo-10.x:latest"
+ assert_file_contains ".env" "VORTEX_DB_IMAGE=drevops/vortex-dev-mariadb-drupal-data-demo-11.x:latest"
}
@test "Install into empty directory; db from container image; storage is container image" {
export VORTEX_DB_DOWNLOAD_SOURCE=container_registry
- export VORTEX_DB_IMAGE="drevops/vortex-dev-mariadb-drupal-data-demo-10.x:latest"
+ export VORTEX_DB_IMAGE="drevops/vortex-dev-mariadb-drupal-data-demo-11.x:latest"
run_installer_quiet
assert_file_contains ".env" "VORTEX_DB_DOWNLOAD_SOURCE=container_registry"
assert_file_not_contains ".env" "VORTEX_DB_DOWNLOAD_CURL_URL="
- assert_file_contains ".env" "VORTEX_DB_IMAGE=drevops/vortex-dev-mariadb-drupal-data-demo-10.x:latest"
+ assert_file_contains ".env" "VORTEX_DB_IMAGE=drevops/vortex-dev-mariadb-drupal-data-demo-11.x:latest"
}
@test "Install into empty directory; Vortex scripts are not modified" {
diff --git a/.vortex/tests/bats/provision.bats b/.vortex/tests/bats/provision.bats
index 299fb3ea2..b82430826 100644
--- a/.vortex/tests/bats/provision.bats
+++ b/.vortex/tests/bats/provision.bats
@@ -133,8 +133,9 @@ assert_provision_info() {
"@drush -y php:eval \Drupal::service('config.factory')->getEditable('system.site')->set('name', 'YOURSITE')->save();"
"@drush -y pm:install admin_toolbar coffee config_split config_update media environment_indicator pathauto redirect shield stage_file_proxy"
"@drush -y pm:install redis"
- "@drush -y pm:install clamav"
- "@drush -y config-set clamav.settings mode_daemon_tcpip.hostname clamav"
+ # @see https://github.com/drevops/vortex/issues/1461
+ # "@drush -y pm:install clamav"
+ # "@drush -y config-set clamav.settings mode_daemon_tcpip.hostname clamav"
"@drush -y pm:install search_api search_api_solr"
"@drush -y pm:install ys_core ys_search"
"@drush -y deploy:hook"
@@ -253,8 +254,9 @@ assert_provision_info() {
"@drush -y php:eval \Drupal::service('config.factory')->getEditable('system.site')->set('name', 'YOURSITE')->save();"
"@drush -y pm:install admin_toolbar coffee config_split config_update media environment_indicator pathauto redirect shield stage_file_proxy"
"@drush -y pm:install redis"
- "@drush -y pm:install clamav"
- "@drush -y config-set clamav.settings mode_daemon_tcpip.hostname clamav"
+ # @see https://github.com/drevops/vortex/issues/1461
+ # "@drush -y pm:install clamav"
+ # "@drush -y config-set clamav.settings mode_daemon_tcpip.hostname clamav"
"@drush -y pm:install search_api search_api_solr"
"@drush -y pm:install ys_core ys_search"
"@drush -y deploy:hook"
@@ -382,8 +384,9 @@ assert_provision_info() {
"@drush -y php:eval \Drupal::service('config.factory')->getEditable('system.site')->set('name', 'YOURSITE')->save();"
"@drush -y pm:install admin_toolbar coffee config_split config_update media environment_indicator pathauto redirect shield stage_file_proxy"
"@drush -y pm:install redis"
- "@drush -y pm:install clamav"
- "@drush -y config-set clamav.settings mode_daemon_tcpip.hostname clamav"
+ # @see https://github.com/drevops/vortex/issues/1461
+ # "@drush -y pm:install clamav"
+ # "@drush -y config-set clamav.settings mode_daemon_tcpip.hostname clamav"
"@drush -y pm:install search_api search_api_solr"
"@drush -y pm:install ys_core ys_search"
"@drush -y deploy:hook"
@@ -515,8 +518,9 @@ assert_provision_info() {
"@drush -y php:eval \Drupal::service('config.factory')->getEditable('system.site')->set('name', 'YOURSITE')->save();"
"@drush -y pm:install admin_toolbar coffee config_split config_update media environment_indicator pathauto redirect shield stage_file_proxy"
"@drush -y pm:install redis"
- "@drush -y pm:install clamav"
- "@drush -y config-set clamav.settings mode_daemon_tcpip.hostname clamav"
+ # @see https://github.com/drevops/vortex/issues/1461
+ # "@drush -y pm:install clamav"
+ # "@drush -y config-set clamav.settings mode_daemon_tcpip.hostname clamav"
"@drush -y pm:install search_api search_api_solr"
"@drush -y pm:install ys_core ys_search"
"@drush -y deploy:hook"
@@ -644,8 +648,9 @@ assert_provision_info() {
"@drush -y php:eval \Drupal::service('config.factory')->getEditable('system.site')->set('name', 'YOURSITE')->save();"
"@drush -y pm:install admin_toolbar coffee config_split config_update media environment_indicator pathauto redirect shield stage_file_proxy"
"@drush -y pm:install redis"
- "@drush -y pm:install clamav"
- "@drush -y config-set clamav.settings mode_daemon_tcpip.hostname clamav"
+ # @see https://github.com/drevops/vortex/issues/1461
+ # "@drush -y pm:install clamav"
+ # "@drush -y config-set clamav.settings mode_daemon_tcpip.hostname clamav"
"@drush -y pm:install search_api search_api_solr"
"@drush -y pm:install ys_core ys_search"
"@drush -y deploy:hook"
@@ -767,8 +772,9 @@ assert_provision_info() {
"@drush -y php:eval \Drupal::service('config.factory')->getEditable('system.site')->set('name', 'YOURSITE')->save();"
"@drush -y pm:install admin_toolbar coffee config_split config_update media environment_indicator pathauto redirect shield stage_file_proxy"
"@drush -y pm:install redis"
- "@drush -y pm:install clamav"
- "@drush -y config-set clamav.settings mode_daemon_tcpip.hostname clamav"
+ # @see https://github.com/drevops/vortex/issues/1461
+ # "@drush -y pm:install clamav"
+ # "@drush -y config-set clamav.settings mode_daemon_tcpip.hostname clamav"
"@drush -y pm:install search_api search_api_solr"
"@drush -y pm:install ys_core ys_search"
"@drush -y deploy:hook"
@@ -897,8 +903,9 @@ assert_provision_info() {
"@drush -y php:eval \Drupal::service('config.factory')->getEditable('system.site')->set('name', 'YOURSITE')->save();"
"@drush -y pm:install admin_toolbar coffee config_split config_update media environment_indicator pathauto redirect shield stage_file_proxy"
"@drush -y pm:install redis"
- "@drush -y pm:install clamav"
- "@drush -y config-set clamav.settings mode_daemon_tcpip.hostname clamav"
+ # @see https://github.com/drevops/vortex/issues/1461
+ # "@drush -y pm:install clamav"
+ # "@drush -y config-set clamav.settings mode_daemon_tcpip.hostname clamav"
"@drush -y pm:install search_api search_api_solr"
"@drush -y pm:install ys_core ys_search"
"@drush -y deploy:hook"
diff --git a/.vortex/tests/bats/workflow.install.bats b/.vortex/tests/bats/workflow.install.bats
deleted file mode 100644
index fb90fff47..000000000
--- a/.vortex/tests/bats/workflow.install.bats
+++ /dev/null
@@ -1,108 +0,0 @@
-#!/usr/bin/env bats
-#
-# Workflows using different types of install source.
-#
-
-# shellcheck disable=SC2030,SC2031,SC2129
-
-load _helper.bash
-load _helper.workflow.bash
-
-@test "Workflow: DB-driven" {
- prepare_sut "Starting DB-driven WORKFLOW tests in build directory ${BUILD_DIR}"
-
- assert_ahoy_download_db
-
- assert_ahoy_build
- assert_gitignore
-
- assert_solr
-
- assert_ahoy_cli
-
- assert_env_changes
-
- assert_timezone
-
- assert_ahoy_composer
-
- assert_ahoy_drush
-
- assert_ahoy_info
-
- assert_ahoy_container_logs
-
- assert_ahoy_login
-
- assert_ahoy_export_db
-
- assert_ahoy_lint
-
- assert_ahoy_test
-
- assert_ahoy_fei
-
- assert_ahoy_fe
-
- assert_ahoy_debug
-
- # Run this test as a last one to make sure that there is no concurrency issues
- # with enabled Redis.
- assert_redis
-
- assert_ahoy_reset
-
- assert_ahoy_reset_hard
-}
-
-@test "Workflow: DB-driven, custom webroot" {
- prepare_sut "Starting DB-driven WORKFLOW with custom webroot tests in build directory ${BUILD_DIR}" "rootdoc"
-
- assert_ahoy_download_db
-
- assert_ahoy_build "rootdoc"
- assert_gitignore "" "rootdoc"
-
- assert_ahoy_cli
-
- assert_env_changes
-
- assert_ahoy_drush
-
- assert_ahoy_info "rootdoc"
-
- assert_ahoy_export_db
-
- assert_ahoy_lint "rootdoc"
-
- assert_ahoy_test "rootdoc" "1"
-
- assert_ahoy_fei "rootdoc"
-
- assert_ahoy_fe "rootdoc"
-
- assert_ahoy_reset "rootdoc"
-
- assert_ahoy_reset_hard "rootdoc"
-}
-
-@test "Workflow: profile-driven" {
- rm -f .data/db.sql
- export VORTEX_INSTALL_DEMO_SKIP=1
- assert_file_not_exists .data/db.sql
-
- prepare_sut "Starting fresh install WORKFLOW tests in build directory ${BUILD_DIR}"
- # Assert that the database was not downloaded because VORTEX_INSTALL_DEMO_SKIP was set.
- assert_file_not_exists .data/db.sql
-
- echo "VORTEX_PROVISION_USE_PROFILE=1" >>.env
-
- assert_ahoy_build
- assert_gitignore
-
- assert_ahoy_lint
-
- assert_ahoy_test "web" "1"
-
- assert_ahoy_fe
-}
diff --git a/.vortex/tests/bats/workflow.install.db.rootweb.bats b/.vortex/tests/bats/workflow.install.db.rootweb.bats
new file mode 100644
index 000000000..4b9d2e871
--- /dev/null
+++ b/.vortex/tests/bats/workflow.install.db.rootweb.bats
@@ -0,0 +1,40 @@
+#!/usr/bin/env bats
+#
+# Workflows using different types of install source.
+#
+
+# shellcheck disable=SC2030,SC2031,SC2129
+
+load _helper.bash
+load _helper.workflow.bash
+
+@test "Workflow: DB-driven, custom webroot" {
+ prepare_sut "Starting DB-driven WORKFLOW with custom webroot tests in build directory ${BUILD_DIR}" "rootdoc"
+
+ assert_ahoy_download_db
+
+ assert_ahoy_build "rootdoc"
+ assert_gitignore "" "rootdoc"
+
+ assert_ahoy_cli
+
+ assert_env_changes
+
+ assert_ahoy_drush
+
+ assert_ahoy_info "rootdoc"
+
+ assert_ahoy_export_db
+
+ assert_ahoy_lint "rootdoc"
+
+ assert_ahoy_test "rootdoc" "1"
+
+ assert_ahoy_fei "rootdoc"
+
+ assert_ahoy_fe "rootdoc"
+
+ assert_ahoy_reset "rootdoc"
+
+ assert_ahoy_reset_hard "rootdoc"
+}
diff --git a/.vortex/tests/bats/workflow.install.db.webroot.bats b/.vortex/tests/bats/workflow.install.db.webroot.bats
new file mode 100644
index 000000000..63fb7d6e3
--- /dev/null
+++ b/.vortex/tests/bats/workflow.install.db.webroot.bats
@@ -0,0 +1,56 @@
+#!/usr/bin/env bats
+#
+# Workflows using different types of install source.
+#
+
+# shellcheck disable=SC2030,SC2031,SC2129
+
+load _helper.bash
+load _helper.workflow.bash
+
+@test "Workflow: DB-driven" {
+ prepare_sut "Starting DB-driven WORKFLOW tests in build directory ${BUILD_DIR}"
+
+ assert_ahoy_download_db
+
+ assert_ahoy_build
+ assert_gitignore
+
+ assert_solr
+
+ assert_ahoy_cli
+
+ assert_env_changes
+
+ assert_timezone
+
+ assert_ahoy_composer
+
+ assert_ahoy_drush
+
+ assert_ahoy_info
+
+ assert_ahoy_container_logs
+
+ assert_ahoy_login
+
+ assert_ahoy_export_db
+
+ assert_ahoy_lint
+
+ assert_ahoy_test
+
+ assert_ahoy_fei
+
+ assert_ahoy_fe
+
+ assert_ahoy_debug
+
+ # Run this test as a last one to make sure that there is no concurrency issues
+ # with enabled Redis.
+ assert_redis
+
+ assert_ahoy_reset
+
+ assert_ahoy_reset_hard
+}
diff --git a/.vortex/tests/bats/workflow.install.profile.bats b/.vortex/tests/bats/workflow.install.profile.bats
new file mode 100644
index 000000000..9bf373fc2
--- /dev/null
+++ b/.vortex/tests/bats/workflow.install.profile.bats
@@ -0,0 +1,30 @@
+#!/usr/bin/env bats
+#
+# Workflows using different types of install source.
+#
+
+# shellcheck disable=SC2030,SC2031,SC2129
+
+load _helper.bash
+load _helper.workflow.bash
+
+@test "Workflow: profile-driven" {
+ rm -f .data/db.sql
+ export VORTEX_INSTALL_DEMO_SKIP=1
+ assert_file_not_exists .data/db.sql
+
+ prepare_sut "Starting fresh install WORKFLOW tests in build directory ${BUILD_DIR}"
+ # Assert that the database was not downloaded because VORTEX_INSTALL_DEMO_SKIP was set.
+ assert_file_not_exists .data/db.sql
+
+ echo "VORTEX_PROVISION_USE_PROFILE=1" >>.env
+
+ assert_ahoy_build
+ assert_gitignore
+
+ assert_ahoy_lint
+
+ assert_ahoy_test "web" "1"
+
+ assert_ahoy_fe
+}
diff --git a/.vortex/tests/bats/workflow.storage.curl.bats b/.vortex/tests/bats/workflow.storage.curl.bats
index 98799c8aa..e79f76e05 100644
--- a/.vortex/tests/bats/workflow.storage.curl.bats
+++ b/.vortex/tests/bats/workflow.storage.curl.bats
@@ -2,7 +2,7 @@
#
# Workflows using different types of DB storage.
#
-# Throughout these tests, a "drevops/vortex-dev-mariadb-drupal-data-test-10.x:latest"
+# Throughout these tests, a "drevops/vortex-dev-mariadb-drupal-data-test-11.x:latest"
# test image is used: it is seeded with content from the pre-built fixture
# "Star wars" test site.
#
@@ -27,7 +27,7 @@ load _helper.workflow.bash
# @todo: build.sh may need to have a support to create a local image if
# it does not exist.
# Use a test image. Image always must use a tag.
- export VORTEX_DB_IMAGE="drevops/vortex-dev-mariadb-drupal-data-test-10.x:latest"
+ export VORTEX_DB_IMAGE="drevops/vortex-dev-mariadb-drupal-data-test-11.x:latest"
# Explicitly specify that we do not want to login into the public registry
# to use test image.
@@ -50,7 +50,7 @@ load _helper.workflow.bash
assert_file_contains ".env" "VORTEX_DB_DOWNLOAD_SOURCE=curl"
assert_file_contains ".env" "VORTEX_DB_IMAGE=${VORTEX_DB_IMAGE}"
# Assert that demo config was removed as a part of the installation.
- assert_file_not_contains ".env" "VORTEX_DB_IMAGE=drevops/vortex-dev-mariadb-drupal-data-demo-10.x:latest"
+ assert_file_not_contains ".env" "VORTEX_DB_IMAGE=drevops/vortex-dev-mariadb-drupal-data-demo-11.x:latest"
assert_file_contains ".env" "VORTEX_DB_DOWNLOAD_CURL_URL="
assert_ahoy_build
@@ -65,11 +65,11 @@ load _helper.workflow.bash
step "Case 1: Site is built from DB file, site reloaded from image, while DB file exists."
substep "Assert that the text is from the DB dump."
- assert_webpage_contains "/" "test database dump"
+ assert_webpage_contains "/" "This test page is sourced from the Vortex database dump file"
substep "Set content to a different path."
ahoy drush config-set system.site page.front /user -y
- assert_webpage_not_contains "/" "test database dump"
+ assert_webpage_not_contains "/" "This test page is sourced from the Vortex database dump file"
substep "Reloading DB from image with DB dump file present"
assert_file_exists .data/db.sql
@@ -77,7 +77,7 @@ load _helper.workflow.bash
assert_success
substep "Assert that the text is from the DB dump after reload."
- assert_webpage_contains "/" "test database dump"
+ assert_webpage_contains "/" "This test page is sourced from the Vortex database dump file"
# Skipped: the provision.sh expects DB dump file to exist; this logic
# needs to be refactored. Currently, reloading without DB dump file present
@@ -90,7 +90,7 @@ load _helper.workflow.bash
# assert_success
#
# substep "Assert that the text is from the container image."
- # assert_page_contains "/" "test database Docker image"
+ # assert_page_contains "/" "This test page is sourced from the Vortex database container image"
assert_ahoy_export_db "mydb.tar"
}
diff --git a/.vortex/tests/bats/workflow.storage.image.bats b/.vortex/tests/bats/workflow.storage.image.bats
index 8bd854f93..49a3eb343 100644
--- a/.vortex/tests/bats/workflow.storage.image.bats
+++ b/.vortex/tests/bats/workflow.storage.image.bats
@@ -2,7 +2,7 @@
#
# Workflows using different types of DB storage.
#
-# Throughout these tests, a "drevops/vortex-dev-mariadb-drupal-data-test-10.x:latest"
+# Throughout these tests, a "drevops/vortex-dev-mariadb-drupal-data-test-11.x:latest"
# test image is used: it is seeded with content from the pre-built fixture
# "Star wars" test site.
#
@@ -23,7 +23,7 @@ load _helper.workflow.bash
export VORTEX_DB_DOWNLOAD_SOURCE=container_registry
# Use a test image. Image always must use a tag.
- export VORTEX_DB_IMAGE="drevops/vortex-dev-mariadb-drupal-data-test-10.x:latest"
+ export VORTEX_DB_IMAGE="drevops/vortex-dev-mariadb-drupal-data-test-11.x:latest"
# Do not use demo database - testing demo database discovery is another test.
export VORTEX_INSTALL_DEMO_SKIP=1
@@ -50,7 +50,7 @@ load _helper.workflow.bash
assert_file_contains ".env" "VORTEX_DB_DOWNLOAD_SOURCE=container_registry"
assert_file_contains ".env" "VORTEX_DB_IMAGE=${VORTEX_DB_IMAGE}"
# Assert that demo config was removed as a part of the installation.
- assert_file_not_contains ".env" "VORTEX_DB_IMAGE=drevops/vortex-dev-mariadb-drupal-data-demo-10.x:latest"
+ assert_file_not_contains ".env" "VORTEX_DB_IMAGE=drevops/vortex-dev-mariadb-drupal-data-demo-11.x:latest"
assert_file_not_contains ".env" "VORTEX_DB_DOWNLOAD_CURL_URL="
assert_ahoy_build
@@ -59,14 +59,14 @@ load _helper.workflow.bash
step "Reload DB image"
# Assert that used DB image has content.
- assert_webpage_contains "/" "test database Docker image"
+ assert_webpage_contains "/" "This test page is sourced from the Vortex database container image"
# Change homepage content and assert that the change was applied.
ahoy drush config-set system.site page.front /user -y
- assert_webpage_not_contains "/" "test database Docker image"
+ assert_webpage_not_contains "/" "This test page is sourced from the Vortex database container image"
ahoy reload-db
- assert_webpage_contains "/" "test database Docker image"
+ assert_webpage_contains "/" "This test page is sourced from the Vortex database container image"
# Other stack assertions - these run only for this container image-related test.
assert_gitignore
diff --git a/.vortex/tests/bats/workflow.storage.image_cached.bats b/.vortex/tests/bats/workflow.storage.image_cached.bats
index 62c684890..3e846aa12 100644
--- a/.vortex/tests/bats/workflow.storage.image_cached.bats
+++ b/.vortex/tests/bats/workflow.storage.image_cached.bats
@@ -2,7 +2,7 @@
#
# Workflows using different types of DB storage.
#
-# Throughout these tests, a "drevops/vortex-dev-mariadb-drupal-data-test-10.x:latest"
+# Throughout these tests, a "drevops/vortex-dev-mariadb-drupal-data-test-11.x:latest"
# test image is used: it is seeded with content from the pre-built fixture
# "Star wars" test site.
#
@@ -25,7 +25,7 @@ load _helper.workflow.bash
export VORTEX_DB_DOWNLOAD_SOURCE=container_registry
# Use a test image. Image always must use a tag.
- export VORTEX_DB_IMAGE="drevops/vortex-dev-mariadb-drupal-data-test-10.x:latest"
+ export VORTEX_DB_IMAGE="drevops/vortex-dev-mariadb-drupal-data-test-11.x:latest"
# Do not use demo database - testing demo database discovery is another test.
export VORTEX_INSTALL_DEMO_SKIP=1
@@ -52,7 +52,7 @@ load _helper.workflow.bash
assert_file_contains ".env" "VORTEX_DB_DOWNLOAD_SOURCE=container_registry"
assert_file_contains ".env" "VORTEX_DB_IMAGE=${VORTEX_DB_IMAGE}"
# Assert that demo config was removed as a part of the installation.
- assert_file_not_contains ".env" "VORTEX_DB_IMAGE=drevops/vortex-dev-mariadb-drupal-data-demo-10.x:latest"
+ assert_file_not_contains ".env" "VORTEX_DB_IMAGE=drevops/vortex-dev-mariadb-drupal-data-demo-11.x:latest"
assert_file_not_contains ".env" "VORTEX_DB_DOWNLOAD_CURL_URL="
step "Initial build to use data image."
@@ -70,18 +70,18 @@ load _helper.workflow.bash
# Make a change to current site, export the DB image, remove existing DB image
# and rebuild the stack - the used image should have the expected changes.
substep "Assert that used DB image has content."
- assert_webpage_contains "/" "test database Docker image"
+ assert_webpage_contains "/" "This test page is sourced from the Vortex database container image"
assert_webpage_not_contains "/" "Username"
substep "Change homepage content and assert that the change was applied."
ahoy drush config-set system.site page.front /user -y
- assert_webpage_not_contains "/" "test database Docker image"
+ assert_webpage_not_contains "/" "This test page is sourced from the Vortex database container image"
assert_webpage_contains "/" "Username"
substep "Exporting DB image to a file"
run ahoy export-db "db.tar"
assert_success
- assert_output_contains "Found mariadb service container with id"
+ assert_output_contains "Found database service container with id"
assert_output_contains "Committing exported container image with name docker.io/${VORTEX_DB_IMAGE}"
assert_output_contains "Committed exported container image with id"
assert_output_contains "Exporting database image archive to file ./.data/db.tar."
@@ -107,7 +107,7 @@ load _helper.workflow.bash
assert_output_contains "Finished building project"
step "Assert that the contents of the DB was loaded from the exported DB image file."
- assert_webpage_not_contains "/" "test database Docker image"
+ assert_webpage_not_contains "/" "This test page is sourced from the Vortex database container image"
assert_webpage_contains "/" "Username"
ahoy clean
}
diff --git a/.vortex/tests/composer.lock b/.vortex/tests/composer.lock
index 9238d9630..7b7149dc6 100644
--- a/.vortex/tests/composer.lock
+++ b/.vortex/tests/composer.lock
@@ -4,32 +4,33 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "3e784019b90e67adb20ce44e1dec3799",
+ "content-hash": "a342f3ba1e2ee2b9cb43900607275192",
"packages": [],
"packages-dev": [
{
"name": "alexskrypnyk/csvtable",
- "version": "0.3.0",
+ "version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/AlexSkrypnyk/CsvTable.git",
- "reference": "5303c3da8ac071215e103c77a83f89e553394d8d"
+ "reference": "0c38988ccd1bded988b31b6432d003516c99bb51"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/AlexSkrypnyk/CsvTable/zipball/5303c3da8ac071215e103c77a83f89e553394d8d",
- "reference": "5303c3da8ac071215e103c77a83f89e553394d8d",
+ "url": "https://api.github.com/repos/AlexSkrypnyk/CsvTable/zipball/0c38988ccd1bded988b31b6432d003516c99bb51",
+ "reference": "0c38988ccd1bded988b31b6432d003516c99bb51",
"shasum": ""
},
"require": {
- "php": ">=8.1"
+ "php": ">=8.2"
},
"require-dev": {
"drupal/coder": "^8.3",
+ "ergebnis/composer-normalize": "^2.44",
"phpmd/phpmd": "^2.15",
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^10.1",
- "rector/rector": "^0.19.0"
+ "rector/rector": "^1.0.0"
},
"type": "library",
"autoload": {
@@ -60,24 +61,24 @@
"type": "patreon"
}
],
- "time": "2024-01-16T12:00:12+00:00"
+ "time": "2024-11-17T00:27:25+00:00"
},
{
"name": "alexskrypnyk/shellvar",
- "version": "1.0.0",
+ "version": "1.1.0",
"source": {
"type": "git",
"url": "https://github.com/AlexSkrypnyk/shellvar.git",
- "reference": "0e1e389d164806a3a778df4dba7e32eba4ed9e95"
+ "reference": "658e20750755d85959cab6b1fb6326a3997fca9a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/AlexSkrypnyk/shellvar/zipball/0e1e389d164806a3a778df4dba7e32eba4ed9e95",
- "reference": "0e1e389d164806a3a778df4dba7e32eba4ed9e95",
+ "url": "https://api.github.com/repos/AlexSkrypnyk/shellvar/zipball/658e20750755d85959cab6b1fb6326a3997fca9a",
+ "reference": "658e20750755d85959cab6b1fb6326a3997fca9a",
"shasum": ""
},
"require": {
- "alexskrypnyk/csvtable": "^0.3",
+ "alexskrypnyk/csvtable": "^1",
"php": ">=8.2",
"symfony/console": "^7"
},
@@ -88,6 +89,7 @@
"bamarni/composer-bin-plugin": "^1.8.2",
"dealerdirect/phpcodesniffer-composer-installer": "^1",
"drupal/coder": "^8.3",
+ "ergebnis/composer-normalize": "^2.44",
"mikey179/vfsstream": "^1.6",
"opis/closure": "^3.6",
"phpmd/phpmd": "^2.15",
@@ -128,7 +130,7 @@
"type": "patreon"
}
],
- "time": "2024-04-29T01:29:09+00:00"
+ "time": "2024-11-18T10:24:03+00:00"
},
{
"name": "psr/container",
@@ -185,16 +187,16 @@
},
{
"name": "symfony/console",
- "version": "v7.0.6",
+ "version": "v7.1.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "fde915cd8e7eb99b3d531d3d5c09531429c3f9e5"
+ "reference": "ff04e5b5ba043d2badfb308197b9e6b42883fcd5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/fde915cd8e7eb99b3d531d3d5c09531429c3f9e5",
- "reference": "fde915cd8e7eb99b3d531d3d5c09531429c3f9e5",
+ "url": "https://api.github.com/repos/symfony/console/zipball/ff04e5b5ba043d2badfb308197b9e6b42883fcd5",
+ "reference": "ff04e5b5ba043d2badfb308197b9e6b42883fcd5",
"shasum": ""
},
"require": {
@@ -258,7 +260,74 @@
"terminal"
],
"support": {
- "source": "https://github.com/symfony/console/tree/v7.0.6"
+ "source": "https://github.com/symfony/console/tree/v7.1.8"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-11-06T14:23:19+00:00"
+ },
+ {
+ "name": "symfony/deprecation-contracts",
+ "version": "v3.5.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/deprecation-contracts.git",
+ "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1",
+ "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.5-dev"
+ },
+ "thanks": {
+ "name": "symfony/contracts",
+ "url": "https://github.com/symfony/contracts"
+ }
+ },
+ "autoload": {
+ "files": [
+ "function.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "A generic function and convention to trigger deprecation notices",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0"
},
"funding": [
{
@@ -274,24 +343,24 @@
"type": "tidelift"
}
],
- "time": "2024-04-01T11:04:53+00:00"
+ "time": "2024-04-18T09:32:20+00:00"
},
{
"name": "symfony/polyfill-ctype",
- "version": "v1.29.0",
+ "version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
- "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4"
+ "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4",
- "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4",
+ "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638",
+ "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638",
"shasum": ""
},
"require": {
- "php": ">=7.1"
+ "php": ">=7.2"
},
"provide": {
"ext-ctype": "*"
@@ -337,7 +406,7 @@
"portable"
],
"support": {
- "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0"
+ "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0"
},
"funding": [
{
@@ -353,24 +422,24 @@
"type": "tidelift"
}
],
- "time": "2024-01-29T20:11:03+00:00"
+ "time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-intl-grapheme",
- "version": "v1.29.0",
+ "version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-grapheme.git",
- "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f"
+ "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/32a9da87d7b3245e09ac426c83d334ae9f06f80f",
- "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe",
+ "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe",
"shasum": ""
},
"require": {
- "php": ">=7.1"
+ "php": ">=7.2"
},
"suggest": {
"ext-intl": "For best performance"
@@ -415,7 +484,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.29.0"
+ "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0"
},
"funding": [
{
@@ -431,24 +500,24 @@
"type": "tidelift"
}
],
- "time": "2024-01-29T20:11:03+00:00"
+ "time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-intl-normalizer",
- "version": "v1.29.0",
+ "version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
- "reference": "bc45c394692b948b4d383a08d7753968bed9a83d"
+ "reference": "3833d7255cc303546435cb650316bff708a1c75c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/bc45c394692b948b4d383a08d7753968bed9a83d",
- "reference": "bc45c394692b948b4d383a08d7753968bed9a83d",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c",
+ "reference": "3833d7255cc303546435cb650316bff708a1c75c",
"shasum": ""
},
"require": {
- "php": ">=7.1"
+ "php": ">=7.2"
},
"suggest": {
"ext-intl": "For best performance"
@@ -496,7 +565,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.29.0"
+ "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0"
},
"funding": [
{
@@ -512,24 +581,24 @@
"type": "tidelift"
}
],
- "time": "2024-01-29T20:11:03+00:00"
+ "time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-mbstring",
- "version": "v1.29.0",
+ "version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
- "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec"
+ "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
- "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341",
+ "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341",
"shasum": ""
},
"require": {
- "php": ">=7.1"
+ "php": ">=7.2"
},
"provide": {
"ext-mbstring": "*"
@@ -576,7 +645,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0"
+ "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0"
},
"funding": [
{
@@ -592,25 +661,26 @@
"type": "tidelift"
}
],
- "time": "2024-01-29T20:11:03+00:00"
+ "time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/service-contracts",
- "version": "v3.4.2",
+ "version": "v3.5.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/service-contracts.git",
- "reference": "11bbf19a0fb7b36345861e85c5768844c552906e"
+ "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/service-contracts/zipball/11bbf19a0fb7b36345861e85c5768844c552906e",
- "reference": "11bbf19a0fb7b36345861e85c5768844c552906e",
+ "url": "https://api.github.com/repos/symfony/service-contracts/zipball/bd1d9e59a81d8fa4acdcea3f617c581f7475a80f",
+ "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f",
"shasum": ""
},
"require": {
"php": ">=8.1",
- "psr/container": "^1.1|^2.0"
+ "psr/container": "^1.1|^2.0",
+ "symfony/deprecation-contracts": "^2.5|^3"
},
"conflict": {
"ext-psr": "<1.1|>=2"
@@ -618,7 +688,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "3.4-dev"
+ "dev-main": "3.5-dev"
},
"thanks": {
"name": "symfony/contracts",
@@ -658,7 +728,7 @@
"standards"
],
"support": {
- "source": "https://github.com/symfony/service-contracts/tree/v3.4.2"
+ "source": "https://github.com/symfony/service-contracts/tree/v3.5.0"
},
"funding": [
{
@@ -674,20 +744,20 @@
"type": "tidelift"
}
],
- "time": "2023-12-19T21:51:00+00:00"
+ "time": "2024-04-18T09:32:20+00:00"
},
{
"name": "symfony/string",
- "version": "v7.0.4",
+ "version": "v7.1.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
- "reference": "f5832521b998b0bec40bee688ad5de98d4cf111b"
+ "reference": "591ebd41565f356fcd8b090fe64dbb5878f50281"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/string/zipball/f5832521b998b0bec40bee688ad5de98d4cf111b",
- "reference": "f5832521b998b0bec40bee688ad5de98d4cf111b",
+ "url": "https://api.github.com/repos/symfony/string/zipball/591ebd41565f356fcd8b090fe64dbb5878f50281",
+ "reference": "591ebd41565f356fcd8b090fe64dbb5878f50281",
"shasum": ""
},
"require": {
@@ -701,6 +771,7 @@
"symfony/translation-contracts": "<2.5"
},
"require-dev": {
+ "symfony/emoji": "^7.1",
"symfony/error-handler": "^6.4|^7.0",
"symfony/http-client": "^6.4|^7.0",
"symfony/intl": "^6.4|^7.0",
@@ -744,7 +815,7 @@
"utf8"
],
"support": {
- "source": "https://github.com/symfony/string/tree/v7.0.4"
+ "source": "https://github.com/symfony/string/tree/v7.1.8"
},
"funding": [
{
@@ -760,7 +831,7 @@
"type": "tidelift"
}
],
- "time": "2024-02-01T13:17:36+00:00"
+ "time": "2024-11-13T13:31:21+00:00"
}
],
"aliases": [],
diff --git a/.vortex/tests/package-lock.json b/.vortex/tests/package-lock.json
index e800615f9..ea04c7f98 100644
--- a/.vortex/tests/package-lock.json
+++ b/.vortex/tests/package-lock.json
@@ -1,7 +1,7 @@
{
"name": "vortex-tests",
"version": "1.0.0",
- "lockfileVersion": 2,
+ "lockfileVersion": 3,
"requires": true,
"packages": {
"": {
@@ -13,10 +13,11 @@
}
},
"node_modules/bats": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/bats/-/bats-1.10.0.tgz",
- "integrity": "sha512-yOQrC7npuCrN+Ic3TyjTjJlzHa0qlK3oEO6VAYPWwFeutx/GmpljIyB6uNSl/UTASyc2w4FgVuA/QMMf9OdsCw==",
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/bats/-/bats-1.11.0.tgz",
+ "integrity": "sha512-qiKdnS4ID3bJ1MaEOKuZe12R4w+t+psJF0ICj+UdkiHBBoObPMHv8xmD3w6F4a5qwUyZUHS+413lxENBNy8xcQ==",
"dev": true,
+ "license": "MIT",
"bin": {
"bats": "bin/bats"
}
@@ -27,26 +28,10 @@
"resolved": "https://registry.npmjs.org/@drevops/bats-helpers/-/bats-helpers-1.3.1.tgz",
"integrity": "sha512-CeKDILw3+o1W+L3EzMEo5E1is5UMc+tCbjMYJT+NE42cjNhH5l2+HTWksX5nc8unhmqysTOTn7zkp79LJLoX8w==",
"dev": true,
+ "license": "GPL-2.0-or-later",
"dependencies": {
"bats": "^1"
}
}
- },
- "dependencies": {
- "bats": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/bats/-/bats-1.10.0.tgz",
- "integrity": "sha512-yOQrC7npuCrN+Ic3TyjTjJlzHa0qlK3oEO6VAYPWwFeutx/GmpljIyB6uNSl/UTASyc2w4FgVuA/QMMf9OdsCw==",
- "dev": true
- },
- "bats-helpers": {
- "version": "npm:@drevops/bats-helpers@1.3.1",
- "resolved": "https://registry.npmjs.org/@drevops/bats-helpers/-/bats-helpers-1.3.1.tgz",
- "integrity": "sha512-CeKDILw3+o1W+L3EzMEo5E1is5UMc+tCbjMYJT+NE42cjNhH5l2+HTWksX5nc8unhmqysTOTn7zkp79LJLoX8w==",
- "dev": true,
- "requires": {
- "bats": "^1"
- }
- }
}
}
diff --git a/.vortex/tests/test.workflow.sh b/.vortex/tests/test.workflow.sh
index 614b4394c..0ddb517a1 100755
--- a/.vortex/tests/test.workflow.sh
+++ b/.vortex/tests/test.workflow.sh
@@ -46,11 +46,16 @@ case ${index} in
;;
1)
- bats "${TEST_DIR}"/bats/workflow.install.bats
+ bats "${TEST_DIR}"/bats/workflow.install.db.webroot.bats
;;
2)
+ bats "${TEST_DIR}"/bats/workflow.install.profile.bats
+ ;;
+
+ 3)
bats "${TEST_DIR}"/bats/workflow.utilities.bats
+ bats "${TEST_DIR}"/bats/workflow.install.db.rootweb.bats
# Disabled due to intermittent failures.
# @see https://github.com/drevops/vortex/issues/893
# bats "${TEST_DIR}"/bats/workflow.storage.image_cached.bats
@@ -59,7 +64,9 @@ case ${index} in
*)
bats "${TEST_DIR}"/bats/workflow.smoke.bats
- bats "${TEST_DIR}"/bats/workflow.install.bats
+ bats "${TEST_DIR}"/bats/workflow.install.db.webroot.bats
+ bats "${TEST_DIR}"/bats/workflow.install.db.rootweb.bats
+ bats "${TEST_DIR}"/bats/workflow.install.profile.bats
bats "${TEST_DIR}"/bats/workflow.storage.image.bats
# Disabled due to intermittent failures.
# @see https://github.com/drevops/vortex/issues/893
diff --git a/README.md b/README.md
index 339a65540..a6a51cf18 100644
--- a/README.md
+++ b/README.md
@@ -30,6 +30,9 @@ it easier to switch between them and get up to speed quickly.
Track our current progress and view planned updates on [the GitHub project board](https://github.com/orgs/drevops/projects/2/views/1).
+> [!IMPORTANT]
+> Vortex 2.0 is coming soon! Planned changes are captured in [this issue](https://github.com/drevops/vortex/issues/698).
+
## Installation
Our [installer](https://github.com/drevops/vortex-installer) simplifies setup, letting you choose only the features you need. It will integrate the latest Vortex release into your codebase and you will choose which changes to commit.
diff --git a/behat.yml b/behat.yml
index abecd577d..22cc9d4f7 100644
--- a/behat.yml
+++ b/behat.yml
@@ -59,9 +59,10 @@ default:
error_message_selector: '.messages.error'
success_message_selector: '.messages.status'
warning_message_selector: '.messages.warning'
- # Capture HTML and JPG screenshots on demand and on failure.
+ # Capture HTML and PNG screenshots on demand and on failure.
DrevOps\BehatScreenshotExtension:
dir: '%paths.base%/.logs/screenshots'
+ on_failed: true # Change to 'false' (no quotes) to disable screenshots on failure.
purge: false # Change to 'true' (no quotes) to purge screenshots on each run.
# Show explicit fail information and continue the test run.
DrevOps\BehatFormatProgressFail\FormatExtension: ~
diff --git a/composer.json b/composer.json
index 7d28983b4..6c638baf0 100644
--- a/composer.json
+++ b/composer.json
@@ -8,43 +8,42 @@
"composer/installers": "^2.3",
"cweagans/composer-patches": "^1.7",
"drupal/admin_toolbar": "^3.5",
- "drupal/clamav": "^2.0",
- "drupal/coffee": "^1.4",
- "drupal/config_split": "^1.9",
+ "drupal/coffee": "^2",
+ "drupal/config_split": "^2",
"drupal/config_update": "^2@alpha",
- "drupal/core-composer-scaffold": "~10.3.2",
- "drupal/core-recommended": "~10.3.2",
+ "drupal/core-composer-scaffold": "~11.1.0",
+ "drupal/core-recommended": "~11.1.0",
"drupal/environment_indicator": "^4.0",
"drupal/pathauto": "^1.13",
"drupal/redirect": "^1.10",
"drupal/redis": "^1.8",
- "drupal/search_api": "^1.35",
+ "drupal/search_api": "^1.37",
"drupal/search_api_solr": "^4.3",
"drupal/seckit": "^2.0",
"drupal/shield": "^1.8",
- "drupal/stage_file_proxy": "^2.1",
+ "drupal/stage_file_proxy": "^3.1",
"drush/drush": "^13",
"oomphinc/composer-installers-extender": "^2",
"webflo/drupal-finder": "^1.3"
},
"require-dev": {
- "behat/behat": "^3.14",
+ "behat/behat": "^3.18",
"dantleech/gherkin-lint": "^0.2.3",
"dealerdirect/phpcodesniffer-composer-installer": "^0.7",
- "drevops/behat-format-progress-fail": "^1.2",
- "drevops/behat-screenshot": "^1.5",
- "drevops/behat-steps": "^2.4",
- "drupal/core-dev": "~10.3.2",
+ "drevops/behat-format-progress-fail": "^1.3",
+ "drevops/behat-screenshot": "^2",
+ "drevops/behat-steps": "^2.6",
+ "drupal/core-dev": "~11.1.0",
"drupal/drupal-extension": "^5",
- "ergebnis/composer-normalize": "^2.43",
+ "ergebnis/composer-normalize": "^2.45",
"mglaman/phpstan-drupal": "^1.3",
"palantirnet/drupal-rector": "^0.20",
"phpcompatibility/php-compatibility": "^9.3",
"phpmd/phpmd": "^2.15",
- "phpspec/prophecy-phpunit": "^2.2",
+ "phpspec/prophecy-phpunit": "^2.3",
"phpstan/extension-installer": "^1.4",
"pyrech/composer-changelogs": "^1.8",
- "vincentlanglet/twig-cs-fixer": "^3"
+ "vincentlanglet/twig-cs-fixer": "^3.5"
},
"conflict": {
"drupal/drupal": "*"
diff --git a/docker-compose.yml b/docker-compose.yml
index ccd83ab33..5c562bf5e 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -47,15 +47,15 @@ x-environment: &default-environment
# Local development route used in Lagoon images and Pygmy to route requests.
LAGOON_ROUTE: *default-url
# Local database host (not used in production).
- MARIADB_HOST: mariadb
+ DATABASE_HOST: database
# Local database name (not used in production).
- MARIADB_DATABASE: drupal
+ DATABASE_NAME: drupal
# Local database user (not used in production).
- MARIADB_USERNAME: drupal
+ DATABASE_USERNAME: drupal
# Local database password (not used in production).
- MARIADB_PASSWORD: drupal
+ DATABASE_PASSWORD: drupal
# Local database port (not used in production).
- MARIADB_PORT: 3306
+ DATABASE_PORT: 3306
# Pass-through 'XDEBUG_ENABLE' to enable XDebug with "ahoy debug" or "XDEBUG_ENABLE=true docker compose up -d".
XDEBUG_ENABLE: ${XDEBUG_ENABLE:-}
# Pass-through 'CI' variable used to identify the CI environment.
@@ -150,17 +150,17 @@ services:
lagoon.persistent.class: slow
#;> LAGOON
- mariadb:
+ database:
build:
context: .
- dockerfile: .docker/mariadb.dockerfile
+ dockerfile: .docker/database.dockerfile
args:
- IMAGE: "${VORTEX_DB_IMAGE:-uselagoon/mariadb-10.11-drupal:24.10.0}" # Use custom database image (if defined) or fallback to standard database image.
+ IMAGE: "${VORTEX_DB_IMAGE:-uselagoon/mariadb-10.11-drupal:24.12.0}" # Use custom database image (if defined) or fallback to standard database image.
<<: *default-user
environment:
<<: *default-environment
ports:
- - "3306" # MariaDB port in container. Find port on host with `ahoy info` or `docker compose port mariadb 3306`.
+ - "3306" # MariaDB port in container. Find port on host with `ahoy info` or `docker compose port database 3306`.
#;< LAGOON
labels:
lagoon.type: mariadb # See https://docs.lagoon.sh/using-lagoon-advanced/service-types/
@@ -168,7 +168,7 @@ services:
#;< REDIS
redis:
- image: uselagoon/redis-6:24.10.0
+ image: uselagoon/redis-6:24.12.0
#;< LAGOON
labels:
lagoon.type: redis # Change to 'none' if dedicated Redis service is used. See https://docs.lagoon.sh/using-lagoon-advanced/service-types/
@@ -218,7 +218,7 @@ services:
# Chrome container, used for browser testing.
chrome:
- image: selenium/standalone-chromium:130.0
+ image: selenium/standalone-chromium:131.0
ports:
- "7900" # Find port on host with `docker compose port chrome 7900`.
shm_size: '1gb' # Increase '/dev/shm' partition size to avoid browser crashing.
@@ -234,12 +234,12 @@ services:
# Helper container to wait for services to become available.
wait_dependencies:
- image: drevops/docker-wait-for-dependencies:24.9.0
+ image: drevops/docker-wait-for-dependencies:25.1.1
depends_on:
- cli
- - mariadb
+ - database
- clamav
- command: mariadb:3306 clamav:3310
+ command: database:3306 clamav:3310
#;< LAGOON
labels:
lagoon.type: none # Do not deploy in Lagoon.
diff --git a/phpcs.xml b/phpcs.xml
index d4989de17..e4eab295b 100644
--- a/phpcs.xml
+++ b/phpcs.xml
@@ -22,10 +22,6 @@
-
- circle\.yml
- \.circle\/config\.yml
-
web\/themes\/custom\/.*\/build\/.*
web\/themes\/custom\/.*\/fonts\/.*
@@ -48,13 +44,6 @@
warning
-
-
- *.Test\.php
- *.TestCase\.php
- *.test
-
-
*.Test\.php
@@ -62,7 +51,7 @@
*.test
-
+
*\/tests\/behat\/bootstrap/*\.php
diff --git a/phpunit.xml b/phpunit.xml
index b60814590..7141f1bdd 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -1,20 +1,24 @@
-
-
+
+ xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
+ cacheDirectory=".phpunit.cache">
@@ -22,22 +26,17 @@
-
+
-
-
-
-
-
-
+
-
-
-
+
+
+
@@ -93,21 +90,23 @@
pathCoverage="false"
ignoreDeprecatedCodeUnits="true"
disableCodeCoverageIgnore="false">
+
+
+
+
+
+
web/modules/custom
web/themes/custom
web/sites/default/includes
web/sites/default/settings.php
-
+
+
+
- web/modules/custom/*/tests
- web/themes/custom/*/tests
+ skipped
-
-
-
-
-
-
+
diff --git a/recipes/.gitignore b/recipes/.gitignore
new file mode 100644
index 000000000..739a33944
--- /dev/null
+++ b/recipes/.gitignore
@@ -0,0 +1 @@
+/README.txt
\ No newline at end of file
diff --git a/renovate.json b/renovate.json
index 84f0f5f4e..f28fe68ac 100644
--- a/renovate.json
+++ b/renovate.json
@@ -51,7 +51,8 @@
"/.*/",
"!drupal/core-composer-scaffold",
"!drupal/core-project-message",
- "!drupal/core-recommended"
+ "!drupal/core-recommended",
+ "!drupal/core-dev"
]
},
{
@@ -70,7 +71,8 @@
"matchDepNames": [
"drupal/core-composer-scaffold",
"drupal/core-project-message",
- "drupal/core-recommended"
+ "drupal/core-recommended",
+ "drupal/core-dev"
]
},
{
diff --git a/scripts/custom/provision-10-example.sh b/scripts/custom/provision-10-example.sh
index 53d4769e6..8b6026760 100644
--- a/scripts/custom/provision-10-example.sh
+++ b/scripts/custom/provision-10-example.sh
@@ -38,8 +38,9 @@ if echo "${VORTEX_PROVISION_ENVIRONMENT:-}" | grep -q -e dev -e test -e ci -e lo
#;> REDIS
#;< CLAMAV
- drush pm:install clamav
- drush config-set clamav.settings mode_daemon_tcpip.hostname clamav
+ # @see https://github.com/drevops/vortex/issues/1461
+ # drush pm:install clamav
+ # drush config-set clamav.settings mode_daemon_tcpip.hostname clamav
#;> CLAMAV
#;< SOLR
diff --git a/scripts/vortex/doctor.sh b/scripts/vortex/doctor.sh
index 22f89790a..af16dc7b9 100755
--- a/scripts/vortex/doctor.sh
+++ b/scripts/vortex/doctor.sh
@@ -121,7 +121,7 @@ main() {
# Check that the stack is running.
if [ "${VORTEX_DOCTOR_CHECK_CONTAINERS}" = "1" ]; then
- container_services=(cli php nginx mariadb)
+ container_services=(cli php nginx database)
for container_service in "${container_services[@]}"; do
if ! docker compose ps --status=running --services | grep -q "${container_service}"; then
fail "${container_service} container is not running."
diff --git a/scripts/vortex/export-db-file.sh b/scripts/vortex/export-db-file.sh
index 05cc65fd3..4033f0559 100755
--- a/scripts/vortex/export-db-file.sh
+++ b/scripts/vortex/export-db-file.sh
@@ -35,7 +35,7 @@ dump_file=$([ "${1:-}" ] && echo "${VORTEX_DB_EXPORT_FILE_DIR}/${1}" || echo "${
dump_file_drush="${dump_file/#.\//../}"
# Create a directory to store database dump.
-mkdir -p "$(dirname "${dump_file_drush}")"
+mkdir -p "${VORTEX_DB_EXPORT_FILE_DIR}"
# Dump database into a file.
drush sql:dump --skip-tables-key=common --extra-dump=--no-tablespaces --result-file="${dump_file_drush}" -q
diff --git a/scripts/vortex/export-db-image.sh b/scripts/vortex/export-db-image.sh
index 55c71d225..befcda6c0 100755
--- a/scripts/vortex/export-db-image.sh
+++ b/scripts/vortex/export-db-image.sh
@@ -19,7 +19,7 @@ VORTEX_DB_EXPORT_IMAGE="${VORTEX_DB_EXPORT_IMAGE:-}"
VORTEX_DB_EXPORT_CONTAINER_REGISTRY="${VORTEX_DB_EXPORT_CONTAINER_REGISTRY:-${VORTEX_CONTAINER_REGISTRY:-docker.io}}"
# The service name to capture.
-VORTEX_DB_EXPORT_SERVICE_NAME="${VORTEX_DB_EXPORT_SERVICE_NAME:-mariadb}"
+VORTEX_DB_EXPORT_SERVICE_NAME="${VORTEX_DB_EXPORT_SERVICE_NAME:-database}"
# Directory with database image archive file.
VORTEX_DB_EXPORT_IMAGE_DIR="${VORTEX_DB_EXPORT_IMAGE_DIR:-${VORTEX_DB_DIR}}"
@@ -54,7 +54,7 @@ sleep 5
docker compose exec -T "${VORTEX_DB_EXPORT_SERVICE_NAME}" mysql -e "UNLOCK TABLES;"
note "Running forced service upgrade."
-docker compose exec -T "${VORTEX_DB_EXPORT_SERVICE_NAME}" sh -c "mysql_upgrade --force"
+docker compose exec -T "${VORTEX_DB_EXPORT_SERVICE_NAME}" sh -c "mariadb-upgrade --force || mariadb-upgrade --force"
note "Locking tables after upgrade."
docker compose exec -T "${VORTEX_DB_EXPORT_SERVICE_NAME}" mysql -e "FLUSH TABLES WITH READ LOCK;"
diff --git a/scripts/vortex/export-db.sh b/scripts/vortex/export-db.sh
index 2549c0281..bbede88c1 100755
--- a/scripts/vortex/export-db.sh
+++ b/scripts/vortex/export-db.sh
@@ -43,7 +43,7 @@ else
# Deploy container image.
# @todo Move deployment into a separate script.
if [ "${VORTEX_EXPORT_DB_CONTAINER_REGISTRY_DEPLOY_PROCEED:-}" = "1" ]; then
- VORTEX_DEPLOY_CONTAINER_REGISTRY_MAP=mariadb=${VORTEX_DB_IMAGE} \
+ VORTEX_DEPLOY_CONTAINER_REGISTRY_MAP=database=${VORTEX_DB_IMAGE} \
./scripts/vortex/deploy-container-registry.sh
fi
fi
diff --git a/scripts/vortex/info.sh b/scripts/vortex/info.sh
index 4b8d97f3a..2c86dc7a3 100755
--- a/scripts/vortex/info.sh
+++ b/scripts/vortex/info.sh
@@ -33,10 +33,10 @@ note "Project name : ${VORTEX_PROJECT}"
note "Docker Compose project name : ${COMPOSE_PROJECT_NAME:-}"
note "Site local URL : http://${VORTEX_LOCALDEV_URL}"
note "Path to web root : $(pwd)/${VORTEX_WEBROOT}"
-note "DB host : ${MARIADB_HOST}"
-note "DB username : ${MARIADB_USERNAME}"
-note "DB password : ${MARIADB_PASSWORD}"
-note "DB port : ${MARIADB_PORT}"
+note "DB host : ${DATABASE_HOST}"
+note "DB username : ${DATABASE_USERNAME}"
+note "DB password : ${DATABASE_PASSWORD}"
+note "DB port : ${DATABASE_PORT}"
note "DB port on host : ${VORTEX_HOST_DB_PORT} ${sequelace}"
if [ -n "${VORTEX_DB_IMAGE:-}" ]; then
note "DB-in-image : ${VORTEX_DB_IMAGE}"
diff --git a/tests/behat/features/clamav.feature b/tests/behat/features/clamav.feature
index e352e0d9e..e33e4efce 100644
--- a/tests/behat/features/clamav.feature
+++ b/tests/behat/features/clamav.feature
@@ -1,4 +1,4 @@
-@clamav @p0
+@clamav @p0 @skipped
Feature: ClamAV Anti-virus
Ensure that ClamAV is working correctly.
diff --git a/tests/phpunit/Drupal/DatabaseSettingsTest.php b/tests/phpunit/Drupal/DatabaseSettingsTest.php
index 843ae196f..070e2a383 100644
--- a/tests/phpunit/Drupal/DatabaseSettingsTest.php
+++ b/tests/phpunit/Drupal/DatabaseSettingsTest.php
@@ -50,11 +50,11 @@ public static function dataProviderDatabases(): array {
[
[
- 'MARIADB_DATABASE' => 'test_db_name',
- 'MARIADB_USERNAME' => 'test_db_user',
- 'MARIADB_PASSWORD' => 'test_db_pass',
- 'MARIADB_HOST' => 'test_db_host',
- 'MARIADB_PORT' => 'test_db_port',
+ 'DATABASE_NAME' => 'test_db_name',
+ 'DATABASE_USERNAME' => 'test_db_user',
+ 'DATABASE_PASSWORD' => 'test_db_pass',
+ 'DATABASE_HOST' => 'test_db_host',
+ 'DATABASE_PORT' => 'test_db_port',
],
[
'default' => [
diff --git a/tests/phpunit/Drupal/EnvironmentSettingsTest.php b/tests/phpunit/Drupal/EnvironmentSettingsTest.php
index d31944064..3beecb160 100644
--- a/tests/phpunit/Drupal/EnvironmentSettingsTest.php
+++ b/tests/phpunit/Drupal/EnvironmentSettingsTest.php
@@ -37,9 +37,7 @@ public function testEnvironmentTypeResolution(array $vars, string $expected_env)
/**
* Data provider for testing of the resulting environment.
*/
- public function dataProviderEnvironmentTypeResolution(): array {
- $this->requireSettingsFile();
-
+ public static function dataProviderEnvironmentTypeResolution(): array {
return [
// By default, the default environment type is local.
[[], static::ENVIRONMENT_LOCAL],
@@ -315,12 +313,11 @@ public function testEnvironmentGeneric(): void {
'bower_components',
];
$settings['file_temp_path'] = static::TMP_PATH_TESTING;
- $settings['hash_salt'] = hash('sha256', getenv('MARIADB_HOST') ?: 'localhost');
+ $settings['hash_salt'] = hash('sha256', getenv('DATABASE_HOST') ?: 'localhost');
$settings['trusted_host_patterns'] = [
'^.+\.docker\.amazee\.io$',
'^nginx$',
];
- $settings['state_cache'] = TRUE;
$this->assertSettings($settings);
}
@@ -361,13 +358,12 @@ public function testEnvironmentLocal(): void {
'bower_components',
];
$settings['file_temp_path'] = static::TMP_PATH_TESTING;
- $settings['hash_salt'] = hash('sha256', getenv('MARIADB_HOST') ?: 'localhost');
+ $settings['hash_salt'] = hash('sha256', getenv('DATABASE_HOST') ?: 'localhost');
$settings['skip_permissions_hardening'] = TRUE;
$settings['trusted_host_patterns'] = [
'^.+\.docker\.amazee\.io$',
'^nginx$',
];
- $settings['state_cache'] = TRUE;
$this->assertSettings($settings);
}
@@ -407,14 +403,13 @@ public function testEnvironmentCi(): void {
'bower_components',
];
$settings['file_temp_path'] = static::TMP_PATH_TESTING;
- $settings['hash_salt'] = hash('sha256', getenv('MARIADB_HOST') ?: 'localhost');
+ $settings['hash_salt'] = hash('sha256', getenv('DATABASE_HOST') ?: 'localhost');
$settings['skip_permissions_hardening'] = TRUE;
$settings['suspend_mail_send'] = TRUE;
$settings['trusted_host_patterns'] = [
'^.+\.docker\.amazee\.io$',
'^nginx$',
];
- $settings['state_cache'] = TRUE;
$this->assertSettings($settings);
}
@@ -454,10 +449,9 @@ public function testEnvironmentAcquiaDynamic(): void {
'bower_components',
];
$settings['file_temp_path'] = static::TMP_PATH_TESTING;
- $settings['hash_salt'] = hash('sha256', getenv('MARIADB_HOST') ?: 'localhost');
+ $settings['hash_salt'] = hash('sha256', getenv('DATABASE_HOST') ?: 'localhost');
$settings['trusted_host_patterns'][] = '^.+\.docker\.amazee\.io$';
$settings['trusted_host_patterns'][] = '^nginx$';
- $settings['state_cache'] = TRUE;
$this->assertSettings($settings);
}
@@ -496,10 +490,9 @@ public function testEnvironmentAcquiaDev(): void {
'bower_components',
];
$settings['file_temp_path'] = static::TMP_PATH_TESTING;
- $settings['hash_salt'] = hash('sha256', getenv('MARIADB_HOST') ?: 'localhost');
+ $settings['hash_salt'] = hash('sha256', getenv('DATABASE_HOST') ?: 'localhost');
$settings['trusted_host_patterns'][] = '^.+\.docker\.amazee\.io$';
$settings['trusted_host_patterns'][] = '^nginx$';
- $settings['state_cache'] = TRUE;
$this->assertSettings($settings);
}
@@ -538,10 +531,9 @@ public function testEnvironmentAcquiaStage(): void {
'bower_components',
];
$settings['file_temp_path'] = static::TMP_PATH_TESTING;
- $settings['hash_salt'] = hash('sha256', getenv('MARIADB_HOST') ?: 'localhost');
+ $settings['hash_salt'] = hash('sha256', getenv('DATABASE_HOST') ?: 'localhost');
$settings['trusted_host_patterns'][] = '^.+\.docker\.amazee\.io$';
$settings['trusted_host_patterns'][] = '^nginx$';
- $settings['state_cache'] = TRUE;
$this->assertSettings($settings);
}
@@ -578,10 +570,9 @@ public function testEnvironmentAcquiaProd(): void {
'bower_components',
];
$settings['file_temp_path'] = static::TMP_PATH_TESTING;
- $settings['hash_salt'] = hash('sha256', getenv('MARIADB_HOST') ?: 'localhost');
+ $settings['hash_salt'] = hash('sha256', getenv('DATABASE_HOST') ?: 'localhost');
$settings['trusted_host_patterns'][] = '^.+\.docker\.amazee\.io$';
$settings['trusted_host_patterns'][] = '^nginx$';
- $settings['state_cache'] = TRUE;
$this->assertSettings($settings);
}
// phpcs:ignore #;> ACQUIA
@@ -626,7 +617,7 @@ public function testEnvironmentLagoonDynamic(): void {
'bower_components',
];
$settings['file_temp_path'] = static::TMP_PATH_TESTING;
- $settings['hash_salt'] = hash('sha256', getenv('MARIADB_HOST') ?: 'localhost');
+ $settings['hash_salt'] = hash('sha256', getenv('DATABASE_HOST') ?: 'localhost');
$settings['reverse_proxy'] = TRUE;
$settings['reverse_proxy_header'] = 'HTTP_TRUE_CLIENT_IP';
$settings['trusted_host_patterns'][] = '^.+\.docker\.amazee\.io$';
@@ -634,7 +625,6 @@ public function testEnvironmentLagoonDynamic(): void {
$settings['trusted_host_patterns'][] = '^nginx\-php$';
$settings['trusted_host_patterns'][] = '^.+\.au\.amazee\.io$';
$settings['trusted_host_patterns'][] = '^example1\.com|example2/com$';
- $settings['state_cache'] = TRUE;
$this->assertSettings($settings);
}
@@ -678,7 +668,7 @@ public function testEnvironmentLagoonDev(): void {
'bower_components',
];
$settings['file_temp_path'] = static::TMP_PATH_TESTING;
- $settings['hash_salt'] = hash('sha256', getenv('MARIADB_HOST') ?: 'localhost');
+ $settings['hash_salt'] = hash('sha256', getenv('DATABASE_HOST') ?: 'localhost');
$settings['reverse_proxy'] = TRUE;
$settings['reverse_proxy_header'] = 'HTTP_TRUE_CLIENT_IP';
$settings['trusted_host_patterns'][] = '^.+\.docker\.amazee\.io$';
@@ -686,7 +676,6 @@ public function testEnvironmentLagoonDev(): void {
$settings['trusted_host_patterns'][] = '^nginx\-php$';
$settings['trusted_host_patterns'][] = '^.+\.au\.amazee\.io$';
$settings['trusted_host_patterns'][] = '^example1\.com|example2/com$';
- $settings['state_cache'] = TRUE;
$this->assertSettings($settings);
}
@@ -730,7 +719,7 @@ public function testEnvironmentLagoonTest(): void {
'bower_components',
];
$settings['file_temp_path'] = static::TMP_PATH_TESTING;
- $settings['hash_salt'] = hash('sha256', getenv('MARIADB_HOST') ?: 'localhost');
+ $settings['hash_salt'] = hash('sha256', getenv('DATABASE_HOST') ?: 'localhost');
$settings['reverse_proxy'] = TRUE;
$settings['reverse_proxy_header'] = 'HTTP_TRUE_CLIENT_IP';
$settings['trusted_host_patterns'][] = '^.+\.docker\.amazee\.io$';
@@ -738,7 +727,6 @@ public function testEnvironmentLagoonTest(): void {
$settings['trusted_host_patterns'][] = '^nginx\-php$';
$settings['trusted_host_patterns'][] = '^.+\.au\.amazee\.io$';
$settings['trusted_host_patterns'][] = '^example1\.com|example2/com$';
- $settings['state_cache'] = TRUE;
$this->assertSettings($settings);
}
@@ -781,7 +769,7 @@ public function testEnvironmentLagoonProd(): void {
'bower_components',
];
$settings['file_temp_path'] = static::TMP_PATH_TESTING;
- $settings['hash_salt'] = hash('sha256', getenv('MARIADB_HOST') ?: 'localhost');
+ $settings['hash_salt'] = hash('sha256', getenv('DATABASE_HOST') ?: 'localhost');
$settings['reverse_proxy'] = TRUE;
$settings['reverse_proxy_header'] = 'HTTP_TRUE_CLIENT_IP';
$settings['trusted_host_patterns'][] = '^.+\.docker\.amazee\.io$';
@@ -789,7 +777,6 @@ public function testEnvironmentLagoonProd(): void {
$settings['trusted_host_patterns'][] = '^nginx\-php$';
$settings['trusted_host_patterns'][] = '^.+\.au\.amazee\.io$';
$settings['trusted_host_patterns'][] = '^example1\.com|example2/com$';
- $settings['state_cache'] = TRUE;
$this->assertSettings($settings);
}
// phpcs:ignore #;> LAGOON
diff --git a/tests/phpunit/Drupal/SettingsTestCase.php b/tests/phpunit/Drupal/SettingsTestCase.php
index 3c4128925..896a1267f 100644
--- a/tests/phpunit/Drupal/SettingsTestCase.php
+++ b/tests/phpunit/Drupal/SettingsTestCase.php
@@ -139,7 +139,7 @@ protected function setEnvVars(array $vars): void {
// Filtered real vars without a value to unset them in the lines below.
$vars_real = self::getRealEnvVarsFilteredNoValues([
// Service variables.
- 'MARIADB_',
+ 'DATABASE_',
'REDIS_',
'COMPOSE_',
'GITHUB_',
@@ -201,10 +201,12 @@ protected function unsetEnvVars(): void {
* Require settings file.
*/
protected function requireSettingsFile(): void {
- $app_root = getcwd();
- if (empty($app_root)) {
+ $app_root = getcwd() . '/web';
+
+ if (!file_exists($app_root)) {
throw new \RuntimeException('Could not determine application root.');
}
+
$site_path = 'sites/default';
$config = [];
$settings = [];
diff --git a/tests/phpunit/Drupal/SwitchableSettingsTest.php b/tests/phpunit/Drupal/SwitchableSettingsTest.php
index 75802a7b5..dea27671b 100644
--- a/tests/phpunit/Drupal/SwitchableSettingsTest.php
+++ b/tests/phpunit/Drupal/SwitchableSettingsTest.php
@@ -19,6 +19,9 @@ class SwitchableSettingsTest extends SettingsTestCase {
/**
* Test ClamAV configs in Daemon mode with defaults.
+ *
+ * @group skipped
+ * @see https://github.com/drevops/vortex/issues/1461
*/
public function testClamavDaemonCustom(): void {
$this->setEnvVars([
@@ -39,6 +42,9 @@ public function testClamavDaemonCustom(): void {
/**
* Test ClamAV configs in Executable mode.
+ *
+ * @group skipped
+ * @see https://github.com/drevops/vortex/issues/1461
*/
public function testClamavExecutable(): void {
$this->setEnvVars([
@@ -57,6 +63,9 @@ public function testClamavExecutable(): void {
/**
* Test ClamAV configs in Daemon mode with defaults.
+ *
+ * @group skipped
+ * @see https://github.com/drevops/vortex/issues/1461
*/
public function testClamavDaemonDefaults(): void {
$this->setEnvVars([
diff --git a/web/.htaccess b/web/.htaccess
index 8ec7a8939..8aded2767 100644
--- a/web/.htaccess
+++ b/web/.htaccess
@@ -106,7 +106,7 @@ AddEncoding gzip svgz
# Redirect all non-ww requests to www.
RewriteCond %{HTTP_HOST} .
#;< ACQUIA
- RewriteCond %{ENV:AH_SITE_ENVIRONMENT} prod [NC] # only Production environment.
+ RewriteCond %{HTTP_HOST} !\.acquia-sites\.com [NC]
#;> ACQUIA
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteRule ^ http%{ENV:protossl}://www.%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
diff --git a/web/modules/custom/ys_core/tests/src/Traits/MockTrait.php b/web/modules/custom/ys_core/tests/src/Traits/MockTrait.php
index 555710a4c..ea214b29e 100644
--- a/web/modules/custom/ys_core/tests/src/Traits/MockTrait.php
+++ b/web/modules/custom/ys_core/tests/src/Traits/MockTrait.php
@@ -19,7 +19,7 @@ trait MockTrait {
*
* @param string $class
* Class name to generate the mock.
- * @param array $methodsMap
+ * @param array $methods_map
* Optional array of methods and values, keyed by method name.
* @param array|bool $args
* Optional array of constructor arguments. If omitted, a constructor will
@@ -31,34 +31,23 @@ trait MockTrait {
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.ElseExpression)
*/
- protected function prepareMock(string $class, array $methodsMap = [], array|bool $args = []): MockObject {
- $methods = array_keys($methodsMap);
+ protected function prepareMock(string $class, array $methods_map = [], array|bool $args = []): MockObject {
+ $methods = array_filter(array_keys($methods_map));
if (!class_exists($class)) {
throw new \InvalidArgumentException(sprintf('Class %s does not exist', $class));
}
- $reflection_class = new \ReflectionClass($class);
-
- if ($reflection_class->isAbstract()) {
- $mock = $this->getMockForAbstractClass(
- $class, is_array($args) ? $args : [], '', !empty($args), TRUE, TRUE, $methods
- );
+ $mock = $this->getMockBuilder($class);
+ if (is_array($args) && !empty($args)) {
+ $mock = $mock->enableOriginalConstructor()->setConstructorArgs($args);
}
- else {
- $mock = $this->getMockBuilder($class);
- if (is_array($args) && !empty($args)) {
- $mock = $mock->enableOriginalConstructor()
- ->setConstructorArgs($args);
- }
- elseif ($args === FALSE) {
- $mock = $mock->disableOriginalConstructor();
- }
- $mock = $mock->onlyMethods($methods)
- ->getMock();
+ elseif ($args === FALSE) {
+ $mock = $mock->disableOriginalConstructor();
}
+ $mock = $mock->onlyMethods($methods)->getMock();
- foreach ($methodsMap as $method => $value) {
+ foreach ($methods_map as $method => $value) {
// Handle callback values differently.
if ($value instanceof Stub) {
$mock->expects($this->any())
diff --git a/web/modules/custom/ys_core/ys_core.info.yml b/web/modules/custom/ys_core/ys_core.info.yml
index 0d9d12313..322b0fbd4 100644
--- a/web/modules/custom/ys_core/ys_core.info.yml
+++ b/web/modules/custom/ys_core/ys_core.info.yml
@@ -1,5 +1,5 @@
name: YOURSITE Core
type: module
description: Core feature for YOURSITE site.
-core_version_requirement: ^10
+core_version_requirement: ^11
package: your_site
diff --git a/web/modules/custom/ys_search/config/install/search_api.index.content.yml b/web/modules/custom/ys_search/config/install/search_api.index.content.yml
index 0e42659e1..a5ada20e1 100644
--- a/web/modules/custom/ys_search/config/install/search_api.index.content.yml
+++ b/web/modules/custom/ys_search/config/install/search_api.index.content.yml
@@ -5,9 +5,8 @@ dependencies:
- field.storage.node.body
- search_api.server.solr
module:
- - search_api_solr
- node
- - search_api
+ - search_api_solr
third_party_settings:
search_api_solr:
finalize: false
@@ -43,6 +42,7 @@ third_party_settings:
term_modifiers:
slop: 3
fuzzy: 1
+ fuzzy_analyzer: true
advanced:
index_prefix: ''
collection: ''
@@ -87,6 +87,7 @@ processor_settings:
add_url: { }
aggregated_field: { }
auto_aggregated_fulltext_field: { }
+ custom_value: { }
entity_type: { }
language_with_fallback: { }
rendered_item: { }
@@ -96,6 +97,7 @@ tracker_settings:
indexing_order: fifo
options:
cron_limit: 50
+ delete_on_fail: true
index_directly: true
track_changes_in_references: true
server: solr
diff --git a/web/modules/custom/ys_search/config/install/views.view.search.yml b/web/modules/custom/ys_search/config/install/views.view.search.yml
index ada4f7c7c..0535388d4 100644
--- a/web/modules/custom/ys_search/config/install/views.view.search.yml
+++ b/web/modules/custom/ys_search/config/install/views.view.search.yml
@@ -80,6 +80,7 @@ display:
type: mini
options:
offset: 0
+ pagination_heading_level: h4
items_per_page: 10
total_pages: null
id: 0
@@ -110,7 +111,7 @@ display:
type: none
options: { }
cache:
- type: none
+ type: search_api_none
options: { }
empty: { }
sorts: { }
@@ -148,6 +149,7 @@ display:
expose_fields: false
placeholder: ''
searched_fields_id: search_api_fulltext_searched_fields
+ value_maxlength: 128
is_grouped: false
group_info:
label: ''
@@ -167,6 +169,12 @@ display:
type: default
row:
type: search_api
+ options:
+ view_modes:
+ 'entity:node':
+ ':default': default
+ article: ':default'
+ page: ':default'
query:
type: search_api_query
options:
diff --git a/web/modules/custom/ys_search/ys_search.info.yml b/web/modules/custom/ys_search/ys_search.info.yml
index f293d5e35..25a499cf4 100644
--- a/web/modules/custom/ys_search/ys_search.info.yml
+++ b/web/modules/custom/ys_search/ys_search.info.yml
@@ -1,7 +1,7 @@
name: YOURSITE Search
type: module
description: Search functionality for YOURSITE site.
-core_version_requirement: ^10
+core_version_requirement: ^11
package: your_site
dependencies:
- search_api:search_api
diff --git a/web/robots.txt b/web/robots.txt
index 3ad8e2e8d..a1e1d3431 100644
--- a/web/robots.txt
+++ b/web/robots.txt
@@ -46,7 +46,6 @@ Disallow: /composer/Template/README.txt
Disallow: /modules/README.txt
Disallow: /sites/README.txt
Disallow: /themes/README.txt
-Disallow: /web.config
# Paths (clean URLs)
Disallow: /admin/
Disallow: /comment/reply/
diff --git a/web/sites/default/default.services.yml b/web/sites/default/default.services.yml
index dacb3f7e9..1243d0600 100644
--- a/web/sites/default/default.services.yml
+++ b/web/sites/default/default.services.yml
@@ -38,6 +38,10 @@ parameters:
# To maximize compatibility and normalize the behavior across user agents,
# the cookie domain should start with a dot.
#
+ # Sessions themselves will only be synchronized across subdomains if they
+ # are all served from the same Drupal installation or if some other session
+ # sharing mechanism is implemented.
+ #
# @default none
# cookie_domain: '.example.com'
#
@@ -47,23 +51,6 @@ parameters:
# information.
# @default no value
cookie_samesite: Lax
- #
- # Set the session ID string length. The length can be between 22 to 256. The
- # PHP recommended value is 48. See
- # https://www.php.net/manual/session.security.ini.php for more information.
- # This value should be kept in sync with
- # \Drupal\Core\Session\SessionConfiguration::__construct()
- # @default 48
- sid_length: 48
- #
- # Set the number of bits in encoded session ID character. The possible
- # values are '4' (0-9, a-f), '5' (0-9, a-v), and '6' (0-9, a-z, A-Z, "-",
- # ","). The PHP recommended value is 6. See
- # https://www.php.net/manual/session.security.ini.php for more information.
- # This value should be kept in sync with
- # \Drupal\Core\Session\SessionConfiguration::__construct()
- # @default 6
- sid_bits_per_character: 6
# By default, Drupal generates a session cookie name based on the full
# domain name. Set the name_suffix to a short random string to ensure this
# session cookie name is unique on different installations on the same
@@ -216,20 +203,39 @@ parameters:
# Note: By default the configuration is disabled.
cors.config:
enabled: false
- # Specify allowed headers, like 'x-allowed-header'.
+ # Specifies allowed headers and sets the Access-Control-Allow-Headers
+ # header. For example, ['X-Custom-Header']. See
+ # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers
allowedHeaders: []
- # Specify allowed request methods, specify ['*'] to allow all possible ones.
+ # Specifies allowed request methods and sets the
+ # Access-Control-Allow-Methods header. For example, ['POST', 'GET',
+ # 'OPTIONS'] or ['*'] to allow all. Note the wildcard is not yet implemented
+ # in all browsers. See
+ # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods
allowedMethods: []
- # Configure requests allowed from specific origins. Do not include trailing
- # slashes with URLs.
+ # Configure requests allowed from specific origins and sets the
+ # Access-Control-Allow-Origin header. For example,
+ # ['https://www.drupal.org'] or ['*'] to allow any origin to access your
+ # resource. See
+ # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
allowedOrigins: ['*']
# Configure requests allowed from origins, matching against regex patterns.
allowedOriginsPatterns: []
- # Sets the Access-Control-Expose-Headers header.
+ # Sets the Access-Control-Expose-Headers header. The default is false which
+ # means the header will not be set. To set the header use a comma delimited
+ # list within square brackets. For example, ['Content-Type', 'Expires'] or
+ # ['*'] to expose all headers. Setting exposedHeaders: ['*'] will result in
+ # a Access-Control-Expose-Headers: * response header. See
+ # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers
exposedHeaders: false
- # Sets the Access-Control-Max-Age header.
+ # Setting Access-Control-Max-Age header value to '0' or false will omit this
+ # from the response. However, setting it to '-1' will explicitly disable
+ # caching. For example, setting the value to 600 will cache results of a
+ # preflight request for 10 minutes. See
+ # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age
maxAge: false
- # Sets the Access-Control-Allow-Credentials header.
+ # Sets the Access-Control-Allow-Credentials header if set to true. See
+ # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials
supportsCredentials: false
queue.config:
diff --git a/web/sites/default/default.settings.local.php b/web/sites/default/default.settings.local.php
index f0931a2a9..498770e79 100644
--- a/web/sites/default/default.settings.local.php
+++ b/web/sites/default/default.settings.local.php
@@ -29,6 +29,3 @@
// Hide admin toolbar. Useful for themeing while logged in as admin.
// $settings['hide_admin_toolbar'] = TRUE;
-
-// Disable state cache.
-$settings['state_cache'] = FALSE;
diff --git a/web/sites/default/default.settings.php b/web/sites/default/default.settings.php
index 264597b16..cd364bb00 100644
--- a/web/sites/default/default.settings.php
+++ b/web/sites/default/default.settings.php
@@ -144,7 +144,7 @@
* in deadlocks, the other two options are 'READ UNCOMMITTED' and 'SERIALIZABLE'.
* They are available but not supported; use them at your own risk. For more
* info:
- * https://dev.mysql.com/doc/refman/5.7/en/innodb-transaction-isolation-levels.html
+ * https://dev.mysql.com/doc/refman/8.0/en/innodb-transaction-isolation-levels.html
*
* On your settings.php, change the isolation level:
* @code
@@ -724,15 +724,6 @@
*/
# $settings['container_base_class'] = '\Drupal\Core\DependencyInjection\Container';
-/**
- * Override the default yaml parser class.
- *
- * Provide a fully qualified class name here if you would like to provide an
- * alternate implementation YAML parser. The class must implement the
- * \Drupal\Component\Serialization\SerializationInterface interface.
- */
-# $settings['yaml_parser_class'] = NULL;
-
/**
* Trusted host configuration.
*
@@ -807,16 +798,6 @@
*/
$settings['entity_update_backup'] = TRUE;
-/**
- * State caching.
- *
- * State caching uses the cache collector pattern to cache all requested keys
- * from the state API in a single cache entry, which can greatly reduce the
- * amount of database queries. However, some sites may use state with a
- * lot of dynamic keys which could result in a very large cache.
- */
-$settings['state_cache'] = TRUE;
-
/**
* Node migration type.
*
diff --git a/web/sites/default/includes/providers/settings.acquia.php b/web/sites/default/includes/providers/settings.acquia.php
index 04a16fb89..064d73678 100644
--- a/web/sites/default/includes/providers/settings.acquia.php
+++ b/web/sites/default/includes/providers/settings.acquia.php
@@ -22,7 +22,6 @@
if (file_exists('/var/www/site-php/your_site/your_site-settings.inc')) {
// @codeCoverageIgnoreStart
require '/var/www/site-php/your_site/your_site-settings.inc';
- $settings['config_sync_directory'] = $settings['config_vcs_directory'];
// @codeCoverageIgnoreEnd
}
diff --git a/web/sites/default/settings.php b/web/sites/default/settings.php
index a9ca2d0ea..8c5c79105 100644
--- a/web/sites/default/settings.php
+++ b/web/sites/default/settings.php
@@ -5,7 +5,7 @@
* Drupal site-specific configuration file.
*
* The structure of this file:
- * - Environment constants definitions.
+ * - Environment type constants definitions.
* - Site-specific settings.
* - Inclusion of hosting providers settings.
* - Per-environment overrides.
@@ -22,7 +22,7 @@
declare(strict_types=1);
////////////////////////////////////////////////////////////////////////////////
-/// ENVIRONMENT CONSTANTS ///
+/// ENVIRONMENT TYPE CONSTANTS ///
////////////////////////////////////////////////////////////////////////////////
// Use these constants anywhere in code to alter behaviour for a specific
@@ -50,6 +50,7 @@
////////////////////////////////////////////////////////////////////////////////
/// SITE-SPECIFIC SETTINGS ///
////////////////////////////////////////////////////////////////////////////////
+
$app_root = $app_root ?? DRUPAL_ROOT;
$site_path = $site_path ?? 'sites/default';
$contrib_path = $app_root . DIRECTORY_SEPARATOR . (is_dir($app_root . DIRECTORY_SEPARATOR . 'modules/contrib') ? 'modules/contrib' : 'modules');
@@ -67,7 +68,7 @@
$settings['file_temp_path'] = getenv('DRUPAL_TEMPORARY_FILES') ?: '/tmp';
// Base salt on the DB host name.
-$settings['hash_salt'] = hash('sha256', getenv('MARIADB_HOST') ?: 'localhost');
+$settings['hash_salt'] = hash('sha256', getenv('DATABASE_HOST') ?: 'localhost');
// Expiration of cached pages.
$config['system.performance']['cache']['page']['max_age'] = 900;
@@ -76,9 +77,6 @@
$config['system.performance']['css']['preprocess'] = TRUE;
$config['system.performance']['js']['preprocess'] = TRUE;
-// Enable state cache.
-$settings['state_cache'] = TRUE;
-
// The default list of directories that will be ignored by Drupal's file API.
$settings['file_scan_ignore_directories'] = [
'node_modules',
@@ -115,11 +113,11 @@
[
'default' =>
[
- 'database' => getenv('MARIADB_DATABASE') ?: 'drupal',
- 'username' => getenv('MARIADB_USERNAME') ?: 'drupal',
- 'password' => getenv('MARIADB_PASSWORD') ?: 'drupal',
- 'host' => getenv('MARIADB_HOST') ?: 'localhost',
- 'port' => getenv('MARIADB_PORT') ?: '',
+ 'database' => getenv('DATABASE_NAME') ?: getenv('MARIADB_DATABASE') ?: 'drupal',
+ 'username' => getenv('DATABASE_USERNAME') ?: getenv('MARIADB_USERNAME') ?: 'drupal',
+ 'password' => getenv('DATABASE_PASSWORD') ?: getenv('MARIADB_PASSWORD') ?: 'drupal',
+ 'host' => getenv('DATABASE_HOST') ?: getenv('MARIADB_HOST') ?: 'localhost',
+ 'port' => getenv('DATABASE_PORT') ?: getenv('MARIADB_PORT') ?: '',
'prefix' => '',
'driver' => 'mysql',
],
@@ -127,10 +125,10 @@
];
////////////////////////////////////////////////////////////////////////////////
-/// ENVIRONMENT DETECTION ///
+/// ENVIRONMENT TYPE DETECTION ///
////////////////////////////////////////////////////////////////////////////////
-// Load environment-specific settings.
+// Load provider-specific settings.
if (file_exists($app_root . '/' . $site_path . '/includes/providers')) {
$files = glob($app_root . '/' . $site_path . '/includes/providers/settings.*.php');
if ($files) {
diff --git a/web/themes/custom/your_site_theme/logo.png b/web/themes/custom/your_site_theme/logo.png
index 4591bcb9a..fbb779a01 100644
Binary files a/web/themes/custom/your_site_theme/logo.png and b/web/themes/custom/your_site_theme/logo.png differ
diff --git a/web/themes/custom/your_site_theme/logo.svg b/web/themes/custom/your_site_theme/logo.svg
index 8f1a47f9e..5160e747f 100644
--- a/web/themes/custom/your_site_theme/logo.svg
+++ b/web/themes/custom/your_site_theme/logo.svg
@@ -1,9 +1,10 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
diff --git a/web/themes/custom/your_site_theme/screenshot.png b/web/themes/custom/your_site_theme/screenshot.png
index bb1f306ef..393d1b781 100644
Binary files a/web/themes/custom/your_site_theme/screenshot.png and b/web/themes/custom/your_site_theme/screenshot.png differ
diff --git a/web/themes/custom/your_site_theme/scss/_variables.scss b/web/themes/custom/your_site_theme/scss/_variables.scss
index 3381e5d56..e17be2952 100644
--- a/web/themes/custom/your_site_theme/scss/_variables.scss
+++ b/web/themes/custom/your_site_theme/scss/_variables.scss
@@ -4,4 +4,4 @@
// Color definitions.
$color-white: #fff;
-$color-primary: #370564;
+$color-primary: #164c58;
diff --git a/web/themes/custom/your_site_theme/your_site_theme.info.yml b/web/themes/custom/your_site_theme/your_site_theme.info.yml
index f7df3a139..39ed9e3dd 100644
--- a/web/themes/custom/your_site_theme/your_site_theme.info.yml
+++ b/web/themes/custom/your_site_theme/your_site_theme.info.yml
@@ -1,7 +1,7 @@
name: your_site_theme
type: theme
description: 'YOURSITE theme.'
-core_version_requirement: ^10
+core_version_requirement: ^11
base theme: olivero
libraries: