diff --git a/.circleci/config.yml b/.circleci/config.yml
index b6fde8121c..6a18158dbf 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -131,16 +131,86 @@ job-build-isolated: &job-build-isolated
- attach_workspace:
at: /tmp/workspace
- checkout
- - run: cd web/themes/contrib/civictheme && ./.devtools/build.sh
- - run: cd web/themes/contrib/civictheme && ./.devtools/lint.sh
- - run: cd web/themes/contrib/civictheme && ./.devtools/test.sh
+
- run:
- command: cd web/themes/contrib/civictheme && ./.devtools/process-artifacts.sh
- when: always
+ name: Upgrade sqlite3
+ command: |
+ wget https://www.sqlite.org/2024/sqlite-autoconf-3450300.tar.gz -O /tmp/sqlite.tar.gz
+ tar -xzf /tmp/sqlite.tar.gz -C /tmp
+ cd /tmp/sqlite-autoconf-3450300
+ ./configure CFLAGS="-DSQLITE_ENABLE_COLUMN_METADATA=1" --prefix=/usr/local
+ make && sudo make install
+ sudo ldconfig
+ echo "export LD_LIBRARY_PATH=/usr/local/lib" >> $BASH_ENV
+
+ - run:
+ name: Install PCOV
+ command: |
+ sudo pecl install pcov
+ echo "extension=pcov.so" | sudo tee -a /usr/local/etc/php/conf.d/pcov.ini
+ echo "pcov.enabled=1" | sudo tee -a /usr/local/etc/php/conf.d/pcov.ini
+
+ - run:
+ name: Assemble the codebase
+ command: .devtools/assemble.sh
+ working_directory: web/themes/contrib/civictheme
+
+ - run:
+ name: Start built-in PHP server
+ command: .devtools/start.sh
+ working_directory: web/themes/contrib/civictheme
+
+ - run:
+ name: Provision site
+ command: .devtools/provision.sh
+ working_directory: web/themes/contrib/civictheme
+
+ - run:
+ name: Lint code with PHPCS
+ command: vendor/bin/phpcs || [ "${CI_PHPCS_IGNORE_FAILURE:-0}" -eq 1 ]
+ working_directory: web/themes/contrib/civictheme/build
+
+ - run:
+ name: Lint code with PHPStan
+ command: vendor/bin/phpstan || [ "${CI_PHPSTAN_IGNORE_FAILURE:-0}" -eq 1 ]
+ working_directory: web/themes/contrib/civictheme/build
+
+ - run:
+ name: Lint code with Rector
+ command: vendor/bin/rector --clear-cache --dry-run || [ "${CI_RECTOR_IGNORE_FAILURE:-0}" -eq 1 ]
+ working_directory: web/themes/contrib/civictheme/build
+
+ - run:
+ name: Lint code with PHPMD
+ command: vendor/bin/phpmd . text phpmd.xml || [ "${CI_PHPMD_IGNORE_FAILURE:-0}" -eq 1 ]
+ working_directory: web/themes/contrib/civictheme/build
+
+ - run:
+ name: Lint code with Twig CS Fixer
+ command: vendor/bin/twig-cs-fixer || [ "${CI_TWIGCSFIXER_IGNORE_FAILURE:-0}" -eq 1 ]
+ working_directory: web/themes/contrib/civictheme/build
+
+ - run:
+ name: Run tests
+ command: vendor/bin/phpunit || [ "${CI_TEST_IGNORE_FAILURE:-0}" -eq 1 ]
+ working_directory: web/themes/contrib/civictheme/build
+
- store_test_results:
- path: /tmp/test_results
+ path: web/themes/contrib/civictheme/.logs/test_results
+
+ - store_artifacts:
+ path: web/themes/contrib/civictheme/build/web/sites/simpletest/browser_output
+
- store_artifacts:
- path: /tmp/artifacts
+ path: web/themes/contrib/civictheme/.logs/coverage
+
+ - run:
+ name: Upload code coverage reports to Codecov
+ command: |
+ if [ -z "${CIRCLE_TAG-}" ] && [ -n "${CODECOV_TOKEN-}" ] && [ -d .logs/coverage/phpunit ]; then
+ curl -Os https://cli.codecov.io/latest/linux/codecov && sudo chmod +x codecov
+ ./codecov --verbose upload-process --fail-on-error -n "circleci-$CIRCLE_JOB" -s web/themes/contrib/civictheme/.logs/coverage
+ fi
job-build: &job-build
parallelism: 2
@@ -262,8 +332,8 @@ jobs:
docker:
- image: cimg/php:8.3-browsers
environment:
- DRUPAL_VERSION: 11@alpha
- DRUPAL_PROJECT_SHA: 11.x
+ DRUPAL_VERSION: 10@beta
+ DRUPAL_PROJECT_SHA: 10.x
<<: *job-build-isolated
# Used to pass the built code down the pipeline.
diff --git a/web/themes/contrib/civictheme/.ahoy.yml b/web/themes/contrib/civictheme/.ahoy.yml
index 9394fb08f2..78783b66ab 100644
--- a/web/themes/contrib/civictheme/.ahoy.yml
+++ b/web/themes/contrib/civictheme/.ahoy.yml
@@ -1,8 +1,5 @@
-#
# Ahoy configuration file.
# http://www.ahoycli.com/
-#
-# Provides development experience shortcuts.
---
ahoyapi: v2
@@ -10,16 +7,100 @@ commands:
build:
usage: Build or rebuild the project.
- cmd: ./.devtools/build.sh
+ cmd: |
+ ahoy stop > /dev/null 2>&1 || true
+ ahoy assemble
+ ahoy start
+ ahoy provision
+
+ assemble:
+ usage: Assemble a codebase using project code and all required dependencies.
+ cmd: ./.devtools/assemble.sh
+
+ start:
+ usage: Start development environment.
+ cmd: ./.devtools/start.sh
+
+ stop:
+ usage: Stop development environment.
+ cmd: ./.devtools/stop.sh
+
+ provision:
+ usage: Provision application within assembled codebase.
+ cmd: ./.devtools/provision.sh
+
+ drush:
+ usage: Run Drush command.
+ cmd: build/vendor/bin/drush -l http://${WEBSERVER_HOST:-localhost}:${WEBSERVER_PORT:-8000} $*
+
+ login:
+ usage: Login to a website.
+ cmd: ahoy drush uli
lint:
- usage: Lint code.
- cmd: ./.devtools/lint.sh
+ usage: Check coding standards for violations.
+ cmd: |
+ pushd "build" >/dev/null || exit 1
+ vendor/bin/phpcs
+ vendor/bin/phpstan
+ vendor/bin/rector --clear-cache --dry-run
+ vendor/bin/phpmd . text phpmd.xml
+ vendor/bin/twig-cs-fixer
+ popd >/dev/null || exit 1
lint-fix:
- usage: Lint code.
- cmd: ./.devtools/lint-fix.sh
+ usage: Fix violations in coding standards.
+ cmd: |
+ pushd "build" >/dev/null || exit 1
+ vendor/bin/rector --clear-cache
+ vendor/bin/phpcbf
+ popd >/dev/null || exit 1
test:
- usage: Run all tests.
- cmd: ./.devtools/test.sh
+ usage: Run tests.
+ cmd: |
+ pushd "build" >/dev/null || exit 1
+ vendor/bin/phpunit
+ popd >/dev/null || exit 1
+
+ test-unit:
+ usage: Run unit tests.
+ cmd: |
+ pushd "build" >/dev/null || exit 1
+ vendor/bin/phpunit --testsuite unit
+ popd >/dev/null || exit 1
+
+ test-kernel:
+ usage: Run kernel tests.
+ cmd: |
+ pushd "build" >/dev/null || exit 1
+ vendor/bin/phpunit --testsuite kernel
+ popd >/dev/null || exit 1
+
+ test-functional:
+ usage: Run functional tests.
+ cmd: |
+ pushd "build" >/dev/null || exit 1
+ vendor/bin/phpunit --testsuite functional
+ popd >/dev/null || exit 1
+
+ reset:
+ usage: Reset project to the default state.
+ cmd: |
+ killall -9 php >/dev/null 2>&1 || true
+ chmod -Rf 777 build > /dev/null
+ rm -Rf build > /dev/null || true
+ rm -Rf .logs > /dev/null || true
+
+# Override entrypoint to alter default behaviour of Ahoy.
+entrypoint:
+ - bash
+ - -c
+ - -e
+ - |
+ extension="$(basename -s .info.yml -- ./*.info.yml)"
+ export SIMPLETEST_BASE_URL=http://${WEBSERVER_HOST:-localhost}:${WEBSERVER_PORT:-8000}
+ export SIMPLETEST_DB=sqlite://localhost/drupal_test_${extension}.sqlite
+ bash -e -c "$0" "$@"
+ - '{{cmd}}'
+ - '{{name}}'
diff --git a/web/themes/contrib/civictheme/.devtools/README.nd b/web/themes/contrib/civictheme/.devtools/README.nd
new file mode 100644
index 0000000000..59e2745329
--- /dev/null
+++ b/web/themes/contrib/civictheme/.devtools/README.nd
@@ -0,0 +1,3 @@
+This directory contains scripts used for development.
+
+These can be used locally and in the CI environment.
diff --git a/web/themes/contrib/civictheme/.devtools/README.txt b/web/themes/contrib/civictheme/.devtools/README.txt
deleted file mode 100644
index cdda9a51a5..0000000000
--- a/web/themes/contrib/civictheme/.devtools/README.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-This directory contains development tools to help with building and testing.
-
-These can be used locally and in the CI environment.
diff --git a/web/themes/contrib/civictheme/.devtools/assemble.sh b/web/themes/contrib/civictheme/.devtools/assemble.sh
new file mode 100755
index 0000000000..03b92c1360
--- /dev/null
+++ b/web/themes/contrib/civictheme/.devtools/assemble.sh
@@ -0,0 +1,166 @@
+#!/usr/bin/env bash
+##
+# Assemble a codebase using project code and all required dependencies.
+#
+# Allows to use the latest Drupal core as well as specified versions (for
+# testing backward compatibility).
+#
+# - Retrieves the scaffold from drupal-composer/drupal-project or custom scaffold.
+# - Builds Drupal site codebase with current extension and it's dependencies.
+# - Adds development dependencies.
+# - Installs composer dependencies.
+#
+# This script will re-build the codebase from scratch every time it runs.
+
+# shellcheck disable=SC2015,SC2094,SC2002
+
+set -eu
+[ -n "${DEBUG:-}" ] && set -x
+
+#-------------------------------------------------------------------------------
+# Variables (passed from environment; provided for reference only).
+#-------------------------------------------------------------------------------
+
+# Drupal core version to use. If not provided - the latest stable version will be used.
+# Must be coupled with DRUPAL_PROJECT_SHA below.
+DRUPAL_VERSION="${DRUPAL_VERSION:-10}"
+
+# Commit SHA of the drupal-project to install custom core version. If not
+# provided - the latest version will be used.
+# Must be coupled with DRUPAL_VERSION above.
+DRUPAL_PROJECT_SHA="${DRUPAL_PROJECT_SHA:-10.x}"
+
+# Repository for "drupal-composer/drupal-project" project.
+# May be overwritten to use forked repos that may have not been accepted
+# yet (i.e., when major Drupal version is about to be released).
+DRUPAL_PROJECT_REPO="${DRUPAL_PROJECT_REPO:-https://github.com/drupal-composer/drupal-project.git}"
+
+#-------------------------------------------------------------------------------
+
+# @formatter:off
+note() { printf " %s\n" "${1}"; }
+info() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[34m[INFO] %s\033[0m\n" "${1}" || printf "[INFO] %s\n" "${1}"; }
+pass() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[32m[ OK ] %s\033[0m\n" "${1}" || printf "[ OK ] %s\n" "${1}"; }
+fail() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[31m[FAIL] %s\033[0m\n" "${1}" || printf "[FAIL] %s\n" "${1}"; }
+# @formatter:on
+
+#-------------------------------------------------------------------------------
+
+echo "==============================="
+echo " 🏗️ ASSEMBLE "
+echo "==============================="
+echo
+
+# Make sure Composer doesn't run out of memory.
+export COMPOSER_MEMORY_LIMIT=-1
+
+info "Validate tools."
+! command -v git >/dev/null && echo "ERROR: Git is required for this script to run." && exit 1
+! command -v php >/dev/null && echo "ERROR: PHP is required for this script to run." && exit 1
+! command -v composer >/dev/null && echo "ERROR: Composer (https://getcomposer.org/) is required for this script to run." && exit 1
+! command -v jq >/dev/null && echo "ERROR: jq (https://stedolan.github.io/jq/) is required for this script to run." && exit 1
+pass "Tools are valid."
+
+# Extension name, taken from the .info file.
+extension="$(basename -s .info.yml -- ./*.info.yml)"
+[ "${extension}" == "*" ] && echo "ERROR: No .info.yml file found." && exit 1
+
+# Extension type.
+type=$(grep -q "type: theme" "${extension}.info.yml" && echo "themes" || echo "modules")
+
+info "Validate Composer configuration."
+composer validate --ansi --strict
+
+# Reset the environment.
+if [ -d "build" ]; then
+ info "Removing existing build directory."
+ chmod -Rf 777 "build" >/dev/null || true
+ rm -rf "build" >/dev/null || true
+ pass "Existing build directory removed."
+fi
+
+info "Creating Drupal codebase."
+# Allow installing custom version of Drupal core from drupal-composer/drupal-project,
+# but only coupled with drupal-project SHA (required to get correct dependencies).
+if [ -n "${DRUPAL_VERSION:-}" ] && [ -n "${DRUPAL_PROJECT_SHA:-}" ]; then
+ note "Initialising Drupal site from the scaffold repo ${DRUPAL_PROJECT_REPO} commit ${DRUPAL_PROJECT_SHA}."
+
+ # Clone Drupal core at the specific commit SHA.
+ git clone -n "${DRUPAL_PROJECT_REPO}" "build"
+ git --git-dir="build/.git" --work-tree="build" checkout "${DRUPAL_PROJECT_SHA}"
+ rm -rf "build/.git" >/dev/null
+
+ note "Pin Drupal to a specific version ${DRUPAL_VERSION}."
+ sed_opts=(-i) && [ "$(uname)" == "Darwin" ] && sed_opts=(-i '')
+ sed "${sed_opts[@]}" 's|\(.*"drupal\/core"\): "\(.*\)",.*|\1: '"\"$DRUPAL_VERSION\",|" "build/composer.json"
+ cat "build/composer.json"
+else
+ note "Initialising Drupal site from the latest scaffold."
+ # There are no releases in "drupal-composer/drupal-project", so have to use "@dev".
+ composer create-project drupal-composer/drupal-project:@dev "build" --no-interaction --no-install
+fi
+pass "Drupal codebase created."
+
+info "Merging configuration from composer.dev.json."
+php -r "echo json_encode(array_replace_recursive(json_decode(file_get_contents('composer.dev.json'), true),json_decode(file_get_contents('build/composer.json'), true)),JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);" >"build/composer2.json" && mv -f "build/composer2.json" "build/composer.json"
+
+info "Merging configuration from extension's composer.json."
+php -r "echo json_encode(array_replace_recursive(json_decode(file_get_contents('composer.json'), true),json_decode(file_get_contents('build/composer.json'), true)),JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);" >"build/composer2.json" && mv -f "build/composer2.json" "build/composer.json"
+
+if [ -n "${GITHUB_TOKEN:-}" ]; then
+ info "Adding GitHub authentication token if provided."
+ composer config --global github-oauth.github.com "${GITHUB_TOKEN}"
+ composer config --global github-oauth.github.com | grep -q "gh" || fail "GitHub token not added."
+ pass "GitHub token added."
+fi
+
+info "Creating custom directories."
+mkdir -p build/web/modules/custom build/web/themes/custom
+
+info "Installing dependencies."
+composer --working-dir="build" install
+pass "Dependencies installed."
+
+# Suggested dependencies allow to install them for testing without requiring
+# them in extension's composer.json.
+info "Installing suggested dependencies from extension's composer.json."
+composer_suggests=$(cat composer.json | jq -r 'select(.suggest != null) | .suggest | keys[]')
+for composer_suggest in $composer_suggests; do
+ composer --working-dir="build" require "${composer_suggest}"
+done
+pass "Suggested dependencies installed."
+
+info "Copying tools configuration files."
+cp phpcs.xml phpstan.neon phpmd.xml rector.php .twig-cs-fixer.php phpunit.xml "build/"
+pass "Tools configuration files copied."
+
+info "Symlinking extension's code."
+rm -rf "build/web/${type}/custom" >/dev/null && mkdir -p "build/web/${type}/custom/${extension}"
+ln -s "$(pwd)"/* "build/web/${type}/custom/${extension}" && rm "build/web/${type}/custom/${extension}/build"
+pass "Extension's code symlinked."
+
+# If front-end dependencies are used in the project, package-lock.json is
+# expected to be committed to the repository.
+if [ -f "build/web/${type}/custom/${extension}/package-lock.json" ]; then
+ pushd "build/web/${type}/custom/${extension}" >/dev/null || exit 1
+
+ info "Installing front-end dependencies."
+ if [ -f ".nvmrc" ]; then nvm use; fi
+ if [ ! -d "node_modules" ]; then npm ci; fi
+ # Disable building front-end dependencies for now.
+ # echo "> Build front-end dependencies."
+ # npm run build
+ pass "Front-end dependencies installed."
+
+ popd >/dev/null || exit 1
+fi
+
+echo
+echo "==============================="
+echo " 🏗 ASSEMBLE COMPLETE ✅ "
+echo "==============================="
+echo
+echo "> Next steps:"
+echo " .devtools/start.sh # Start the webserver"
+echo " .devtools/provision.sh # Provision the website"
+echo
diff --git a/web/themes/contrib/civictheme/.devtools/build.sh b/web/themes/contrib/civictheme/.devtools/build.sh
deleted file mode 100755
index cbe733abdb..0000000000
--- a/web/themes/contrib/civictheme/.devtools/build.sh
+++ /dev/null
@@ -1,219 +0,0 @@
-#!/usr/bin/env bash
-##
-# Build Drupal site using SQLite database, install current theme and serve
-# using in-built PHP server.
-#
-# Allows to use the latest Drupal core as well as specified versions (for
-# testing backward compatibility).
-#
-# - Retrieves the scaffold from drupal-composer/drupal-project or custom scaffold.
-# - Builds Drupal site codebase with current theme and it's dependencies.
-# - Installs Drupal using SQLite database.
-# - Starts in-built PHP-server
-# - Enables theme
-# - Serves site and generates one-time login link
-#
-# This script will re-build everything from scratch every time it runs.
-
-# shellcheck disable=SC2015,SC2094,SC2002
-
-set -eu
-[ -n "${DEBUG:-}" ] && set -x
-
-#-------------------------------------------------------------------------------
-# Variables (passed from environment; provided for reference only).
-#-------------------------------------------------------------------------------
-
-# Webserver hostname.
-WEBSERVER_HOST="${WEBSERVER_HOST:-localhost}"
-
-# Webserver port.
-WEBSERVER_PORT="${WEBSERVER_PORT:-8000}"
-
-# Drupal core version to use. If not provided - the latest stable version will be used.
-# Must be coupled with DRUPAL_PROJECT_SHA below.
-DRUPAL_VERSION="${DRUPAL_VERSION:-}"
-
-# Commit SHA of the drupal-project to install custom core version. If not
-# provided - the latest version will be used.
-# Must be coupled with DRUPAL_VERSION above.
-DRUPAL_PROJECT_SHA="${DRUPAL_PROJECT_SHA:-}"
-
-# Repository for "drupal-composer/drupal-project" project.
-# May be overwritten to use forked repos that may have not been accepted
-# yet (i.e., when major Drupal version is about to be released).
-DRUPAL_PROJECT_REPO="${DRUPAL_PROJECT_REPO:-https://github.com/drupal-composer/drupal-project.git}"
-
-# Drupal profile to use when installing the site.
-DRUPAL_PROFILE="${DRUPAL_PROFILE:-standard}"
-
-#-------------------------------------------------------------------------------
-
-echo
-echo "==> Started build in \"build\" directory."
-echo
-
-echo "-------------------------------"
-echo " Validating requirements "
-echo "-------------------------------"
-
-echo " > Validating tools."
-! command -v git > /dev/null && echo "ERROR: Git is required for this script to run." && exit 1
-! command -v php > /dev/null && echo "ERROR: PHP is required for this script to run." && exit 1
-! command -v composer > /dev/null && echo "ERROR: Composer (https://getcomposer.org/) is required for this script to run." && exit 1
-! command -v jq > /dev/null && echo "ERROR: jq (https://stedolan.github.io/jq/) is required for this script to run." && exit 1
-
-drush() { "build/vendor/bin/drush" -r "$(pwd)/build/web" -y "$@"; }
-
-# Theme name, taken from the .info file.
-theme="$(basename -s .info.yml -- ./*.info.yml)"
-[ "${theme}" == "*" ] && echo "ERROR: No .info.yml file found." && exit 1
-
-# Database file path.
-db_file="/tmp/site_${theme}.sqlite"
-
-export COMPOSER_MEMORY_LIMIT=-1
-
-echo " > Validating Composer configuration."
-composer validate --ansi --strict
-
-# Reset the environment.
-[ -d "build" ] && echo " > Removing existing build directory." && chmod -Rf 777 "build" && rm -rf "build"
-
-echo "-------------------------------"
-echo " Installing Composer packages "
-echo "-------------------------------"
-
-# Allow installing custom version of Drupal core from drupal-composer/drupal-project,
-# but only coupled with drupal-project SHA (required to get correct dependencies).
-if [ -n "${DRUPAL_VERSION:-}" ] && [ -n "${DRUPAL_PROJECT_SHA:-}" ]; then
- echo " > Initialising Drupal site from the scaffold repo ${DRUPAL_PROJECT_REPO} commit ${DRUPAL_PROJECT_SHA}."
-
- # Clone Drupal core at the specific commit SHA.
- git clone -n "${DRUPAL_PROJECT_REPO}" "build"
- git --git-dir="build/.git" --work-tree="build" checkout "${DRUPAL_PROJECT_SHA}"
- rm -rf "build/.git" > /dev/null
-
- echo " > Pinning Drupal to a specific version ${DRUPAL_VERSION}."
- sed_opts=(-i) && [ "$(uname)" == "Darwin" ] && sed_opts=(-i '')
- sed "${sed_opts[@]}" 's|\(.*"drupal\/core"\): "\(.*\)",.*|\1: '"\"$DRUPAL_VERSION\",|" "build/composer.json"
- cat "build/composer.json"
-else
- echo " > Initialising Drupal site from the latest scaffold."
- # There are no releases in "drupal-composer/drupal-project", so have to use "@dev".
- composer create-project drupal-composer/drupal-project:@dev "build" --no-interaction --no-install
-fi
-
-echo " > Updating scaffold."
-composer --working-dir="build" config allow-plugins.php-http/discovery true
-cat <<< "$(jq --indent 4 '.extra["enable-patching"] = true' "build/composer.json")" > "build/composer.json"
-cat <<< "$(jq --indent 4 '.extra["phpcodesniffer-search-depth"] = 10' "build/composer.json")" > "build/composer.json"
-
-echo " > Merging configuration from theme's composer.json."
-php -r "echo json_encode(array_replace_recursive(json_decode(file_get_contents('composer.json'), true),json_decode(file_get_contents('build/composer.json'), true)),JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);" > "build/composer2.json" && mv -f "build/composer2.json" "build/composer.json"
-
-echo " > Adding custom patches."
-cat <<< "$(jq --indent 4 '.extra.patches = {"drupal/core": {"Builds failing on missing layout column plugin": "https://www.drupal.org/files/issues/2023-07-16/3204271-20-missing-layout-exception.patch"}}' "build/composer.json")" > "build/composer.json"
-
-echo " > Creating GitHub authentication token if provided."
-[ -n "${GITHUB_TOKEN:-}" ] && composer config --global github-oauth.github.com "${GITHUB_TOKEN}" && echo "Token: " && composer config --global github-oauth.github.com
-
-echo " > Installing dependencies."
-composer --working-dir="build" install
-
-# Suggested dependencies allow to install them for testing without requiring
-# them in theme's composer.json.
-echo " > Installing suggested dependencies from theme's composer.json."
-composer_suggests=$(cat composer.json | jq -r 'select(.suggest != null) | .suggest | keys[]')
-for composer_suggest in $composer_suggests; do
- composer --working-dir="build" require "${composer_suggest}"
-done
-
-echo " > Installing other dev dependencies."
-composer --working-dir="build" config allow-plugins.phpstan/extension-installer true
-composer --working-dir="build" require --dev \
- dealerdirect/phpcodesniffer-composer-installer \
- phpspec/prophecy-phpunit:^2 \
- phpstan/extension-installer \
- phpcompatibility/php-compatibility \
- phpmd/phpmd \
- mglaman/phpstan-drupal:^1.2 \
- palantirnet/drupal-rector:^0.20 \
- friendsoftwig/twigcs:^6.2
-
-echo " > Symlinking theme code."
-rm -rf "build/web/themes/custom" > /dev/null && mkdir -p "build/web/themes/custom/${theme}"
-ln -s "$(pwd)"/* "build/web/themes/custom/${theme}" && rm "build/web/themes/custom/${theme}/build"
-
-if [ -f "build/web/themes/custom/${theme}/package-lock.json" ]; then
- pushd "build/web/themes/custom/${theme}/" > /dev/null || exit 1
- echo " > Installing theme assets."
- if [ -f ".nvmrc" ]; then nvm use; fi
- if [ ! -d "node_modules" ]; then npm ci; fi
- popd > /dev/null || exit 1
-fi
-
-echo "-------------------------------"
-echo " Starting builtin PHP server "
-echo "-------------------------------"
-
-# Stop previously started services.
-killall -9 php > /dev/null 2>&1 || true
-# Start the PHP webserver.
-nohup php -S "${WEBSERVER_HOST}:${WEBSERVER_PORT}" -t "$(pwd)/build/web" "$(pwd)/build/web/.ht.router.php" > /tmp/php.log 2>&1 &
-sleep 4 # Waiting for the server to be ready.
-netstat_opts='-tulpn'; [ "$(uname)" == "Darwin" ] && netstat_opts='-anv' || true;
-# Check that the server was started.
-netstat "${netstat_opts[@]}" | grep -q "${WEBSERVER_PORT}" || (echo "ERROR: Unable to start inbuilt PHP server" && cat /tmp/php.log && exit 1)
-# Check that the server can serve content.
-curl -s -o /dev/null -w "%{http_code}" -L -I "http://${WEBSERVER_HOST}:${WEBSERVER_PORT}" | grep -q 200 || (echo "ERROR: Server is started, but site cannot be served" && exit 1)
-echo " > Started builtin PHP server at http://${WEBSERVER_HOST}:${WEBSERVER_PORT} in $(pwd)/build/web."
-
-echo "-------------------------------"
-echo " Installing Drupal and themes "
-echo "-------------------------------"
-
-echo " > Installing Drupal into SQLite database ${db_file}."
-drush si "${DRUPAL_PROFILE}" -y --db-url "sqlite://${db_file}" --account-name=admin install_configure_form.enable_update_status_theme=NULL install_configure_form.enable_update_status_emails=NULL
-drush status
-
-########################################
-
-echo " > Enabling theme ${theme} dependent modules."
-drush -r "build/web" php:eval "require_once dirname(\Drupal::getContainer()->get('theme_handler')->rebuildThemeData()['civictheme']->getPathname()) . '/theme-settings.provision.inc'; civictheme_enable_modules();"
-
-echo " > Enabling theme ${theme}."
-drush -r "build/web" theme:install "${theme}" -y
-drush -r "build/web" cr
-
-echo " > Setting theme ${theme} as default."
-drush -r "build/web" config:set system.theme default "${theme}" -y
-
-echo " > Provisioning content from theme defaults."
-drush -r "build/web" php:eval "require_once dirname(\Drupal::getContainer()->get('theme_handler')->rebuildThemeData()['civictheme']->getPathname()) . '/theme-settings.provision.inc'; civictheme_provision_cli();"
-
-########################################
-
-echo " > Enabling suggested modules, if any."
-drupal_suggests=$(cat composer.json | jq -r 'select(.suggest != null) | .suggest | keys[]' | sed "s/drupal\///" | cut -f1 -d":")
-for drupal_suggest in $drupal_suggests; do
- drush pm:enable "${drupal_suggest}" -y
-done
-
-# Visit site to pre-warm caches.
-curl -s "http://${WEBSERVER_HOST}:${WEBSERVER_PORT}" > /dev/null
-
-echo
-echo "-------------------------------"
-echo " Build finished 🚀🚀🚀"
-echo "-------------------------------"
-echo
-echo " > Site URL: http://${WEBSERVER_HOST}:${WEBSERVER_PORT}"
-echo -n " > One-time login link: "
-drush -l "http://${WEBSERVER_HOST}:${WEBSERVER_PORT}" uli --no-browser
-echo
-echo " > Available commands:"
-echo " ahoy build # rebuild"
-echo " ahoy lint # check code standards"
-echo " ahoy test # run tests"
-echo
diff --git a/web/themes/contrib/civictheme/.devtools/deploy.sh b/web/themes/contrib/civictheme/.devtools/deploy.sh
new file mode 100755
index 0000000000..dfdccf6411
--- /dev/null
+++ b/web/themes/contrib/civictheme/.devtools/deploy.sh
@@ -0,0 +1,129 @@
+#!/usr/bin/env bash
+##
+# Deploy code to a remote repository.
+#
+# - configures local git
+# - force-pushes code to a remote code repository branch
+#
+# Add the following variables through CI provider UI.
+# - DEPLOY_USER_NAME - name of the user who will be committing to a remote repository.
+# - DEPLOY_USER_EMAIL - email address of the user who will be committing to a remote repository.
+# - DEPLOY_REMOTE - remote repository to push code to.
+# - DEPLOY_PROCEED - set to 1 if the deployment should proceed. Useful for testing CI configuration before an actual code push.
+
+set -eu
+[ -n "${DEBUG:-}" ] && set -x
+
+#-------------------------------------------------------------------------------
+# Variables (passed from environment; provided for reference only).
+#-------------------------------------------------------------------------------
+
+# Name of the user who will be committing to a remote repository.
+DEPLOY_USER_NAME="${DEPLOY_USER_NAME:-}"
+
+# Email address of the user who will be committing to a remote repository.
+DEPLOY_USER_EMAIL="${DEPLOY_USER_EMAIL:-}"
+
+# Remote repository to push code to.
+DEPLOY_REMOTE="${DEPLOY_REMOTE:-}"
+
+# Git branch to deploy. If not provided - current branch will be used.
+DEPLOY_BRANCH="${DEPLOY_BRANCH:-}"
+
+# Set to 1 if the deployment should proceed. Useful for testing CI configuration
+# before an actual code push.
+DEPLOY_PROCEED="${DEPLOY_PROCEED:-0}"
+
+# The fingerprint of the SSH key.
+DEPLOY_SSH_KEY_FINGERPRINT="${DEPLOY_SSH_KEY_FINGERPRINT:-}"
+
+#-------------------------------------------------------------------------------
+
+# @formatter:off
+note() { printf " %s\n" "${1}"; }
+info() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[34m[INFO] %s\033[0m\n" "${1}" || printf "[INFO] %s\n" "${1}"; }
+pass() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[32m[ OK ] %s\033[0m\n" "${1}" || printf "[ OK ] %s\n" "${1}"; }
+fail() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[31m[FAIL] %s\033[0m\n" "${1}" || printf "[FAIL] %s\n" "${1}"; }
+# @formatter:on
+
+#-------------------------------------------------------------------------------
+
+if [ -n "${DEPLOY_SSH_KEY_FINGERPRINT}" ]; then
+ echo "-------------------------------"
+ echo " Setup SSH "
+ echo "-------------------------------"
+
+ mkdir -p "${HOME}/.ssh/"
+ echo -e "\nHost *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null\n" >"${HOME}/.ssh/config"
+
+ # Find the MD5 hash if the SSH_KEY_FINGERPRINT starts with SHA256.
+ if [ "${DEPLOY_SSH_KEY_FINGERPRINT#SHA256:}" != "${DEPLOY_SSH_KEY_FINGERPRINT}" ]; then
+ for file in "${HOME}"/.ssh/id_rsa*; do
+ calculated_sha256_fingerprint=$(ssh-keygen -l -E sha256 -f "${file}" | awk '{print $2}')
+ if [ "${calculated_sha256_fingerprint}" = "${DEPLOY_SSH_KEY_FINGERPRINT}" ]; then
+ DEPLOY_SSH_KEY_FINGERPRINT=$(ssh-keygen -l -E md5 -f "${file}" | awk '{print $2}')
+ DEPLOY_SSH_KEY_FINGERPRINT="${DEPLOY_SSH_KEY_FINGERPRINT#MD5:}"
+ break
+ fi
+ done
+ fi
+
+ file="${DEPLOY_SSH_KEY_FINGERPRINT//:/}"
+ file="${HOME}/.ssh/id_rsa_${file//\"/}"
+
+ if [ ! -f "${file:-}" ]; then
+ fail "ERROR: Unable to find SSH key file ${file}."
+ exit 1
+ fi
+
+ if [ -z "${SSH_AGENT_PID:-}" ]; then
+ eval "$(ssh-agent)"
+ fi
+
+ ssh-add -D
+ ssh-add "${file}"
+ ssh-add -l
+fi
+
+echo "==============================="
+echo " 🚚 DEPLOY "
+echo "==============================="
+echo
+
+[ -z "${DEPLOY_USER_NAME}" ] && fail "ERROR: Missing required value for DEPLOY_USER_NAME" && exit 1
+[ -z "${DEPLOY_USER_EMAIL}" ] && fail "ERROR: Missing required value for DEPLOY_USER_EMAIL" && exit 1
+[ -z "${DEPLOY_REMOTE}" ] && fail "ERROR: Missing required value for DEPLOY_REMOTE" && exit 1
+
+[ "${DEPLOY_PROCEED}" != "1" ] && pass "Skip deployment because $DEPLOY_PROCEED is not set to 1" && exit 0
+
+[ "$(git config --global user.name)" == "" ] && note "Configuring global git user name ${DEPLOY_USER_NAME}." && git config --global user.name "${DEPLOY_USER_NAME}"
+[ "$(git config --global user.email)" == "" ] && note "Configuring global git user email ${DEPLOY_USER_EMAIL}." && git config --global user.email "${DEPLOY_USER_EMAIL}"
+
+note "Setting git to push to a matching remote branch."
+git config --global push.default matching
+
+note "> Adding remote ${DEPLOY_REMOTE}."
+git remote add deployremote "${DEPLOY_REMOTE}"
+
+if [ -z "${DEPLOY_BRANCH}" ]; then
+ DEPLOY_BRANCH="$(git symbolic-ref --short HEAD)"
+fi
+
+info "Pushing code to branch ${DEPLOY_BRANCH}."
+git push --force deployremote HEAD:"${DEPLOY_BRANCH}"
+pass "Code pushed to ${DEPLOY_REMOTE}:${DEPLOY_BRANCH}."
+
+info "Pushing tags."
+git push --force --tags deployremote || true
+pass "Tags pushed to ${DEPLOY_REMOTE}."
+
+echo "==============================="
+echo " 🚚 DEPLOY COMPLETE ✅ "
+echo "==============================="
+echo
+echo "Remote URL : ${DEPLOY_REMOTE}"
+echo "Remote branch : ${DEPLOY_BRANCH}"
+echo
+echo "> Next steps:"
+echo " Navigate to Drupal.org and check that the code was successfully pushed."
+echo
diff --git a/web/themes/contrib/civictheme/.devtools/lint-fix.sh b/web/themes/contrib/civictheme/.devtools/lint-fix.sh
deleted file mode 100755
index 2f99f9405e..0000000000
--- a/web/themes/contrib/civictheme/.devtools/lint-fix.sh
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/usr/bin/env bash
-##
-# Run lint checks.
-#
-
-set -eu
-[ -n "${DEBUG:-}" ] && set -x
-
-BUILD_DIR="${BUILD_DIR:-build}"
-
-echo
-echo "-------------------------------"
-echo " Fixing theme code "
-echo "-------------------------------"
-echo
-
-echo " > Running Drupal Rector fixer."
-"${BUILD_DIR}"/vendor/bin/rector --clear-cache
-
-echo " > Running PHPCS fixer."
-"${BUILD_DIR}"/vendor/bin/phpcbf
-
diff --git a/web/themes/contrib/civictheme/.devtools/lint.sh b/web/themes/contrib/civictheme/.devtools/lint.sh
deleted file mode 100755
index c6167020e8..0000000000
--- a/web/themes/contrib/civictheme/.devtools/lint.sh
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/usr/bin/env bash
-##
-# Run lint checks.
-#
-
-set -eu
-[ -n "${DEBUG:-}" ] && set -x
-
-BUILD_DIR="${BUILD_DIR:-build}"
-
-echo
-echo "-------------------------------"
-echo " Linting theme code "
-echo "-------------------------------"
-echo
-
-echo " > Running PHPCS."
-"${BUILD_DIR}"/vendor/bin/phpcs
-
-echo " > Running TWIGCS."
-"${BUILD_DIR}"/vendor/bin/twigcs
-
-echo " > Running phpstan."
-"${BUILD_DIR}"/vendor/bin/phpstan
-
-echo " > Running Drupal Rector."
-"${BUILD_DIR}"/vendor/bin/rector --dry-run --clear-cache
-
-echo " > Running PHPMD."
-"${BUILD_DIR}"/vendor/bin/phpmd . text phpmd.xml
diff --git a/web/themes/contrib/civictheme/.devtools/process-artifacts.sh b/web/themes/contrib/civictheme/.devtools/process-artifacts.sh
deleted file mode 100755
index bec5ee0dd0..0000000000
--- a/web/themes/contrib/civictheme/.devtools/process-artifacts.sh
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/usr/bin/env bash
-##
-# Process test artifacts.
-#
-# This runs only in CI.
-#
-
-set -eu
-[ -n "${DEBUG:-}" ] && set -x
-
-if [ -d "$(pwd)/build/web/sites/simpletest/browser_output" ]; then
- echo "==> Copying Simpletest test artifacts"
- mkdir -p /tmp/artifacts/simpletest
- cp -Rf "$(pwd)/build/web/sites/simpletest/browser_output/." /tmp/artifacts/simpletest
-fi
diff --git a/web/themes/contrib/civictheme/.devtools/provision.sh b/web/themes/contrib/civictheme/.devtools/provision.sh
new file mode 100755
index 0000000000..ca6973ba3e
--- /dev/null
+++ b/web/themes/contrib/civictheme/.devtools/provision.sh
@@ -0,0 +1,117 @@
+#!/usr/bin/env bash
+##
+# Provision a website using existing codebase.
+#
+# - Installs Drupal using SQLite database.
+# - Enables modules
+# - Serves site and generates one-time login link
+#
+# shellcheck disable=SC2015,SC2094,SC2002
+
+set -eu
+[ -n "${DEBUG:-}" ] && set -x
+
+#-------------------------------------------------------------------------------
+# Variables (passed from environment; provided for reference only).
+#-------------------------------------------------------------------------------
+
+# Webserver hostname.
+WEBSERVER_HOST="${WEBSERVER_HOST:-localhost}"
+
+# Webserver port.
+WEBSERVER_PORT="${WEBSERVER_PORT:-8000}"
+
+# Drupal profile to use when installing the site.
+DRUPAL_PROFILE="${DRUPAL_PROFILE:-standard}"
+
+#-------------------------------------------------------------------------------
+
+# @formatter:off
+note() { printf " %s\n" "${1}"; }
+info() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[34m[INFO] %s\033[0m\n" "${1}" || printf "[INFO] %s\n" "${1}"; }
+pass() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[32m[ OK ] %s\033[0m\n" "${1}" || printf "[ OK ] %s\n" "${1}"; }
+fail() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[31m[FAIL] %s\033[0m\n" "${1}" || printf "[FAIL] %s\n" "${1}"; }
+# @formatter:on
+
+drush() { "build/vendor/bin/drush" -r "$(pwd)/build/web" -y "$@"; }
+
+#-------------------------------------------------------------------------------
+
+echo "==============================="
+echo " 🚀 PROVISION "
+echo "==============================="
+echo
+
+# Extension name, taken from .info file.
+extension="$(basename -s .info.yml -- ./*.info.yml)"
+[ "${extension}" == "*" ] && fail "ERROR: No .info.yml file found." && exit 1
+extension_type="module"
+if cat "${extension}.info.yml" | grep -Fq "type: theme"; then
+ extension_type="theme"
+fi
+
+# Database file path.
+db_file="/tmp/site_${extension}.sqlite"
+
+info "Installing Drupal into SQLite database ${db_file}."
+drush site-install "${DRUPAL_PROFILE}" -y --db-url="sqlite://localhost/${db_file}" --account-name=admin install_configure_form.enable_update_status_module=NULL install_configure_form.enable_update_status_emails=NULL
+
+pass "Drupal installed."
+
+drush status
+
+########################################
+# CIVICTHEME SPECIFIC
+########################################
+
+info "Enabling extension ${extension}."
+drush -r "build/web" php:eval "require_once dirname(\Drupal::getContainer()->get('theme_handler')->rebuildThemeData()['civictheme']->getPathname()) . '/theme-settings.provision.inc'; civictheme_enable_modules();"
+
+########################################
+
+echo " > Enabling theme ${extension} dependent modules."
+if [ "${extension_type}" = "theme" ]; then
+ drush theme:enable "${extension}" -y
+else
+ drush pm:enable "${extension}" -y
+fi
+
+info "Clearing caches."
+drush cr
+
+########################################
+# CIVICTHEME SPECIFIC
+########################################
+
+echo " > Setting theme ${extension} as default."
+drush -r "build/web" config:set system.theme default "${extension}" -y
+
+echo " > Provisioning content from theme defaults."
+drush -r "build/web" php:eval "require_once dirname(\Drupal::getContainer()->get('theme_handler')->rebuildThemeData()['civictheme']->getPathname()) . '/theme-settings.provision.inc'; civictheme_provision_cli();"
+
+########################################
+
+info "Enabling suggested modules, if any."
+drupal_suggests=$(cat composer.json | jq -r 'select(.suggest != null) | .suggest | keys[]' | sed "s/drupal\///" | cut -f1 -d":")
+for drupal_suggest in $drupal_suggests; do
+ drush pm:enable "${drupal_suggest}" -y
+done
+pass "Suggested modules enabled."
+
+info "Pre-warming caches."
+curl -s "http://${WEBSERVER_HOST}:${WEBSERVER_PORT}" >/dev/null
+
+echo
+echo "==============================="
+echo " 🚀 PROVISION COMPLETE ✅ "
+echo "==============================="
+echo
+echo "Site URL: http://${WEBSERVER_HOST}:${WEBSERVER_PORT}"
+echo -n "One-time login link: "
+drush -l "http://${WEBSERVER_HOST}:${WEBSERVER_PORT}" uli --no-browser
+echo
+echo "> Available commands:"
+echo " ahoy build # Rebuild"
+echo " ahoy lint # Check coding standards"
+echo " ahoy test # Run tests"
+echo
diff --git a/web/themes/contrib/civictheme/.devtools/start.sh b/web/themes/contrib/civictheme/.devtools/start.sh
new file mode 100755
index 0000000000..7eb950553e
--- /dev/null
+++ b/web/themes/contrib/civictheme/.devtools/start.sh
@@ -0,0 +1,71 @@
+#!/usr/bin/env bash
+##
+# Start development environment.
+#
+# shellcheck disable=SC2015,SC2094,SC2002
+
+set -eu
+[ -n "${DEBUG:-}" ] && set -x
+
+#-------------------------------------------------------------------------------
+# Variables (passed from environment; provided for reference only).
+#-------------------------------------------------------------------------------
+
+# Webserver hostname.
+WEBSERVER_HOST="${WEBSERVER_HOST:-localhost}"
+
+# Webserver port.
+WEBSERVER_PORT="${WEBSERVER_PORT:-8000}"
+
+# Webserver wait timeout.
+WEBSERVER_WAIT_TIMEOUT="${WEBSERVER_WAIT_TIMEOUT:-5}"
+
+#-------------------------------------------------------------------------------
+
+# @formatter:off
+note() { printf " %s\n" "${1}"; }
+info() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[34m[INFO] %s\033[0m\n" "${1}" || printf "[INFO] %s\n" "${1}"; }
+pass() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[32m[ OK ] %s\033[0m\n" "${1}" || printf "[ OK ] %s\n" "${1}"; }
+fail() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[31m[FAIL] %s\033[0m\n" "${1}" || printf "[FAIL] %s\n" "${1}"; }
+# @formatter:on
+
+#-------------------------------------------------------------------------------
+
+echo "==============================="
+echo " 💻 START ENVIRONMENT "
+echo "==============================="
+echo
+
+info "Stopping previously started services, if any."
+killall -9 php >/dev/null 2>&1 || true
+
+info "Starting the PHP webserver."
+nohup php -S "${WEBSERVER_HOST}:${WEBSERVER_PORT}" -t "$(pwd)/build/web" "$(pwd)/build/web/.ht.router.php" >/tmp/php.log 2>&1 &
+
+note "Waiting ${WEBSERVER_WAIT_TIMEOUT} seconds for the server to be ready."
+sleep "${WEBSERVER_WAIT_TIMEOUT}"
+
+note "Checking that the server was started."
+netstat_opts='-tulpn'
+[ "$(uname)" == "Darwin" ] && netstat_opts='-anv' || true
+netstat "${netstat_opts[@]}" | grep -q "${WEBSERVER_PORT}" || (echo "ERROR: Unable to start inbuilt PHP server" && cat /tmp/php.log && exit 1)
+
+pass "Server started successfully."
+
+info "Checking that the server can serve content."
+curl -s -o /dev/null -w "%{http_code}" -L -I "http://${WEBSERVER_HOST}:${WEBSERVER_PORT}" | grep -q 200 || (echo "ERROR: Server is started, but site cannot be served" && exit 1)
+pass "Server can serve content."
+
+echo
+echo "==============================="
+echo " 💻 ENVIRONMENT READY ✅ "
+echo "==============================="
+echo
+echo "Directory : $(pwd)/build/web"
+echo "URL : http://${WEBSERVER_HOST}:${WEBSERVER_PORT}"
+echo
+echo "Re-run when you enable or disable XDebug."
+echo
+echo "> Next steps:"
+echo " .devtools/provision.sh # Provision the website"
+echo
diff --git a/web/themes/contrib/civictheme/.devtools/stop.sh b/web/themes/contrib/civictheme/.devtools/stop.sh
new file mode 100755
index 0000000000..d629d6f08d
--- /dev/null
+++ b/web/themes/contrib/civictheme/.devtools/stop.sh
@@ -0,0 +1,35 @@
+#!/usr/bin/env bash
+##
+# Stop development environment.
+#
+# shellcheck disable=SC2015,SC2094,SC2002
+
+set -eu
+[ -n "${DEBUG:-}" ] && set -x
+
+#-------------------------------------------------------------------------------
+
+# @formatter:off
+note() { printf " %s\n" "${1}"; }
+info() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[34m[INFO] %s\033[0m\n" "${1}" || printf "[INFO] %s\n" "${1}"; }
+pass() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[32m[ OK ] %s\033[0m\n" "${1}" || printf "[ OK ] %s\n" "${1}"; }
+fail() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[31m[FAIL] %s\033[0m\n" "${1}" || printf "[FAIL] %s\n" "${1}"; }
+# @formatter:on
+
+#-------------------------------------------------------------------------------
+
+echo "==============================="
+echo " 💻 STOP ENVIRONMENT "
+echo "==============================="
+echo
+
+info "Stopping previously started services, if any."
+killall -9 php >/dev/null 2>&1 || true
+sleep 1
+pass "Services stopped."
+
+echo
+echo "==============================="
+echo " 💻 ENVIRONMENT STOPPED ✅ "
+echo "==============================="
+echo
diff --git a/web/themes/contrib/civictheme/.devtools/test.sh b/web/themes/contrib/civictheme/.devtools/test.sh
deleted file mode 100755
index e9b2bc1393..0000000000
--- a/web/themes/contrib/civictheme/.devtools/test.sh
+++ /dev/null
@@ -1,53 +0,0 @@
-#!/usr/bin/env bash
-##
-# Run tests.
-#
-
-set -eu
-[ -n "${DEBUG:-}" ] && set -x
-
-#-------------------------------------------------------------------------------
-# Variables (passed from environment; provided for reference only).
-#-------------------------------------------------------------------------------
-
-# Webserver hostname.
-WEBSERVER_HOST="${WEBSERVER_HOST:-localhost}"
-
-# Webserver port.
-WEBSERVER_PORT="${WEBSERVER_PORT:-8000}"
-
-# Directory to store test results.
-TEST_RESULTS_DIR="${TEST_RESULTS_DIR:-/tmp/test_results/simpletest}"
-
-#-------------------------------------------------------------------------------
-
-echo "==> Run tests."
-
-# Do not fail if there are no tests.
-[ ! -d "tests" ] && echo "==> No tests were found. Skipping." && exit 0
-
-# Module name, taken from .info file.
-theme="$(basename -s .info.yml -- ./*.info.yml)"
-[ "${theme}" == "*" ] && echo "ERROR: No .info.yml file found." && exit 1
-
-# Test database file path.
-test_db_file="/tmp/test_${theme}.sqlite"
-
-# Re-create test results directory.
-rm -rf "${TEST_RESULTS_DIR}" > /dev/null
-mkdir -p "${TEST_RESULTS_DIR}"
-
-# Remove existing test DB file.
-rm -f "${test_db_file}" > /dev/null
-
-# Run tests using script provided by Drupal.
-php "./build/web/core/scripts/run-tests.sh" \
- --sqlite "${test_db_file}" \
- --dburl "sqlite://localhost/${test_db_file}" \
- --url "http://${WEBSERVER_HOST}:${WEBSERVER_PORT}" \
- --non-html \
- --xml "${TEST_RESULTS_DIR}" \
- --color \
- --verbose \
- --suppress-deprecations \
- --module "${theme}"
diff --git a/web/themes/contrib/civictheme/.twig-cs-fixer.php b/web/themes/contrib/civictheme/.twig-cs-fixer.php
new file mode 100644
index 0000000000..246fbee0cb
--- /dev/null
+++ b/web/themes/contrib/civictheme/.twig-cs-fixer.php
@@ -0,0 +1,17 @@
+addStandard(new TwigCsFixer\Standard\Twig());
+
+$finder = new TwigCsFixer\File\Finder();
+$finder->in(__DIR__ . '/web/themes/custom/civictheme/templates');
+$finder->in(__DIR__ . '/web/themes/custom/civictheme/civictheme_starter_kit/components');
+$finder->in(__DIR__ . '/web/themes/custom/civictheme/civictheme_starter_kit/templates');
+
+$config = new TwigCsFixer\Config\Config();
+$config->setRuleset($ruleset);
+$config->setFinder($finder);
+
+return $config;
diff --git a/web/themes/contrib/civictheme/.twig_cs.php b/web/themes/contrib/civictheme/.twig_cs.php
deleted file mode 100644
index 68b90d424f..0000000000
--- a/web/themes/contrib/civictheme/.twig_cs.php
+++ /dev/null
@@ -1,16 +0,0 @@
-setName('custom-config')
- ->setSeverity('error')
- ->setReporter('console')
- ->setRuleSet(Twigcs\Ruleset\Official::class)
- ->addFinder(Twigcs\Finder\TemplateFinder::create()->in([
- __DIR__ . '/templates',
- __DIR__ . '/civictheme_starter_kit/components',
- __DIR__ . '/civictheme_starter_kit/templates',
- ])->followLinks());
diff --git a/web/themes/contrib/civictheme/README.md b/web/themes/contrib/civictheme/README.md
index 694db08d1a..6e1fe40f34 100644
--- a/web/themes/contrib/civictheme/README.md
+++ b/web/themes/contrib/civictheme/README.md
@@ -38,6 +38,47 @@ See [Color selector](https://docs.civictheme.io/development/drupal-theme/color-s
## Development
+### Local development
+
+Provided that you have PHP installed locally, you can develop an extension using
+the provided scripts.
+
+#### Build
+
+Run `.devtools/assemble.sh` (or `ahoy assemble`
+if [Ahoy](https://github.com/ahoy-cli/ahoy) is installed) to start inbuilt PHP
+server locally and run the same commands as in CI, plus installing a site and
+your extension automatically.
+
+#### Code linting
+
+Run tools individually (or `ahoy lint` to run all tools
+if [Ahoy](https://github.com/ahoy-cli/ahoy) is installed) to lint your code
+according to
+the [Drupal coding standards](https://www.drupal.org/docs/develop/standards).
+
+```
+cd build
+
+vendor/bin/phpcs
+vendor/bin/phpstan
+vendor/bin/rector --clear-cache --dry-run
+vendor/bin/phpmd . text phpmd.xml
+vendor/bin/twig-cs-fixer
+```
+
+- PHPCS config: [`phpcs.xml`](phpcs.xml)
+- PHPStan config: [`phpstan.neon`](phpstan.neon)
+- PHPMD config: [`phpmd.xml`](phpmd.xml)
+- Rector config: [`rector.php`](rector.php)
+- Twig CS Fixer config: [`.twig-cs-fixer.php`](.twig-cs-fixer.php)
+
+### Browsing SQLite database
+
+To browse the contents of created SQLite database
+(located at `/tmp/site_[EXTENSION_NAME].sqlite`),
+use [DB Browser for SQLite](https://sqlitebrowser.org/).
+
### Switching to a new version of the UI Kit
The UI Kit is included as a dependency in the `package.json` file and then
diff --git a/web/themes/contrib/civictheme/composer.dev.json b/web/themes/contrib/civictheme/composer.dev.json
new file mode 100644
index 0000000000..84ae2445a3
--- /dev/null
+++ b/web/themes/contrib/civictheme/composer.dev.json
@@ -0,0 +1,23 @@
+{
+ "require-dev": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^1.0",
+ "jangregor/phpstan-prophecy": "^1.0",
+ "mglaman/phpstan-drupal": "^1.2",
+ "palantirnet/drupal-rector": "^0.18",
+ "phpcompatibility/php-compatibility": "^9.3",
+ "phpmd/phpmd": "^2.15",
+ "phpspec/prophecy-phpunit": "^2",
+ "phpstan/extension-installer": "^1.3",
+ "vincentlanglet/twig-cs-fixer": "^2.8"
+ },
+ "config": {
+ "allow-plugins": {
+ "dealerdirect/phpcodesniffer-composer-installer": true,
+ "phpstan/extension-installer": true
+ }
+ },
+ "extra": {
+ "phpcodesniffer-search-depth": 10,
+ "patches": {}
+ }
+}
diff --git a/web/themes/contrib/civictheme/includes/utilities.inc b/web/themes/contrib/civictheme/includes/utilities.inc
index 6576f0b4c5..0196b965bb 100644
--- a/web/themes/contrib/civictheme/includes/utilities.inc
+++ b/web/themes/contrib/civictheme/includes/utilities.inc
@@ -93,8 +93,7 @@ function civictheme_media_get_variables(MediaInterface $media): ?array {
'media_name' => t('@name', ['@name' => $media->label()]),
'ext' => pathinfo((string) $file->getFileUri(), PATHINFO_EXTENSION) ?: '',
'url' => civictheme_media_get_url($media),
- // @phpstan-ignore-next-line
- 'size' => DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '10.2.0', static fn(): TranslatableMarkup => ByteSizeMarkup::create($file->getSize()), static fn() => format_size($file->getSize())),
+ 'size' => ByteSizeMarkup::create($file->getSize()),
'created' => civictheme_format_datetime($media->getCreatedTime(), 'civictheme_short_date'),
'changed' => civictheme_format_datetime($media->getChangedTime(), 'civictheme_short_date'),
'icon' => civictheme_get_icon_from_file($file),
diff --git a/web/themes/contrib/civictheme/phpcs.xml b/web/themes/contrib/civictheme/phpcs.xml
index 29d524de3a..dd15cdb184 100644
--- a/web/themes/contrib/civictheme/phpcs.xml
+++ b/web/themes/contrib/civictheme/phpcs.xml
@@ -2,23 +2,25 @@
Custom PHPCS Standard, based on Drupal standards.
- .
+ web/modules/custom
+ web/themes/custom
+
-
+
-
- node_modules/*
- vendor/*
+
+ circle\.yml
+ \.circle\/config\.yml
\.storybook
build\/
@@ -28,6 +30,13 @@
lib\/
webpack
+
+ web\/themes\/custom\/.*\/build\/.*
+ web\/themes\/custom\/.*\/fonts\/.*
+ web\/themes\/custom\/.*\/images\/.*
+ web\/themes\/custom\/.*\/node_modules\/.*
+ *\.min\.*
+
warning
@@ -48,6 +57,7 @@
*.Test\.php
*.TestCase\.php
*.TestBase\.php
+ *.test
@@ -55,12 +65,6 @@
*.Test\.php
*.TestCase\.php
*.TestBase\.php
-
-
-
-
-
-
-
+ *.test
diff --git a/web/themes/contrib/civictheme/phpstan.neon b/web/themes/contrib/civictheme/phpstan.neon
index e36f520f77..230be6d2f9 100644
--- a/web/themes/contrib/civictheme/phpstan.neon
+++ b/web/themes/contrib/civictheme/phpstan.neon
@@ -12,36 +12,30 @@ parameters:
level: 7
paths:
- - .
+ - web/modules/custom
+ - web/themes/custom
excludePaths:
- - build
- '*/vendor/*'
- '*/node_modules/*'
- '*/lib/*'
drupal:
- drupal_root: build/web
-
- # Do not report on ignored errors that do not occur in the results.
- reportUnmatchedIgnoredErrors: false
+ drupal_root: web
ignoreErrors:
- -
- # Since tests and data providers do not have to have parameter docblocks,
- # it is not possible to specify the type of the parameter, so we ignore
- # this error.
- message: '#.*no value type specified in iterable type array.#'
+ - # Hook implementations do not provide docblocks for parameters, so there
+ # is no way to provide this information.
+ messages:
+ - '#.* no value type specified in iterable type array#'
+ - '#.* has no return type specified#'
paths:
- - ./tests/*
- -
- # Hook implementations do not provide docblocks for parameters, so there
+ - web/modules/custom/*
+ - web/themes/custom/*
+ reportUnmatched: false
+ - # Hook implementations do not provide docblocks for parameters, so there
# is no way to provide this information.
message: '#.* with no value type specified in iterable type array#'
- -
- # Allow using Drupal::service() in Drush commands.
- message: '#\Drupal calls should be avoided in classes, use dependency injection instead#'
- -
- # Deprecations are manually handled in the codebase.
- message: '#\Call to deprecated function#'
reportUnmatched: false
+ - # Allow using Drupal::service() in Drush commands.
+ message: '#\Drupal calls should be avoided in classes, use dependency injection instead#'
diff --git a/web/themes/contrib/civictheme/phpunit.xml b/web/themes/contrib/civictheme/phpunit.xml
new file mode 100644
index 0000000000..47285753fa
--- /dev/null
+++ b/web/themes/contrib/civictheme/phpunit.xml
@@ -0,0 +1,112 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ web/modules/custom/*/tests/src/Unit
+ web/themes/custom/*/tests/src/Unit
+
+
+ web/modules/custom/*/tests/src/Kernel
+ web/themes/custom/*/tests/src/Kernel
+
+
+ web/modules/custom/*/tests/src/Functional
+ web/themes/custom/*/tests/src/Functional
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ web/modules/custom
+ web/themes/custom
+
+
+
+ web/modules/custom/*/tests
+ web/themes/custom/*/tests
+ web/modules/custom/civictheme/rector.php
+ web/themes/custom/civictheme/rector.php
+
+
+
+
+
+
+
+
diff --git a/web/themes/contrib/civictheme/rector.php b/web/themes/contrib/civictheme/rector.php
index 6bd43643e2..915fa110d2 100644
--- a/web/themes/contrib/civictheme/rector.php
+++ b/web/themes/contrib/civictheme/rector.php
@@ -12,7 +12,7 @@
declare(strict_types=1);
-use DrupalFinder\DrupalFinder;
+use DrupalFinder\DrupalFinderComposerRuntime;
use DrupalRector\Set\Drupal10SetList;
use DrupalRector\Set\Drupal8SetList;
use DrupalRector\Set\Drupal9SetList;
@@ -27,12 +27,9 @@
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([
- __DIR__ . '/**',
- ]);
-
$rectorConfig->sets([
// Provided by Rector.
SetList::PHP_80,
@@ -49,8 +46,9 @@
Drupal10SetList::DRUPAL_10,
]);
- $drupalFinder = new DrupalFinder();
- $drupalFinder->locateRoot(__DIR__);
+ $rectorConfig->rule(DeclareStrictTypesRector::class);
+
+ $drupalFinder = new DrupalFinderComposerRuntime();
$drupalRoot = $drupalFinder->getDrupalRoot();
$rectorConfig->autoloadPaths([
$drupalRoot . '/core',
@@ -73,9 +71,20 @@
// Dependencies.
'*/vendor/*',
'*/node_modules/*',
+ // Core and contribs.
+ '*/core/*',
+ '*/modules/contrib/*',
+ '*/themes/contrib/*',
+ '*/profiles/contrib/*',
+ // Files.
+ '*/sites/simpletest/*',
+ '*/sites/default/files/*',
+ // Composer scripts.
+ '*/scripts/composer/*',
+ // Dependencies.
+ '*/vendor/*',
+ '*/node_modules/*',
'*/lib/*',
- // Build.
- '*/build/*',
]);
$rectorConfig->fileExtensions([
diff --git a/web/themes/contrib/civictheme/src/CivicthemeColorManager.php b/web/themes/contrib/civictheme/src/CivicthemeColorManager.php
index 557ecd794a..4d2e3f87df 100644
--- a/web/themes/contrib/civictheme/src/CivicthemeColorManager.php
+++ b/web/themes/contrib/civictheme/src/CivicthemeColorManager.php
@@ -5,8 +5,10 @@
namespace Drupal\civictheme;
use Drupal\civictheme\Color\CivicthemeColor;
+use Drupal\Component\Utility\DeprecationHelper;
use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\DrupalKernel;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
@@ -316,6 +318,8 @@ public function save($invalidate_caches = FALSE): static {
*
* @return $this
* Instance of the current class.
+ *
+ * @SuppressWarnings(PHPMD.StaticAccess)
*/
public function invalidateCache(): static {
$this->stylesheetGenerator->purge();
@@ -323,7 +327,11 @@ public function invalidateCache(): static {
$this->cacheTagsInvalidator->invalidateTags(['library_info']);
// Force browser reload by changing the dummy query string.
- _drupal_flush_css_js();
+ DeprecationHelper:: backwardsCompatibleCall(\Drupal::VERSION, '11.0.0', static function () {
+ \Drupal::service('asset.query_string')->reset();
+ }, static function () {
+ _drupal_flush_css_js();
+ });
return $this;
}
@@ -501,9 +509,9 @@ protected function saveMatrixToConfig(): static {
}
$values = $matrix + [
- 'use_color_selector' => TRUE,
- 'use_brand_colors' => $use_brand_colors,
- ];
+ 'use_color_selector' => TRUE,
+ 'use_brand_colors' => $use_brand_colors,
+ ];
$this->configManager->save('colors', $values);