From cd59c54aa86efc433ab7e9abd872f36f6f234df4 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Wed, 3 Jan 2024 20:00:00 +0000 Subject: [PATCH 001/501] Create Home page --- .envrc | 1 + .gitignore | 3 + .tmux | 24 + TODO.md | 4 + app/SculpinKernel.php | 14 + app/config/sculpin_kernel.yml | 3 + app/config/sculpin_site.yml | 42 + app/config/sculpin_site_prod.yml | 3 + composer.json | 11 + composer.lock | 3655 +++++++++++++++++++++++ flake.lock | 114 + flake.nix | 21 + run | 33 + source/_includes/about-me.html.twig | 8 + source/_includes/main-menu.html.twig | 5 + source/_includes/testimonials.html.twig | 25 + source/_layouts/default.html.twig | 13 + source/index.md | 27 + 18 files changed, 4006 insertions(+) create mode 100644 .envrc create mode 100644 .gitignore create mode 100755 .tmux create mode 100644 TODO.md create mode 100644 app/SculpinKernel.php create mode 100644 app/config/sculpin_kernel.yml create mode 100644 app/config/sculpin_site.yml create mode 100644 app/config/sculpin_site_prod.yml create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 flake.lock create mode 100644 flake.nix create mode 100755 run create mode 100644 source/_includes/about-me.html.twig create mode 100644 source/_includes/main-menu.html.twig create mode 100644 source/_includes/testimonials.html.twig create mode 100644 source/_layouts/default.html.twig create mode 100644 source/index.md diff --git a/.envrc b/.envrc new file mode 100644 index 000000000..3550a30f2 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..26b6a7a60 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.direnv/ +output_*/ +vendor/ diff --git a/.tmux b/.tmux new file mode 100755 index 000000000..b8a38614e --- /dev/null +++ b/.tmux @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +set -o errexit +set -o nounset + +session_name="${1:-oliverdavies_sculpin}" +session_path="${2:-$(pwd)}" + +if tmux has-session -t="${session_name}" 2> /dev/null; then + tmux attach -t "${session_name}" + exit +fi + +tmux new-session -d -s "${session_name}" -n vim -c "${session_path}" + +# 1. Main window: Vim +tmux send-keys -t "${session_name}:vim" "nvim +GoToFile" Enter +# vendor/bin/sculpin generate --server --watch + +# 2. General shell use. +tmux new-window -t "${session_name}" -c "${session_path}" + +tmux switch-client -t "${session_name}:vim.left" || + tmux attach -t "${session_name}:vim.left" diff --git a/TODO.md b/TODO.md new file mode 100644 index 000000000..fd507d64e --- /dev/null +++ b/TODO.md @@ -0,0 +1,4 @@ +# TODOs + +- Add and configure Tailwind CSS. +- Style the Home page. diff --git a/app/SculpinKernel.php b/app/SculpinKernel.php new file mode 100644 index 000000000..a88406150 --- /dev/null +++ b/app/SculpinKernel.php @@ -0,0 +1,14 @@ +=5.3" + }, + "require-dev": { + "phpunit/phpunit": "^4|^5|^6|^7|^8|^9" + }, + "type": "library", + "autoload": { + "psr-0": { + "dflydev\\util\\antPathMatcher": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + } + ], + "description": "Ant Path Matcher Utility", + "homepage": "http://github.com/dflydev/dflydev-util-antPathMatcher", + "keywords": [ + "ant", + "matcher", + "path", + "pattern" + ], + "support": { + "issues": "https://github.com/dflydev/dflydev-util-antPathMatcher/issues", + "source": "https://github.com/dflydev/dflydev-util-antPathMatcher/tree/v1.0.4" + }, + "time": "2023-01-23T23:02:42+00:00" + }, + { + "name": "dflydev/apache-mime-types", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-apache-mime-types.git", + "reference": "f30a57e59b7476e4c5270b6a0727d79c9c0eb861" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-apache-mime-types/zipball/f30a57e59b7476e4c5270b6a0727d79c9c0eb861", + "reference": "f30a57e59b7476e4c5270b6a0727d79c9c0eb861", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "require-dev": { + "twig/twig": "1.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-0": { + "Dflydev\\ApacheMimeTypes": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + } + ], + "description": "Apache MIME Types", + "keywords": [ + "apache", + "mime", + "mimetypes" + ], + "support": { + "issues": "https://github.com/dflydev/dflydev-apache-mime-types/issues", + "source": "https://github.com/dflydev/dflydev-apache-mime-types/tree/v1.0.1" + }, + "time": "2013-05-14T02:02:01+00:00" + }, + { + "name": "dflydev/canal", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-canal.git", + "reference": "668af213d86f0f378f5dcce6799b974044fa6a51" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-canal/zipball/668af213d86f0f378f5dcce6799b974044fa6a51", + "reference": "668af213d86f0f378f5dcce6799b974044fa6a51", + "shasum": "" + }, + "require": { + "dflydev/apache-mime-types": "1.0.*", + "php": ">=5.3.3", + "webignition/internet-media-type": "0.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-0": { + "Dflydev\\Canal": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + } + ], + "description": "Content analysis for the purpose of determining Internet media types.", + "keywords": [ + "content", + "detection", + "mime", + "type" + ], + "support": { + "issues": "https://github.com/dflydev/dflydev-canal/issues", + "source": "https://github.com/dflydev/dflydev-canal/tree/master" + }, + "time": "2013-05-14T05:22:25+00:00" + }, + { + "name": "dflydev/dot-access-configuration", + "version": "v1.0.3", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-configuration.git", + "reference": "2e6eb0c8b8830b26bb23defcfc38d4276508fc49" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-configuration/zipball/2e6eb0c8b8830b26bb23defcfc38d4276508fc49", + "reference": "2e6eb0c8b8830b26bb23defcfc38d4276508fc49", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "1.*", + "dflydev/placeholder-resolver": "1.*", + "php": ">=5.3.2" + }, + "require-dev": { + "symfony/yaml": "~2.1" + }, + "suggest": { + "symfony/yaml": "Required for using the YAML Configuration Builders" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-0": { + "Dflydev\\DotAccessConfiguration": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + } + ], + "description": "Given a deep data structure representing a configuration, access configuration by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-configuration", + "keywords": [ + "config", + "configuration" + ], + "support": { + "issues": "https://github.com/dflydev/dflydev-dot-access-configuration/issues", + "source": "https://github.com/dflydev/dflydev-dot-access-configuration/tree/master" + }, + "time": "2018-09-08T23:00:17+00:00" + }, + { + "name": "dflydev/dot-access-data", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "3fbd874921ab2c041e899d044585a2ab9795df8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/3fbd874921ab2c041e899d044585a2ab9795df8a", + "reference": "3fbd874921ab2c041e899d044585a2ab9795df8a", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-0": { + "Dflydev\\DotAccessData": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "support": { + "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/master" + }, + "time": "2017-01-20T21:14:22+00:00" + }, + { + "name": "dflydev/placeholder-resolver", + "version": "v1.0.3", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-placeholder-resolver.git", + "reference": "d0161b4be1e15838327b01b21d0149f382d69906" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-placeholder-resolver/zipball/d0161b4be1e15838327b01b21d0149f382d69906", + "reference": "d0161b4be1e15838327b01b21d0149f382d69906", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-0": { + "Dflydev\\PlaceholderResolver": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + } + ], + "description": "Given a data source representing key => value pairs, resolve placeholders like ${foo.bar} to the value associated with the 'foo.bar' key in the data source.", + "homepage": "https://github.com/dflydev/dflydev-placeholder-resolver", + "keywords": [ + "placeholder", + "resolver" + ], + "support": { + "issues": "https://github.com/dflydev/dflydev-placeholder-resolver/issues", + "source": "https://github.com/dflydev/dflydev-placeholder-resolver/tree/v1.0.3" + }, + "time": "2021-12-03T16:48:58+00:00" + }, + { + "name": "doctrine/inflector", + "version": "1.4.4", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "4bd5c1cdfcd00e9e2d8c484f79150f67e5d355d9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/4bd5c1cdfcd00e9e2d8c484f79150f67e5d355d9", + "reference": "4bd5c1cdfcd00e9e2d8c484f79150f67e5d355d9", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^8.0", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Inflector\\": "lib/Doctrine/Inflector", + "Doctrine\\Common\\Inflector\\": "lib/Doctrine/Common/Inflector" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", + "homepage": "https://www.doctrine-project.org/projects/inflector.html", + "keywords": [ + "inflection", + "inflector", + "lowercase", + "manipulation", + "php", + "plural", + "singular", + "strings", + "uppercase", + "words" + ], + "support": { + "issues": "https://github.com/doctrine/inflector/issues", + "source": "https://github.com/doctrine/inflector/tree/1.4.4" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", + "type": "tidelift" + } + ], + "time": "2021-04-16T17:34:40+00:00" + }, + { + "name": "evenement/evenement", + "version": "v3.0.2", + "source": { + "type": "git", + "url": "https://github.com/igorw/evenement.git", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^9 || ^6" + }, + "type": "library", + "autoload": { + "psr-4": { + "Evenement\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "description": "Événement is a very simple event dispatching library for PHP", + "keywords": [ + "event-dispatcher", + "event-emitter" + ], + "support": { + "issues": "https://github.com/igorw/evenement/issues", + "source": "https://github.com/igorw/evenement/tree/v3.0.2" + }, + "time": "2023-08-08T05:53:35+00:00" + }, + { + "name": "fig/http-message-util", + "version": "1.1.5", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message-util.git", + "reference": "9d94dc0154230ac39e5bf89398b324a86f63f765" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message-util/zipball/9d94dc0154230ac39e5bf89398b324a86f63f765", + "reference": "9d94dc0154230ac39e5bf89398b324a86f63f765", + "shasum": "" + }, + "require": { + "php": "^5.3 || ^7.0 || ^8.0" + }, + "suggest": { + "psr/http-message": "The package containing the PSR-7 interfaces" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Fig\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Utility classes and constants for use with PSR-7 (psr/http-message)", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "issues": "https://github.com/php-fig/http-message-util/issues", + "source": "https://github.com/php-fig/http-message-util/tree/1.1.5" + }, + "time": "2020-11-24T22:02:12+00:00" + }, + { + "name": "michelf/php-markdown", + "version": "1.9.1", + "source": { + "type": "git", + "url": "https://github.com/michelf/php-markdown.git", + "reference": "5024d623c1a057dcd2d076d25b7d270a1d0d55f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/michelf/php-markdown/zipball/5024d623c1a057dcd2d076d25b7d270a1d0d55f3", + "reference": "5024d623c1a057dcd2d076d25b7d270a1d0d55f3", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": ">=4.3 <5.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Michelf\\": "Michelf/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Michel Fortin", + "email": "michel.fortin@michelf.ca", + "homepage": "https://michelf.ca/", + "role": "Developer" + }, + { + "name": "John Gruber", + "homepage": "https://daringfireball.net/" + } + ], + "description": "PHP Markdown", + "homepage": "https://michelf.ca/projects/php-markdown/", + "keywords": [ + "markdown" + ], + "support": { + "issues": "https://github.com/michelf/php-markdown/issues", + "source": "https://github.com/michelf/php-markdown/tree/1.9.1" + }, + "time": "2021-11-24T02:52:38+00:00" + }, + { + "name": "netcarver/textile", + "version": "v3.8.0", + "source": { + "type": "git", + "url": "https://github.com/textile/php-textile.git", + "reference": "02ed0cbe6832c2100342dabb6d01d7ba558cb8e7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/textile/php-textile/zipball/02ed0cbe6832c2100342dabb6d01d7ba558cb8e7", + "reference": "02ed0cbe6832c2100342dabb6d01d7ba558cb8e7", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpstan/phpstan": "1.6.3", + "phpunit/phpunit": "^9.5.20", + "psy/psysh": "^0.11.2", + "squizlabs/php_codesniffer": "3.*", + "symfony/yaml": "^4.4.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.8-dev" + } + }, + "autoload": { + "psr-4": { + "Netcarver\\Textile\\": "src/Netcarver/Textile/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Textile markup language parser", + "homepage": "https://github.com/textile/php-textile", + "keywords": [ + "document", + "format", + "html", + "language", + "markup", + "parser", + "php-textile", + "plaintext", + "textile" + ], + "support": { + "issues": "https://github.com/textile/php-textile/issues", + "source": "https://github.com/textile/php-textile", + "wiki": "https://github.com/textile/php-textile/wiki" + }, + "time": "2022-12-03T18:19:42+00:00" + }, + { + "name": "opdavies/sculpin-twig-markdown-bundle", + "version": "0.2.0", + "source": { + "type": "git", + "url": "https://github.com/opdavies/sculpin-twig-markdown-bundle.git", + "reference": "da9b055a1981419a0e7d5b5e8e1de517d1d9d91e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opdavies/sculpin-twig-markdown-bundle/zipball/da9b055a1981419a0e7d5b5e8e1de517d1d9d91e", + "reference": "da9b055a1981419a0e7d5b5e8e1de517d1d9d91e", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "sculpin/sculpin": "@stable" + }, + "type": "library", + "autoload": { + "psr-4": { + "Opdavies\\Sculpin\\Bundle\\TwigMarkdownBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Oliver Davies", + "email": "oliver@oliverdavies.uk", + "homepage": "https://www.oliverdavies.uk" + } + ], + "keywords": [ + "markdown", + "sculpin", + "twig" + ], + "support": { + "issues": "https://github.com/opdavies/sculpin-twig-markdown-bundle/issues", + "source": "https://github.com/opdavies/sculpin-twig-markdown-bundle/tree/master" + }, + "time": "2019-11-11T15:04:02+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "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/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/http-message", + "version": "1.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba", + "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/1.1" + }, + "time": "2023-04-04T09:50:52+00:00" + }, + { + "name": "psr/log", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/ef29f6d262798707a9edd554e2b82517ef3a9376", + "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/2.0.0" + }, + "time": "2021-07-14T16:41:46+00:00" + }, + { + "name": "react/cache", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/cache.git", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "react/promise": "^3.0 || ^2.0 || ^1.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async, Promise-based cache interface for ReactPHP", + "keywords": [ + "cache", + "caching", + "promise", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/cache/issues", + "source": "https://github.com/reactphp/cache/tree/v1.2.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2022-11-30T15:59:55+00:00" + }, + { + "name": "react/dns", + "version": "v1.12.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/dns.git", + "reference": "c134600642fa615b46b41237ef243daa65bb64ec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/dns/zipball/c134600642fa615b46b41237ef243daa65bb64ec", + "reference": "c134600642fa615b46b41237ef243daa65bb64ec", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "react/cache": "^1.0 || ^0.6 || ^0.5", + "react/event-loop": "^1.2", + "react/promise": "^3.0 || ^2.7 || ^1.2.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4 || ^3 || ^2", + "react/promise-timer": "^1.9" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Dns\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async DNS resolver for ReactPHP", + "keywords": [ + "async", + "dns", + "dns-resolver", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/dns/issues", + "source": "https://github.com/reactphp/dns/tree/v1.12.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2023-11-29T12:41:06+00:00" + }, + { + "name": "react/event-loop", + "version": "v1.5.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/event-loop.git", + "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", + "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "suggest": { + "ext-pcntl": "For signal handling support when using the StreamSelectLoop" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\EventLoop\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", + "keywords": [ + "asynchronous", + "event-loop" + ], + "support": { + "issues": "https://github.com/reactphp/event-loop/issues", + "source": "https://github.com/reactphp/event-loop/tree/v1.5.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2023-11-13T13:48:05+00:00" + }, + { + "name": "react/http", + "version": "v1.9.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/http.git", + "reference": "bb3154dbaf2dfe3f0467f956a05f614a69d5f1d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/http/zipball/bb3154dbaf2dfe3f0467f956a05f614a69d5f1d0", + "reference": "bb3154dbaf2dfe3f0467f956a05f614a69d5f1d0", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "fig/http-message-util": "^1.1", + "php": ">=5.3.0", + "psr/http-message": "^1.0", + "react/event-loop": "^1.2", + "react/promise": "^3 || ^2.3 || ^1.2.1", + "react/socket": "^1.12", + "react/stream": "^1.2", + "ringcentral/psr7": "^1.2" + }, + "require-dev": { + "clue/http-proxy-react": "^1.8", + "clue/reactphp-ssh-proxy": "^1.4", + "clue/socks-react": "^1.4", + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35", + "react/async": "^4 || ^3 || ^2", + "react/promise-stream": "^1.4", + "react/promise-timer": "^1.9" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Http\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Event-driven, streaming HTTP client and server implementation for ReactPHP", + "keywords": [ + "async", + "client", + "event-driven", + "http", + "http client", + "http server", + "https", + "psr-7", + "reactphp", + "server", + "streaming" + ], + "support": { + "issues": "https://github.com/reactphp/http/issues", + "source": "https://github.com/reactphp/http/tree/v1.9.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2023-04-26T10:29:24+00:00" + }, + { + "name": "react/promise", + "version": "v3.1.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "e563d55d1641de1dea9f5e84f3cccc66d2bfe02c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/e563d55d1641de1dea9f5e84f3cccc66d2bfe02c", + "reference": "e563d55d1641de1dea9f5e84f3cccc66d2bfe02c", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpstan/phpstan": "1.10.39 || 1.4.10", + "phpunit/phpunit": "^9.6 || ^7.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "React\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "keywords": [ + "promise", + "promises" + ], + "support": { + "issues": "https://github.com/reactphp/promise/issues", + "source": "https://github.com/reactphp/promise/tree/v3.1.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2023-11-16T16:21:57+00:00" + }, + { + "name": "react/socket", + "version": "v1.15.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/socket.git", + "reference": "216d3aec0b87f04a40ca04f481e6af01bdd1d038" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/socket/zipball/216d3aec0b87f04a40ca04f481e6af01bdd1d038", + "reference": "216d3aec0b87f04a40ca04f481e6af01bdd1d038", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/dns": "^1.11", + "react/event-loop": "^1.2", + "react/promise": "^3 || ^2.6 || ^1.2.1", + "react/stream": "^1.2" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4 || ^3 || ^2", + "react/promise-stream": "^1.4", + "react/promise-timer": "^1.10" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Socket\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP", + "keywords": [ + "Connection", + "Socket", + "async", + "reactphp", + "stream" + ], + "support": { + "issues": "https://github.com/reactphp/socket/issues", + "source": "https://github.com/reactphp/socket/tree/v1.15.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2023-12-15T11:02:10+00:00" + }, + { + "name": "react/stream", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/stream.git", + "reference": "6fbc9672905c7d5a885f2da2fc696f65840f4a66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/stream/zipball/6fbc9672905c7d5a885f2da2fc696f65840f4a66", + "reference": "6fbc9672905c7d5a885f2da2fc696f65840f4a66", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.8", + "react/event-loop": "^1.2" + }, + "require-dev": { + "clue/stream-filter": "~1.2", + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Stream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", + "keywords": [ + "event-driven", + "io", + "non-blocking", + "pipe", + "reactphp", + "readable", + "stream", + "writable" + ], + "support": { + "issues": "https://github.com/reactphp/stream/issues", + "source": "https://github.com/reactphp/stream/tree/v1.3.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2023-06-16T10:52:11+00:00" + }, + { + "name": "ringcentral/psr7", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/ringcentral/psr7.git", + "reference": "360faaec4b563958b673fb52bbe94e37f14bc686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ringcentral/psr7/zipball/360faaec4b563958b673fb52bbe94e37f14bc686", + "reference": "360faaec4b563958b673fb52bbe94e37f14bc686", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "psr/http-message": "~1.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "RingCentral\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "PSR-7 message implementation", + "keywords": [ + "http", + "message", + "stream", + "uri" + ], + "support": { + "source": "https://github.com/ringcentral/psr7/tree/master" + }, + "time": "2018-05-29T20:21:04+00:00" + }, + { + "name": "sculpin/sculpin", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/sculpin/sculpin.git", + "reference": "5f705d845b2dc980ed91b79c49ccaa5f64cbdda0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sculpin/sculpin/zipball/5f705d845b2dc980ed91b79c49ccaa5f64cbdda0", + "reference": "5f705d845b2dc980ed91b79c49ccaa5f64cbdda0", + "shasum": "" + }, + "require": { + "dflydev/ant-path-matcher": "^1.0.3", + "dflydev/apache-mime-types": "^1.0.1", + "dflydev/canal": "^1.0", + "dflydev/dot-access-configuration": "^1.0.3", + "doctrine/inflector": "^1.3", + "ext-mbstring": "*", + "michelf/php-markdown": "^1.9", + "netcarver/textile": "^3.6", + "php": "^8.0 || ^7.3", + "react/http": "^1.0", + "sculpin/sculpin-theme-composer-plugin": "^1.0", + "symfony/config": "^4.4.13", + "symfony/console": "^4.4.13", + "symfony/dependency-injection": "^4.4.13", + "symfony/event-dispatcher": "^4.4.13", + "symfony/filesystem": "^4.4.13", + "symfony/finder": "^4.4.13", + "symfony/http-kernel": "^4.4.13", + "symfony/yaml": "^4.4.13", + "twig/twig": "^2.5", + "webignition/internet-media-type": "^0.4.8" + }, + "replace": { + "sculpin/core": "self.version", + "sculpin/markdown-bundle": "self.version", + "sculpin/markdown-twig-compat-bundle": "self.version", + "sculpin/posts-bundle": "self.version", + "sculpin/proxy-source-collection-contrib": "self.version", + "sculpin/sculpin-bundle": "self.version", + "sculpin/standalone-bundle": "self.version", + "sculpin/taxonomy-contrib": "self.version", + "sculpin/textile-bundle": "self.version", + "sculpin/twig-bundle": "self.version" + }, + "require-dev": { + "phpdocumentor/reflection-docblock": "^5.2", + "phpstan/phpstan": "^1.2", + "phpunit/phpunit": "^9.4", + "squizlabs/php_codesniffer": "^3.3", + "symfony/css-selector": "^4.1", + "symfony/dom-crawler": "^4.1", + "symfony/process": "^4.1" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8.", + "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well." + }, + "bin": [ + "bin/sculpin", + "bin/sculpin.php" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Sculpin\\": "src/Sculpin/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + } + ], + "description": "Static Site Generator", + "homepage": "https://sculpin.io", + "keywords": [ + "generator", + "site", + "static" + ], + "support": { + "issues": "https://github.com/sculpin/sculpin/issues", + "source": "https://github.com/sculpin/sculpin/tree/3.2.0" + }, + "time": "2022-10-31T19:34:13+00:00" + }, + { + "name": "sculpin/sculpin-theme-composer-plugin", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/sculpin/sculpin-theme-composer-plugin.git", + "reference": "e3f4e1d6a10368709d07933f8391ef7e534c5db4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sculpin/sculpin-theme-composer-plugin/zipball/e3f4e1d6a10368709d07933f8391ef7e534c5db4", + "reference": "e3f4e1d6a10368709d07933f8391ef7e534c5db4", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1|^2.0" + }, + "require-dev": { + "composer/composer": "*" + }, + "type": "composer-plugin", + "extra": { + "class": "Sculpin\\Composer\\SculpinThemePlugin\\SculpinThemePlugin", + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Sculpin\\Composer\\SculpinThemePlugin\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Plugin for theming sculpin sites", + "support": { + "issues": "https://github.com/sculpin/sculpin-theme-composer-plugin/issues", + "source": "https://github.com/sculpin/sculpin-theme-composer-plugin/tree/1.0.3" + }, + "time": "2020-10-29T13:20:43+00:00" + }, + { + "name": "symfony/config", + "version": "v4.4.44", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "ed42f8f9da528d2c6cae36fe1f380b0c1d8f0658" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/ed42f8f9da528d2c6cae36fe1f380b0c1d8f0658", + "reference": "ed42f8f9da528d2c6cae36fe1f380b0c1d8f0658", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/filesystem": "^3.4|^4.0|^5.0", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-php80": "^1.16", + "symfony/polyfill-php81": "^1.22" + }, + "conflict": { + "symfony/finder": "<3.4" + }, + "require-dev": { + "symfony/event-dispatcher": "^3.4|^4.0|^5.0", + "symfony/finder": "^3.4|^4.0|^5.0", + "symfony/messenger": "^4.1|^5.0", + "symfony/service-contracts": "^1.1|^2", + "symfony/yaml": "^3.4|^4.0|^5.0" + }, + "suggest": { + "symfony/yaml": "To use the yaml reference dumper" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "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": "Helps you find, load, combine, autofill and validate configuration values of any kind", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/config/tree/v4.4.44" + }, + "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": "2022-07-20T09:59:04+00:00" + }, + { + "name": "symfony/console", + "version": "v4.4.49", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "33fa45ffc81fdcc1ca368d4946da859c8cdb58d9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/33fa45ffc81fdcc1ca368d4946da859c8cdb58d9", + "reference": "33fa45ffc81fdcc1ca368d4946da859c8cdb58d9", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.8", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1|^2" + }, + "conflict": { + "psr/log": ">=3", + "symfony/dependency-injection": "<3.4", + "symfony/event-dispatcher": "<4.3|>=5", + "symfony/lock": "<4.4", + "symfony/process": "<3.3" + }, + "provide": { + "psr/log-implementation": "1.0|2.0" + }, + "require-dev": { + "psr/log": "^1|^2", + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/event-dispatcher": "^4.3", + "symfony/lock": "^4.4|^5.0", + "symfony/process": "^3.4|^4.0|^5.0", + "symfony/var-dumper": "^4.3|^5.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "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", + "support": { + "source": "https://github.com/symfony/console/tree/v4.4.49" + }, + "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": "2022-11-05T17:10:16+00:00" + }, + { + "name": "symfony/debug", + "version": "v4.4.44", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug.git", + "reference": "1a692492190773c5310bc7877cb590c04c2f05be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/debug/zipball/1a692492190773c5310bc7877cb590c04c2f05be", + "reference": "1a692492190773c5310bc7877cb590c04c2f05be", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "psr/log": "^1|^2|^3" + }, + "conflict": { + "symfony/http-kernel": "<3.4" + }, + "require-dev": { + "symfony/http-kernel": "^3.4|^4.0|^5.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Debug\\": "" + }, + "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 tools to ease debugging PHP code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/debug/tree/v4.4.44" + }, + "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" + } + ], + "abandoned": "symfony/error-handler", + "time": "2022-07-28T16:29:46+00:00" + }, + { + "name": "symfony/dependency-injection", + "version": "v4.4.49", + "source": { + "type": "git", + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "9065fe97dbd38a897e95ea254eb5ddfe1310f734" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/9065fe97dbd38a897e95ea254eb5ddfe1310f734", + "reference": "9065fe97dbd38a897e95ea254eb5ddfe1310f734", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "psr/container": "^1.0", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1.6|^2" + }, + "conflict": { + "symfony/config": "<4.3|>=5.0", + "symfony/finder": "<3.4", + "symfony/proxy-manager-bridge": "<3.4", + "symfony/yaml": "<4.4.26" + }, + "provide": { + "psr/container-implementation": "1.0", + "symfony/service-implementation": "1.0|2.0" + }, + "require-dev": { + "symfony/config": "^4.3", + "symfony/expression-language": "^3.4|^4.0|^5.0", + "symfony/yaml": "^4.4.26|^5.0" + }, + "suggest": { + "symfony/config": "", + "symfony/expression-language": "For using expressions in service container configuration", + "symfony/finder": "For using double-star glob patterns or when GLOB_BRACE portability is required", + "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them", + "symfony/yaml": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\DependencyInjection\\": "" + }, + "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": "Allows you to standardize and centralize the way objects are constructed in your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/dependency-injection/tree/v4.4.49" + }, + "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": "2022-11-16T16:18:09+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-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.4.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": "2023-05-23T14:45:45+00:00" + }, + { + "name": "symfony/error-handler", + "version": "v4.4.44", + "source": { + "type": "git", + "url": "https://github.com/symfony/error-handler.git", + "reference": "be731658121ef2d8be88f3a1ec938148a9237291" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/be731658121ef2d8be88f3a1ec938148a9237291", + "reference": "be731658121ef2d8be88f3a1ec938148a9237291", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "psr/log": "^1|^2|^3", + "symfony/debug": "^4.4.5", + "symfony/var-dumper": "^4.4|^5.0" + }, + "require-dev": { + "symfony/http-kernel": "^4.4|^5.0", + "symfony/serializer": "^4.4|^5.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\ErrorHandler\\": "" + }, + "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 tools to manage errors and ease debugging PHP code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/error-handler/tree/v4.4.44" + }, + "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": "2022-07-28T16:29:46+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v4.4.44", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "1e866e9e5c1b22168e0ce5f0b467f19bba61266a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/1e866e9e5c1b22168e0ce5f0b467f19bba61266a", + "reference": "1e866e9e5c1b22168e0ce5f0b467f19bba61266a", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/event-dispatcher-contracts": "^1.1", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "symfony/dependency-injection": "<3.4" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "1.1" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/error-handler": "~3.4|~4.4", + "symfony/expression-language": "^3.4|^4.0|^5.0", + "symfony/http-foundation": "^3.4|^4.0|^5.0", + "symfony/service-contracts": "^1.1|^2", + "symfony/stopwatch": "^3.4|^4.0|^5.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "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 tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v4.4.44" + }, + "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": "2022-07-20T09:59:04+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v1.10.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "761c8b8387cfe5f8026594a75fdf0a4e83ba6974" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/761c8b8387cfe5f8026594a75fdf0a4e83ba6974", + "reference": "761c8b8387cfe5f8026594a75fdf0a4e83ba6974", + "shasum": "" + }, + "require": { + "php": ">=7.1.3" + }, + "suggest": { + "psr/event-dispatcher": "", + "symfony/event-dispatcher-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.1-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "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 dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v1.10.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": "2022-07-20T09:59:04+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v4.4.42", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "815412ee8971209bd4c1eecd5f4f481eacd44bf5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/815412ee8971209bd4c1eecd5f4f481eacd44bf5", + "reference": "815412ee8971209bd4c1eecd5f4f481eacd44bf5", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-php80": "^1.16" + }, + "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/v4.4.42" + }, + "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": "2022-05-20T08:49:14+00:00" + }, + { + "name": "symfony/finder", + "version": "v4.4.44", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "66bd787edb5e42ff59d3523f623895af05043e4f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/66bd787edb5e42ff59d3523f623895af05043e4f", + "reference": "66bd787edb5e42ff59d3523f623895af05043e4f", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "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": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v4.4.44" + }, + "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": "2022-07-29T07:35:46+00:00" + }, + { + "name": "symfony/http-client-contracts", + "version": "v2.5.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "ba6a9f0e8f3edd190520ee3b9a958596b6ca2e70" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/ba6a9f0e8f3edd190520ee3b9a958596b6ca2e70", + "reference": "ba6a9f0e8f3edd190520ee3b9a958596b6ca2e70", + "shasum": "" + }, + "require": { + "php": ">=7.2.5" + }, + "suggest": { + "symfony/http-client-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\HttpClient\\": "" + } + }, + "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 HTTP clients", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/http-client-contracts/tree/v2.5.2" + }, + "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": "2022-04-12T15:48:08+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v5.4.32", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "cbcd80a4c36f59772d62860fdb0cb6a38da63fd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/cbcd80a4c36f59772d62860fdb0cb6a38da63fd2", + "reference": "cbcd80a4c36f59772d62860fdb0cb6a38da63fd2", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "predis/predis": "~1.0", + "symfony/cache": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4", + "symfony/mime": "^4.4|^5.0|^6.0", + "symfony/rate-limiter": "^5.2|^6.0" + }, + "suggest": { + "symfony/mime": "To use the file extension guesser" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "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": "Defines an object-oriented layer for the HTTP specification", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-foundation/tree/v5.4.32" + }, + "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": "2023-11-20T15:40:25+00:00" + }, + { + "name": "symfony/http-kernel", + "version": "v4.4.51", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "ad8ab192cb619ff7285c95d28c69b36d718416c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/ad8ab192cb619ff7285c95d28c69b36d718416c7", + "reference": "ad8ab192cb619ff7285c95d28c69b36d718416c7", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "psr/log": "^1|^2", + "symfony/error-handler": "^4.4", + "symfony/event-dispatcher": "^4.4", + "symfony/http-client-contracts": "^1.1|^2", + "symfony/http-foundation": "^4.4.30|^5.3.7", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-php73": "^1.9", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "symfony/browser-kit": "<4.3", + "symfony/config": "<3.4", + "symfony/console": ">=5", + "symfony/dependency-injection": "<4.3", + "symfony/translation": "<4.2", + "twig/twig": "<1.43|<2.13,>=2" + }, + "provide": { + "psr/log-implementation": "1.0|2.0" + }, + "require-dev": { + "psr/cache": "^1.0|^2.0|^3.0", + "symfony/browser-kit": "^4.3|^5.0", + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/console": "^3.4|^4.0", + "symfony/css-selector": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^4.3|^5.0", + "symfony/dom-crawler": "^3.4|^4.0|^5.0", + "symfony/expression-language": "^3.4|^4.0|^5.0", + "symfony/finder": "^3.4|^4.0|^5.0", + "symfony/process": "^3.4|^4.0|^5.0", + "symfony/routing": "^3.4|^4.0|^5.0", + "symfony/stopwatch": "^3.4|^4.0|^5.0", + "symfony/templating": "^3.4|^4.0|^5.0", + "symfony/translation": "^4.2|^5.0", + "symfony/translation-contracts": "^1.1|^2", + "twig/twig": "^1.43|^2.13|^3.0.4" + }, + "suggest": { + "symfony/browser-kit": "", + "symfony/config": "", + "symfony/console": "", + "symfony/dependency-injection": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpKernel\\": "" + }, + "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 a structured process for converting a Request into a Response", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-kernel/tree/v4.4.51" + }, + "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": "2023-11-10T13:31:29+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", + "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/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.28.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": "2023-01-26T09:26:14+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "42292d99c55abe617799667f454222c54c60e229" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", + "reference": "42292d99c55abe617799667f454222c54c60e229", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/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.28.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": "2023-07-28T09:04:16+00:00" + }, + { + "name": "symfony/polyfill-php72", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "70f4aebd92afca2f865444d30a4d2151c13c3179" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/70f4aebd92afca2f865444d30a4d2151c13c3179", + "reference": "70f4aebd92afca2f865444d30a4d2151c13c3179", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + } + }, + "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 backporting some PHP 7.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php72/tree/v1.28.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": "2023-01-26T09:26:14+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "fe2f306d1d9d346a7fee353d0d5012e401e984b5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fe2f306d1d9d346a7fee353d0d5012e401e984b5", + "reference": "fe2f306d1d9d346a7fee353d0d5012e401e984b5", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "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 backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.28.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": "2023-01-26T09:26:14+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.28.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": "2023-01-26T09:26:14+00:00" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "7581cd600fa9fd681b797d00b02f068e2f13263b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/7581cd600fa9fd681b797d00b02f068e2f13263b", + "reference": "7581cd600fa9fd681b797d00b02f068e2f13263b", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "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 backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.28.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": "2023-01-26T09:26:14+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.5.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/4b426aac47d6427cc1a1d0f7e2ac724627f5966c", + "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "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/v2.5.2" + }, + "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": "2022-05-30T19:17:29+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v5.4.29", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "6172e4ae3534d25ee9e07eb487c20be7760fcc65" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/6172e4ae3534d25ee9e07eb487c20be7760fcc65", + "reference": "6172e4ae3534d25ee9e07eb487c20be7760fcc65", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "symfony/console": "<4.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^4.4|^5.0|^6.0", + "symfony/http-kernel": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/uid": "^5.1|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "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 mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v5.4.29" + }, + "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": "2023-09-12T10:09:58+00:00" + }, + { + "name": "symfony/yaml", + "version": "v4.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "aeccc4dc52a9e634f1d1eebeb21eacfdcff1053d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/aeccc4dc52a9e634f1d1eebeb21eacfdcff1053d", + "reference": "aeccc4dc52a9e634f1d1eebeb21eacfdcff1053d", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/console": "<3.4" + }, + "require-dev": { + "symfony/console": "^3.4|^4.0|^5.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "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/v4.4.45" + }, + "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": "2022-08-02T15:47:23+00:00" + }, + { + "name": "twig/twig", + "version": "v2.16.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "0c9cc7ef2e0ec6d20c5af1200522a89ba101f623" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/0c9cc7ef2e0ec6d20c5af1200522a89ba101f623", + "reference": "0c9cc7ef2e0ec6d20c5af1200522a89ba101f623", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "^1.3", + "symfony/polyfill-php72": "^1.8" + }, + "require-dev": { + "psr/container": "^1.0", + "symfony/phpunit-bridge": "^5.4.9|^6.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.16-dev" + } + }, + "autoload": { + "psr-0": { + "Twig_": "lib/" + }, + "psr-4": { + "Twig\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Twig Team", + "role": "Contributors" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "https://twig.symfony.com", + "keywords": [ + "templating" + ], + "support": { + "issues": "https://github.com/twigphp/Twig/issues", + "source": "https://github.com/twigphp/Twig/tree/v2.16.0" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2023-12-22T07:22:15+00:00" + }, + { + "name": "webignition/disallowed-character-terminated-string", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/webignition/disallowed-character-terminated-string.git", + "reference": "1c35b8bacbb2e76837c0aa8538dc2468a1f10e6e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webignition/disallowed-character-terminated-string/zipball/1c35b8bacbb2e76837c0aa8538dc2468a1f10e6e", + "reference": "1c35b8bacbb2e76837c0aa8538dc2468a1f10e6e", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.3", + "phpunit/phpunit": "~8.0", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "webignition\\DisallowedCharacterTerminatedString\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jon Cram", + "email": "webignition@gmail.com" + } + ], + "description": "A string terminated by one or more disallowed characters", + "homepage": "https://github.com/webignition/disallowed-character-terminated-string", + "keywords": [ + "string", + "terminated" + ], + "support": { + "issues": "https://github.com/webignition/disallowed-character-terminated-string/issues", + "source": "https://github.com/webignition/disallowed-character-terminated-string/tree/master" + }, + "time": "2019-12-20T15:52:44+00:00" + }, + { + "name": "webignition/internet-media-type", + "version": "0.4.8", + "source": { + "type": "git", + "url": "https://github.com/webignition/internet-media-type.git", + "reference": "1a5bbe38033b00b23acd5e1dd10489bb07eed77c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webignition/internet-media-type/zipball/1a5bbe38033b00b23acd5e1dd10489bb07eed77c", + "reference": "1a5bbe38033b00b23acd5e1dd10489bb07eed77c", + "shasum": "" + }, + "require": { + "php": ">=5.6.0", + "webignition/quoted-string": ">=0.2.1,<1.0", + "webignition/string-parser": ">=0.2.3,<1.0" + }, + "require-dev": { + "phpunit/phpunit": "~5.0", + "squizlabs/php_codesniffer": "3.*" + }, + "type": "library", + "autoload": { + "psr-4": { + "webignition\\InternetMediaType\\": "src/", + "webignition\\Tests\\InternetMediaType\\": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jon Cram", + "email": "jon@webignition.net" + } + ], + "description": "PHP model of an http://en.wikipedia.org/wiki/Internet_media_type", + "homepage": "https://github.com/webignition/internet-media-type", + "keywords": [ + "content type", + "content-type", + "internet media type", + "media type", + "media-type" + ], + "support": { + "issues": "https://github.com/webignition/internet-media-type/issues", + "source": "https://github.com/webignition/internet-media-type/tree/master" + }, + "time": "2018-03-12T14:54:00+00:00" + }, + { + "name": "webignition/quoted-string", + "version": "0.2.1", + "source": { + "type": "git", + "url": "https://github.com/webignition/quoted-string.git", + "reference": "88b36b7be067796683ab3668e175322842dd5313" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webignition/quoted-string/zipball/88b36b7be067796683ab3668e175322842dd5313", + "reference": "88b36b7be067796683ab3668e175322842dd5313", + "shasum": "" + }, + "require": { + "php": ">=5.5.0", + "webignition/string-parser": ">=0.2.3,<1" + }, + "require-dev": { + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "3.*" + }, + "type": "library", + "autoload": { + "psr-4": { + "webignition\\QuotedString\\": "src/", + "webignition\\Tests\\QuotedString\\": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jon Cram", + "email": "jon@webignition.net" + } + ], + "description": "A parser for string values that are encapsulated in double quotes (ASCII 34)", + "homepage": "https://github.com/webignition/quoted-string", + "keywords": [ + "parser", + "quoted-string" + ], + "support": { + "issues": "https://github.com/webignition/quoted-string/issues", + "source": "https://github.com/webignition/quoted-string/tree/master" + }, + "time": "2017-05-11T11:41:31+00:00" + }, + { + "name": "webignition/string-parser", + "version": "0.2.3", + "source": { + "type": "git", + "url": "https://github.com/webignition/string-parser.git", + "reference": "8591e28c05bd250bcc67b8001f3588995b9ef74b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webignition/string-parser/zipball/8591e28c05bd250bcc67b8001f3588995b9ef74b", + "reference": "8591e28c05bd250bcc67b8001f3588995b9ef74b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "webignition/disallowed-character-terminated-string": ">=1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "3.*" + }, + "type": "library", + "autoload": { + "psr-4": { + "webignition\\StringParser\\": "src/", + "webignition\\Tests\\StringParser\\": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jon Cram", + "email": "jon@webignition.net" + } + ], + "description": "Abstract state-based string parser", + "homepage": "https://github.com/webignition/string-parser", + "keywords": [ + "parser", + "string" + ], + "support": { + "issues": "https://github.com/webignition/string-parser/issues", + "source": "https://github.com/webignition/string-parser/tree/master" + }, + "time": "2017-05-11T10:04:12+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.6.0" +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 000000000..ec727d9d1 --- /dev/null +++ b/flake.lock @@ -0,0 +1,114 @@ +{ + "nodes": { + "devshell": { + "inputs": { + "nixpkgs": "nixpkgs", + "systems": "systems" + }, + "locked": { + "lastModified": 1695195896, + "narHash": "sha256-pq9q7YsGXnQzJFkR5284TmxrLNFc0wo4NQ/a5E93CQU=", + "owner": "numtide", + "repo": "devshell", + "rev": "05d40d17bf3459606316e3e9ec683b784ff28f16", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "devshell", + "type": "github" + } + }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1693611461, + "narHash": "sha256-aPODl8vAgGQ0ZYFIRisxYG5MOGSkIczvu2Cd8Gb9+1Y=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "7f53fdb7bdc5bb237da7fefef12d099e4fd611ca", + "type": "github" + }, + "original": { + "id": "flake-parts", + "type": "indirect" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1677383253, + "narHash": "sha256-UfpzWfSxkfXHnb4boXZNaKsAcUrZT9Hw+tao1oZxd08=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "9952d6bc395f5841262b006fbace8dd7e143b634", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-lib": { + "locked": { + "dir": "lib", + "lastModified": 1693471703, + "narHash": "sha256-0l03ZBL8P1P6z8MaSDS/MvuU8E75rVxe5eE1N6gxeTo=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "3e52e76b70d5508f3cec70b882a29199f4d1ee85", + "type": "github" + }, + "original": { + "dir": "lib", + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1703200384, + "narHash": "sha256-q5j06XOsy0qHOarsYPfZYJPWbTbc8sryRxianlEPJN0=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "0b3d618173114c64ab666f557504d6982665d328", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-23.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "devshell": "devshell", + "flake-parts": "flake-parts", + "nixpkgs": "nixpkgs_2" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 000000000..5fa7e1b45 --- /dev/null +++ b/flake.nix @@ -0,0 +1,21 @@ +{ + description = "oliverdavies.uk-sculpin"; + + inputs = { + devshell.url = "github:numtide/devshell"; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11"; + }; + + outputs = inputs@{ flake-parts, ... }: + flake-parts.lib.mkFlake { inherit inputs; } { + imports = [ inputs.devshell.flakeModule ]; + + systems = [ "x86_64-linux" ]; + + perSystem = { config, self', inputs', pkgs, system, ... }: { + devshells.default = { + packages = with pkgs; [ "php81" "php81Packages.composer" ]; + }; + }; + }; +} diff --git a/run b/run new file mode 100755 index 000000000..bf67cd28a --- /dev/null +++ b/run @@ -0,0 +1,33 @@ +#!/usr/bin/env bash + +set -o nounset +set -o pipefail + +function clean { + rm -fr output_*/ vendor/ +} + +function generate { + rm -rf output_*/ + + local args=() + + if [[ "${APP_ENV:-}" == "production" ]]; then + args=(--env="prod") + else + args=(--server --watch) + fi + + sculpin generate "${args[@]}" +} + +function help { + printf "%s [args]\n\nTasks:\n" "${0}" + + compgen -A function | grep -v "^_" | cat -n + + printf "\nExtended help:\n Each task has comments for general usage\n" +} + +TIMEFORMAT="Task completed in %3lR" +time "${@:-help}" diff --git a/source/_includes/about-me.html.twig b/source/_includes/about-me.html.twig new file mode 100644 index 000000000..4bf157b19 --- /dev/null +++ b/source/_includes/about-me.html.twig @@ -0,0 +1,8 @@ +
+

About me

+ +
+ Picture of Oliver +

I'm an Acquia-certified Drupal Triple Expert with 16 years of experience, an open-source software maintainer and Drupal core contributor, public speaker, and host of the Beyond Blocks podcast.

+
+
diff --git a/source/_includes/main-menu.html.twig b/source/_includes/main-menu.html.twig new file mode 100644 index 000000000..1860d4d2c --- /dev/null +++ b/source/_includes/main-menu.html.twig @@ -0,0 +1,5 @@ + diff --git a/source/_includes/testimonials.html.twig b/source/_includes/testimonials.html.twig new file mode 100644 index 000000000..b4b1471fc --- /dev/null +++ b/source/_includes/testimonials.html.twig @@ -0,0 +1,25 @@ +
+

Testimonials

+ +
+ {% for testimonial in site.testimonials %} +
+ {{ testimonial.text|markdown }} + + {% if testimonial.image %} + Photo of {{ testimonial.name }} + {% endif %} + + +
+ {% endfor %} +
+
diff --git a/source/_layouts/default.html.twig b/source/_layouts/default.html.twig new file mode 100644 index 000000000..14b48242d --- /dev/null +++ b/source/_layouts/default.html.twig @@ -0,0 +1,13 @@ +
+
+

{{ page.title }}

+ + {% block content %}{% endblock %} +
+ + {% block content_bottom %}{% endblock %} + +
+ {% include 'main-menu.html.twig' %} +
+
diff --git a/source/index.md b/source/index.md new file mode 100644 index 000000000..a629146ba --- /dev/null +++ b/source/index.md @@ -0,0 +1,27 @@ +--- +layout: default +title: Do you need a certified Drupal expert, core contributor and module maintainer? +--- + +{% block content %} + +## What I can help you with + +If you are stuck on an unsupported version of Drupal, such as 7, 8 or 9, and don't know what's involved in upgrading, [book a Drupal upgrade consultation call][call] or [roadmap for your website][roadmap]. + +If you need help or another pair of eyes on your code, book a 1-on-1 consultation call or an online pair programming session with me, with a 100% money-back guarantee. + +If you want to learn to write better software faster, I'm available for development team coaching. + +## Looking for something else? + +Here are all of my products and services. If you still can't find what you need, send me an email, and I'll see what I can do. +{% endblock %} + +{% block content_bottom %} + {% include 'testimonials.html.twig' %} + {% include 'about-me.html.twig' %} +{% endblock %} + +[call]: {{site.url}}/call +[roadmap]: {{site.url}}/roadmap From d1034af1d7fdf7ee37e9d14b7a70b738cdf7bad1 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Wed, 3 Jan 2024 20:00:00 +0000 Subject: [PATCH 002/501] Add Tailwind CSS --- .gitignore | 2 + TODO.md | 1 - flake.nix | 7 +- package.json | 5 + pnpm-lock.yaml | 727 ++++++++++++++++++++++++++++++ run | 15 +- source/_layouts/default.html.twig | 2 + 7 files changed, 756 insertions(+), 3 deletions(-) create mode 100644 package.json create mode 100644 pnpm-lock.yaml diff --git a/.gitignore b/.gitignore index 26b6a7a60..4216ba1d2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ .direnv/ +node_modules/ output_*/ +source/build/ vendor/ diff --git a/TODO.md b/TODO.md index fd507d64e..c134fa55d 100644 --- a/TODO.md +++ b/TODO.md @@ -1,4 +1,3 @@ # TODOs -- Add and configure Tailwind CSS. - Style the Home page. diff --git a/flake.nix b/flake.nix index 5fa7e1b45..b7dc2957b 100644 --- a/flake.nix +++ b/flake.nix @@ -14,7 +14,12 @@ perSystem = { config, self', inputs', pkgs, system, ... }: { devshells.default = { - packages = with pkgs; [ "php81" "php81Packages.composer" ]; + packages = with pkgs; [ + "nodejs" + "nodePackages.pnpm" + "php81" + "php81Packages.composer" + ]; }; }; }; diff --git a/package.json b/package.json new file mode 100644 index 000000000..11607f945 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "tailwindcss": "^3.4.0" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 000000000..6cb047bd9 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,727 @@ +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +dependencies: + tailwindcss: + specifier: ^3.4.0 + version: 3.4.0 + +packages: + + /@alloc/quick-lru@5.2.0: + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + dev: false + + /@isaacs/cliui@8.0.2: + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + dependencies: + string-width: 5.1.2 + string-width-cjs: /string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: /strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: /wrap-ansi@7.0.0 + dev: false + + /@jridgewell/gen-mapping@0.3.3: + resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/set-array': 1.1.2 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.20 + dev: false + + /@jridgewell/resolve-uri@3.1.1: + resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==} + engines: {node: '>=6.0.0'} + dev: false + + /@jridgewell/set-array@1.1.2: + resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} + engines: {node: '>=6.0.0'} + dev: false + + /@jridgewell/sourcemap-codec@1.4.15: + resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + dev: false + + /@jridgewell/trace-mapping@0.3.20: + resolution: {integrity: sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==} + dependencies: + '@jridgewell/resolve-uri': 3.1.1 + '@jridgewell/sourcemap-codec': 1.4.15 + dev: false + + /@nodelib/fs.scandir@2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + dev: false + + /@nodelib/fs.stat@2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + dev: false + + /@nodelib/fs.walk@1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.16.0 + dev: false + + /@pkgjs/parseargs@0.11.0: + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + requiresBuild: true + dev: false + optional: true + + /ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + dev: false + + /ansi-regex@6.0.1: + resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + engines: {node: '>=12'} + dev: false + + /ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + dependencies: + color-convert: 2.0.1 + dev: false + + /ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + dev: false + + /any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + dev: false + + /anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + dev: false + + /arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + dev: false + + /balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + dev: false + + /binary-extensions@2.2.0: + resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} + engines: {node: '>=8'} + dev: false + + /brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + dependencies: + balanced-match: 1.0.2 + dev: false + + /braces@3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.0.1 + dev: false + + /camelcase-css@2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} + dev: false + + /chokidar@3.5.3: + resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + engines: {node: '>= 8.10.0'} + dependencies: + anymatch: 3.1.3 + braces: 3.0.2 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + dev: false + + /color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + dependencies: + color-name: 1.1.4 + dev: false + + /color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + dev: false + + /commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + dev: false + + /cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + dev: false + + /cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + dev: false + + /didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + dev: false + + /dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + dev: false + + /eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + dev: false + + /emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + dev: false + + /emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + dev: false + + /fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.5 + dev: false + + /fastq@1.16.0: + resolution: {integrity: sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==} + dependencies: + reusify: 1.0.4 + dev: false + + /fill-range@7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + dependencies: + to-regex-range: 5.0.1 + dev: false + + /foreground-child@3.1.1: + resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} + engines: {node: '>=14'} + dependencies: + cross-spawn: 7.0.3 + signal-exit: 4.1.0 + dev: false + + /fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + dev: false + + /glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + dev: false + + /glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + dependencies: + is-glob: 4.0.3 + dev: false + + /glob@10.3.10: + resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + dependencies: + foreground-child: 3.1.1 + jackspeak: 2.3.6 + minimatch: 9.0.3 + minipass: 7.0.4 + path-scurry: 1.10.1 + dev: false + + /hasown@2.0.0: + resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==} + engines: {node: '>= 0.4'} + dependencies: + function-bind: 1.1.2 + dev: false + + /is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + dependencies: + binary-extensions: 2.2.0 + dev: false + + /is-core-module@2.13.1: + resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} + dependencies: + hasown: 2.0.0 + dev: false + + /is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + dev: false + + /is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + dev: false + + /is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + dev: false + + /is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + dev: false + + /isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + dev: false + + /jackspeak@2.3.6: + resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} + engines: {node: '>=14'} + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + dev: false + + /jiti@1.21.0: + resolution: {integrity: sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==} + hasBin: true + dev: false + + /lilconfig@2.1.0: + resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} + engines: {node: '>=10'} + dev: false + + /lilconfig@3.0.0: + resolution: {integrity: sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==} + engines: {node: '>=14'} + dev: false + + /lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + dev: false + + /lru-cache@10.1.0: + resolution: {integrity: sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==} + engines: {node: 14 || >=16.14} + dev: false + + /merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + dev: false + + /micromatch@4.0.5: + resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} + engines: {node: '>=8.6'} + dependencies: + braces: 3.0.2 + picomatch: 2.3.1 + dev: false + + /minimatch@9.0.3: + resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + brace-expansion: 2.0.1 + dev: false + + /minipass@7.0.4: + resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==} + engines: {node: '>=16 || 14 >=14.17'} + dev: false + + /mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + dev: false + + /nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: false + + /normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + dev: false + + /object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + dev: false + + /object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + dev: false + + /path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + dev: false + + /path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + dev: false + + /path-scurry@1.10.1: + resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + lru-cache: 10.1.0 + minipass: 7.0.4 + dev: false + + /picocolors@1.0.0: + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + dev: false + + /picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + dev: false + + /pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + dev: false + + /pirates@4.0.6: + resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} + engines: {node: '>= 6'} + dev: false + + /postcss-import@15.1.0(postcss@8.4.32): + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} + engines: {node: '>=14.0.0'} + peerDependencies: + postcss: ^8.0.0 + dependencies: + postcss: 8.4.32 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.8 + dev: false + + /postcss-js@4.0.1(postcss@8.4.32): + resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + dependencies: + camelcase-css: 2.0.1 + postcss: 8.4.32 + dev: false + + /postcss-load-config@4.0.2(postcss@8.4.32): + resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} + engines: {node: '>= 14'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + dependencies: + lilconfig: 3.0.0 + postcss: 8.4.32 + yaml: 2.3.4 + dev: false + + /postcss-nested@6.0.1(postcss@8.4.32): + resolution: {integrity: sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + dependencies: + postcss: 8.4.32 + postcss-selector-parser: 6.0.14 + dev: false + + /postcss-selector-parser@6.0.14: + resolution: {integrity: sha512-65xXYsT40i9GyWzlHQ5ShZoK7JZdySeOozi/tz2EezDo6c04q6+ckYMeoY7idaie1qp2dT5KoYQ2yky6JuoHnA==} + engines: {node: '>=4'} + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + dev: false + + /postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + dev: false + + /postcss@8.4.32: + resolution: {integrity: sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.7 + picocolors: 1.0.0 + source-map-js: 1.0.2 + dev: false + + /queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + dev: false + + /read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + dependencies: + pify: 2.3.0 + dev: false + + /readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + dependencies: + picomatch: 2.3.1 + dev: false + + /resolve@1.22.8: + resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} + hasBin: true + dependencies: + is-core-module: 2.13.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + dev: false + + /reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + dev: false + + /run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + dependencies: + queue-microtask: 1.2.3 + dev: false + + /shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + dependencies: + shebang-regex: 3.0.0 + dev: false + + /shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + dev: false + + /signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + dev: false + + /source-map-js@1.0.2: + resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} + engines: {node: '>=0.10.0'} + dev: false + + /string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + dev: false + + /string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + dev: false + + /strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + dependencies: + ansi-regex: 5.0.1 + dev: false + + /strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + dependencies: + ansi-regex: 6.0.1 + dev: false + + /sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + dependencies: + '@jridgewell/gen-mapping': 0.3.3 + commander: 4.1.1 + glob: 10.3.10 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.6 + ts-interface-checker: 0.1.13 + dev: false + + /supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + dev: false + + /tailwindcss@3.4.0: + resolution: {integrity: sha512-VigzymniH77knD1dryXbyxR+ePHihHociZbXnLZHUyzf2MMs2ZVqlUrZ3FvpXP8pno9JzmILt1sZPD19M3IxtA==} + engines: {node: '>=14.0.0'} + hasBin: true + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.5.3 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.2 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.0 + lilconfig: 2.1.0 + micromatch: 4.0.5 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.0.0 + postcss: 8.4.32 + postcss-import: 15.1.0(postcss@8.4.32) + postcss-js: 4.0.1(postcss@8.4.32) + postcss-load-config: 4.0.2(postcss@8.4.32) + postcss-nested: 6.0.1(postcss@8.4.32) + postcss-selector-parser: 6.0.14 + resolve: 1.22.8 + sucrase: 3.35.0 + transitivePeerDependencies: + - ts-node + dev: false + + /thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + dependencies: + thenify: 3.3.1 + dev: false + + /thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + dependencies: + any-promise: 1.3.0 + dev: false + + /to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + dependencies: + is-number: 7.0.0 + dev: false + + /ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + dev: false + + /util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + dev: false + + /which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + dependencies: + isexe: 2.0.0 + dev: false + + /wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + dev: false + + /wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + dev: false + + /yaml@2.3.4: + resolution: {integrity: sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==} + engines: {node: '>= 14'} + dev: false diff --git a/run b/run index bf67cd28a..d2ea62dba 100755 --- a/run +++ b/run @@ -3,8 +3,21 @@ set -o nounset set -o pipefail +function build:css { + local args=() + + if [[ "${NODE_ENV:-}" == "production" ]]; then + args=(--minify) + else + args=(--watch) + fi + + npx tailwindcss --content "source/**/*.html.twig" \ + --output source/build/tailwind.css "${args[@]}" +} + function clean { - rm -fr output_*/ vendor/ + rm -fr node_modules/ output_*/ source/build/ vendor/ } function generate { diff --git a/source/_layouts/default.html.twig b/source/_layouts/default.html.twig index 14b48242d..e9dc99ea1 100644 --- a/source/_layouts/default.html.twig +++ b/source/_layouts/default.html.twig @@ -1,3 +1,5 @@ + +

{{ page.title }}

From d9c38790c875ae92ae5b1c0ed4787cb400e5164b Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Wed, 3 Jan 2024 20:00:00 +0000 Subject: [PATCH 003/501] Initial layout styling --- source/_includes/testimonials.html.twig | 2 +- source/_layouts/default.html.twig | 26 +++++++++++++------------ 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/source/_includes/testimonials.html.twig b/source/_includes/testimonials.html.twig index b4b1471fc..e9d1bd392 100644 --- a/source/_includes/testimonials.html.twig +++ b/source/_includes/testimonials.html.twig @@ -7,7 +7,7 @@ {{ testimonial.text|markdown }} {% if testimonial.image %} - Photo of {{ testimonial.name }} + Photo of {{ testimonial.name }} {% endif %} diff --git a/source/_layouts/base.html.twig b/source/_layouts/base.html.twig index abf7e4770..81d0a0f6b 100644 --- a/source/_layouts/base.html.twig +++ b/source/_layouts/base.html.twig @@ -6,16 +6,16 @@ - + - + - + diff --git a/source/_layouts/podcast_episode.html.twig b/source/_layouts/podcast_episode.html.twig index 0c1347189..4a4a93d1d 100644 --- a/source/_layouts/podcast_episode.html.twig +++ b/source/_layouts/podcast_episode.html.twig @@ -23,7 +23,7 @@ frameborder="no" scrolling="no" seamless="" - src="https://share.transistor.fm/e/{{ page.transistor.id }}"> + src="{{ site.transistor.share.url }}/{{ page.transistor.id }}">
{% endif %} diff --git a/source/_pages/podcast.md b/source/_pages/podcast.md index 4c32a535b..8cd7e0490 100644 --- a/source/_pages/podcast.md +++ b/source/_pages/podcast.md @@ -9,7 +9,7 @@ use: {% block content %} A weekly podcast about Drupal, open-source, and related software development topics. -Subscribe at . +Subscribe at {{ site.transistor.feed.url }}. ## Episodes diff --git a/source/_pages/press.md b/source/_pages/press.md index eb07af999..77fadc332 100644 --- a/source/_pages/press.md +++ b/source/_pages/press.md @@ -36,7 +36,7 @@ Oliver is a Software Developer and Drupal expert with {{ macros.yearsExperience ## Photo -![]({{site.assets_url}}/assets/images/social-avatar.jpg) +![]({{site.assets.url}}/assets/images/social-avatar.jpg) [drupal]: https://www.drupal.org/u/opdavies [github]: https://github.com/opdavies diff --git a/source/_talks/deploying-php-fabric.md b/source/_talks/deploying-php-fabric.md index a39252cd3..dfce37aa9 100644 --- a/source/_talks/deploying-php-fabric.md +++ b/source/_talks/deploying-php-fabric.md @@ -12,7 +12,7 @@ meta: description: "You've built your PHP application, now learn how to deploy it with Fabric." type: website image: - url: '%site.assets_url%/assets/images/talks/deploying-php-fabric.png' + url: '%site.assets.url%/assets/images/talks/deploying-php-fabric.png' width: 2560 height: 1440 type: image/png diff --git a/source/_talks/drupal-8-module-development.md b/source/_talks/drupal-8-module-development.md index 78d6cb1f5..8518de089 100644 --- a/source/_talks/drupal-8-module-development.md +++ b/source/_talks/drupal-8-module-development.md @@ -15,7 +15,7 @@ meta: og: title: Getting Started with Drupal 8 Module Development image: - url: '%site.assets_url%/assets/images/talks/dclondon16.png' + url: '%site.assets.url%/assets/images/talks/dclondon16.png' type: "image/png" height: 540 width: 960 diff --git a/source/_talks/drupal-8-php-libraries-drupalorg-api.md b/source/_talks/drupal-8-php-libraries-drupalorg-api.md index dd2409057..6f573cbbf 100644 --- a/source/_talks/drupal-8-php-libraries-drupalorg-api.md +++ b/source/_talks/drupal-8-php-libraries-drupalorg-api.md @@ -6,7 +6,7 @@ speakerdeck: ratio: "1.77777777777778" url: https://speakerdeck.com/opdavies/having-fun-with-drupal-8-php-libraries-and-the-drupal-dot-org-api image: - url: '%site.assets_url%/assets/images/talks/having-fun-drupalorg-api.png' + url: '%site.assets.url%/assets/images/talks/having-fun-drupalorg-api.png' width: 2000 height: 1125 type: image/png diff --git a/source/_talks/getting-your-data-into-drupal-8.md b/source/_talks/getting-your-data-into-drupal-8.md index eee4fbf06..0318d7fc1 100644 --- a/source/_talks/getting-your-data-into-drupal-8.md +++ b/source/_talks/getting-your-data-into-drupal-8.md @@ -15,7 +15,7 @@ meta: description: "How I migrated the Drupal Bristol website onto Drupal 8." type: website image: - url: '%site.assets_url%/assets/images/talks/getting-your-data-into-drupal-8.png' + url: '%site.assets.url%/assets/images/talks/getting-your-data-into-drupal-8.png' width: 2560 height: 1440 type: image/png diff --git a/source/_talks/taking-flight-with-tailwind-css.md b/source/_talks/taking-flight-with-tailwind-css.md index 8f715bbf6..d4f819739 100644 --- a/source/_talks/taking-flight-with-tailwind-css.md +++ b/source/_talks/taking-flight-with-tailwind-css.md @@ -100,7 +100,7 @@ meta: description: An introduction to utility CSS and Tailwind. type: website image: - url: '%site.assets_url%/assets/images/talks/taking-flight-tailwind.jpg' + url: '%site.assets.url%/assets/images/talks/taking-flight-tailwind.jpg' width: 2560 height: 1440 type: "image/png" diff --git a/source/_talks/tdd-test-driven-drupal.md b/source/_talks/tdd-test-driven-drupal.md index d14a0587c..de9ee8c3a 100644 --- a/source/_talks/tdd-test-driven-drupal.md +++ b/source/_talks/tdd-test-driven-drupal.md @@ -9,7 +9,7 @@ video: type: youtube id: 81J0dPvqG-g image: - url: '%site.assets_url%/assets/images/talks/test-driven-drupal-development.png' + url: '%site.assets.url%/assets/images/talks/test-driven-drupal-development.png' width: 2560 height: 1440 type: image/png diff --git a/source/_talks/things-you-should-know-about-php.md b/source/_talks/things-you-should-know-about-php.md index f1ae05681..dcccd4aec 100644 --- a/source/_talks/things-you-should-know-about-php.md +++ b/source/_talks/things-you-should-know-about-php.md @@ -11,7 +11,7 @@ video: meta: og: image: - url: '%site.assets_url%/assets/images/talks/things-you-should-know-about-php.png' + url: '%site.assets.url%/assets/images/talks/things-you-should-know-about-php.png' events: - name: PHP Stoke From 11f4b1b1c3afb7facf4c1e96efca69e2761300d4 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Wed, 28 Feb 2024 01:01:42 +0000 Subject: [PATCH 236/501] Update YouTube settings --- app/config/sculpin_site.yml | 10 +++++----- source/_includes/about-me.html.twig | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/config/sculpin_site.yml b/app/config/sculpin_site.yml index fbe5f1eb3..cf676b357 100644 --- a/app/config/sculpin_site.yml +++ b/app/config/sculpin_site.yml @@ -24,11 +24,6 @@ ctas: module: | If you're creating a new Drupal module, try my free Drupal module template. -links: - youtube: - name: opdavies - url: https://www.youtube.com/@%links.youtube.name% - menu_links: - title: About @@ -309,3 +304,8 @@ transistor: url: https://feeds.transistor.fm/beyond-blocks share: url: https://share.transistor.fm/e + +youtube: + channel: + slug: opdavies + url: https://www.youtube.com/@%youtube.channel.slug% diff --git a/source/_includes/about-me.html.twig b/source/_includes/about-me.html.twig index 1f7728674..a664454c6 100644 --- a/source/_includes/about-me.html.twig +++ b/source/_includes/about-me.html.twig @@ -10,7 +10,7 @@
-

I'm an Acquia-certified Drupal Triple Expert with {{ macros.yearsExperience }} years of experience, an open-source software maintainer and Drupal core contributor, public speaker, live streamer, and host of the Beyond Blocks podcast.

+

I'm an Acquia-certified Drupal Triple Expert with {{ macros.yearsExperience }} years of experience, an open-source software maintainer and Drupal core contributor, public speaker, live streamer, and host of the Beyond Blocks podcast.

From 8a279a0c26374a6420c26ab673e309b125180e06 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Wed, 28 Feb 2024 08:56:58 +0000 Subject: [PATCH 237/501] Fix assets URL on localhost --- app/config/sculpin_site.yml | 2 +- app/config/sculpin_site_prod.yml | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/config/sculpin_site.yml b/app/config/sculpin_site.yml index cf676b357..d13785759 100644 --- a/app/config/sculpin_site.yml +++ b/app/config/sculpin_site.yml @@ -5,7 +5,7 @@ email: oliver+website@oliverdavies.uk url: http://localhost:8000 assets: - url: https://oliverdavies-uk.s3.eu-west-2.amazonaws.com + url: '%url%' version: 3 banner_text: | diff --git a/app/config/sculpin_site_prod.yml b/app/config/sculpin_site_prod.yml index ef9b43d95..01d2b3e03 100644 --- a/app/config/sculpin_site_prod.yml +++ b/app/config/sculpin_site_prod.yml @@ -1,4 +1,5 @@ imports: - sculpin_site.yml url: https://www.oliverdavies.uk -assets_url: https://oliverdavies-uk.s3.eu-west-2.amazonaws.com +assets: + url: https://oliverdavies-uk.s3.eu-west-2.amazonaws.com From 988b39868e34be5788c59280979ad41f564ba386 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Wed, 28 Feb 2024 08:57:24 +0000 Subject: [PATCH 238/501] Add email CTA buttons --- source/_pages/index.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/source/_pages/index.md b/source/_pages/index.md index d48ce881c..ba4c22592 100644 --- a/source/_pages/index.md +++ b/source/_pages/index.md @@ -15,6 +15,12 @@ Hi, I'm Oliver - a certified Drupal Expert and Software Development Consultant w I'm a Drupal core contributor, module and theme maintainer, former Developer for the Drupal Association, and multiple-time DrupalCon speaker. +{% include 'button' with { + position: 'centre', + text: 'Click here to email Oliver →', + url: 'mailto:' ~ site.email, +} %} + ## What I can help you with If you have a Drupal application, register for a [Drupal development subscription][subscription] and have unlimited access to an expert Drupal developer for a fixed monthly price. @@ -35,6 +41,22 @@ Here are [all my products and services][pricing]. If you still can't find what y title: 'Kind words from clients, subscribers, and past colleagues', } %} +
+

Get in touch

+
+
+

There's no reason to wait. Send me an email and I'll get back to you ASAP.

+
+
+ {% include 'button' with { + position: 'centre', + text: 'Click here to email Oliver →', + url: 'mailto:' ~ site.email, + } %} +
+
+
+ {% include 'daily-email-form.html.twig' with { title: 'Register for daily software development emails', intro: 'Sign up and get daily emails about Drupal, PHP and software development.', From 3ddf00bf5f3c185673fccf153087d1786bb7d276 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Wed, 28 Feb 2024 09:04:16 +0000 Subject: [PATCH 239/501] Fix testimonial image URLs --- app/config/sculpin_site.yml | 42 ++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/app/config/sculpin_site.yml b/app/config/sculpin_site.yml index d13785759..8886717eb 100644 --- a/app/config/sculpin_site.yml +++ b/app/config/sculpin_site.yml @@ -58,7 +58,7 @@ testimonials: title: Drupal Expert url: https://matthieuscarset.com image: - url: '%site.assets_url%/assets/images/recommendations/matthieu-scarset.jpg' + url: '%site.assets.url%/assets/images/recommendations/matthieu-scarset.jpg' tags: [testing] - text: | @@ -66,7 +66,7 @@ testimonials: name: Alexander Carr title: Full Stack Software Engineer at School of Code image: - url: '%site.assets_url%/assets/images/recommendations/alexander-carr.jpg' + url: '%site.assets.url%/assets/images/recommendations/alexander-carr.jpg' tags: [daily] - text: | @@ -74,7 +74,7 @@ testimonials: name: Adam Nuttall title: Drupal Engineer image: - url: '%site.assets_url%/assets/images/recommendations/adam-nuttall.jpg' + url: '%site.assets.url%/assets/images/recommendations/adam-nuttall.jpg' tags: [daily] - text: @@ -82,7 +82,7 @@ testimonials: name: Mike Karthauser title: Senior Software Engineer image: - url: '%site.assets_url%/assets/images/recommendations/mike-karthauser.jpg' + url: '%site.assets.url%/assets/images/recommendations/mike-karthauser.jpg' tags: [daily, testing, coaching] - text: | @@ -103,7 +103,7 @@ testimonials: name: Marcos Duran title: Senior Software Engineer image: - url: '%site.assets_url%/assets/images/recommendations/marcos-duran.jpg' + url: '%site.assets.url%/assets/images/recommendations/marcos-duran.jpg' tags: [git, daily, coaching] - text: | @@ -111,7 +111,7 @@ testimonials: name: Stephen Mulvihill title: Solutions Architect image: - url: '%site.assets_url%/assets/images/recommendations/stephen-mulvihill.jpg' + url: '%site.assets.url%/assets/images/recommendations/stephen-mulvihill.jpg' tags: [git, daily, coaching] - text: | @@ -121,7 +121,7 @@ testimonials: name: Patty O'Callaghan title: Tech Lead image: - url: '%site.assets_url%/assets/images/recommendations/patty-ocallaghan.jpg' + url: '%site.assets.url%/assets/images/recommendations/patty-ocallaghan.jpg' tags: [daily] - text: | @@ -136,7 +136,7 @@ testimonials: title: Senior Drupal Developer url: https://www.playingwithpixels.co.uk image: - url: '%site.assets_url%/assets/images/recommendations/tawny.jpg' + url: '%site.assets.url%/assets/images/recommendations/tawny.jpg' tags: [testing, coaching, call] - text: | @@ -149,7 +149,7 @@ testimonials: title: Director, Bastion Insurance url: https://www.bastioninsurance.co.uk image: - url: '%site.assets_url%/assets/images/recommendations/joe-howell.jpg' + url: '%site.assets.url%/assets/images/recommendations/joe-howell.jpg' tags: [subscription, coaching] - text: | @@ -158,7 +158,7 @@ testimonials: title: Senior Systems Administrator at the University of Bristol url: https://bristol.ac.uk image: - url: '%site.assets_url%/assets/images/recommendations/jon-hallett.jpeg' + url: '%site.assets.url%/assets/images/recommendations/jon-hallett.jpeg' tags: [subscription] - text: | @@ -167,7 +167,7 @@ testimonials: title: Cofounder url: https://www.daylightbooks.org image: - url: '%site.assets_url%/assets/images/recommendations/michael-itkoff.jpg' + url: '%site.assets.url%/assets/images/recommendations/michael-itkoff.jpg' tags: [subscription] - text: | @@ -184,7 +184,7 @@ testimonials: title: Publisher at Poetry Wales Press Ltd (Seren Books) url: https://www.serenbooks.com image: - url: '%site.assets_url%/assets/images/recommendations/mick-felton.jpg' + url: '%site.assets.url%/assets/images/recommendations/mick-felton.jpg' tags: [subscription, coaching] - text: | @@ -193,7 +193,7 @@ testimonials: title: Director at Rohallion url: https://rohallion.agency image: - url: '%site.assets_url%/assets/images/recommendations/duncan.jpeg' + url: '%site.assets.url%/assets/images/recommendations/duncan.jpeg' tags: [subscription, coaching] - text: | @@ -212,7 +212,7 @@ testimonials: title: Web Development Manager url: ~ image: - url: '%site.assets_url%/assets/images/recommendations/adam.jpeg' + url: '%site.assets.url%/assets/images/recommendations/adam.jpeg' tags: [subscription] - text: | @@ -225,7 +225,7 @@ testimonials: title: Web Dev Manager / DevOps / Team Manager at Admiral Group Plc url: https://admiral.com image: - url: '%site.assets_url%/assets/images/recommendations/huw.jpeg' + url: '%site.assets.url%/assets/images/recommendations/huw.jpeg' tags: [subscription] - text: | @@ -234,7 +234,7 @@ testimonials: title: Head of Web Development url: ~ image: - url: '%site.assets_url%/assets/images/recommendations/scott-euser.jpg' + url: '%site.assets.url%/assets/images/recommendations/scott-euser.jpg' tags: [testing, coaching] - text: | @@ -243,7 +243,7 @@ testimonials: title: Senior Drupal Developer at Microserve url: ~ image: - url: '%site.assets_url%/assets/images/recommendations/alan.jpeg' + url: '%site.assets.url%/assets/images/recommendations/alan.jpeg' tags: [coaching] - text: | @@ -254,7 +254,7 @@ testimonials: title: Executive Director at the Drupal Association url: https://www.drupal.org/association image: - url: '%site.assets_url%/assets/images/recommendations/holly-ross.png' + url: '%site.assets.url%/assets/images/recommendations/holly-ross.png' tags: [subscription] - text: | @@ -265,7 +265,7 @@ testimonials: title: CTO at Drupal Association url: https://joshuami.com image: - url: '%site.assets_url%/assets/images/recommendations/josh-mitchell.png' + url: '%site.assets.url%/assets/images/recommendations/josh-mitchell.png' tags: [subscription] - text: | @@ -282,7 +282,7 @@ testimonials: title: Director of Business Development at Tincan url: ~ image: - url: '%site.assets_url%/assets/images/recommendations/brian-healy.png' + url: '%site.assets.url%/assets/images/recommendations/brian-healy.png' tags: [subscription] - text: | @@ -291,7 +291,7 @@ testimonials: title: Developer at Microserve url: ~ image: - url: '%site.assets_url%/assets/images/recommendations/chris-jarvis.jpg' + url: '%site.assets.url%/assets/images/recommendations/chris-jarvis.jpg' - text: | Oliver is seasoned Drupal and all round highly skilled and experienced web developer. I have worked with Oliver on an important project where he was reliable, prompt and ensured strict client deadline delivery and confidentiality at all times. From bcc1874337e640572dddac922a16e9a215addc18 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Wed, 28 Feb 2024 09:05:00 +0000 Subject: [PATCH 240/501] Consolidate prose classes --- app/config/sculpin_site.yml | 3 +++ source/_includes/about-me.html.twig | 2 +- source/_includes/banner.html.twig | 2 +- source/_includes/button.html.twig | 4 ++-- source/_includes/daily-email-form.html.twig | 10 +++------- source/_includes/message.html.twig | 4 ++-- source/_includes/testimonials.html.twig | 20 ++++++++------------ source/_layouts/default.html.twig | 2 +- source/_pages/index.md | 2 +- 9 files changed, 22 insertions(+), 27 deletions(-) diff --git a/app/config/sculpin_site.yml b/app/config/sculpin_site.yml index 8886717eb..9c7f43bdd 100644 --- a/app/config/sculpin_site.yml +++ b/app/config/sculpin_site.yml @@ -299,6 +299,9 @@ testimonials: title: Digital Strategy Consultant tags: [subscription] +prose_classes: | + prose marker:text-black prose-a:font-light prose-a:text-blue-primary prose-blockquote:border-blue-primary prose-code:after:content-[''] prose-code:before:content-[''] prose-code:font-normal prose-figcaption:text-white prose-h2:mb-4 prose-h2:text-xl prose-li:my-1 prose-li:text-black prose-li:text-lg prose-p:text-black prose-p:text-lg prose-ul:my-3 hover:prose-a:no-underline dark:marker:text-white dark:prose-a:text-blue-400 dark:prose-blockquote:border-blue-400 dark:prose-hr:border-grey-400 dark:prose-invert dark:prose-li:text-white dark:prose-p:text-white + transistor: feed: url: https://feeds.transistor.fm/beyond-blocks diff --git a/source/_includes/about-me.html.twig b/source/_includes/about-me.html.twig index a664454c6..641486751 100644 --- a/source/_includes/about-me.html.twig +++ b/source/_includes/about-me.html.twig @@ -9,7 +9,7 @@ Picture of Oliver -
+

I'm an Acquia-certified Drupal Triple Expert with {{ macros.yearsExperience }} years of experience, an open-source software maintainer and Drupal core contributor, public speaker, live streamer, and host of the Beyond Blocks podcast.

diff --git a/source/_includes/banner.html.twig b/source/_includes/banner.html.twig index 8f5f7f47a..f3a741b4c 100644 --- a/source/_includes/banner.html.twig +++ b/source/_includes/banner.html.twig @@ -1,7 +1,7 @@ {% if site.banner_text %}
-
+
{{ site.banner_text|markdown }}
diff --git a/source/_includes/button.html.twig b/source/_includes/button.html.twig index 765af802b..1469860a7 100644 --- a/source/_includes/button.html.twig +++ b/source/_includes/button.html.twig @@ -1,5 +1,5 @@ -
- + diff --git a/source/_includes/daily-email-form.html.twig b/source/_includes/daily-email-form.html.twig index 0dfd854cd..3b4a58b0d 100644 --- a/source/_includes/daily-email-form.html.twig +++ b/source/_includes/daily-email-form.html.twig @@ -119,13 +119,9 @@
+``` + +
+ +## Extracting a Vue component for Drupal blocks + +As well as being able to extract re-usable components within Tailwind, the same +can be done within Vue. As the page could potentially have multiple sidebar +blocks, I extracted a `SidebarBlock` component which would act as a wrapper +around the block’s contents. + +```vue-html +// src/components/Sidebar.vue + + +``` + +The component provides the wrapping div and the appropriate classes in a single +easy-to-maintain location, and +[uses a slot](https://vuejs.org/v2/guide/components-slots.html) as a placeholder +for the main content. + +That means that within `Welcome.vue`, the markup within the `sidebar-block` tags +will be used as the block contents. + +```html + +

My block contents.

+
+``` + +## Adding the Skip to Main Content Link + +One thing +[that was missing](https://github.com/opdavies/rebuilding-bartik/issues/1) was +the 'Skip to main content link'. This an accessibility feature that allows for +users who are navigating the page using only a keyboard to bypass the navigation +links and skip straight to the main content if they wish by clicking a link that +is hidden and only visible whilst it’s focussed on. + +Here is the markup that I used, which is placed directly after the opening +`` tag. + +```html + +``` + +I initially tried to implement the same feature on this website using +[Tailwind’s visually hidden plugin](https://www.npmjs.com/package/tailwindcss-visuallyhidden) +which also contains a `focussable` class, though I wasn’t able to style it the +way that I needed. I created my own +[Tailwind skip link plugin](https://www.npmjs.com/package/tailwindcss-skip-link) +and moved the re-usable styling there. + +To enable the plugin, I needed to add it within the `plugins` section of my +`tailwind.js` file: + +```js +plugins: [ + require('tailwindcss/plugins/container')(), + require('tailwindcss-skip-link')(), +], +``` + +I added only the page-specific styling classes to the link (as well as the +`skip-link` class that the plugin requires) as well as my own focus state to the +skip link that I did within the `style` section of `App.vue`. + +```vue-html + +``` + +![The Bartik clone with the skip to main content link visible](/images/blog/rebuilding-bartik-vue-tailwind-part-2/skip-link.png){.border} + +## Adding the DrupalMessage component + +I also added a version of Drupal’s status message as another Vue component. This +also uses a slot to include the message contents and accepts a +[prop](https://vuejs.org/v2/guide/components-props.html) for the message type. + +```html + +``` + +The value of the `type` prop is then used within some computed properties to +determine the type specific classes to add (e.g. green for success, and red for +warning), as well as whether or not to include the checkmark SVG image. + +```js + +``` + +I did need to make one change to the `tailwind.js` file in order to change the +border on links when they are hovered over - within `modules` I needed to enable +the `borderStyle` module for hover and focus states in order for Tailwind to +generate the additional classes. + +```js +modules: { + // ... + borderStyle: ['responsive', 'hover', 'focus'], + // ... +} +``` + +The message is included within the Welcome component by including the +`` element, though rather than importing it there, it’s +registed as a global component so it would be available to any other components +that could be added in the future. + +This is done within `main.js`: + +```js +// ... + +Vue.component('drupal-message', require('@/components/DrupalMessage').default); + +new Vue({ + render: h => h(App), +}).$mount('#app'); +``` + +![The Bartik clone with the Drupal Message component visible](/images/blog/rebuilding-bartik-vue-tailwind-part-2/drupal-message.png){.border} + +**The updated version is [live on Netlify][netlify], and the [latest source code +is available on GitHub][github].** + +[github]: https://github.com/opdavies/rebuilding-bartik +[netlify]: https://rebuilding-bartik.oliverdavies.uk +[tailwind]: https://tailwindcss.com +[vuejs]: https://vuejs.org diff --git a/source/_posts/rebuilding-bartik-drupals-default-theme-vuejs-tailwind-css.md b/source/_posts/rebuilding-bartik-drupals-default-theme-vuejs-tailwind-css.md new file mode 100644 index 000000000..2e8b03449 --- /dev/null +++ b/source/_posts/rebuilding-bartik-drupals-default-theme-vuejs-tailwind-css.md @@ -0,0 +1,345 @@ +--- +title: Rebuilding Bartik (Drupal’s Default Theme) with Vue.js and Tailwind CSS +date: 2018-11-20 +excerpt: How I rebuilt Drupal’s Bartik theme using Vue.js and Tailwind CSS. +tags: + - drupal + - tailwind-css + - tweet + - vuejs +has_tweets: true +--- + +Earlier this week, I built a clone of [Drupal][0]’s default theme, Bartik, with +[Vue.js][1] and [Tailwind CSS][2]. You can [view the code on GitHub][3] and the +[site itself on Netlify][4]. + +{% include 'tweet' with { + content: '

I built a clone of Bartik, #Drupal's default theme, with @vuejs and @tailwindcss. See the result at https://t.co/nPsTt2cawL, and the code at https://t.co/Dn8eysV4gf.

Blog post coming soon... pic.twitter.com/7BgqjmkCX0

— Oliver Davies (@opdavies) November 20, 2018', + data_cards: true, +} %} + +## Why build a Bartik clone? + +I’m a big fan of utility based styling and Tailwind CSS in particular, I was and +originally thinking of a way to more easily integrate Tailwind within Drupal - +something like I’ve since done with the [Tailwind CSS starter kit theme][5]. +Whilst thinking about that, I wondered about doing the opposite - rebuilding +Drupal (or Bartik) with Tailwind. + +Others including [Adam Wathan](https://adamwathan.me) (one of the creators of +Tailwind CSS) have rebuilt existing UIs like Netlify, YouTube, Twitter, Coinbase +and Transistor.fm with Tailwind as an opportunity for learning and also to +demonstrate using Tailwind - this was my opportunity to do the same. + +Whilst +[Drupal itself has adoped React](https://dri.es/drupal-looking-to-adopt-react), +I’ve personally been looking into Vue.js and have used it for some small +personal projects, including some elements of the site. So I decided to use Vue +for the interactive parts of my Bartik clone to create a fully functional clone +rather than focussing only on the CSS. + +## Building a static template with Tailwind + +The first stage was to build the desktop version, which was done as a simple +HTML file with Tailwind CSS pulled in from it’s CDN. This stage took just over +an hour to complete. + +As Tailwind was added via a CDN, there was no opportunity to customise it’s +configuration, so I needed to use to Tailwind’s default configuration for +colours, padding, spacing, fonts, breakpoints etc. The page is built entirely +with classes provided by Tailwind and uses no custom CSS, except for one inline +style that is used to add the background colour for the Search block, as there +wasn’t a suitable Tailwind option. + +When I decided that I was going to later add some interactivity onto the mobile +navigation menu, the existing code was ported into a new Vue.js application +generated by the Vue CLI, with the majority of the markup within a `Welcome` +component. This meant that Tailwind was also added as a dependency with it’s own +configuration file, though although I had the opportunity to customise it I +decided not to and made no changes to it and continued with the default values. + +`src/App.vue`: + +``` + + + + + +``` + +`src/components/Welcome.vue`: + +
+```vue-html + + + + +```` +
+ +## Making it responsive + +The second stage began with making the existing desktop version responsive - particularly making the navigation menu behave and appear differently on mobile and tablet screens, and stacking the main content area and the sidebar on mobile screens. This was all achieved using Tailwind’s responsive variants. + +```html +
+ ... +
+```` + +In this example, the `pb-4` class adds 1rem of bottom padding to the element by +default, then increases it to 3rem at large screen sizes due to the `lg:pb-12` +class. + +## Adding interactivity + +This is how the main navigation menu works on mobile: + +![The main navigation menu on mobile.](/images/blog/rebuilding-bartik-vue-tailwind/rebuilt-mobile.png) + +The show and hide text appears next to a hamburger menu, and clicking it toggles +the visiblity of the menu links which are stacked below, as well as the wording +of the text itself. + +The code for this was moved into a separate `MainMenu` component, which means +that it was easier to have dedicated data properties for whether the menu was +open or not, as well as computed properties for building the show/hide text. The +`open` value can then be used to apply the appropriate classes to the main menu +to toggle it. + +I also moved the links into `data` too - each link is it’s own object with it's +`title` and `href` values. This means that I can use a `v-for` directive to loop +over the data items and inject dynamic values, removing the duplication of +markup which makes the component easier to read and maintain. + +`src/components/MainMenu.vue`: + +
+ +```vue-html + + + +``` + +
+ +## The result + +The whole task only took around two hours to complete, and although some of the +colours and spacings are slightly different due to the decision to stick with +the default Tailwind configuration values, I’m happy with the result. + +### The original version + +![The original Bartik theme in a new installation of Drupal 8](/images/blog/rebuilding-bartik-vue-tailwind/original.png) + +### The Vue.js and Tailwind CSS version + +![The Vue.js/Tailwind CSS version, hosted on Netlify](/images/blog/rebuilding-bartik-vue-tailwind/rebuilt-desktop.png) + +
+I’ve also made some additional changes since this version, which are described in [this follow-up post](/blog/rebuilding-bartik-with-vuejs-tailwind-css-part-2). +
+ +[0]: https://www.drupal.org +[1]: https://vuejs.org +[2]: https://tailwindcss.com +[3]: https://github.com/opdavies/rebuilding-bartik +[4]: https://rebuilding-bartik.oliverdavies.uk +[5]: https://www.drupal.org/project/tailwindcss diff --git a/source/_posts/reflections-speaking-unifieddiff.md b/source/_posts/reflections-speaking-unifieddiff.md new file mode 100644 index 000000000..72eac3891 --- /dev/null +++ b/source/_posts/reflections-speaking-unifieddiff.md @@ -0,0 +1,40 @@ +--- +title: Reflections on speaking at UnifiedDiff +date: 2012-09-06 +excerpt: Yesterday evening I went along and spoke at the UnifiedDiff meetup in Cardiff. +tags: + - speaking +--- + +Yesterday evening I went along and spoke at the +[UnifiedDiff meetup](http://www.unifieddiff.co.uk) in Cardiff, having offered +previously to do a presentation providing an introduction to Drupal. + +I'm an experienced Drupal Developer, but not an experienced public speaker +(although I have done several user training sessions and Drupal demonstrations +for clients previously), and I think that some of the nerves that I had +beforehand were apparent during the presentation, and being the first speaker +for the evening probably didn't help, although I did get a +[nice tweet](https://twitter.com/craigmarvelley/status/243418608720543745) +mid-way through. + +Initially, after aiming for a 20-minute presentation plus Q&A, I think I wrapped +up the presentation in around 14 minutes, although I did about 6 minutes of +answering questions afterwards including the apparently mandatory "Why use +Drupal compared to WordPress or Joomla?" question, some Drupal 8 and Symfony +questions, as well as an interesting question about the White House development +project after I'd listed it within a list of example sites. Next time, I think +that some more detailed presenter notes are needed. Typically, as soon as it sat +back in my seat, the majority of things that I'd managed to remember beforehand +all came flooding back to me and I thought "I should have said that whilst I was +up speaking". + +Overall, considering my inexperience at speaking to this type of audience, I was +fairly happy with my presentation, although I'm sure that I'll change my mind +once I've watched the video of it on the UnifiedDiff website. Regardless, I +think that it was a great experience and I enjoyed doing it, and I'd like to +thank the organisers of UnifiedDiff for having me speak at their meetup. It was +great to have a more relaxed conversation with some people after the other +speakers had been up, and having introduced Drupal I would be more than happy to +come back and do a more in-depth presentation if there is an interest for me to +do so. diff --git a/source/_posts/renaming-gray-grey-tailwind-css.md b/source/_posts/renaming-gray-grey-tailwind-css.md new file mode 100644 index 000000000..25194d357 --- /dev/null +++ b/source/_posts/renaming-gray-grey-tailwind-css.md @@ -0,0 +1,32 @@ +--- +title: Renaming gray to grey in Tailwind CSS +excerpt: How to change the colour "gray" to "grey" in Tailwind CSS. +tags: + - tailwind-css +date: 2020-09-04 +--- + +In `tailwind.config.js`: + +``` +const { colors } = require('tailwindcss/defaultTheme') + +module.exports = { + purge: ["./public/**/*.html"], + theme: { + extend: { + colors: { + // Remove the "gray" colours from the theme. + gray: {}, + + // Create a new set of "grey" colours, using the original "gray" values. + grey: colors['gray'] + } + }, + }, + variants: {}, + plugins: [], +}; +``` + +Based on a configuration file from https://github.com/tailwindlabs/tailwindcss-playground. diff --git a/source/_posts/restructuring-my-tailwindjs-configuration-files.md b/source/_posts/restructuring-my-tailwindjs-configuration-files.md new file mode 100644 index 000000000..59bc62c2b --- /dev/null +++ b/source/_posts/restructuring-my-tailwindjs-configuration-files.md @@ -0,0 +1,236 @@ +--- +title: Restructuring my tailwind.js configuration files +date: 2019-03-08 +excerpt: How I’ve started structuring my tailwind.js configuration files in preparation for Tailwind 1.0. +tags: + - laravel-mix + - tailwind-css +--- + +After watching Adam Wathan’s recent +["Working on Tailwind 1.0" YouTube video](https://www.youtube.com/watch?v=SkTKN38wSEM) +and seeing some of the proposed changes to the `tailwind.js` configuration file, +I’ve started to structure my current config files a little differently in +preparation for 1.0. + +## The current tailwind.js file format + +Currently when you run `tailwind init` to create a new config file, it includes +all of Tailwind’s default values, and then you can add, edit and remove values +as needed. + +Some values like colours, font families, plugins and modules you are likely to +change for each project, whilst others like shadows, leading, z-index and +opacity, you’re less likely to need to change. + +It’s 952 lines including comments, which is quite long and could potentially be +daunting for new Tailwind users. + +The contents of the full file can be found in the +[Tailwind CSS documentation](https://tailwindcss.com/docs/configuration#default-configuration), +or it can be found in +[various GitHub repositories](https://github.com/tailwindcss/plugin-examples/blob/master/tailwind.js). + +## A preview of the new tailwind.js file format + +In Adam’s [Laracon Online](https://laracon.net) talk, Tailwind CSS by Example, +he showed the new configuration file format. Here is a snippet: + +```js +module.exports { + theme: { + extend: { + spacing: { + 7: '1.75rem', + }, + }, + colors: { + white: { + default: '#fff', + 20: 'rgba(255,255,255,.2)', + 40: 'rgba(255,255,255,.4)', + 60: 'rgba(255,255,255,.6)', + 80: 'rgba(255,255,255,.8)', + }, + ... + } + ... + } +} +``` + +You’ll notice that the structure of the file is quite different, and that all of +the default values have been removed and are now maintained by Tailwind itself. + +This means that the configuration file contains only your custom changes, where +you've either overridden a default value (e.g. colours) or added your own using +`extend` (e.g. adding another spacing value, as in this example). + +I think that's a great improvement and makes the file so much cleaner, and +easier to read and understand. + +## An interim approach + +If you don’t want to wait until 1.0, or potentially 0.8, you can get some of +this functionality now by restructuring your Tailwind configuration file. + +Here is the complete `tailwind.js` file for the +[DrupalCamp Bristol 2019 static landing page](https://dcb-2019-static.netlify.com), +which uses Tailwind in addition to the existing traditional CSS: + +```js +let defaultConfig = require('tailwindcss/defaultConfig')(); + +var colors = { + ...defaultConfig.colors, + black: '#000', +}; + +module.exports = { + ...defaultConfig, + colors: colors, + textColors: colors, + backgroundColors: colors, + borderColors: Object.assign({ default: colors['grey-light'] }, colors), + plugins: [ + require('tailwindcss-interaction-variants')(), + require('tailwindcss-spaced-items'), + ], + modules: { + ...defaultConfig.modules, + textStyle: [...defaultConfig.modules.textStyle, 'hocus'], + }, + options: { + ...defaultConfig.options, + prefix: 'tw-', + important: true, + }, +}; +``` + +Here are the steps that I took to create this file: + +
    +
  1. +

    **Get the default configuration**. This is done using `require('tailwindcss/defaultConfig')()`. Essentially this has the same contents as the current `tailwind.js` file, though now it’s owned and maintained within Tailwind itself, and not by the user.

    +

    Also any new or updated values within the default configuration will be automatically available.

    +

    This line is present but commented out in the current generated `tailwind.js` file.

    +
  2. + +
  3. +

    **Create the colors object.** This will by default override Tailwind’s default colours, however you can add `...defaultConfig.colors` to include them and then add or edit values as needed.

    +

    In this example, I’m overridding the value used for the `black` colour classes to match the existing colour in the other CSS.

    +
  4. + +
  5. +

    **Return the main configuration object.** For sites with no overrides, this could just be `module.exports = defaultConfig` for a minimal configuration.

    +

    To extend the defaults, add `...defaultConfig` at the beginning.

    +
  6. + +
  7. +

    **Assign our colours.** Use them for `colors`, `textColors`, `backgroundColors` and `borderColours`.

    +
  8. + +
  9. +

    **Add any plugins**. I use plugins on most projects, in this case I’m using [tailwindcss-interaction-variants](https://www.npmjs.com/package/tailwindcss-interaction-variants) and [tailwindcss-spaced-items](https://www.npmjs.com/package/tailwindcss-spaced-items). Usually the default `container` plugin would be here too.

    +
  10. + +
  11. +

    **Add or override modules.** Here I’m adding the `hocus` (hover and focus) variant provided by the interaction variants plugin to the text style classes.

    +
  12. + +
  13. +

    **Add or override options.** As this markup was originally from a Drupal website, I needed to override some of the options values. I’ve added the `tw-` prefix to avoid Tailwind classes from clashing with Drupal’s default markup, and set all Tailwind classes to use `!important` so that they override any existing styles.

    +
  14. +
+ +This file is only 27 lines long, so considerably shorter than the default file, +and I think that it’s much easier to see what your additional and overridden +values are, as well able to quickly recognise whether a class is generated from +a custom value or from a Tailwind default value. + +To move this file to the new format I think would be much easier as there’s no +default configuration to filter out, and you can move across only what is +needed. + +## Other changes + +### Consistent spacing for padding and margin + +Similar to defining colours, you could also set some standard spacing values, +and using those for padding, margin and negative margin to ensure that they are +all consistent. + +In this case, we can use `defaultConfig.margin` to get the default, add or +override any values, and then assign it to the relevant sections of the main +object. + +```js +const spacing = { + ...defaultConfig.margin, + '2px': '2px', +}; + +module.exports = { + ...defaultConfig, + // ... + padding: spacing, + margin: spacing, + negativeMargin: spacing, + // ... +}; +``` + +### Picking values with lodash + +In the opposite to extending, if we wanted to limit the number of values within +a part of the configuration, we can do that too. I’d suggest using the +[pick method](https://lodash.com/docs/4.17.11#pick) provided by +[Lodash](https://lodash.com). + +From the documentation: + +> Creates an object composed of the picked object properties. + +For example, if we only wanted normal, medium and bold font weights: + +```js +module.exports = { + ...defaultConfig, + // ... + fontWeights: _.pick(defaultConfig.fontWeights, ['normal', 'medium', 'bold']), + // ... +}; +``` + +### Renaming the file + +Also in Tailwind 1.0, it seems that the configuration file name is changing from +`tailwind.js` to `tailwind.config.js`. + +If you use [Laravel Mix](https://laravel-mix.com) and the +[Laravel Mix Tailwind plugin](https://github.com/JeffreyWay/laravel-mix-tailwind) +like I do on this site (even though it’s a Sculpin site), it will look for a +`tailwind.js` file by default or you can specify whatever filename you need. + +Here is an excerpt of the Tailwind configuration file for this site, using +`tailwind.config.js`: + +```js +mix + .postCss('assets/css/app.css', 'source/dist/css') + .tailwind('tailwind.config.js'); +``` + +## Looking foward to Tailwind CSS 1.0! + +Adam has said that Tailwind 1.0 should be released within a few weeks of the +time of writing this, I assume once +[the 1.0 To-Do list](https://github.com/tailwindcss/tailwindcss/issues/692) is +completed. + +I really like some of the improvements that are coming in 1.0, including the new +configuration file format and the ability to easily add and extend values, as +well as the file itself now being completely optional. + +I can’t wait for it to land! diff --git a/source/_posts/review-adminhover-module.md b/source/_posts/review-adminhover-module.md new file mode 100644 index 000000000..fcca76958 --- /dev/null +++ b/source/_posts/review-adminhover-module.md @@ -0,0 +1,58 @@ +--- +title: Review of the Admin:hover Module +date: 2010-08-10 +excerpt: My review of Drupal’s admin:hover module. +tags: + - admin:hover + - administration + - drupal-6 + - drupal-modules + - drupal-planet +--- + +Sorry for the lack of Blog posts lately, but +[my new job](http://horseandcountry.tv) that I started a few weeks ago has +certainly been keeping me busy! I've got a few more posts that I'm preparing +content for, and I'll hopefully be back into my weekly-post routine before too +long! + +Today, I'd like to just give a quick overview of the +[Admin:hover](http://drupal.org/project/admin_hover) module. It basically adds +an administrative menu that pops up when you hover over a node or block within +your Drupal website - the kind of functionality that was present within previous +versions of the [Admin module](http://drupal.org/project/admin). It also +integrates well with the [Devel](http://drupal.org/project/devel) and +[Clone](http://drupal.org/project/node_clone) modules. + +I've found this to be extremely useful whilst working on photo galleries etc. +where multiple nodes are displayed in a grid format and I quickly need to +publish or unpublish something for testing purposes. No longer do I need to open +each node, or go into the administration area to perform the required actions. + +It is also possible to customise which links are available from within the +adminstration area. The possible selections that I currently have on this site +are as follows: + +**Node links:** + +- Edit +- Publish +- Unpublish +- Promote +- Unpromote +- Make sticky +- Make unsticky +- Delete +- Clone +- Dev load +- View author +- Edit author +- Add + +**Block links:** + +- Configure block +- Add block + +Although, as I have additional contributed modules installed, some of these may +not neccassaily be available out of the box. diff --git a/source/_posts/review-image-caption-module.md b/source/_posts/review-image-caption-module.md new file mode 100644 index 000000000..d7983ec09 --- /dev/null +++ b/source/_posts/review-image-caption-module.md @@ -0,0 +1,40 @@ +--- +title: Review of the Image Caption Module +date: 2010-08-20 +excerpt: My review of Drupal’s Image Caption module. +tags: + - drupal + - drupal-6 + - drupal-planet + - image-caption + - imagefield +--- + +Up until as recent as last week, whenever I added an image into one of my Blog +posts, I was manually adding the caption below each image and styling it +accordingly. That was until I installed the +[Image Caption](http://drupal.org/project/image_caption) module. + +The Image Caption module uses jQuery to dynamically add captions to images. Here +is a walkthrough of the process that I followed to install and configure the +module. As always, I used Drush to download and enable the module, then visited +the Image Caption Settings page (admin/settings/image_caption). Here, I select +which node types should be included in image captioning. In my case, I only +wanted this to apply to Blog posts. + +As I use the [FileField](http://drupal.org/project/filefield), +[ImageField](http://drupal.org/project/imagefield) and +[Insert](http://drupal.org/project/insert) modules to add images to my posts, as +opposed to via a WYSIWYG editor, I'm able to add the CSS class of 'caption' to +my images. + +Now, all images inserted this way will have the CSS class of 'caption'. + +As the Image Caption module uses the image's title tag to create the displayed +caption, I enabled the custom title text for my Image field so that when I +upload an image, I'm prompted to enter text for the caption. + +This results in a span called `image-caption-container` around the inserted +image, and a caption below it called `image-caption` containing the text. + +All that's left is to style these classes within your CSS stylesheet. diff --git a/source/_posts/review-teleport-module.md b/source/_posts/review-teleport-module.md new file mode 100644 index 000000000..3aa995c7e --- /dev/null +++ b/source/_posts/review-teleport-module.md @@ -0,0 +1,39 @@ +--- +title: Review of the Teleport Module +date: 2010-07-12 +excerpt: My review of Drupal’s Teleport module. +tags: + - drupal-planet + - drupal-6 + - drupal-modules + - teleport +--- + +As a heavily-reliant +[Quicksilver](http://en.wikipedia.org/wiki/Quicksilver_%28software%29) user on +my MacBook Pro, I was glad when I found the +[Teleport](http://drupal.org/project/teleport) module for +[Drupal](http://drupal.org) _(due to Elliott Rothman's +[tweet](http://twitter.com/elliotttt/status/18044234238))_. + +When you press a configurable hot-key, a jQuery dialog box appears where you can +search for nodes by title or path, or directly enter the path that you want to +navigate to. This will greatly reduce the number of clicks that I need to +perform to get to my desired page - even compared to the +[Admin](http://drupal.org/project/admin) and +[Administration Menu](http://drupal.org/project/admin_menu) modules. + +Although it's not a new module (the first commits were 2 years ago), I hope that +they are still planning on achieving the list of future directions listed on +their Drupal.org project page: + +- Make interface act more like Quicksilver (i.e. you should only have to press + Enter once to launch) +- 'Actions' like Quicksilver: if you select a node, a second input should appear + with options to go to the View page, Edit page, (un)publish, etc. Same with + users. +- Hook into more non-node content, like taxonomy terms and functions in the API + module. + +Personally, this will make navigation around both the front-end and +administration area of my Drupal sites so much easier. diff --git a/source/_posts/running-drupal-88-symfony-local-server.md b/source/_posts/running-drupal-88-symfony-local-server.md new file mode 100644 index 000000000..33de2ccc2 --- /dev/null +++ b/source/_posts/running-drupal-88-symfony-local-server.md @@ -0,0 +1,323 @@ +--- +title: Running Drupal 8.8 with the Symfony Local Server +excerpt: How to use Symfony's local web server to run a Drupal 8.8 website. +date: 2020-03-09 +tags: + - drupal + - drupal-8 + - symfony +--- + +![A screenshot of a terminal window running a Drupal project with the Symfony local server](/images/blog/running-drupal-with-symfony-local-server/terminal.png) + + + +## Installation + + + +The Symfony server is bundled as part of the `symfony` binary that is available +to download from . + +To install it, run this command: + +```bash +curl -sS https://get.symfony.com/cli/installer | bash +``` + +Even though it’s by Symfony, the local webserver works with any type of +project - including Drupal 8 (and 9) and Drupal 7. + +## Getting started + +Here are the basic commands to start and stop the server: + +```bash +# Alias for server:start, starts the server +symfony serve + +# Run the server in daemon mode (in the background) +symfony serve -d + +# Display the status of the server +symfony server:status + +# Stop the server +symfony server:stop +``` + +If your Drupal files are within a `web` or `docroot` directory, it will +automatically be used as the document root for the server, so files are served +from there if you run the serve command. + +If you use a different subdirectory name - one that isn't loaded automatically - +you can use the `--document-root` option: + +```bash +symfony serve --document-root www +``` + +## Different PHP Versions + +One of the most useful features of the Symfony server is that it +[supports multiple versions of PHP](https://symfony.com/doc/current/setup/symfony_server.html#different-php-settings-per-project) +if you have them installed, and a different version can be selected per +directory. + +This is done by adding a `.php-version` file to the root of the project that +contains the PHP version to use. For example: + +```bash +echo "7.3" > .php-version +``` + +Next time the server is started, this file will be read and the correct version +of PHP will be used. + +If you’re using macOS and want to install another version of PHP, you can do it +using Homebrew: + +```bash +# Install PHP 7.3 +brew install php@7.3 +``` + +[Further PHP customisations can be made per project](https://symfony.com/doc/current/setup/symfony_server.html#overriding-php-config-options-per-project) +by adding a `php.ini` file. + +## Securing Sites Locally + +The Symfony server allows for serving sites via HTTPS locally by installing its +own local certificate authority. + +If it’s not installed automatically, run this command to install it: + +``` +symfony server:ca:install +``` + +Now any site will be served via HTTPS by default, and any HTTP requests will be +automatically redirected. + +If you need to run a site with just HTTP, add the `--no-tls` option to the +`serve` command. + +## Adding Databases (and other services) with Docker + +The Symfony server has an integration with Docker for providing extra services - +such as databases that we’ll need to install Drupal. + +This is my `docker-compose.yaml` file which defines a `database` service for +MySQL: + +```yaml +version: '2.1' + +services: + database: + image: mysql:5.7 + ports: [3306] + environment: + MYSQL_ROOT_PASSWORD: secret + volumes: + - mysql-data:/var/lib/mysql + +volumes: + mysql-data: +``` + +Because port 3306 is exposed, the server recognises it as a database service and +automatically creates environment variables prefixed with `DATABASE_`. + +A list of all the environment variables can be seen by running +`symfony var:export` (add `| tr " " "\n"` if you want to view each one on a new +line, and `| sort` if you want to list them alphabetically): + +``` +DATABASE_DATABASE=main +DATABASE_DRIVER=mysql +DATABASE_HOST=127.0.0.1 +DATABASE_NAME=main +DATABASE_PASSWORD=secret +DATABASE_PORT=32776 +DATABASE_SERVER=mysql://127.0.0.1:32776 +DATABASE_URL=mysql://root:secret@127.0.0.1:32776/main?sslmode=disable&charset=utf8mb4 +DATABASE_USER=root +DATABASE_USERNAME=root +SYMFONY_DOCKER_ENV=1 +SYMFONY_TUNNEL= +SYMFONY_TUNNEL_ENV= +``` + +Now these environment variables can be used within `settings.php` file to allow +configure Drupal’s database connection settings: + +```php +// web/sites/default/settings.php + +if ($_SERVER['SYMFONY_DEFAULT_ROUTE_URL']) { + $databases['default']['default'] = [ + 'driver' => $_SERVER['DATABASE_DRIVER'], + 'host' => $_SERVER['DATABASE_HOST'], + 'database' => $_SERVER['DATABASE_NAME'], + 'username' => $_SERVER['DATABASE_USER'], + 'password' => $_SERVER['DATABASE_PASSWORD'], + 'port' => $_SERVER['DATABASE_PORT'], + 'prefix' => '', + 'namespace' => 'Drupal\\Core\\Database\\Driver\\mysql', + 'collation' => 'utf8mb4_general_ci', + ]; +} +``` + +To keep things organised, I usually like to split these settings into their own +file and include it: + +```php +if ($_SERVER['SYMFONY_DEFAULT_ROUTE_URL'] && file_exists(__DIR__ . '/settings.symfony.php')) { + require_once __DIR__ . '/settings.symfony.php'; +} +``` + +## Installing Drupal + +Now that Drupal can connect to the (empty) database, we can install the site. I +usually do this using Drush, which is added as a dependency via Composer. + +The command that I’d usually run is: + +```bash +cd web + +../vendor/bin/drush site-install +``` + +However, this will cause an error like this because Drupal cannot connect to the +database when Drush is run in this way. + +> Error: Class 'Drush\Sql\Sql' not found in Drush\Sql\SqlBase::getInstance() + +To fix this, ensure that the command is prefixed with `symfony php`. This will +ensure that the correct PHP version and configuration is used, and that the +appropriate environment variables are available. + +```bash +symfony php ../vendor/bin/drush site-install +``` + +This also applies to all other Drush commands. + +## Custom Domain Names + +Currently we can only access the site via the localhost URL with a specific +port. The port is determined automatically when the server is started so it can +change if you have multiple projects running. + +Symfony server also allows for +[adding local domain names through a proxy](https://symfony.com/doc/current/setup/symfony_server.html#local-domain-names). +This is useful if you always want to access the site from the same URL, or if +the site relies on using a specific URL such as a multisite setup (multiple +domains served from the same codebase). + +{% include 'figure' with { + image: { + src: '/images/blog/running-drupal-with-symfony-local-server/proxy.png', + alt: 'A screenshot of the proxy overview screen, showing three local projects with their local domains, ports and directories.', + }, + caption: 'The proxy overview screen' +} only %} + +### Setting up a multisite + +Here’s an example of how I would use local domains to configure a multisite +Drupal installation (taken from +). + +The first thing is to add the subdomain to the proxy. In this example, I’m going +to set up a version of the Umami demo installation profile at +`https://umami.wip`. + +```bash +# Add umami.wip to the proxy and attach it to this directory +symfony proxy:domain:attach umami +``` + +Now we can add it to Drupal’s `sites.php` file to route requests to the correct +site directory: + +```php +// web/sites/sites.php + +// This maps https://umami.wip to the sites/umami directory +$sites['umami.wip'] = 'umami'; +``` + +To create the directory, we can duplicate the `default` site directory and its +contents. + +``` +cp -R web/sites/default web/sites/umami +``` + +To create a separate database, we add a new service to the `docker-compose.yaml` +file and a new MySQL volume to store the data. We can use labels to generate +site specific environment variables. + +```diff + version: '2.1' + + services: + database: + image: mysql:5.7 + ports: [3306] + environment: + MYSQL_ROOT_PASSWORD: secret + volumes: + - mysql-data:/var/lib/mysql + ++ database_umami: ++ image: mysql:5.7 ++ ports: [3306] ++ environment: ++ MYSQL_ROOT_PASSWORD: secret ++ volumes: ++ - mysql-data-umami:/var/lib/mysql ++ labels: ++ com.symfony.server.service-prefix: 'UMAMI_DATABASE' + + volumes: + mysql-data: ++ mysql-data-umami: +``` + +These can then be added to `sites/umami/settings.php`: + +```php +$databases['default']['default'] = [ + 'driver' => $_SERVER['UMAMI_DATABASE_DRIVER'], + 'host' => $_SERVER['UMAMI_DATABASE_HOST'], + 'database' => $_SERVER['UMAMI_DATABASE_NAME'], + 'username' => $_SERVER['UMAMI_DATABASE_USER'], + 'password' => $_SERVER['UMAMI_DATABASE_PASSWORD'], + 'port' => $_SERVER['UMAMI_DATABASE_PORT'], + 'prefix' => '', + 'namespace' => 'Drupal\\Core\\Database\\Driver\\mysql', + 'collation' => 'utf8mb4_general_ci', +]; +``` + +Now that the Umami site is able to connect to its own database, we can install +Drupal - specifying the installation profile to use and also the site directory +to target. + +```bash +symfony php ../vendor/bin/drush site-install \ + demo_umami \ + -l umami \ + --no-interaction +``` diff --git a/source/_posts/sculpin-twig-resources.md b/source/_posts/sculpin-twig-resources.md new file mode 100644 index 000000000..0aeb79157 --- /dev/null +++ b/source/_posts/sculpin-twig-resources.md @@ -0,0 +1,51 @@ +--- +title: Sculpin and Twig Resources +date: 2015-07-19 +excerpt: A list of resources that I compiled whilst preparing for my Sculpin and Twig talk at DrupalCamp North. +tags: + - drupalcamp + - drupalcamp-north + - sculpin + - twig +--- + +Here’s a list of resources that I compiled whilst preparing for my +[Sculpin and Twig talk](http://drupalcampnorth.org/session/test-drive-twig-sculpin) +at [DrupalCamp North](http://drupalcampnorth.org). + +## General Information + +- +- + +## Where to Get Sculpin + +- +- +- +- + +## Source Code Examples + +- +- - the source repository for this + site. +- +- +- +- +- +- +- Google for "`sculpin_site.yml site:github.com`" for more examples. + +## Videos + +- - a YouTube playlist of Sculpin videos. + +## Twig + +- +- - variables, filters, + functions, template inheritance, expressions etc. +- Go to http://twig.sensiolabs.org/{foo} to search for a tag, filter, test or + function. diff --git a/source/_posts/simplifying-drupal-migrations-xautoload.md b/source/_posts/simplifying-drupal-migrations-xautoload.md new file mode 100644 index 000000000..d5792ed32 --- /dev/null +++ b/source/_posts/simplifying-drupal-migrations-xautoload.md @@ -0,0 +1,135 @@ +--- +title: Simplifying Drupal Migrations with xautoload +date: 2016-05-03 +excerpt: How to use the xautoload module to autoload migration classes within your Drupal 7 migration modules. +tags: + - autoloading + - drupal + - drupal-7 + - drupal-planet + - php +--- + +How to use the [xautoload][1] module to autoload migration classes within your +Drupal 7 migration modules. + +## What is xautoload? + +[xautoload][1] is a Drupal module that enables the autoloading of PHP classes, +in the same way that you would do so in a [Composer][2] based project such as +Drupal 8 or Symfony. + +It supports both the [PSR-0][3] and [PSR-4][4] standards, as well as providing a +wildcard syntax for Drupal’s `file[]` syntax in .info files. + +To use it, download and enable it from Drupal.org as you would for any other +module, and then add it as a dependency within your module. The xautoload +project page suggests including a minimum version in this format: + +```ini +dependencies[] = xautoload (>= 7.x-5.0) +``` + +This will ensure that the version of xautoload is 7.x-5.0 or newer. + +## How to use it + +### Wildcard syntax for .info files + +Here is an example .info file for a migrate module. + +```ini +; foo_migrate.info + +name = Foo Migration +core = 7.x +package = Foo + +files[] = includes/user.inc +files[] = includes/nodes/article.inc +files[] = includes/nodes/page.inc +``` + +In this example, each custom migration class is stored in it’s own file within +the `includes` directory, and each class needs to be loaded separately using the +`files[] = filename` syntax. + +One thing that the xautoload module does to enable for the use of wildcards +within this syntax. By using wildcards, the module file can be simplified as +follows: + +```ini +files[] = includes/**/*.inc +``` + +This will load any .inc files within the `includes` directory as well as any +sub-directories, like 'node' in the original example. + +This means that any new migration classes that are added will be automatically +loaded, so you don’t need to declare each include separately within +foo_migrate.info again. The great thing about this approach is that it works +with the existing directory and file structure. + +### Use the PSR-4 structure + +If you want to use the [PSR-4][4] approach, you can do that too. + +In order to do so, you’ll need to complete the following steps: + +1. Rename the `includes` directory to `src`. +2. Ensure that there is one PHP class per file, and that the file extension is + `.php` rather than `.inc`. +3. Ensure that the name of the file matches the name of the class - + `FooArticleNodeMigration` would be in a file called + `FooArticleNodeMigration.php`. +4. Add a namespace to each PHP file. This uses the same format as Drupal 8, + including the machine name of the module. For example, `Drupal\foo_migrate`. + - If the class is within a sub-directory, then this will also need to be + included within the namespace - e.g. `Drupal\foo_migrate\Node`. + - You’ll also need to import any class names that you are referencing, + including class names that are you extending, by adding `use` statements at + the top of the file. You may be able to prefix it with `\` instead (e.g. + `\DrupalNode6Migration`), but I prefer to use imports. + +Now your class may look something like this: + +```php + 'Drupal\foo_migrate\Node\FooArticleNodeMigration', + 'source_type' => 'story', + 'destination_type' => 'article', +); +``` + +## Resources + +- [xautoload module][1] +- [migrate module][5] +- [migrate_d2d module][6] +- [PSR-0][3] +- [PSR-4][4] + +[1]: https://www.drupal.org/project/xautoload +[2]: http://getcomposer.org +[3]: http://www.php-fig.org/psr/psr-0/ +[4]: http://www.php-fig.org/psr/psr-4/ +[5]: https://www.drupal.org/project/migrate +[6]: https://www.drupal.org/project/migrate_d2d diff --git a/source/_posts/site-upgraded-drupal-7.md b/source/_posts/site-upgraded-drupal-7.md new file mode 100644 index 000000000..de4ea3878 --- /dev/null +++ b/source/_posts/site-upgraded-drupal-7.md @@ -0,0 +1,22 @@ +--- +title: Site Upgraded to Drupal 7 +date: 2012-01-04 +excerpt: As the vast majority of the Drupal websites that I currently work on are built on Drupal 7, I thought that it was time that I upgraded this site. +tags: + - drupal +--- + +As the vast majority of the Drupal websites that I currently work on are built +on Drupal 7, I thought that it was time that I upgraded this site. Following the +[core upgrade process](http://drupal.org/node/570162) and the +[CCK migration process](http://drupal.org/node/1144136), everything was upgraded +smoothly without any issues. + +I've upgraded a handful of essential contrib modules to the latest stable +version, [Administration Menu](http://drupal.org/project/admin_menu), +[Views](http://drupal.org/project/views) etc., and will continue upgrading the +other modules on the site as time allows. + +I also prefer [Bartik](http://drupal.org/project/bartik) to +[Garland](http://drupal.org/project/garland) - but I will be creating a new +custom theme when I get a chance. diff --git a/source/_posts/some-useful-git-aliases.md b/source/_posts/some-useful-git-aliases.md new file mode 100644 index 000000000..339b67d3c --- /dev/null +++ b/source/_posts/some-useful-git-aliases.md @@ -0,0 +1,34 @@ +--- +title: Some Useful Git Aliases +date: 2014-01-15 +excerpt: Here are some bash aliases that I use and find helpful for quickly writing Git and Git Flow commands. +tags: + - git +--- + +Here are some bash aliases that I use and find helpful for quickly writing Git +and Git Flow commands. + +These should be placed within your `~/.bashrc` or `~/.bash_profile` file: + +```bash +alias gi="git init" +alias gcl="git clone" +alias gco="git checkout" +alias gs="git status" +alias ga="git add" +alias gaa="git add --all" +alias gc="git commit" +alias gcm="git commit -m" +alias gca="git commit -am" +alias gm="git merge" +alias gr="git rebase" +alias gps="git push" +alias gpl="git pull" +alias gd="git diff" +alias gl="git log" +alias gfi="git flow init" +alias gff="git flow feature" +alias gfr="git flow release" +alias gfh="git flow hotfix" +``` diff --git a/source/_posts/some-useful-links-using-simpletest-drupal.md b/source/_posts/some-useful-links-using-simpletest-drupal.md new file mode 100644 index 000000000..9f6f5a231 --- /dev/null +++ b/source/_posts/some-useful-links-using-simpletest-drupal.md @@ -0,0 +1,19 @@ +--- +title: Some useful links for using SimpleTest in Drupal +date: 2013-06-13 +excerpt: Here are some useful links that I've found when researching about unit testing in Drupal using SimpleTest. +tags: + - drupal + - drupal-planet + - simpletest + - tdd + - test-driven-development + - testing +--- + +- [An Introduction to Unit Testing in Drupal](http://www.lullabot.com/blog/articles/introduction-unit-testing-drupal 'An Introduction to Unit Testing in Drupal') +- [Module Developer's Guide to SimpleTest](http://www.lullabot.com/blog/articles/drupal-module-developers-guide-simpletest "Module Developer's Guide to SimpleTest") +- [SimpleTest Tutorial (Drupal 6)](https://drupal.org/simpletest-tutorial 'SimpleTest Tutorial (Drupal 6)') +- [SimpleTest Tutorial (Drupal 7)](https://drupal.org/simpletest-tutorial-drupal7 'SimpleTest Tutorial (Drupal 7)') +- [SimpleTest Reference](https://drupal.org/node/278126 'SimpleTest Reference') +- [Testing with SimpleTest](https://drupal.org/node/1128366 'Testing with SimpleTest') diff --git a/source/_posts/south-wales-drupal-user-group.md b/source/_posts/south-wales-drupal-user-group.md new file mode 100644 index 000000000..30fedffa7 --- /dev/null +++ b/source/_posts/south-wales-drupal-user-group.md @@ -0,0 +1,20 @@ +--- +title: The Inaugural Meetup for the South Wales Drupal User Group +date: 2010-09-26 +excerpt: If you do Drupal and you're in the area, come and join us for the first SWDUG meetup! +tags: + - drupal + - drupal-planet + - meetups + - swdug +--- + +If you do Drupal and you're in the area, come and join us for the first SWDUG +meetup! + +We'll be meeting in the communal area just outside of the +[SubHub](http://www.subhub.com) HQ, at: + +4, The Studios
3 Burt Street
Cardiff
CF10 5FZ + +For more information and to signup, visit . diff --git a/source/_posts/speaking-drupalcon-amsterdam.md b/source/_posts/speaking-drupalcon-amsterdam.md new file mode 100644 index 000000000..8bf235942 --- /dev/null +++ b/source/_posts/speaking-drupalcon-amsterdam.md @@ -0,0 +1,39 @@ +--- +title: Speaking at DrupalCon Amsterdam +date: 2019-07-25 +excerpt: I’m going to be attending DrupalCon Europe again this year, but for the first time as a speaker. +tags: + - drupalcon + - personal + - speaking +has_tweets: true +--- + +

I’ve attended numerous DrupalCons since my first in Prague in 2013, as a delegate, as a [{{site.companies.drupal_association.name}}]({{site.companies.drupal_association.url}}) staff member, and also as a contribution sprint mentor. I’m excited to be attending DrupalCon Amsterdam again this year - but as my first time as a DrupalCon speaker.

+ +{% include 'tweet' with { + content: '

Super excited to be giving my first @DrupalConEur talk!#drupal #drupalcon #php #ansible https://t.co/5ZOPClUjvC pic.twitter.com/TWih82Ny0P

— Oliver Davies (@opdavies) July 16, 2019' +} %} + +The session that I’m going to be presenting is a twenty minute version of my +[Deploying PHP applications with Ansible, Ansible Vault and Ansistrano](/talks/deploying-php-ansible-ansistrano) +talk. + +{% include 'figure' with { + image: { + src: '/images/blog/speaking-drupalcon-amsterdam/drupalcon-schedule.jpg', + alt: 'My session on the DrupalCon Amsterdam schedule.', + }, + caption: 'My session on the DrupalCon Amsterdam schedule.', +} %} + +I’ve been working with Drupal since 2007 (or maybe 2008), and it was the subject +of my [first meetup talk](/talks/so-what-is-this-drupal-thing) in 2012. Since +then I’ve given 48 more talks +(including one workshop) at various user groups and conferences on a range of +development and systems administration topics. So it’s a nice conincedence that +this will be my fiftieth (50th) talk. + +Thanks also to my employer, +[{{site.companies.inviqa.name}}]({{site.companies.inviqa.url}}), who are giving +me the time and covering my costs to attend the conference. diff --git a/source/_posts/speaking-drupalcon-europe-2020.md b/source/_posts/speaking-drupalcon-europe-2020.md new file mode 100644 index 000000000..8be4cab71 --- /dev/null +++ b/source/_posts/speaking-drupalcon-europe-2020.md @@ -0,0 +1,17 @@ +--- +title: Speaking at DrupalCon Europe 2020 +excerpt: I'm excited to be speaking again at DrupalCon, this time online at DrupalCon Europe. +tags: + - drupal + - conferences + - speaking +date: 2020-07-30 +--- + +After giving my [Ansible and Ansistrano talk](/talks/deploying-php-ansible-ansistrano) in Amsterdam, I'm excited that another of my talks has been accepted for DrupalCon! + +This year, my [TDD - Test-Driven Drupal](/talks/tdd-test-driven-drupal) talk has been accepted for DrupalCon Europe, will is being held online from December 8-11th. + +I first gave this talk at DrupalCamp London 2017 and again a number of times over the last few years including at Drupal Developer Days in Lisbon and most recently for the North West (UK) Drupal user group in May. I've always had good feedback from it, and enjoy teaching others about testing and hopefully continue to inspire people to start writing tests themselves. + +This is definitely one of my favourite topics. I've enjoyed updating and improving this talk over the years, and I'm looking forward to giving it at DrupalCon this year. diff --git a/source/_posts/speaking-remotely-during-covid-19.md b/source/_posts/speaking-remotely-during-covid-19.md new file mode 100644 index 000000000..c72af93c1 --- /dev/null +++ b/source/_posts/speaking-remotely-during-covid-19.md @@ -0,0 +1,84 @@ +--- +title: Speaking remotely during COVID-19 +excerpt: I've been quite busy during this lockdown, giving talks remotely at conferences and user groups. +date: 2020-07-07 +tags: + - speaking +--- + +I've been quite busy during COVID-19 and various lockdowns, giving talks remotely at conferences and user groups. + +In mid-April, I send a tweet with an open offer to any user groups that needed a speaker, with some suggestions for talks that I'd given recently that I could present remotely. + + + +As well as this, I also applied to some open calls for papers for remote conferences, such as [CMS Philly](https://cmsphilly.org "The CMS Philly conference") (formerly Drupaldelphia) that was taking place online this year. + +At the time of writing, these are the talks that I've given remotely or are already planned for future dates. I'll be updating this list going forward as new talks are added, as well as my [talks page](/talks "My upcoming and past talks"). + +If you'd like me to speak at your online conference or user group and be added to this list, please contact me and we can see if we can find a suitable date. + +## Test-Driven Drupal + +An overview of automated testing in Drupal, and a demo of building a new Drupal 8 (or 9) module using test driven development. + +- [NWDUG](http://nwdrupal.org.uk) - 11th May +- [BADCamp 2020](https://2020.badcamp.org/session/tdd-test-driven-drupal) - 16th October +- [DrupalCon Europe 2020](https://events.drupal.org/europe2020/sessions/tdd-test-driven-drupal) - 8th December + +## Deploying PHP with Ansible and Ansistrano + +How to use Ansible, Ansible Vault and Ansistrano to deploy PHP applications, using a Drupal 8 application for a demo. + +- [Drupal Edinburgh](https://www.meetup.com/Drupal-Edinburgh/events/267905594) - 11th March +- [CMS Philly](https://cmsphilly.org) - 30th April +- [Drupal Yorkshire](https://www.meetup.com/DrupalYorkshire/events/zwzsfpybchbcc) - 20th May +- [PHP London](https://www.meetup.com/phplondon/events/270930524) - 4th June +- [PHP North East](https://www.meetup.com/phpnortheast) - 16th June +- [PHP Sussex](https://www.meetup.com/PHP-Sussex) - 1st July +- [Midwest PHP 2021](https://midwestphp.org/talks/1q5XUF2tTdXXLYOoujMkpF/Deploying_PHP_applications_with_Ansible,_Ansible_Vault_and_Ansistrano) - 23rd April 2021 + +## Taking Flight with Tailwind CSS + +An introduction to utility-based CSS and how to use Tailwind CSS in PHP projects using tools such as Webpack Encore and Laravel Mix. + +- [CMS Philly](https://cmsphilly.org) - 30th April +- [PHP Hampshire](https://www.meetup.com/meetup-group-yzpbvTYv) - 8th July +- [Drupal Yorkshire](https://www.meetup.com/DrupalYorkshire/events/zwzsfpybclbbc) - 20th August +- [DigitalCamp Atlanta](https://www.drupalcampatlanta.com/2020/sessions/taking-flight-tailwind-css) - 11th September +- [Bristol JS](https://techtalks.io/events/f8e26038-2561-484e-8a74-7a1e3a0369b8) - 30th September +- [Drupal Virtual Cafe](https://groups.drupal.org/node/536142) (Drupal Kyiv) - 15th October +- [PHP Cambridge](https://www.meetup.com/phpcambridge/events/273686561) - 19th January 2021 +- [Nashville PHP](https://www.meetup.com/nashvillephp/events/kzkdwryccdbmb) - 9th February 2021 + +## Updating to Drupal 9 + +How to update your site to Drupal 9, and why it's much different to any major Drupal version upgrade before! + +- [Drupal NYC](https://ti.to/drupalnyc/meetup-2020-08-05) - 2nd September +- [Leeds PHP](https://www.meetup.com/leedsphp/events/272504993) - 23rd September +- [Midwest PHP 2021](https://midwestphp.org/talks/7C0m4I87vq72cDoXvsHFRp/Upgrading_your_site_to_Drupal_9) - 22nd April 2021 + +## Working with Workspace + +- [NWDUG](https://www.meetup.com/nwdrupal/events/272098270) - 11th August (lightning talk) +- [PHP South West](https://www.meetup.com/php-sw/events/272787346) - 9th September (lightning talk) +- PHP North West - 2nd February 2021 + +## Automated Testing and Test-Driven Development in Drupal 8 (workshop) + +- [DrupalCamp London](https://drupalcamp.london/training/Automated-Testing-and-Test-Driven-Development-in-Drupal-8) - 13th March (in-person, just before UK lockdown) +- [DrupalCamp NYC](https://2020.drupalcamp.nyc/training/automated-testing-and-test-driven-development-drupal-8) - 14th November + +## Building Slides and Presenting with rst2pdf + +- [PHP South Wales](https://www.meetup.com/PHP-South-Wales/events/275625320) - January 28th 2021 + +## Soaring with Utility CSS and Tailwind (workshop) + +- [DrupalCamp Florida 2021](https://www.fldrupal.camp/training/soaring-utility-css-and-tailwind) - Feburary 18th 2021 + +## Building Static Websites with Sculpin + +- [Drupal Yorkshire](https://www.meetup.com/DrupalYorkshire/events/280100968) - 19th August 2021 +- PHP North West - 7th September diff --git a/source/_posts/splitting-new-drupal-project-from-repo.md b/source/_posts/splitting-new-drupal-project-from-repo.md new file mode 100644 index 000000000..4805617d5 --- /dev/null +++ b/source/_posts/splitting-new-drupal-project-from-repo.md @@ -0,0 +1,163 @@ +--- +title: How to split a new Drupal contrib project from within another repository +date: 2018-03-10 +excerpt: How to use Git to split a directory from within an existing repository into it’s own. +tags: + - drupal + - drupal-7 + - drupal-8 + - drupal-planet + - git + - open-source +--- + +Yay! You’ve written a new Drupal module, theme or installation profile as part +of your site, and now you’ve decided to open source it and upload it to +Drupal.org as a new contrib project. But how do you split it from the main site +repository into it’s own? + +Well, there are a couple of options. + +## Does it need to be part of the site repository? + +An interesting thing to consider is, does it _need_ to be a part of the site +repository in the first place? + +If from the beginning you intend to contribute the module, theme or distribution +and it’s written as generic and re-usable from the start, then it _could_ be +created as a separate project on Drupal.org or as a private repository on your +Git server from the beginning, and added as a dependency of the main project +rather than part of it. It could already have the correct branch name and adhere +to the Drupal.org release conventions and be managed as a separate project, then +there is no later need to "clean it up" or split it from the main repo at all. + +This is how I worked at the [Drupal Association][2] - with all of the modules +needed for Drupal.org hosted on Drupal.org itself, and managed as a dependency +of the site repository with Drush Make. + +Whether this is a viable option or not will depend on your processes. For +example, if your code needs to go through a peer review process before releasing +it, then pushing it straight to Drupal.org would either complicate that process +or bypass it completely. Pushing it to a separate private repository may depend +on your team's level of familiarity with [Composer][3], for example. + +It does though avoid the “we’ll clean it up and contribute it later” scenario +which probably happens less than people intend. + +## Create a new, empty repository + +If the project is already in the site repo, this is probably the most common +method - to create a new, empty repository for the new project, add everything +to it and push it. + +For example: + +```bash +cd web/modules/custom/my_new_module + +# Create a new Git repository. +git init + +# Add everything and make a new commit. +git add -A . +git commit -m 'Initial commit' + +# Rename the branch. +git branch -m 8.x-1.x + +# Add the new remote and push everything. +git remote add origin username@git.drupal.org:project/my_new_module.git +git push origin 8.x-1.x +``` + +There is a huge issue with this approach though - **you now have only one single +commit, and you’ve lost the commmit history!** + +This means that you lose the story and context of how the project was developed, +and what decisions and changes were made during the lifetime of the project so +far. Also, if multiple people developed it, now there is only one person being +attributed - the one who made the single new commit. + +Also, if I’m considering adding your module to my project, personally I’m less +likely to do so if I only see one "initial commit". I’d like to see the activity +from the days, weeks or months prior to it being released. + +What this does allow though is to easily remove references to client names etc +before pushing the code. + +## Use a subtree split + +An alternative method is to use [git-subtree][0], a Git command that "merges +subtrees together and split repository into subtrees". In this scenario, we can +use `split` to take a directory from within the site repo and split it into it’s +own separate repository, keeping the commit history intact. + +Here is the description for the `split` command from the Git project itself: + +> Extract a new, synthetic project history from the history of the +> subtree. The new history includes only the commits (including merges) that +> affected , and each of those commits now has the contents of +> at the root of the project instead of in a subdirectory. Thus, the newly +> created history is suitable for export as a separate git repository. + +
+__Note__: This command needs to be run at the top level of the repository. Otherwise you will see an error like "You need to run this command from the toplevel of the working tree.". + +To find the path to the top level, run `git rev-parse --show-toplevel`. + +
+ +In order to do this, you need specify the prefix for the subtree (i.e. the +directory that contains the project you’re splitting) as well as a name of a new +branch that you want to split onto. + +``` +git subtree split --prefix web/modules/custom/my_new_module -b split_my_new_module +``` + +When complete, you should see a confirmation message showing the branch name and +the commit SHA of the branch. + +``` +Created branch 'split_my_new_module' +7edcb4b1f4dc34fc3b636b498f4284c7d98c8e4a +``` + +If you run `git branch`, you should now be able to see the new branch, and if +you run `git log --oneline split_my_new_module`, you should only see commits for +that module. + +If you do need to tidy up a particular commit to remove client references etc, +change a commit message or squash some commits together, then you can do that by +checking out the new branch, running an interactive rebase and making the +required amends. + +``` +git checkout split_my_new_module +git rebase -i --root +``` + +Once everything is in the desired state, you can use `git push` to push to the +remote repo - specifying the repo URL, the local branch name and the remote +branch name: + +``` +git push username@git.drupal.org:project/my_new_module.git split_my_new_module:8.x-1.x +``` + +In this case, the new branch will be `8.x-1.x`. + +Here is a screenshot of example module that I’ve split and pushed to GitLab. +Notice that there are multiple commits in the history, and each still attributed +to it’s original author. + +![Screenshot of a split project repo on GitLab](/images/blog/subtree-split-drupal-module.png) + +Also, as this is standard Git functionality, you can follow the same process to +extract PHP libraries, Symfony bundles, WordPress plugins or anything else. + +[0]: https://github.com/git/git/blob/master/contrib/subtree/git-subtree.txt +[1]: + https://github.com/git/git/blob/master/contrib/subtree/git-subtree.txt#L101-L108 +[2]: {{site.companies.drupal_association.url}} +[3]: https://getcomposer.org diff --git a/source/_posts/streaming-spabby-gary-hockin-about-drupal.md b/source/_posts/streaming-spabby-gary-hockin-about-drupal.md new file mode 100644 index 000000000..d536b76d0 --- /dev/null +++ b/source/_posts/streaming-spabby-gary-hockin-about-drupal.md @@ -0,0 +1,22 @@ +--- +title: Streaming with Spabby (Gary Hockin) about Drupal +excerpt: I recently joined my friend Gary on his stream to discuss Drupal. +tags: + - drupal + - php + - drupal-9 + - streaming +date: 2020-07-30 +--- + +I recently joined my friend and fellow [PHP South Wales](https://phpsouthwales.uk) regular [Gary Hockin](https://twitter.com/GeeH "Gary on Twitter") (aka GeeH, aka Spabby) on his stream to discuss one of my favourite topics, Drupal. + +I've noticed that a lot of Developers within the wider PHP community have maybe used or looked at an earlier version of Drupal, like 4, 5 or 6, but not a more recent version, so this seemed like a good opportunity to discuss and demo some of the modern features and improvements in Drupal to Gary's mostly PHP focussed audience. + +You can currently [view the video on Gary's Twitch page](https://www.twitch.tv/videos/689269586), or I've embedded it below. + +We touched on the topic of decoupled Drupal, and we're planning a follow-up stream where we pair program and set up Drupal together with a front-end React application, which would be great fun! + +
+ +
diff --git a/source/_posts/style-drupal-6s-taxonomy-lists-php-css-and-jquery.md b/source/_posts/style-drupal-6s-taxonomy-lists-php-css-and-jquery.md new file mode 100644 index 000000000..783df0016 --- /dev/null +++ b/source/_posts/style-drupal-6s-taxonomy-lists-php-css-and-jquery.md @@ -0,0 +1,75 @@ +--- +title: Style Drupal 6's Taxonomy Lists with PHP, CSS and jQuery +date: 2010-04-05 +excerpt: Getting started with Drupal theming by styling Drupal’s taxonomy lists. +tags: + - drupal-6 + - drupal-planet + - drupal-theming + - taxonomy +--- + +Whilst developing this, and other Drupal websites for clients, I decided that I +wanted to categorise content using the taxonomy system. However, I wasn't happy +with the way that Drupal displayed the terms lists by default, and I started +comparing this to other websites that I look at. + +To start with, I wanted to have something that described what the list was +displaying - like in the second example above. I wanted to have the words +'Posted in' displayed before the list of terms. To do this, I had to edit the +node template file that exists within my theme folder (sites/all/themes). As I +only wanted this change to affect my Blog posts, the file that I needed to +change is **node-blog.tpl.php** + +I scrolled down until I found the piece of code that displayed the terms list: + +```php + +
+ +
+ +``` + +Adding `print t(' Posted in ')` will print the words 'Posted in' before +outputing the terms. + +I then added some CSS to re-size the spacing between the items, and then add the +commas between them to seperate them: + +```css +.terms ul.links li { + margin-right: 1px; + padding: 0; +} + +.terms ul.links li:after { + content: ","; +} + +.terms ul.links li.last:after { + content: "."; +} +``` + +I created a file named **script.js** in my theme folder with the following code +in it. After clearing Drupal's caches, this file is automatically recognised by +Drupal 6. + +```js +if (Drupal.jsEnabled) { + $(document).ready(function() { + $('.terms ul.links li.last').prev().addClass('test'); + }) +} +``` + +This code finds the last item in the list, uses **.prev** to select the one +before it, and then uses **.addClass** to assign it the HTML class of "test". We +can then use this class to target it with specific CSS. + +```css +.terms ul.links li.test:after { + content: " and"; +} +``` diff --git a/source/_posts/survey-results-my-drupalcon-europe-session-test-driven-drupal.md b/source/_posts/survey-results-my-drupalcon-europe-session-test-driven-drupal.md new file mode 100644 index 000000000..5f4e7ebaf --- /dev/null +++ b/source/_posts/survey-results-my-drupalcon-europe-session-test-driven-drupal.md @@ -0,0 +1,96 @@ +--- +title: Survey results from my DrupalCon Europe session (Test-Driven Drupal) +excerpt: Here are the results from the session survey for my DrupalCon session (Test-Driven Drupal) on Drupal automated testing and test-driven development. +tags: + - drupalcon + - speaking +date: 2021-01-22 +--- + +In December [I gave a talk at DrupalCon Europe](/blog/test-driven-drupal-presentation-drupalcon-europe) on automated testing and test-driven development in Drupal. At the end of each session, the attendees were shown a survey to complete and the results have just been released to the speakers. + +Here are the results from my session, and I've included the screenshot of the graphs at the bottom of this post. I'd like to thank everyone who attended the session live, and those who left valuable feedback afterward. + +If you want to see the slides and video for that session, click the link above to see the embedded slides and video. + +## Survey results + +Attendance: 134 + +### Did the session description accurately represent the content presented? + +* Yes - 96.8% +* No - 3.2% + +Total survey responses: 62 + +### Overall, how would you rate this session? + +* 5 - 44.8% +* 4 - 29.3% +* 3 - 19% +* 2 - 6.9% +* 1 - 0% + +Total survey responses: 58 + +### How would you rate the speaker(s)'s mastery of this topic? + +* Excellent - 64.4% +* Very good - 24.4% +* Good - 11.1% +* Fair - 0% +* Poor - 0% + +Total survey responses: 45 + +### How would you rate the speaker(s)'s presentation skills? + +* Excellent - 46.8% +* Very good - 31.9% +* Good - 10.6% +* Fair - 8.5% +* Poor - 2.1% + +Total survey responses: 47 + +### How would you rate the speaker(s)’s slides and other session materials? + +* Excellent - 47.8% +* Very good - 30.4% +* Good - 15.2% +* Fair - 6.5% +* Poor - 0% + +Total survey responses: 46 + +### What changes could the speaker(s) have made for you to give it a higher rating? + +* A little more insights (e.g. on test environment setup) +* Everything was perfect! Thank you :) +* He ist to fast ;) +* Just felt the end examples were rushed through slightly compared to the introduction at the start. +* Less examples to more focus. +* Nothing, was excellent +* Not to shy and interact afterwards +* Slower - too much content in too little time +* Speak a little slower, especially when going through the example code +* Yes + +### What did the speaker(s) do really well? + +* Broken down the steps into real clear examples +* Clear, concise slides, minimal code (Which can be hard to read) +* CodeBlocks +* Covers all relevant topics. +* End to end examples without just being a high level overview. +* Examples +* Good examples +* Good preparation, Good speed +* Good structured walkthrough +* Great knowledge +* Introduce the idea of TDD +* Yes +* You explained your workflow very clearly. You have a skill for making tricky concepts very clear. Thanks for a great talk. + + diff --git a/source/_posts/test-driven-ansible-role-development-molecule.md b/source/_posts/test-driven-ansible-role-development-molecule.md new file mode 100644 index 000000000..9c78b5d3e --- /dev/null +++ b/source/_posts/test-driven-ansible-role-development-molecule.md @@ -0,0 +1,39 @@ +--- +title: Test-Driven Ansible Role Development with Molecule +date: 2019-06-02 +excerpt: Some resources that I found for testing Ansible roles with a tool called Molecule. +tags: + - ansible + - molecule + - testing + - video +--- + +I used to maintain a number of [Ansible roles][roles], and I recently wrote one +for automatically generating `settings.php` files for Drupal projects that I use +for some client projects as part of the [Ansible and Ansistrano deployment +process][talk], as it can populate these files with credentials stored in +Ansible Vault. + +I uploaded an initial version of the role [onto GitHub][github], but haven’t yet +released it onto Ansible Galaxy. + +I’d seen in other people’s roles and read elsewhere about writing automated +tests for Ansible roles using a tool called [Molecule][molecule], and wanted to +write some tests for this role before publishing it onto Galaxy. + +I looked around for resources about Molecule, and found a [blog post by Jeff +Geerling][jeff-post], but also this YouTube video that I found very helpful. + +I’ve since been re-writing the role from scratch based on Molecule, and plan to +release an official version of it soon. + +{% include 'youtube-video' with { id: DAnMyBZ8-Qs } %} + +[github]: https://github.com/opdavies/ansible-role-drupal-settings +[jeff-post]: + https://www.jeffgeerling.com/blog/2018/testing-your-ansible-roles-molecule +[molecule]: https://molecule.readthedocs.io +[roles]: + https://docs.ansible.com/ansible/latest/user_guide/playbooks_reuse_roles.html +[talk]: /talks/deploying-php-ansible-ansistrano diff --git a/source/_posts/test-driven-drupal-on-gitstore-leanpub.md b/source/_posts/test-driven-drupal-on-gitstore-leanpub.md new file mode 100644 index 000000000..db670b81a --- /dev/null +++ b/source/_posts/test-driven-drupal-on-gitstore-leanpub.md @@ -0,0 +1,37 @@ +--- +title: Test-Driven Drupal on Gitstore and Leanpub +excerpt: The work-in-progress codebase for the example application is now on Gitstore. +date: 2020-04-22 +tags: + - drupal + - drupal-8 + - drupal-association + - drupal-planet + - drupalcares + - testing + - test-driven-drupal +has_tweets: true +--- + +Some time ago, I announced that I was planning on writing a book on automated testing and test driven development with Drupal. I [created a landing page][landing page] and set up a mailing list, but I wasn't sure at that point what I was going to cover or create as part of the book. + + +{% include 'tweet' with { + class: 'my-6', + data_cards: true, + content: '

I'm going to write a book on automated testing in Drupal. Join the mailing list for updates, and I'm happy to take suggestions on what to cover. https://t.co/YXNpe6f8Ft #drupal

— Oliver Davies (@opdavies) May 15, 2018', +} %} + +Being a meetup and DrupalCamp conference organiser, after some thought I decided to build a website for an example conference, and that some of this code would then be included in the book as example content. This seemed to cover most of what I originally wanted, through features like a call for papers for potential speakers to propose sessions, allowing organisers to administer and moderate those proposals, automatically sending notification emails to submitters and displaying the accepted sessions. + +I've started building it with Drupal 8.8 and it is [now available on GitStore][gitstore] to purchase access to, including all future updates as I continue building the application - adding new features and upgrading to Drupal 9 once it is released. There are some other interesting things there too, such as using feature flags to enable or disable functionality, and using GitHub Actions to run the tests automatically. + +The book itself I've [added a page for on Leanpub][leanpub], and I'll be continuing to add content to it in parallel to building the example codebase. Once there is enough content, I will release the first draft for purchase. + +Any purchases that are made via Gitstore or Leanpub, an amount will be donated to the [Drupal Association][] and the [#DrupalCares campaign][drupalcares] to help sustain the Association during COVID-19. + +[drupal association]: https://www.drupal.org/association +[drupalcares]: https://www.drupal.org/association/drupal-cares-challenge +[gitstore]: https://enjoy.gitstore.app/repositories/opdavies/test-driven-drupal-conference-app +[landing page]: /test-driven-drupal +[leanpub]: https://leanpub.com/test-driven-drupal diff --git a/source/_posts/test-driven-drupal-presentation-drupalcon-europe.md b/source/_posts/test-driven-drupal-presentation-drupalcon-europe.md new file mode 100644 index 000000000..41393eaa8 --- /dev/null +++ b/source/_posts/test-driven-drupal-presentation-drupalcon-europe.md @@ -0,0 +1,22 @@ +--- +title: Test-Driven Drupal presentation from DrupalCon Europe +excerpt: Links to the video and slides from my automated testing session from DrupalCon Europe. +tags: + - drupal + - drupal-8 + - drupalcon + - speaking +date: 2021-01-12 +--- + +Today, the sessions from DrupalCon Europe were posted on the [Drupal Association YouTube channel](https://www.youtube.com/playlist?list=PLpeDXSh4nHjTP7vRC6LCak9adK2yp1P5S), including my session on automated testing and test-driven development in Drupal 8 (and 9): + +Here is the video of my presentation: + +
+ +
+ +Here is an embedded version of the slides, which I've updated since the talk: + + diff --git a/source/_posts/testing-tailwind-css-plugins-jest.md b/source/_posts/testing-tailwind-css-plugins-jest.md new file mode 100644 index 000000000..d22568aff --- /dev/null +++ b/source/_posts/testing-tailwind-css-plugins-jest.md @@ -0,0 +1,284 @@ +--- +title: Testing Tailwind CSS plugins with Jest +date: 2019-04-29 +excerpt: How to write tests for Tailwind CSS plugins using Jest. +tags: + - javascript + - jest + - tailwind-css + - testing +promoted: true +--- + +
+**Note:** The content of this post is based on tests seen in Adam Wathan’s ["Working on Tailwind 1.0" video][working-on-tailwind-video], the Jest documentation website, and existing tests for other Tailwind plugins that I’ve used such as [Tailwind CSS Interaction Variants][tailwindcss-interaction-variants]. +
+ +## Preface + +In Tailwind 0.x, there was a `list-reset` utility that reset the list style and +padding on a HTML list, though it was removed prior to 1.0 and moved into +Tailwind’s base styles and applied by default. + +However, on a few projects I use Tailwind in addition to either existing custom +styling or another CSS framework, and don’t use `@tailwind base` (formerly +`@tailwind preflight`) so don’t get the base styles. + +Whilst I could re-create this by replacing it with two other classes +(`list-none` and `p-0`), I decided to write [my own Tailwind CSS plugin][repo] +to re-add the `list-reset` class. This way I could keep backwards compatibility +in my projects and only need to add one class in other future instances. + +In this post, I’ll use this as an example to show how to write tests for +Tailwind CSS plugins with a JavaScript testing framework called [Jest][jest]. + +More information about plugins for Tailwind CSS themselves can be found on the +[Tailwind website][tailwind-docs-plugins]. + +## Add dependencies + +To start, we need to include `jest` as a dependency of the plugin, as well as +`jest-matcher-css` to perform assertions against the CSS that the plugin +generates. + +We also need to add `tailwindcss` and `postcss` so that we can use them within +the tests. + +``` +yarn add -D jest jest-matcher-css postcss tailwindcss@next +``` + +This could be done with `yarn add` or `npm install`. + +## Writing the first test + +In this plugin, the tests are going to be added into a new file called +`test.js`. This file is automatically loaded by Jest based on it’s [testRegex +setting][jest-testregex-setting]. + +This is the format for writing test methods: + +```js +test('a description of the test', () => { + // Perform tasks and write assertions +}); +``` + +The first test is to ensure that the correct CSS is generated from the plugin +using no options. + +We do this by generating the plugin’s CSS, and asserting that it matches the +expected CSS within the test. + +```js +test('it generates the list reset class', () => { + generatePluginCss().then(css => { + expect(css).toMatchCss(` + .list-reset { + list-style: none; + padding: 0 + } + `); + }); +}); +``` + +However, there are some additional steps needed to get this working. + +### Generating the plugin’s CSS + +Firstly, we need to import the plugin’s main `index.js` file, as well as PostCSS +and Tailwind. This is done at the beginning of the `test.js` file. + +```js +const plugin = require('./index.js'); +const postcss = require('postcss'); +const tailwindcss = require('tailwindcss'); +``` + +Now we need a way to generate the CSS so assertions can be written against it. + +In this case, I’ve created a function called `generatePluginCss` that accepts +some optional options, processes PostCSS and Tailwind, and returns the CSS. + +```js +const generatePluginCss = (options = {}) => { + return postcss(tailwindcss()) + .process('@tailwind utilities;', { + from: undefined, + }) + .then(result => result.css); +}; +``` + +Alternatively, to test the output of a component, `@tailwind utilities;` would +be replaced with `@tailwind components`. + +```js +.process('@tailwind components;', { + from: undefined +}) +``` + +Whilst `from: undefined` isn’t required, if it’s not included you will get this +message: + +> Without `from` option PostCSS could generate wrong source map and will not +> find Browserslist config. Set it to CSS file path or to `undefined` to prevent +> this warning. + +### Configuring Tailwind + +In order for the plugin to generate CSS, it needs to be enabled within the test, +and Tailwind’s core plugins need to be disabled so that we can assert against +just the output from the plugin. + +As of Tailwind 1.0.0-beta5, this can be done as follows: + +``` +tailwindcss({ + corePlugins: false, + plugins: [plugin(options)] +}) +``` + +In prior versions, each plugin in `corePlugins` needed to be set to `false` +separately. + +I did that using a `disableCorePlugins()` function and [lodash][lodash], using +the keys from `variants`: + +``` +const _ = require('lodash') + +// ... + +const disableCorePlugins = () => { + return _.mapValues(defaultConfig.variants, () => false) +} +``` + +### Enabling CSS matching + +In order to compare the generated and expected CSS, [the CSS matcher for +Jest][jest-css-matcher] needs to be required and added using +[expect.extend][jest-expect-extend]. + +```js +const cssMatcher = require('jest-matcher-css') + +... + +expect.extend({ + toMatchCss: cssMatcher +}) +``` + +Without it, you’ll get an error message like _"TypeError: expect(...).toMatchCss +is not a function"_ when running the tests. + +## The next test: testing variants + +To test variants we can specify the required variant names within as options to +`generatePluginCss`. + +For example, this is how to enable `hover` and `focus` variants. + +```js +generatePluginCss({ variants: ['hover', 'focus'] }); +``` + +Now we can add another test that generates the variant classes too, to ensure +that also works as expected. + +```js +test('it generates the list reset class with variants', () => { + generatePluginCss({ variants: ['hover', 'focus'] }).then(css => { + expect(css).toMatchCss(` + .list-reset { + list-style: none; + padding: 0 + } + + .hover\\:list-reset:hover { + list-style: none; + padding: 0 + } + + .focus\\:list-reset:focus { + list-style: none; + padding: 0 + } + `); + }); +}); +``` + +## Running tests locally + +Now that we have tests, we need to be able to run them. + +With Jest included as a dependency, we can update the `test` script within +`package.json` to execute it rather than returning a stub message. + +```diff +- "test": "echo \"Error: no test specified\" && exit 1" ++ "test": "jest" +``` + +This means that as well as running the `jest` command directly to run the tests, +we can also run `npm test` or `yarn test`. + +After running the tests, Jest will display a summary of the results: + +![A screenshot of the Jest output after running the tests, showing 1 passed test suite and 2 passed tests, as well as the test run time.](/images/blog/testing-tailwindcss-plugins/running-tests.png) + +## Running tests automatically with Travis CI + +As well as running the tests locally, they can also be run automatically via +services like [Travis CI][travis] when a new pull request is submitted or each +time new commits are pushed. + +This is done by adding a `.travis-ci.yml` file to the repository, like this one +which is based on the [JavaScript and Node.js example][travis-nodejs-example]: + +```yaml +language: node_js + +node_js: + - '8' + +cache: + directories: + - node_modules + +before_install: + - npm update + +install: + - npm install + +script: + - npm test +``` + +With this in place, the project can now be enabled on the Travis website, and +the tests will be run automatically. + +For this plugin, you can see the results at +. + +[jest-css-matcher]: https://www.npmjs.com/package/jest-matcher-css +[jest-expect-extend]: https://jestjs.io/docs/en/expect#expectextendmatchers +[jest-testregex-setting]: + https://jestjs.io/docs/en/configuration#testregex-string-array-string +[jest]: https://jestjs.io +[lodash]: https://lodash.com +[repo]: https://github.com/opdavies/tailwindcss-list-reset +[tailwind-docs-plugins]: https://tailwindcss.com/docs/plugins +[tailwindcss-interaction-variants]: + https://www.npmjs.com/package/tailwindcss-interaction-variants +[travis-nodejs-example]: + https://docs.travis-ci.com/user/languages/javascript-with-nodejs +[travis]: https://travis-ci.org +[working-on-tailwind-video]: https://www.youtube.com/watch?v=SkTKN38wSEM diff --git a/source/_posts/thanks.md b/source/_posts/thanks.md new file mode 100644 index 000000000..ac7ce5fe8 --- /dev/null +++ b/source/_posts/thanks.md @@ -0,0 +1,14 @@ +--- +title: Thanks +date: 2014-05-06 +excerpt: Thanks everyone or their comments about my move to the Drupal Association. +tags: + - drupal + - drupal-association + - personal +--- + +This is just a quick post to thank everyone for their comments and +congratulations after my previous post about +[joining the Drupal Association](/blog/drupal-association/). I’m looking forward +to my first day in the job tomorrow. diff --git a/source/_posts/turning-drupal-module-into-feature.md b/source/_posts/turning-drupal-module-into-feature.md new file mode 100644 index 000000000..b34da9b41 --- /dev/null +++ b/source/_posts/turning-drupal-module-into-feature.md @@ -0,0 +1,35 @@ +--- +title: Turning Your Custom Drupal Module into a Feature +date: 2017-05-20 +excerpt: How to turn a custom Drupal module into a Feature. +tags: + - drupal + - drupal-7 + - drupal-planet + - features +--- + +Yesterday I was fixing a bug in an inherited Drupal 7 custom module, and I +decided that I was going to add some tests to ensure that the bug was fixed and +doesn’t get accidentially re-introduced in the future. The test though required +me to have a particular content type and fields which are specific to this site, +so weren’t present within the standard installation profile used to run tests. + +I decided to convert the custom module into a [Feature][0] so that the content +type and it’s fields could be added to it, and therefore present on the testing +site once the module is installed. + +To do this, I needed to expose the module to the Features API. + +All that’s needed is to add this line to the `mymodule.info` file: + +```ini +features[features_api][] = api:2 +``` + +After clearing the cache, the module is now visible in the Features list - and +ready to have the appropriate configuration added to it. + +!['The features list showing the custom module'](/images/blog/custom-module-as-a-feature.png) + +[0]: https://www.drupal.org/project/features diff --git a/source/_posts/tweets-drupalcamp-london.md b/source/_posts/tweets-drupalcamp-london.md new file mode 100644 index 000000000..13506a2bb --- /dev/null +++ b/source/_posts/tweets-drupalcamp-london.md @@ -0,0 +1,69 @@ +--- +title: Tweets from DrupalCamp London +date: 2018-03-04 +excerpt: I wasn’t able to make it to DrupalCamp London, but here are some of the tweets that I saw. +tags: + - drupal + - drupalcamp + - drupalcamp-london +has_tweets: true +--- + +In the end, I wasn’t able to make it to DrupalCamp London because of the heavy +snow that’s hit the UK over the last few days. I did though keep a close eye on +Twitter and still had good conversations with some of the attendees, so it did +feel that in some ways I was still part of the conference. + +Thanks to [@ChandeepKhosa](https://twitter.com/ChandeepKhosa), +[@OrangePunchUK](https://twitter.com/OrangePunchUK), +[@hussainweb](https://twitter.com/hussainweb), +[@littlepixiez](https://twitter.com/littlepixiez), +[@cferthorney](https://twitter.com/cferthorney) and others for taking the time +to tweet whilst enjoying the event. + +Here are some of my favourites that I saw, and no snow next year, please! + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/source/_posts/uis-ive-rebuilt-tailwind-css.md b/source/_posts/uis-ive-rebuilt-tailwind-css.md new file mode 100644 index 000000000..044ab8d4f --- /dev/null +++ b/source/_posts/uis-ive-rebuilt-tailwind-css.md @@ -0,0 +1,22 @@ +--- +title: UIs that I've Rebuilt with Tailwind CSS +excerpt: A collection of all of the UIs that I've rebuilt using Tailwind CSS for talk demos etc. +tags: + - css + - tailwind-css +date: 2020-11-02 +--- + +Like Adam Wathan and other Tailwind CSS users, I've rebuilt a number of existing websites and user interfaces with [Tailwind CSS](https://tailwindcss.com), either for practice, to try out some new features, or for a demo for a talk where I like to have a relevant example for that audience. + +I thought that I'd list them all here, and keep the list up to date for future reference. Most of them are [tagged on GitHub](/tailwind-repos), though I have been using [Tailwind Play](https://play.tailwindcss.com) for the newest ones. + +Here is the list (last updated in November 2020): + +- [Bartik](https://rebuilding-bartik.oliverdavies.uk) (Drupal's default theme), built with Vue.js. I've also made a version using Alpine JS. +- [Acquia's hosting dashboard](https://rebuilding-acquia.oliverdavies.uk), built with Vue.js. +- [The WordPress 2019 theme](https://wp-tailwind.oliverdavies.uk), built for WordCamp Bristol 2019. +- [Symfony.com website](http://rebuilding-symfony.oliverdavies.uk), built for Leeds PHP meetup. +- [Bristol JS website](https://rebuilding-bristol-js.oliverdavies.uk), built with Vue.js for Bristol JS meetup. +- [Platform.sh's hosting dashboard](https://rebuilding-platformsh.oliverdavies.uk) - in progress. +- [Pantheon's hosting dashboard](/rebuilding-pantheon) - in progress. diff --git a/source/_posts/updating-features-adding-components-using-drush.md b/source/_posts/updating-features-adding-components-using-drush.md new file mode 100644 index 000000000..144da7e78 --- /dev/null +++ b/source/_posts/updating-features-adding-components-using-drush.md @@ -0,0 +1,74 @@ +--- +title: Updating Features and Adding Components Using Drush +date: 2014-10-21 +excerpt: How to update features on the command line using Drush. +tags: + - drupal + - drupal-planet + - drush + - features +--- + +If you use the [Features module](http://drupal.org/project/features) to manage +your Drupal configuration, it can be time consuming to update features through +the UI, especially if you are working on a remote server and need to keep +downloading and uploading files. + +If you re-create a feature through the UI, you'll be prompted to download a new +archive of the feature in its entirety onto your local computer. You could +either commit this into a local repository and then pull it remotely, or use a +tool such as SCP to upload the archive onto the server and commit it from there. +You can simplify this process by using [Drush](http://drush.org). + +## Finding Components + +To search for a component, use the `drush features-components` command. This +will display a list of all components on the site. As we're only interested in +components that haven't been exported yet, add the `--not-exported` option to +filter the results. + +To filter further, you can also use the `grep` command to filter the results. +For example, `drush features-components --not-exported field_base | grep foo`, +would only return non-exported field bases containing the word "foo". + +The result is a source and a component, separated by a colon. For example, +`field_base:field_foo`. + +## Exporting the Feature + +Once you have a list of the components that you need to add, you can export the +feature. This is done using the `drush features-export` command, along with the +feature name and the component names. + +For example: + +```bash +$ drush features-export -y myfeature field_base:field_foo field_instance:user-field_foo +``` + +In this example, the base for field_boo and it's instance on the user object is +being added to the "myfeature" feature. + +If you are updating an existing feature, you'll get a message informing you that +the module already exists and asking if you want to continue. This is fine, and +is automatically accepted by including `-y` within the command. If a feature +with the specified name doesn't exist, it will be created. + +If you're creating a new feature, you can define where the feature will be +created using the `--destination` option. + +Once complete, you will see a confirmation message. + +> Created module: my feature in sites/default/modules/custom/features/myfeature + +## The Result + +Once finished, the feature is updated in it's original location, so there's no +download of the feature and then needing to re-upload it. You can add and commit +your changes into Git or continue with your standard workflow straight away. + +## Useful Links + +- [The Features project page on Drupal.org](http://www.drupal.org/project/features) +- [The "drush features-components" command](http://www.drushcommands.com/drush-6x/features/features-components) +- [The "drush features-export" command](http://www.drushcommands.com/drush-6x/features/features-export) diff --git a/source/_posts/updating-forked-github-repos.md b/source/_posts/updating-forked-github-repos.md new file mode 100644 index 000000000..e4602b036 --- /dev/null +++ b/source/_posts/updating-forked-github-repos.md @@ -0,0 +1,121 @@ +--- +title: Updating Forked Repositories on GitHub +date: 2015-06-18 +excerpt: I just had to update a repo that I forked on GitHub. This is how I did it. Did I do it the correct way? +tags: + - git + - github + - phpstorm + - sculpin +--- + +I just had to update a repo that I forked on GitHub. This is how I did it. Did I +do it the correct way? + +## Sculpin + +People may or may not know, but this site runs on +[Sculpin](https://sculpin.io/), a PHP based static site generator (this may be +the first time that I've mentioned it on this site). The source code is hosted +on [GitHub](https://github.com/opdavies/oliverdavies.uk), and I've listed the +site on the [Community page](https://sculpin.io/community/) on the Sculpin +website. + +To get it there, I forked the +[main sculpin.io repository](https://github.com/sculpin/sculpin.io) so that I +had [my own copy](https://github.com/opdavies/sculpin.io), created a branch, +made my additions and submitted a pull request. Easy enough! + +## New Domain + +In the last week or so, I've changed this site URL from .co.uk to just .uk, and +also updated the GitHub repo URL to match, so I wanted to update the Community +page to use the correct URL. + +There had been commits to the main repo since my pull request was merged, I +didn't want to delete my repo and fork again, and making any changes against and +old codebase isn't best practice, so I wanted to merge the latest changes into +my forked repo before I did anything else - just to check that I didn't break +anything! + +## Updating my Local Repo + +I had a quick look for a _Update my fork_ button or something, but couldn't see +one to I added the main repository as an additional remote called `upstream` and +fetched the changes. + +```bash +$ git remote add upstream https://github.com/sculpin/sculpin.io.git + +$ git fetch upstream +remote: Counting objects: 33, done. +remote: Total 33 (delta 6), reused 6 (delta 6), pack-reused 27 +Unpacking objects: 100% (33/33), done. +From https://github.com/sculpin/sculpin.io +* [new branch] master -> upstream/master +* [new branch] pr/4 -> upstream/pr/4 +``` + +Now my local site knows about the upstream repo, and I could rebase the changes +(`git pull upstream master` should have worked too) and push them back to +origin. + +```bash +$ git rebase upstream/master +First, rewinding head to replay your work on top of it... +... +Fast-forwarded master to upstream/master. + +$ git push origin master +``` + +This seems to have worked OK - the commits are still authored by the correct +people and at the correct date and time - and I went ahead and created a new +feature branch and pull request based on that master branch. + +{% include 'figure' with { + image: { + src: '/images/blog/forked-github-repo-commits.png', + alt: 'The commits on my master branch after rebasing', + }, + caption: 'The commits on my forked master branch after rebasing and pushing. All good!', +} %} + +{% include 'figure' with { + image: { + src: '/images/blog/my-commit-to-the-rebased-branch.png', + alt: 'The new feature branch with my additional commit', + }, + caption: 'The new feature branch with the new commit.', +} %} + +## Is There a Better Way? + +Did I miss something? Is there a recommended and/or better way to update your +forked repos, maybe through the UI? Please +send +me a tweet with any comments. + +## Up + +**December 2015:** I’ve found that PhpStorm has an option available to rebase a +fork from within the IDE. This is within the _VCS_ > _Git_ menu. + +I believe that it will use an existing "upstream" remote if it exists, otherwise +it will add one automatically for you, linking to the repository that you forked +from. + +Once you’ve completed the rebase, you can then push your updated branch either +from the terminal, or using the _Push_ command from the same menu. + +![Rebasing a forked repository in PhpStorm using the VCS menu.](/images/blog/github-fork-rebase-phpstorm.png) + +It would be great to see something similar added to +[hub](https://hub.github.com) too (I’ve created +[an issue](https://github.com/github/hub/issues/1047))! + +## Resources + +- [PhpStorm - Advanced GitHub Integration: Rebase My GitHub Fork (blog post)](http://blog.jetbrains.com/idea/2011/02/advanced-github-integration-rebase-my-github-fork/) +- [Rebasing a GitHub fork inside PhpStorm (video)](https://www.youtube.com/watch?v=Twy-dhVgN4k) +- [hub](https://hub.github.com) - makes Git better with GitHub diff --git a/source/_posts/updating-override-node-options-tests.md b/source/_posts/updating-override-node-options-tests.md new file mode 100644 index 000000000..dbf62193d --- /dev/null +++ b/source/_posts/updating-override-node-options-tests.md @@ -0,0 +1,239 @@ +--- +title: Updating Override Node Options Tests +date: 2017-05-05 +excerpt: ~ +tags: + - drupal + - drupal-modules + - drupal-planet + - testing +draft: true +--- + +Recently, I reviewed [a patch][1] in the [Override Node Options][2] module issue +queue. For those not familiar with it, the module adds extra permissions for +node options like "authored by" and "published on" which are normally only +available to users with the `administer nodes` permission. What the patch does +is to optionally add another set of permissions that enable options for all +content types - e.g. "override published option for all node types", in addition +to or instead of the content type specific ones. + +It was quite an old issue and the latest patch needed to be re-rolled due to +merge conflicts, but the existing tests still passed. Though as no new tests +were added for the new functionality, these needed to be added before I +committed it. + +## Reviewing the Existing Tests + +The first thing to do was to run the existing tests and check that they still +passed. I do this on the command line by typing +`php scripts/run-tests.sh --class OverrideNodeOptionsTestCase`. + +``` +Drupal test run +--------------- + +Tests to be run: + - Override node options (OverrideNodeOptionsTestCase) + +Test run started: + Saturday, April 29, 2017 - 14:44 + +Test summary +------------ + +Override node options 142 passes, 0 fails, 0 exceptions, and 38 debug messages + +Test run duration: 32 sec +``` + +After confirming that the existing tests still passed, I reviewed them to see +what could be re-used. + +This is one of the original tests: + +```php +/** + * Test the 'Authoring information' fieldset. + */ +protected function testNodeOptions() { + $this->adminUser = $this->drupalCreateUser(array( + 'create page content', + 'edit any page content', + 'override page published option', + 'override page promote to front page option', + 'override page sticky option', + 'override page comment setting option', + )); + $this->drupalLogin($this->adminUser); + + $fields = array( + 'status' => (bool) !$this->node->status, + 'promote' => (bool) !$this->node->promote, + 'sticky' => (bool) !$this->node->sticky, + 'comment' => COMMENT_NODE_OPEN, + ); + $this->drupalPost('node/' . $this->node->nid . '/edit', $fields, t('Save')); + $this->assertNodeFieldsUpdated($this->node, $fields); + + $this->drupalLogin($this->normalUser); + $this->assertNodeFieldsNoAccess($this->node, array_keys($fields)); +} +``` + +The first part of the test is creating and logging in a user with some content +type specific override permissions (`$this->adminUser`), and then testing that +the fields were updated when the node is saved. The second part is testing that +the fields are not visible for a normal user without the extra permissions +(`$this->normalUser`), which is created in the `setUp()` class' method. + +To test the new "all types" permissions, I created another user to test against +called `$generalUser` and run the first part of the tests in a loop. + +## Beginning to Refactor the Tests + +With the tests passing, I was able to start refactoring. + +```php +// Create a new user with content type specific permissions. +$specificUser = $this->drupalCreateUser(array( + 'create page content', + 'edit any page content', + 'override page published option', + 'override page promote to front page option', + 'override page sticky option', + 'override page comment setting option', +)); + +foreach (array($specificUser) as $account) { + $this->drupalLogin($account); + + // Test all the things. + ... +} +``` + +I started with a small change, renaming `$this->adminUser` to `$specificUser` to +make it clearer what permissions it had, and moving the tests into a loop so +that the tests can be repeated for both users. + +After that change, I ran the tests again to check that everything still worked. + +## Adding Failing Tests + +The next step is to start testing the new permissions. + +```php +... + +$generalUser = $this->drupalCreateUser(array()); + +foreach (array($specificUser, $generalUser) as $account) { + $this->drupalLogin($account); + + // Test all the things. +} +``` + +I added a new `$generalUser` to test the general permissions and added to the +loop, but in order to see the tests failing intially I assigned it no +permissions. When running the tests again, 6 tests have failed. + +``` +Test summary +------------ + +Override node options 183 passes, 6 fails, 0 exceptions, and 49 debug messages + +Test run duration: 28 sec +``` + +Then it was a case of re-adding more permissions to the user and seeing the +number of failures decrease, confirming that the functionality was working +correctly. + +TODO: Add another example. + +## Gotchas + +There was a bug that I found where a permission was added, but wasn't used +within the implementation code. After initially expecting the test to pass after +adding the permission to `$generalUser` and the test still failed, I noticed +that the + +This was fixed by adding the extra code into `override_node_options.module`. + +```diff +- $form['comment_settings']['#access'] |= user_access('override ' . $node->type . ' comment setting option'); ++ $form['comment_settings']['#access'] |= user_access('override ' . $node->type . ' comment setting option') || user_access('override all comment setting option'); +``` + +The other issue that I found was within `testNodeRevisions`. +`assertNodeFieldsUpdated()` was failing after being put in a loop as the `vid` +was not the same as what was expected. + +Note: You can get more verbose output from `run-tests.sh` by adding the +`--verbose` option. + +> Node vid was updated to '3', expected 2. + +```diff +- $fields = array( +- 'revision' => TRUE, +- ); +- $this->drupalPost('node/' . $this->node->nid . '/edit', $fields, t('Save')); +- $this->assertNodeFieldsUpdated($this->node, array('vid' => $this->node->vid + 1)); ++ $generalUser = $this->drupalCreateUser(array( ++ 'create page content', ++ 'edit any page content', ++ 'override all revision option', ++ )); ++ ++ foreach (array($specificUser, $generalUser) as $account) { ++ $this->drupalLogin($account); ++ ++ // Ensure that we have the latest node data. ++ $node = node_load($this->node->nid, NULL, TRUE); ++ ++ $fields = array( ++ 'revision' => TRUE, ++ ); ++ $this->drupalPost('node/' . $node->nid . '/edit', $fields, t('Save')); ++ $this->assertNodeFieldsUpdated($node, array('vid' => $node->vid + 1)); ++ } +``` + +The crucial part of this change was the addition of +`$node = node_load($this->node->nid, NULL, TRUE);` to ensure that the latest +version of the node was loaded during each loop. + +## Conclusion + +- Ensure that the existing tests were passing before starting to refactor. +- Start with small changes and continue to run the tests to ensure that nothing + has broken. +- After the first change, I committed it as `WIP: Refactoring tests`, and used + `git commit --amend --no-edit` to amend that commit each time I had refactored + another test. After the last refactor, I updated the commit message. +- It’s important to see tests failing before making them pass. This was achieved + by initially assigning no permissions to `$generalUser` so that the fails + failed and then added permissions and re-run the tests to ensure that the + failure count decreased with each new permission. + +With the refactoring complete, the number of passing tests increased from 142 +to 213. + +``` +Override node options 213 passes, 0 fails, 0 exceptions, and 60 debug messages + +Test run duration: 25 sec +``` + + + +[Here][3] are my full changes from the previous patch, where I added the new +tests as well as some small refactors. + +[1]: https://www.drupal.org/node/974730 +[2]: https://www.drupal.org/project/override_node_options +[3]: https://www.drupal.org/files/issues/interdiff_25712.txt diff --git a/source/_posts/upgrading-dransible-project-drupal-9.md b/source/_posts/upgrading-dransible-project-drupal-9.md new file mode 100644 index 000000000..33e7d3527 --- /dev/null +++ b/source/_posts/upgrading-dransible-project-drupal-9.md @@ -0,0 +1,51 @@ +--- +title: Upgrading the Dransible project to Drupal 9 +excerpt: How I recently upgraded the Dransible example project from Drupal 8.8 to 9.0. +tags: + - composer + - dransible + - drupal + - drupal-9 + - drupal-planet + - php +date: 2020-09-05 +--- + +This week I gave [a new talk on upgrading to Drupal 9](/talks/upgrading-your-site-drupal-9) for the Drupal NYC meetup. Whilst preparing for that, I decided to upgrade my [Dransible example project](https://github.com/opdavies/dransible) that I use for my [Ansible and Ansistrano talk](/talks/deploying-php-ansible-ansistrano) to Drupal 9 and document the process. + +Whilst the steps taken are in the slides for that talk, here is the full list of steps that I took including the Composer commands. + +## Updating from Drupal 8.8 to 8.9 { #updating-from-drupal-88-to-89 } + +To begin with, let's update to the latest version of Drupal 8 so that we can do some testing and see all of the latest deprecation notices before moving to Drupal 9. + +1. Remove Drush temporarily using `composer remove drush/drush` as it will cause us being stuck on Drupal 8.9.0-beta2 rather than a newer, stable 8.9 version. +1. Update `^8.8` to `^8.9` in composer.json for `drupal/core-recommended`, `drupal/core-dev` and `drupal/core-composer-scaffold`, and run `composer update drupal/core-* --with-dependencies` to update core to 8.9.5. +1. Re-add Drush so that it's present for the deployment by running `composer require drush/drush:^9`. + +## Preparing for Drupal 9 {#preparing-drupal-9} + +1. Add the [Upgrade Status module](https://www.drupal.org/project/upgrade_status) by running `composer require drupal/upgrade_status`. +1. Upgrade to Drush 10 by running `composer require drush/drush:^10`. +1. Remove the [Config Installer module](https://www.drupal.org/project/config_installer) by running `composer remove drupal/config_installer`. This is no longer needed since Drupal 8.6, and there will be no Drupal 9 version. +1. Update the [Admin Toolbar module](https://www.drupal.org/project/admin_toolbar) to 2.3, a Drupal 9 compatible version, by running `composer update drupal/admin_toolbar`. + +As I'd previously updated the Simple Message custom module to be Drupal 9 compatible (adding the `core_version_requirement` key to the info.yml file, and removing usages of deprecated code), no changes needed to be made to that. + +## Upgrading to Drupal 9 {#upgrading-drupal-9} + +1. Update `^8.9` to `^9.0` for the core packages in composer.json, and run `composer update drupal/core-* --with-dependencies` to update to 9.0.5. +1. Re-add Drush by running `composer require drush/drush`. This will install Drush 10 by default. + +## Post upgrade {#post-upgrade} + +Although everything seemed to have updated OK locally, there were some errors when running a deployment to the Vagrant virtual machine that needed to be addressed, as well as some post-upgrade housekeeping steps to perform. + +1. Fix the deployment error by adding the [Symfony Configuration component](https://symfony.com/components/Config) as a dependency by running `composer require symfony/config:^4`. +1. Alias `Drupal\Core\Messenger\MessengerInterface` to `messenger` in `simple_message.services.yml` to fix the autowiring error. +1. Add `settings["config_sync_directory"]` to settings file variables (this will be added automatically in the next version of the [Drupal settings Ansible role](https://github.com/opdavies/ansible-role-drupal-settings)). +1. Remove the Upgrade Status module by running `composer remove drupal/upgrade_status`, as it's no longer needed. + +And that's it! The Dransible demo project is upgraded, and if you see my Ansible deployments talk in the future, the demo site will be running on Drupal 9. + +If you want to see the original pull request, it's at . diff --git a/source/_posts/use-authorized-keys-create-passwordless-ssh-connection.md b/source/_posts/use-authorized-keys-create-passwordless-ssh-connection.md new file mode 100644 index 000000000..eda31b022 --- /dev/null +++ b/source/_posts/use-authorized-keys-create-passwordless-ssh-connection.md @@ -0,0 +1,33 @@ +--- +title: How to use Authorized Keys to Create a Passwordless SSH Connection +date: 2012-02-01 +excerpt: How to generate a SSH key, and how to use to log in to a server using SSH without entering a password. +tags: + - linux + - ssh +--- + +If you're accessing Linux servers or automating tasks between servers, rather +than having to enter your user password every time, you can also use SSH public +key authentication. This is a simple process that involves creating a local key +and storing it within the _authorized_keys_ file on the remote server. + +1. Check if you already have a SSH key. `$ ssh-add -L` +1. If you don't have one, create one. `$ ssh-keygen` +1. Upload the key onto the server. Replace _myserver_ with the hostname or IP + address of your remote server. `$ ssh-copy-id myserver` + +If you're using Mac OS X and you don't have ssh-copy-id installed, download and +install [Homebrew](http://mxcl.github.com/homebrew 'Homebrew') and run the +`brew install ssh-copy-id` command. + +If successful, you should now see a message like: + +> Now try logging into the machine, with "ssh 'myserver'", and check in: +> +> ~/.ssh/authorized_keys +> +> to make sure we haven't added extra keys that you weren't expecting. + +Now the next time that you SSH onto the server, it should log you in without +prompting you for your password. diff --git a/source/_posts/use-regular-expressions-search-replace-coda-or-textmate.md b/source/_posts/use-regular-expressions-search-replace-coda-or-textmate.md new file mode 100644 index 000000000..eaacc8f33 --- /dev/null +++ b/source/_posts/use-regular-expressions-search-replace-coda-or-textmate.md @@ -0,0 +1,57 @@ +--- +title: Use Regular Expressions to Search and Replace in Coda or TextMate +date: 2010-11-04 +excerpt: How to perform searches using regular expressions. +tags: + - coda + - database + - regular-expression + - sequel-pro + - taxonomy + - textmate +--- + +As in +[the original post](/blog/add-taxonomy-term-multiple-nodes-using-sql/ 'Quickly adding a taxonomy term to multiple nodes using SQL'), +I'd generated a list of node ID values, and needed to add structure the SQL +update statment formatted in a certain way. However, I changed my inital query +slightly to out put the same nid value twice. + +```sql +SELECT nid, nid FROM node WHERE TYPE = 'blog' ORDER BY nid ASC; +``` + +Then, I could select all of the returned rows, copy the values, and paste them +into Coda: + +As before, I needed my SQL update statement to be in the following format: + +```sql +INSERT INTO term_node VALUE (nid, vid, tid), (nid2, vid2, tid); +``` + +As I mentioned previously, the nid and vid values are the same for each node, +and the tid will remain constant. In this case, the tid value that I needed to +use was '63'. + +So, using the 'Find and Replace' function within Coda, combined with +[regular expressions](http://en.wikipedia.org/wiki/Regular_expression) (regex), +I can easily format the values as needed. To begin with, I need to ensure that +the RegEx search option is enabled, and that I'm using the correct escape +character. + +The first thing that I wanted to do was add the seperating comma between the two +values. To do this, I perform a search for `\s*\t`. This searches for everything +that is whitespace AND is a tab value. I can then add the comma as the +replacement for each result. + +All 31 lines have been changed. + +Next, I can use `\n` to target the lines between the rows. I'll replace it with +the next comma, the number 63 (the tid value), the closing bracket, another +comma, re-add the line and add the opening bracket. + +The only two lines that aren't changed are the first and last, as they don't +have any line breaks following them. I can complete these lines manually. Now +all I need to do is add the beginning of the SQL update statement, then copy and +paste it into Sequel Pro. diff --git a/source/_posts/use-sass-and-compass-drupal-7-using-sassy.md b/source/_posts/use-sass-and-compass-drupal-7-using-sassy.md new file mode 100644 index 000000000..2ebae84fc --- /dev/null +++ b/source/_posts/use-sass-and-compass-drupal-7-using-sassy.md @@ -0,0 +1,79 @@ +--- +title: How to use SASS and Compass in Drupal 7 using Sassy +date: 2012-12-06 +excerpt: Use PHPSass and the Sassy module to use Sass and Compass in your Drupal theme. +tags: + - compass + - css + - drupal + - drupal-7 + - drupal-planet + - less + - preprocessing + - sass +--- + +I've recently started using [SASS](http://sass-lang.com) rather than LESS to do +my CSS preprocessing - namely due to its integration with +[Compass](http://compass-style.org) and it's built-in CSS3 mixins. Here are +three modules that provide the ability to use SASS within Drupal. + +- [Sassy](http://drupal.org/project/sassy 'Sassy module on drupal.org') +- [Prepro](http://drupal.org/project/prepro 'Prepro module on drupal.org') +- [Libraries API](http://drupal.org/project/libraries 'Libraries API module on drupal.org') + +Alternatively, you could use a base theme like +[Sasson](http://drupal.org/project/sasson 'Sasson theme on drupal.org') that +includes a SASS compiler. + +## Download the PHPSass Library + +The first thing to do is download the PHPSass library from +[GitHub](https://github.com/richthegeek/phpsass 'PHPSass on GitHub'), as this is +a requirement of the Sassy module and we can't enable it without the library. +So, in a Terminal window: + +```bash +$ mkdir -p sites/all/libraries; +$ cd sites/all/libraries; +$ wget https://github.com/richthegeek/phpsass/archive/master.tar.gz; +$ tar zxf master.tar.gz; +$ rm master.tar.gz; +$ mv phpsass-master/ phpsass +``` + +Or, if you're using Drush Make files: + +```ini +libraries[phpsass][download][type] = "get" +libraries[phpsass][download][url] = "https://github.com/richthegeek/phpsass/archive/master.tar.gz" +``` + +The PHPSass library should now be located at `sites/all/libraries/phpsass`. + +## Download and enable the Drupal modules + +This is easy if you use [Drush](http://drupal.org/project/drush): + +```bash +$ drush dl libraries prepro sassy +$ drush en -y libraries prepro sassy sassy_compass +``` + +Otherwise, download the each module from it's respective project page and place +it within your `sites/all/modules` or `sites/all/modules/contrib` directory. + +## Configuring the Prepro module + +The Prepro module provides various settings that can be changed for each +preprocessor. Go to `admin/config/media/prepro` to configure the module as +required. + +Personally, in development, I'd set caching to 'uncached' and the error +reporting method to 'show on page'. In production, I'd change these to "cached" +and "watchdog" respectively. I'd also set the output style to "compressed", + +## Adding SASS files into your theme + +With this done, you can now add SASS and SCSS files by adding a line like +`stylesheets[all][] = css/base.scss` in your theme's .info file. diff --git a/source/_posts/useful-drupal-6-modules.md b/source/_posts/useful-drupal-6-modules.md new file mode 100644 index 000000000..1b482bd72 --- /dev/null +++ b/source/_posts/useful-drupal-6-modules.md @@ -0,0 +1,57 @@ +--- +title: 10 Useful Drupal 6 Modules +date: 2010-06-25 +excerpt: A list of 10 contributed modules that I currently use on each Drupal project. +tags: + - drupal + - drupal-6 + - drupal-modules + - drupal-planet +--- + +Aside from the obvious candidates such as Views, CCK etc, here are a list of 10 +contributed modules that I currently use on each Drupal project. + +So, in no particular order: + +- **[Admin](http://drupal.org/project/admin):**
The admin module provides UI + improvements to the standard Drupal admin interface. I've just upgraded to the + new [6.x-2.0-beta4](http://drupal.org/node/835870) version, and installed the + newly-required + [Rubik](http://code.developmentseed.org/rubik)/[Tao](http://code.developmentseed.org/tao) + themes. So far, so good! +- **[Better Permissions](http://drupal.org/project/better_perms)/[Filter Permissions](http://drupal.org/project/filter_perms): +
**Basic permissions is a basic module which enhances the Drupal + Permissions page to support collapsing and expanding permission rows. Filter + permissions provides filters at the top of the Permissions page for easier + management when your site has a large amount of roles and/or permissions. +- **[Better Formats](http://drupal.org/project/better_formats):
**Better + formats is a module to add more flexibility to Drupal's core input format + system. +- **[Clone module](http://drupal.org/project/node_clone):**
Allows users to + make a copy of an existing item of site content (a node) and then edit that + copy. +- **[Vertical Tabs](http://drupal.org/project/vertical_tabs):
**Integrated + into Drupal 7 core, this module adds vertical tabs to the node add and edit + forms. +- **[Context](http://drupal.org/project/context):
**Context allows you to + manage contextual conditions and reactions for different portions of your + site. You can think of each context as representing a "section" of your site. + For each context, you can choose the conditions that trigger this context to + be active and choose different aspects of Drupal that should react to this + active context. +- **[Node Picker](http://drupal.org/project/nodepicker):**
A rewrite of the + module [TinyMCE Node Picker](http://drupal.org/project/tinymce_node_picker). + Allows you to easily create links to internal nodes. +- **[Module Filter](http://drupal.org/project/module_filter):**
What this + module aims to accomplish is the ability to quickly find the module you are + looking for without having to rely on the browsers search feature which more + times than not shows you the module name in the 'Required by' or 'Depends on' + sections of the various modules or even some other location on the page like a + menu item. +- **[Zenophile](http://drupal.org/project/zenophile):**
Quickly create Zen + subthemes. +- **[Add Another](http://drupal.org/project/addanother):**
Add another + displays a message after a user creates a node, and/or displays an "Add + another" tab on nodes allowing them to make another node of the same type. You + can control what roles and node types see this feature. diff --git a/source/_posts/useful-vagrant-commands.md b/source/_posts/useful-vagrant-commands.md new file mode 100644 index 000000000..0e63a7e70 --- /dev/null +++ b/source/_posts/useful-vagrant-commands.md @@ -0,0 +1,24 @@ +--- +title: Useful Vagrant Commands +date: 2013-11-27 +excerpt: Here are the basic commands that you need to adminster a virtual machine using Vagrant. +tags: + - vagrant +--- + +[Vagrant](http://www.vagrantup.com 'About Vagrant') is a tool for managing +virtual machines within [VirtualBox](https://www.virtualbox.org) from the +command line. Here are some useful commands to know when using Vagrant. + +| Command | Description | +| :--------------------------- | :----------------------------------------------------------------------------------------------------------- | +| vagrant init {box} | Initialise a new VM in the current working directory. Specify a box name, or "base" will be used by default. | +| vagrant status | Shows the status of the Vagrant box(es) within the current working directory tree. | +| vagrant up (--provision) | Boots the Vagrant box. Including "–provision" also runs the "vagrant provision" command. | +| vagrant reload (--provision) | Reloads the Vagrant box. Including "--provision" also runs the "vagrant provision" command. | +| vagrant provision | Provision the Vagrant box using Puppet. | +| vagrant suspend | Suspend the Vagrant box. Use "vagrant up" to start the box again. | +| vagrant halt (-f) | Halt the Vagrant box. Use -f to forcefully shut down the box without prompting for confirmation. | +| vagrant destroy (-f) | Destroys a Vagrant box. Use -f to forcefully shut down the box without prompting for confirmation. | + +The full Vagrant documentation can be found at . diff --git a/source/_posts/using-feature-flags-in-drupal-development.md b/source/_posts/using-feature-flags-in-drupal-development.md new file mode 100644 index 000000000..b57ac762c --- /dev/null +++ b/source/_posts/using-feature-flags-in-drupal-development.md @@ -0,0 +1,13 @@ +--- +title: Using feature flags in Drupal development +excerpt: Different ways of using feature flags witin Drupal development +date: 2020-03-31 +tags: + - drupal + - drupal-7 + - drupal-8 + - php +draft: true +--- + +TODO. diff --git a/source/_posts/using-imagecache-and-imagecrop-my-portfolio.md b/source/_posts/using-imagecache-and-imagecrop-my-portfolio.md new file mode 100644 index 000000000..422901aa4 --- /dev/null +++ b/source/_posts/using-imagecache-and-imagecrop-my-portfolio.md @@ -0,0 +1,44 @@ +--- +title: Using ImageCache and ImageCrop for my Portfolio +date: 2010-04-28 +excerpt: How to create thumbnail images using the ImageCache and ImageCrop modules. +tags: + - drupal + - drupal-6 + - cck + - imagecache + - imagecrop + - imagefield +--- + +Whilst working on my own portfolio/testimonial website, I decided to have a +portfolio page displaying the name of each site and a thumbnail image. For this +Blog post, I'll be using a site called +[Popcorn Strips](http://popcornstrips.com) which I built for a friend earlier +this year as an example. + +I created a content type called 'Project' with a CCK ImageField called +'Screenshot'. I created a project called +[Popcorn Strips](http://popcornstrips.com), used the +[ScreenGrab](https://addons.mozilla.org/addon/1146) add-on for Mozilla Firefox +to take a screenshot of the website, and uploaded it to the project node. + +I created a View to display the published projects, and an ImageCache preset to +create the thumbnail image by scaling and cropping the image to a size of +200x100 pixels. + +Although, this automatically focused the crop on the centre of the image, +whereas I wanted to crop from the top and left of the image - showing the site's +logo and header. + +I installed the [ImageCrop](http://drupal.org/project/imagecrop) module, which +add a jQuery crop function to the standard ImageCache presents. I removed the +original Scale and Crop action and replaced it with a Scale action with a width +of 200px. + +I then added a new 'Javascript crop' action with the following settings: + +- Width: 200px +- Height: 100px +- xoffset: Left +- yoffset: Top diff --git a/source/_posts/using-laravel-collections-drupal.md b/source/_posts/using-laravel-collections-drupal.md new file mode 100644 index 000000000..e3345206d --- /dev/null +++ b/source/_posts/using-laravel-collections-drupal.md @@ -0,0 +1,71 @@ +--- +title: Examples of using Laravel Collections in Drupal +date: 2018-08-23 +excerpt: Some examples of using Laravel’s Illuminate Collections within Drupal projects. +tags: + - drupal + - drupal-7 + - drupal-8 + - drupal-planet + - laravel + - laravel-collections + - php +has_tweets: true +--- + +Since starting to work with Laravel as well as Drupal and Symfony, watching Adam +Wathan’s [Refactoring to Collections][0] course as well as [lessons on +Laracasts][6], I’ve become a fan of [Laravel’s Illuminate Collections][1] and +the object-orientated pipeline approach for interacting with PHP arrays. + +In fact I’ve given a talk on [using Collections outside Laravel][2] and have +written a [Collection class module][3] for Drupal 7. + +I’ve also tweeted several examples of code that I’ve written within Drupal that +use Collections, and I thought it would be good to collate them all here for +reference. + +Thanks again to [Tighten][4] for releasing and maintaining the +[tightenco/collect library][5] that makes it possible to pull in Collections via +Composer. + +
+ {% include 'tweet' with { + class: 'block mb-4 lg:w-1/2 lg:px-2 lg:mb-0', + data_cards: true, + content: '

Putting @laravelphp's Collection class to good use, cleaning up some of my @drupal 8 code. Thanks @TightenCo for the Collect library! pic.twitter.com/Bn1UfudGvp

— Oliver Davies (@opdavies) August 18, 2017', + } %} + + {% include 'tweet' with { + class: 'block mb-4 lg:w-1/2 lg:px-2 lg:mb-0', + data_cards: true, + content: '

Putting more @laravelphp Collections to work in my @drupal code today. 😁 pic.twitter.com/H8xDTT063X

— Oliver Davies (@opdavies) February 14, 2018', + } %} + + {% include 'tweet' with { + class: 'block mb-4 lg:w-1/2 lg:px-2 lg:mb-0', + data_cards: true, + content: '

I knew that you could specify a property like 'price' in Twig and it would also look for methods like 'getPrice()', but I didn't know (or had maybe forgotten) that @laravelphp Collections does it too.

This means that these two Collections return the same result.

Nice! 😎 pic.twitter.com/2g2IfThzdy

— Oliver Davies (@opdavies) June 20, 2018', + } %} + + {% include 'tweet' with { + class: 'block mb-4 lg:w-1/2 lg:px-2 lg:mb-0', + data_cards: true, + content: '

More @laravelphp Collection goodness, within my #Drupal8 project! pic.twitter.com/mWgpNbNIrh

— Oliver Davies (@opdavies) August 10, 2018', + } %} + + {% include 'tweet' with { + class: 'block mb-4 lg:w-1/2 lg:px-2 lg:mb-0', + data_cards: true, + content: '

Some more #Drupal 8 fun with Laravel Collections. Loading the tags for a post and generating a formatted string of tweetable hashtags. pic.twitter.com/GbyiRPzIRo

— Oliver Davies (@opdavies) August 23, 2018', + } %} + +
+ +[0]: https://adamwathan.me/refactoring-to-collections +[1]: https://laravel.com/docs/collections +[2]: /talks/using-laravel-collections-outside-laravel +[3]: https://www.drupal.org/project/collection_class +[4]: https://tighten.co +[5]: https://packagist.org/packages/tightenco/collect +[6]: https://laracasts.com/series/how-do-i/episodes/18 diff --git a/source/_posts/using-pcss-extension-postcss-webpack-encore.md b/source/_posts/using-pcss-extension-postcss-webpack-encore.md new file mode 100644 index 000000000..9033e5dc3 --- /dev/null +++ b/source/_posts/using-pcss-extension-postcss-webpack-encore.md @@ -0,0 +1,94 @@ +--- +title: Using the pcss extension for PostCSS with Webpack Encore +excerpt: How to use the .pcss file extension for PostCSS files with Webpack Encore. +date: 2020-04-01 +tags: + - encore + - postcss + - symfony + - webpack +--- + +I’ve been watching Christopher Pitt ([assertchris][assertchris-twitter])’s [streams on Twitch][assertchris-twitch] over the last few months, in one of which he was doing some work with Tailwind CSS and using a `.pcss` file extension for his PostCSS files. + +I couldn’t remember seeing this extension before, but this made a lot of sense to me compared to the standard `.css` extension - both to make it clear that it’s a PostCSS file and features like nesting can be used, and also for better integration and highlighting with IDEs and text editors such as PhpStorm. + +It’s also shorter that the `.postcss` extension, and has been suggested by [@PostCSS on Twitter](https://twitter.com/PostCSS/status/661645290622083073) previously. + +Some of my projects use [Laravel Mix][] which support this extension by default, but some of them use Symfony’s [Webpack Encore][] which didn’t seem to, so I decided to look into it. (Note that both are agnostic and not coupled to their respective frameworks, so can be used with other projects too including Drupal and Sculpin). + +## Updating Webpack Encore’s configuration + +I was able to review the existing configuration and confirm this by using `console.log()` to output Encore’s generated webpack configuration - specifically the module rules. + +```js +console.log(Encore.getWebpackConfig().module.rules) +``` + +There I can see the the test for PostCSS supports the `.css` and `.postcss` file extensions, but not `.pcss`. + +``` +test: /\.(css|postcss)$/, +``` + +There is documentation on the Symfony website for [adding custom webpack loaders and plugins](https://symfony.com/doc/current/frontend/encore/custom-loaders-plugins.html) but this wasn’t quite what I needed, as I needed to edit the existing `css` loader rather than add a new one. + +The page that I needed was [Advanced Webpack Config](https://symfony.com/doc/current/frontend/encore/advanced-config.html#having-the-full-control-on-loaders-rules) - specifically the section on 'Having the full control on Loaders Rules'. + +This suggests using `.configureLoaderRule()` and using that to override `test` directly. + +It does though come with a warning: + +> This is a low-level method. All your modifications will be applied just before pushing the loaders rules to Webpack. It means that you can override the default configuration provided by Encore, which may break things. Be careful when using it. + +My first pass was to add the full `.pcss` extension, but as this is a regular expression, I did a second pass that adds an second capturing group that would cover both PostCSS extensions. + +``` +// First pass +loaderRule.test = /\.(css|pcss|postcss)$/ + +// Second pass +loaderRule.test = /\.(css|p(ost)?css)$/ +``` + +To see this running, go to . + +## The final configuration + +This is my full `webpack.config.js` file for this site, including the `.pcss` extension support: + +```js +Encore + .disableSingleRuntimeChunk() + .cleanupOutputBeforeBuild() + .setOutputPath('source/build/') + .setPublicPath('/build') + .addEntry('app', './assets/js/app.js') + .enablePostCssLoader() + .configureLoaderRule('css', loaderRule => { + loaderRule.test = /\.(css|p(ost)?css)$/ + }) + .enableSourceMaps(!Encore.isProduction()) + +if (Encore.isProduction()) { + Encore + .enableVersioning() + .addPlugin(new PurgecssPlugin(purgecssConfig)) +} else { + Encore.enableSourceMaps() +} + +module.exports = Encore.getWebpackConfig() +``` + +Alternatively, you can view it in the [codebase on GitHub](https://github.com/opdavies/oliverdavies.uk/blob/796578d7f0f3332724cb8335982c69b36bc11e53/webpack.config.js). + +## Contributing back to Encore + +I’ve also submitted a pull request to Encore to add support for the `.pcss` extension by default: . If accepted, then these changes in `webpack.config.js` would no longer be needed. + +[assertchris-twitch]: https://www.twitch.tv/assertchris "assertchris on Twitch" +[assertchris-twitter]: https://twitter.com/assertchris "assertchris on Twitter" +[gitstore]: https://gitstore.app +[laravel mix]: https://laravel-mix.com +[webpack encore]: https://github.com/symfony/webpack-encore diff --git a/source/_posts/using-remote-files-when-developing-locally-stage-file-proxy-module.md b/source/_posts/using-remote-files-when-developing-locally-stage-file-proxy-module.md new file mode 100644 index 000000000..084212cfb --- /dev/null +++ b/source/_posts/using-remote-files-when-developing-locally-stage-file-proxy-module.md @@ -0,0 +1,37 @@ +--- +title: Using Remote Files when Developing Locally with Stage File Proxy Module +date: 2014-11-20 +excerpt: How to install and configure the Stage File Proxy module to serve remote images on your local Drupal site. +tags: + - drupal + - drupal-planet + - servers +--- + +How to install and configure the +[Stage File Proxy](https://www.drupal.org/project/stage_file_proxy) module to +serve remote images on your local Drupal site. + +As this module is only going to be needed on pre-production sites, it would be +better to configure this within your settings.php or settings.local.php file. We +do this using the `$conf` array which removes the need to configure the module +through the UI and store the values in the database. + +```php +// File proxy to the live site. +$conf['stage_file_proxy_origin'] = 'http://www.example.com'; + +// Don't copy the files, just link to them. +$conf['stage_file_proxy_hotlink'] = TRUE; + +// Image style images are the wrong size otherwise. +$conf['stage_file_proxy_use_imagecache_root'] = FALSE; +``` + +If the origin site is not publicly accessible yet, maybe it's a pre-live or +staging site, and protected with a basic access authentication, you can include +the username and password within the origin URL. + +```php +$conf['stage_file_proxy_origin'] = 'http://user:password@prelive.example.com'; +``` diff --git a/source/_posts/using-tailwind-css-your-drupal-theme.md b/source/_posts/using-tailwind-css-your-drupal-theme.md new file mode 100644 index 000000000..2a81d89c1 --- /dev/null +++ b/source/_posts/using-tailwind-css-your-drupal-theme.md @@ -0,0 +1,112 @@ +--- +title: Using Tailwind CSS in your Drupal Theme +date: 2018-02-05 +excerpt: What is Tailwind CSS, and how do I use it in Drupal theme? +tags: + - drupal + - drupal-planet + - drupal-theming + - tailwind-css +--- + +## What is Tailwind? + +> Tailwind is a utility-first CSS framework for rapidly building custom user +> interfaces. + +It generates a number of utility classes that you can add to your theme's markup +to apply different styling, as well as the ability to apply classes to other +markup and create components comprised of utility classes using a custom +`@apply` PostCSS directive. + +## Initial Configuration + +The installation and configuration steps are essentially the same as those +outlined within the [Tailwind documentation][1], and should be performed within +your custom theme's directory (e.g. `sites/all/themes/custom/mytheme` for Drupal +7 or `themes/custom/mytheme` for Drupal 8: + +1. Require PostCSS and Tailwind via `npm` or `yarn`. +1. Generate a configuration file using `./node_modules/.bin/tailwind init`. +1. Tweak the settings as needed. +1. Add a `postcss.config.js` file. +1. Configure your build tool (Gulp, Grunt, Webpack). +1. Generate the CSS. +1. Include a path to the generated CSS in your `MYTHEME.info`, + `MYTHEME.info.yml` or `MYTHEME.libraries.yml` file. + +## PostCSS Configuration + +Create a `postcss.config.js` file and add `tailwindcss` as a plugin, passing the +path to the config file: + +```js +module.exports = { + plugins: [ + require('tailwindcss')('./tailwind.js'), + ] +} +``` + +## Configuration for Drupal + +There are some configuration settings within `tailwind.js` that you’ll need to +change to make things work nicely with Drupal. These are within the `options` +section: + +```js +options: { + prefix: 'tw-', + important: true, + ... +} +``` + +### Prefix + +By adding a prefix like `tw-`, we can ensure that the Tailwind classes don’t +conflict with core HTML classes like `block`. We can also ensure that they won't +conflict with any other existing HTML or CSS. + +No prefix: + +![](/images/blog/using-tailwind-drupal/prefix-1.png){.with-border} + +With prefix: + +![](/images/blog/using-tailwind-drupal/prefix-2.png){.with-border} + +### Important + +We can also set the `!important` rule on all Tailwind’s generated classes. We +need to do this if we want to override core styles which have more specific +rules. + +For example: if I had this core markup then the left margin added by `tw-ml-4` +would be overridden by core’s `.item-list ul` styling. + +```html +
+
    + ... +
+
+``` + +![](/images/blog/using-tailwind-drupal/important-1.png){.with-border} + +With the `!important` rule enabled though, the Tailwind’s class takes precedence +and is applied. + +![](/images/blog/using-tailwind-drupal/important-2.png){.with-border} + +## Example + +For an example of Tailwind within a Drupal 8 theme, see the custom theme for the +[Drupal Bristol website][0] on GitHub. + +[0]: + https://github.com/drupalbristol/drupal-bristol-website/tree/master/web/themes/custom/drupalbristol +[1]: https://tailwindcss.com/docs/installation +[2]: https://www.npmjs.com/get-npm +[3]: https://yarnpkg.com/lang/en/docs/install diff --git a/source/_posts/using-traefik-local-proxy-sculpin.md b/source/_posts/using-traefik-local-proxy-sculpin.md new file mode 100644 index 000000000..0f663c723 --- /dev/null +++ b/source/_posts/using-traefik-local-proxy-sculpin.md @@ -0,0 +1,64 @@ +--- +title: Using Traefik as a local proxy with Sculpin +tags: + - docker + - sculpin +draft: true +date: ~ +--- + + + +## Before + +```yaml +services: + app: + build: + context: . + dockerfile: tools/docker/images/Dockerfile + target: app + volumes: + - assets:/app/source/build + - /app/output_dev + - .:/app + ports: + - 8000:8000 +``` + +## Adding the proxy service + +```yaml +services: + proxy: + image: traefik:v2.0-alpine + command: + - --api.insecure=true + - --providers.docker + volumes: + - /var/run/docker.sock:/var/run/docker.sock + ports: + - 80:80 + - 8080:8080 + labels: + - "traefik.enable=false" +``` + +## Updating the app service + +```yaml +app: + build: + context: . + dockerfile: tools/docker/images/Dockerfile + target: app + expose: + - 80 + command: [generate, --server, --watch, --port, '80', --url, http://oliverdavies.localhost] + volumes: + - assets:/app/source/build + - /app/output_dev + - .:/app + labels: + - "traefik.http.routers.oliverdavies.rule=Host(`oliverdavies.localhost`)" +``` diff --git a/source/_posts/using-transition-props-vuejs.md b/source/_posts/using-transition-props-vuejs.md new file mode 100644 index 000000000..5d6d8d4cf --- /dev/null +++ b/source/_posts/using-transition-props-vuejs.md @@ -0,0 +1,13 @@ +--- +title: Using Transition Class Props in Vue.js +date: 2019-06-06 +type: tweet +excerpt: Adam Wathan shows a more Tailwind-esque approach to writing Vue.js transitions. +tags: + - vuejs +external_url: https://twitter.com/adamwathan/status/1118670393030537217 +--- + +{% include 'tweet' with { + content: '🔥 Using the transition class props instead of the `name` prop for @vuejs transitions makes it really easy to compose transitions on the fly using utility classes.

This is how I do all my transitions in Vue these days — fits a lot better with the @tailwindcss philosophy 👌🏻 pic.twitter.com/shQCxaFZ8A

— Adam Wathan (@adamwathan) April 18, 2019' +} %} diff --git a/source/_posts/weeknotes-2021-06-05.md b/source/_posts/weeknotes-2021-06-05.md new file mode 100644 index 000000000..b9719faee --- /dev/null +++ b/source/_posts/weeknotes-2021-06-05.md @@ -0,0 +1,26 @@ +--- +title: 'Weeknotes: June 5th' +excerpt: Starting at Transport for Wales. +date: ~ +tags: + - personal + - week-notes +--- + +After the Bank Holiday weekend, I started working this week as a Lead Developer at [Transport for Wales](https://trc.cymru). I really enjoyed working at Inviqa, but felt that moving again to an in-house team would offer some new types of challenges whilst also getting to lead and manage a team. + +It's been an interesting first week, and I've met a lot of new colleagues whilst also going through the regular TfW induction processes and getting to know the current team. + +
+
+ +
+
Image
+
A screenshot of the tweet where I announced my move to Transport for Wales + +
+
+ +
+ +I'm looking forward to helping build and lead the development team at TfW, and plan on publishing regular weeknotes on the work that we're doing at Transport for Wales and TfW Rail. diff --git a/source/_posts/weeknotes-2021-06-12.md b/source/_posts/weeknotes-2021-06-12.md new file mode 100644 index 000000000..2c68a4bb2 --- /dev/null +++ b/source/_posts/weeknotes-2021-06-12.md @@ -0,0 +1,36 @@ +--- +title: 'Weeknotes: June 12th' +excerpt: Developing on Windows, organising dotfiles, and helping organise DrupalCon. +date: 2021-06-12 +tags: + - personal + - week-notes +--- + +## Local development with Windows and WSL 2 + +As a long-time Linux and macOS user, the last couple of weeks have been my first experience of using the Windows operating system for some time. After some research, I've been using the WSL 2 (Windows Subsystem for Linux) functionality built into Windows 10, with Ubuntu 20.04 installed within it. The codebase that I'm currently working on is using Lando, and that seems to be running fine within this setup after following some [instructions on the Lando documentation](https://docs.lando.dev/guides/setup-lando-on-windows-with-wsl-2.html) and a [blog post by Cal Evans](https://blog.calevans.com/2020/06/18/making-lando-work-inside-wsl2). + +I spend most of the day working within the WSL 2 environment which is a lot more familiar for me for development, but also for more simple tasks like generating SSH keys and cloning and configuring [my dotfiles](https://github.com/opdavies/dotfiles). + +## Re-organised dotfiles + +It was easy to clone my Dotfiles repository into the WSL 2 container but they still required to be symlinked into the correct place for them to be used. I'd previously used [rcm](https://github.com/thoughtbot/rcm), a tool from Thoughtbot, to do this but I wanted to review other approaches. + +I decided to try an approach of [using a local bare Git repository](https://www.atlassian.com/git/tutorials/dotfiles) and using Git's worktree functionality to clone the files into my home directory. This means no more symlinks, and no additional tool to manage the files. The structure of my dotfiles repo is now a lot simpler, though I do miss the grouping of files by 'tag' so I might look to re-implement this somehow in the future. + +## DrupalCon Europe kick-off meeting + +This week was the kick-off meeting for the DrupalCon Program Track Chair team, which I'm a part of this year. + +I've been proud to speak at the last two DrupalCon Europe conferences (2019 in Amsterdam, and online in 2020) and this year I wanted to contribute in a different way. + +I'm part of the Open Web & Community track and I'm looking forward to reviewing all of the sessions and experiencing DrupalCon in a new way again this year. + +## Inviqa blog post published + +My final task before leaving Inviqa a few weeks ago was to upgrade the inviqa.com and inviqa.de sites, which I co-developed, to Drupal 9. + +I wrote an article for the Inviqa blog, [Drupal 9 upgrade from Drupal 8](https://inviqa.com/blog/drupal-9-upgrade-from-drupal-8), which was published this week. + +I wrote my own post, [Upgrading the Dransible project to Drupal 9](/blog/upgrading-dransible-project-drupal-9) last year where I reviewed the commands and steps that I ran to upgrade one of my personal projects, whereas this post covers more about the process that we took, and the differences between this upgrade and previous Drupal upgrades that I've done. diff --git a/source/_posts/weeknotes-2021-07-24.md b/source/_posts/weeknotes-2021-07-24.md new file mode 100644 index 000000000..de5dfd10d --- /dev/null +++ b/source/_posts/weeknotes-2021-07-24.md @@ -0,0 +1,44 @@ +--- +title: 'Weeknotes: July 24th' +excerpt: Windows utilities, continuous integration and delivery, and writing tests. +tags: + - personal + - week-notes +date: ~ +--- + +## Using PowerToys and FancyZones + +I've been missing some of the features from Pop!_OS whilst working on my Windows laptop, so this week, I've been experimenting with [Microsoft PowerToys](https://docs.microsoft.com/en-us/windows/powertoys) which adds additional utilities to Windows 10 - similar to Gnome Tweak Tools. + +The main features that I'm trying are [FancyZones](https://docs.microsoft.com/en-us/windows/powertoys/fancyzones), which is similar to Pop!_OS's tiling window manager, and [Keyboard Manager](https://docs.microsoft.com/en-us/windows/powertoys/keyboard-manager](https://docs.microsoft.com/en-us/windows/powertoys/keyboard-manager)) so that I can remap some keys to match my personal laptop. + +## Continuous integration vs. feature branching + +I've been researching more about the continuous integration, or trunk-based development, approach to version control. Dave Farley's blog and [Continuous Delivery YouTube channel](https://www.youtube.com/channel/UCCfqyGl3nq_V0bo64CjZh8g) have been a great resource for this. I've also purchased his and Jez Humble's "Continuous Delivery" book, and have moved it to the top of my "To read" list. + +My current team at Transport for Wales is following a CI workflow, and it's been interesting to see code being pushed more often and moving from local to staging compared to waiting for async code reviews, so I'm looking forward to learning more about this approach and how to integrate it more with pair programming and test-driven development. + +I gave my first conference talk at DrupalCamp London 2014 on [Git Flow](/talks/git-flow), so I've been using the feature branch workflow for some time. As always, I'm willing to try and evaluate new approaches with an open mind. + +## Growing an automated test suite + +I [posted a tweet yesterday](https://twitter.com/opdavies/status/1418500778428338177) with a screenshot of the output from running some of the tests that I've added to my current work codebase. These are Drupal-based PHPUnit tests and are a combination of browser/functional and unit tests. + +I'm very keen on adding tests where possible for new functionality or when fixing bugs, which will make it much easier and less risky to refactor that code and ensure that the same bugs aren't re-added again. + +## Git hooks + +I've been using Git hooks for a few months to run checks locally, such as code linting and static analysis prior to pushing to a CI pipeline, to shorten the feedback loop and more quickly fix any failures. + +I've added some initial pre-push hooks to the current work codebase, to run some non-intrusive tasks such as code linting, with a view to adding to it over time. + +Sebastian Feldmann gave a talk in May for PHP South Wales, which included Git hooks. [The video is available on YouTube](https://www.youtube.com/watch?v=b85MoYmwUYs). + +## Deploying (and reverting) with CI and feature toggles + +Continuing with the CI/CD topic, I pushed a new, incomplete feature to production for one of my freelance clients' projects alongside some other changes. + +It was hidden behind a feature flag and not enabled, but it was merged and pushed to production. + +We decided to remove that implementation and use a different approach, so the code and the feature flag were removed, but for a time, that code was on production. diff --git a/source/_posts/weeknotes-2021-08-06.md b/source/_posts/weeknotes-2021-08-06.md new file mode 100644 index 000000000..d08f0a779 --- /dev/null +++ b/source/_posts/weeknotes-2021-08-06.md @@ -0,0 +1,32 @@ +--- +title: 'Weeknotes: August 6th' +excerpt: TODO +tags: + - personal + - week-notes +draft: true +date: ~ +--- + +## Vim + +- https://gist.github.com/opdavies/f944261b54f70b43f2297cab6779cf59 +- surround.vim - https://github.com/tpope/vim-surround +- https://towardsdatascience.com/how-i-learned-to-enjoy-vim-e310e53e8d56 + +## Re-watching invoice.space streams + +https://www.youtube.com/playlist?list=PLasJXc7CbyYfsdXu6t0406-kGwDN8aUG9 + +## Trialing Conventional Commits + +https://nitayneeman.com/posts/understanding-semantic-commit-messages-using-git-and-angular + +https://www.conventionalcommits.org/en/v1.0.0-beta.2 + +https://github.com/angular/angular/blob/22b96b9/CONTRIBUTING.md#-commit-message-guidelines + +https://github.com/vuejs/vue/commits/dev +https://github.com/vuejs/vue-cli/commits/dev + +https://github.com/pestphp/pest-intellij/commits/main diff --git a/source/_posts/what-git-flow.md b/source/_posts/what-git-flow.md new file mode 100644 index 000000000..5be5cbf31 --- /dev/null +++ b/source/_posts/what-git-flow.md @@ -0,0 +1,59 @@ +--- +title: 'DrupalCamp London: What is Git Flow?' +date: 2014-03-03 +excerpt: Here are my slides from my "What is Git Flow?" session at DrupalCamp London. +tags: + - git + - git-flow + - drupalcamp-london + - talks +tweets: true +--- + +Here are my slides from my "What is Git Flow?" session at +[DrupalCamp London](http://2014.drupalcamplondon.co.uk). + +{% include 'talk/slides' with { speakerdeck: { + data_id: '201559e0f103013198dd5a5f6f23ab67' } +} only %} + +## Take aways + +The main take aways are: + +- Git Flow adds various commands into Git to enhance its native functionality, + which creates a branching model to separate your stable production code from + your unstable development code. +- Never commit directly to the master branch - this is for production code only! +- You can commit directly to the develop branch, but this should be done + sparingly. +- Use feature branches as much as possible - one per feature, user story or bug. +- Commit early and often, and push to a remote often to encourage collaboration + as well as to provide a backup of your code. +- You can use settings within services like GitHub and Bitbucket to only allow + certain users to push to the master and develop branches, and restrict other + Developers to only commit and push to feature branches. Changes can then be + committed and pushed, then reviewed as part of a peer code review, and merged + back into the develop branch. + +## Feedback + +If you've got any questions, please feel free to +tweet at me +or fill in the +session +evaluation form that you can complete on the DrupalCamp London website. + +I've had some great feedback via Twitter: + +{% include 'tweet' with { + content: '

@opdavies @DrupalCampLDN always had trouble with git. Your talk + Git flow has made it all very easy.

— James Tombs (@jtombs) March 2, 2014' +} %} + +{% include 'tweet' with { + content: '

Great presentation by @opdavies on git flow at #dclondon very well prepared and presented. pic.twitter.com/tDINp2Nsbn

— Greg Franklin (@gfranklin) March 2, 2014' +} %} + +{% include 'tweet' with { + content: '

Great talk on git flow @opdavies #dclondon

— Curve Agency (@CurveAgency) March 2, 2014' +} %} diff --git a/source/_posts/writing-article-linux-journal.md b/source/_posts/writing-article-linux-journal.md new file mode 100644 index 000000000..13b5dc7be --- /dev/null +++ b/source/_posts/writing-article-linux-journal.md @@ -0,0 +1,22 @@ +--- +title: Writing an Article for Linux Journal +date: 2012-07-27 +excerpt: I'm absolutely delighted to announce that I'm going to be writing an article for Linux Journal magazine's upcoming Drupal special. +tags: + - distributions + - drupal + - installation-profiles + - linux-journal + - writing +--- + +I'm absolutely delighted to announce that I'm going to be writing an article for +[Linux Journal](http://www.linuxjournal.com) magazine's upcoming Drupal special. + +The article is going to be entitled "Speeding Up Your Drupal Development Using +Installation Profiles and Distributions" and will be mentioning existing +distributions available on Drupal.org, but mainly focussing on the steps needed +to create your own custom distribution. Needless to say, I'm quite excited about +it! + +The article is expected to be published in October. diff --git a/source/_posts/writing-info-file-drupal-7-theme.md b/source/_posts/writing-info-file-drupal-7-theme.md new file mode 100644 index 000000000..e3d623c18 --- /dev/null +++ b/source/_posts/writing-info-file-drupal-7-theme.md @@ -0,0 +1,40 @@ +--- +title: Writing a .info file for a Drupal 7 theme +date: 2012-05-23 +excerpt: An example .info file for a Drupal 7 theme. +tags: + - code + - drupal + - drupal-theming + - theming +--- + +```ini +name = My Theme +description = A description of my theme +core = 7.x + +# Add a base theme, if you want to use one. +base = mybasetheme + +# Define regions, otherwise the default regions will be used. +regions[header] = Header +regions[navigation] = Navigation +regions[content] = Content +regions[sidebar] = Sidebar +regions[footer] = Footer + +# Define which features are available. If none are specified, all the default +# features will be available. +features[] = logo +features[] = name +features[] = favicon + +# Add stylesheets +stylesheets[all][] = css/reset.css +stylesheets[all][] = css/mytheme.css +stylesheets[print][] = css/print.css + +# Add javascript files +styles[] = js/mytheme.js +``` diff --git a/source/_posts/writing-new-drupal-8-module-using-test-driven-development-tdd.md b/source/_posts/writing-new-drupal-8-module-using-test-driven-development-tdd.md new file mode 100644 index 000000000..d39909751 --- /dev/null +++ b/source/_posts/writing-new-drupal-8-module-using-test-driven-development-tdd.md @@ -0,0 +1,661 @@ +--- +title: Writing a new Drupal 8 Module using Test-Driven Development (TDD) +date: 2017-11-07 +tags: + - drupal + - phpunit + - simpletest + - tdd + - testing +excerpt: How to write automated tests and follow test driven development for Drupal modules. +meta: + image: + url: /images/talks/test-driven-drupal-development.png + width: 2560 + height: 1440 + type: image/png +promoted: true +--- + +

![](/images/blog/drupalcamp-dublin.jpg)

+ +I recently gave a [talk on automated testing in Drupal][0] talk at [DrupalCamp +Dublin][1] and as a lunch and learn session for my colleagues at Microserve. As +part of the talk, I gave an example of how to build a Drupal 8 module using a +test driven approach. I’ve released the [module code on GitHub][2], and this +post outlines the steps of the process. + +## Prerequisites + +You have created a `core/phpunit.xml` file based on `core/phpunit.xml.dist`, and +populated it with your database credentials so that PHPUnit can bootstrap the +Drupal database as part of the tests. [Here is an example][5]. + +## Acceptance Criteria + +For the module, we are going to satisfy this example acceptance criteria: + +> As a site visitor,
I want to see all published pages at /pages
Ordered +> alphabetically by title + +## Initial Setup + +Let’s start by writing the minimal code needed in order for the new module to be +enabled. In Drupal 8, this is the `.info.yml` file. + +```yaml +# tdd_dublin.info.yml + +name: 'TDD Dublin' +excerpt: 'A demo module for DrupalCamp Dublin to show test driven module development.' +core: 8.x +type: module +``` + +We can also add the test file structure at this point too. We’ll call it +`PageTestTest.php` and put it within a `tests/src/Functional` directory. As this +is a functional test, it extends the `BrowserTestBase` class, and we need to +ensure that the tdd_dublin module is enabled by adding it to the `$modules` +array. + +```php +// tests/src/Functional/PageListTest.php + +namespace Drupal\Tests\tdd_dublin\Functional; + +use Drupal\Tests\BrowserTestBase\BrowserTestBase; + +class PageListTest extends BrowserTestBase { + + protected static $modules = ['tdd_dublin']; + +} +``` + +With this in place, we can now start adding test methods. + +## Ensure that the Listing page Exists + +### Writing the First Test + +Let’s start by testing that the listing page exists at /pages. We can do this by +loading the page and checking the status code. If the page exists, the code will +be 200, otherwise it will be 404. + +I usually like to write comments first within the test method, just to outline +the steps that I'm going to take and then replace it with code. + +```php +public function testListingPageExists() { + // Go to /pages and check that it is accessible by checking the status + // code. +} +``` + +We can use the `drupalGet()` method to browse to the required path, i.e. +`/pages`, and then write an assertion for the response code value. + +```php +public function testListingPageExists() { + $this->drupalGet('pages'); + + $this->assertSession()->statusCodeEquals(200); +} +``` + +### Running the Test + +In order to run the tests, you either need to include `-c core` or be inside the +`core` directory when running the command, to ensure that the test classes are +autoloaded so can be found, though the path to the `vendor` directory may be +different depending on your project structure. You can also specify a path +within which to run the tests - e.g. within the module’s `test` directory. + +``` +$ vendor/bin/phpunit -c core modules/custom/tdd_dublin/tests +``` + +
+Note: I’m using Docksal, and I’ve noticed that I need to run the tests from within the CLI container. You can do this by running the `fin bash` command. +
+ +``` +1) Drupal\Tests\tdd_dublin\Functional\PageListTest::testListingPageExists +Behat\Mink\Exception\ExpectationException: Current response status code is 404, but 200 expected. + +FAILURES! +Tests: 1, Assertions: 1, Errors: 1. +``` + +Because the route does not yet exist, the response code returned is 404, so the +test fails. + +Now we can make it pass by adding the page. For this, I will use the Views +module, though you could achieve the same result with a custom route and a +Controller. + +### Building the View + +To begin with, I will create a view showing all types of content with a default +sort order of newest first. We will use further tests to ensure that only the +correct content is returned and that it is ordered correctly. + +![](/images/blog/tdd-drupal-1.png) { .with-border } + +The only addition I will make to the view is to add a path at `pages`, as per +the acceptance criteria. + +![](/images/blog/tdd-drupal-2.png) { .with-border } + +### Exporting the View + +With the first version of the view built, it needs to be incldued within the +module so that it can be enabled when the test is run. To do this, we need to +export the configuration for the view, and place it within the module’s +`config/install` directory. This can be done using the `drush config-export` +command or from within the Drupal UI. In either case, the `uid` line at the top +of the file needs to be removed so the configuration can be installed. + +Here is the exported view configuration: + +```yaml +langcode: en +status: true +dependencies: + module: + - node + - user +id: pages +label: pages +module: views +excerpt: '' +tag: '' +base_table: node_field_data +base_field: nid +core: 8.x +display: + default: + display_plugin: default + id: default + display_title: Master + position: 0 + display_options: + access: + type: perm + options: + perm: 'access content' + cache: + type: tag + options: { } + query: + type: views_query + options: + disable_sql_rewrite: false + distinct: false + replica: false + query_comment: '' + query_tags: { } + exposed_form: + type: basic + options: + submit_button: Apply + reset_button: false + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: true + sort_asc_label: Asc + sort_desc_label: Desc + pager: + type: mini + options: + items_per_page: 10 + offset: 0 + id: 0 + total_pages: null + expose: + items_per_page: false + items_per_page_label: 'Items per page' + items_per_page_options: '5, 10, 25, 50' + items_per_page_options_all: false + items_per_page_options_all_label: '- All -' + offset: false + offset_label: Offset + tags: + previous: ‹‹ + next: ›› + style: + type: default + options: + grouping: { } + row_class: '' + default_row_class: true + uses_fields: false + row: + type: fields + options: + inline: { } + separator: '' + hide_empty: false + default_field_elements: true + fields: + title: + id: title + table: node_field_data + field: title + entity_type: node + entity_field: title + label: '' + alter: + alter_text: false + make_link: false + absolute: false + trim: false + word_boundary: false + ellipsis: false + strip_tags: false + html: false + hide_empty: false + empty_zero: false + settings: + link_to_entity: true + plugin_id: field + relationship: none + group_type: group + admin_label: '' + exclude: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_alter_empty: true + click_sort_column: value + type: string + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + filters: + status: + value: '1' + table: node_field_data + field: status + plugin_id: boolean + entity_type: node + entity_field: status + id: status + expose: + operator: '' + group: 1 + sorts: + created: + id: created + table: node_field_data + field: created + order: DESC + entity_type: node + entity_field: created + plugin_id: date + relationship: none + group_type: group + admin_label: '' + exposed: false + expose: + label: '' + granularity: second + header: { } + footer: { } + empty: { } + relationships: { } + arguments: { } + display_extenders: { } + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url.query_args + - 'user.node_grants:view' + - user.permissions + tags: { } + page_1: + display_plugin: page + id: page_1 + display_title: Page + position: 1 + display_options: + display_extenders: { } + path: pages + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url.query_args + - 'user.node_grants:view' + - user.permissions + tags: { } +``` + +When the test is run again, we see a different error that leads us to the next +step. + +``` +1) Drupal\Tests\tdd_dublin\Functional\PageListTest::testListingPageExists +Drupal\Core\Config\UnmetDependenciesException: Configuration objects provided by tdd_dublin have unmet dependencies: node.type.page (node), views.view.pages (node, views) + +FAILURES! +Tests: 1, Assertions: 0, Errors: 1. +``` + +This error is identifying unmet dependencies within the module’s configuration. +In this case, the view that we’ve added depends on the node and views modules, +but these aren’t enabled. To fix this, we can add the extra modules as +dependencies of tdd_dublin so they will be enabled too. + +```yaml +# tdd_dublin.info.yml + +dependencies: + - drupal:node + - drupal:views +``` + +``` +1) Drupal\Tests\tdd_dublin\Functional\PageListTest::testListingPageExists +Drupal\Core\Config\UnmetDependenciesException: Configuration objects provided by tdd_dublin have unmet dependencies: views.view.pages (node.type.page) + +FAILURES! +Tests: 1, Assertions: 0, Errors: 1. +``` + +With the modules enabled, we can see one more unmet dependency for +`node.type.page`. This means that we need a page content type to be able to +install the view. We can fix this in the same way as before, by exporting the +configuration and copying it into the `config/install` directory. + +With this in place, the test should now pass - and it does. + +``` +Time: 26.04 seconds, Memory: 6.00MB + +OK (1 test, 1 assertion) +``` + +We now have a test to ensure that the listing page exists. + +## Ensure that only Published Pages are Shown + +### Writing the Test + +Now that we have a working page, we can now move on to checking that the correct +content is returned. Again, I’ll start by writing comments and then translate +that into code. + +The objectives of this test are: + +- To ensure that only page nodes are returned. +- To ensure that only published nodes are returned. + +```php +public function testOnlyPublishedPagesAreShown() { + // Given that a have a mixture of published and unpublished pages, as well + // as other types of content. + + // When I view the page. + + // Then I should only see the published pages. +} +``` + +In order to test the different scenarios, I will create an additional "article" +content type, create a node of this type as well as one published and one +unpublished page. From this combination, I only expect one node to be visible. + +```php +public function testOnlyPublishedPagesAreShown() { + $this->drupalCreateContentType(['type' => 'article']); + + $this->drupalCreateNode(['type' => 'page', 'status' => TRUE]); + $this->drupalCreateNode(['type' => 'article']); + $this->drupalCreateNode(['type' => 'page', 'status' => FALSE]); + + // When I view the page. + + // Then I should only see the published pages. +} +``` + +We could use `drupalGet()` again to browse to the page and write assertions +based on the rendered HTML, though I’d rather do this against the data returned +from the view itself. This is so that the test isn’t too tightly coupled to the +presentation logic, and we won’t be in a situation where at a later date the +test fails because of changes made to how the data is displayed. + +Rather, I’m going to use `views_get_view_result()` to programmatically get the +result of the view. This returns an array of `Drupal\views\ResultRow` objects, +which contain the nodes. I can use `array_column` to extract the node IDs from +the view result into an array. + +```php +public function testOnlyPublishedPagesAreShown() { + $this->drupalCreateContentType(['type' => 'article']); + + $this->drupalCreateNode(['type' => 'page', 'status' => TRUE]); + $this->drupalCreateNode(['type' => 'article']); + $this->drupalCreateNode(['type' => 'page', 'status' => FALSE]); + + $result = views_get_view_result('pages'); + $nids = array_column($result, 'nid'); + + // Then I should only see the published pages. +} +``` + +From the generated nodes, I can use `assertEquals()` to compare the returned +node IDs from the view against an array of expected node IDs - in this case, I +expect only node 1 to be returned. + +```php +public function testOnlyPublishedPagesAreShown() { + $this->drupalCreateContentType(['type' => 'article']); + + $this->drupalCreateNode(['type' => 'page', 'status' => TRUE]); + $this->drupalCreateNode(['type' => 'article']); + $this->drupalCreateNode(['type' => 'page', 'status' => FALSE]); + + $result = views_get_view_result('pages'); + $nids = array_column($result, 'nid'); + + $this->assertEquals([1], $nids); +} +``` + +### Running the Test + +The test fails as no extra conditions have been added to the view, though the +default "Content: Published" filter is already excluding one of the page nodes. +We can see from the output from the test that node 1 (a page) and node 2 (the +article) are both being returned. + +``` +1) Drupal\Tests\tdd_dublin\Functional\PageListTest::testOnlyPublishedPagesAreShown +Failed asserting that two arrays are equal. +--- Expected ++++ Actual +@@ @@ + Array ( +- 0 => 1 ++ 0 => '2' ++ 1 => '1' + ) + +FAILURES! +Tests: 1, Assertions: 3, Failures: 1. +``` + +### Updating the Test + +We can fix this by adding another condition to the view, to only show content +based on the node type - i.e. only return page nodes. + +![](/images/blog/tdd-drupal-3.png) { .with-border } + +Once the view is updated and the configuration is updated within the module, the +test should then pass - and it does. + +``` +Time: 24.76 seconds, Memory: 6.00MB + +OK (1 test, 3 assertions) +``` + +## Ensure that the Pages are in the Correct Order + +### Writing the Test + +As we know that the correct content is being returned, we can now focus on +displaying it in the correct order. We’ll start again by adding a new test +method and filling out the comments. + +```php +public function testResultsAreOrderedAlphabetically() { + // Given I have multiple nodes with different titles. + + // When I view the pages list. + + // Then I should see pages in the correct order. +} +``` + +To begin with this time, I’ll create a number of different nodes and specify the +title for each. These are intentionally in the incorrect order alphabetically so +that we can see the test fail initially and then see it pass after making a +change so we know that the change worked. + +```php +public function testResultsAreOrderedAlphabetically() { + $this->drupalCreateNode(['title' => 'Page A']); + $this->drupalCreateNode(['title' => 'Page D']); + $this->drupalCreateNode(['title' => 'Page C']); + $this->drupalCreateNode(['title' => 'Page B']); + + // When I view the pages list. + + // Then I should see pages in the correct order. +} +``` + +We can use the same method as the previous test to get the returned IDs, using +`views_get_view_result()` and `array_column()`, and assert that the returned +node IDs match the expected node IDs in the specified order. Based on the +defined titles, the order should be 1, 4, 3, 2. + +```php +public function testResultsAreOrderedAlphabetically() { + $this->drupalCreateNode(['title' => 'Page A']); + $this->drupalCreateNode(['title' => 'Page D']); + $this->drupalCreateNode(['title' => 'Page C']); + $this->drupalCreateNode(['title' => 'Page B']); + + $nids = array_column(views_get_view_result('pages'), 'nid'); + + $this->assertEquals([1, 4, 3, 2], $nids); +} +``` + +### Running the Test + +As expected the test fails, as the default sort criteria in the view orders the +results by their created date. + +In the test output, we can see the returned results are in sequential order so +the results array does not match the expected one. + +This would be particularly more complicated to test if I was using `drupalGet()` +and having to parse the HTML, compared to getting the results as an array from +the view programmatically. + +``` +1) Drupal\Tests\tdd_dublin\Functional\PageListTest::testResultsAreOrderedAlphabetically +Failed asserting that two arrays are equal. +--- Expected ++++ Actual +@@ @@ + Array ( +- 0 => 1 +- 1 => 4 +- 2 => 3 +- 3 => 2 ++ 0 => '1' ++ 1 => '2' ++ 2 => '3' ++ 3 => '4' + ) + +FAILURES! +Tests: 1, Assertions: 2, Failures: 1. +``` + +### Updating the Test + +This can be fixed by removing the default sort criteria and adding a new one +based on "Content: Title". + +![](/images/blog/tdd-drupal-4.png) { .with-border } + +Again, once the view has been updated and exported, the test should pass - and +it does. + +``` +Time: 27.55 seconds, Memory: 6.00MB + +OK (1 test, 2 assertions) +``` + +## Ensure all Tests Still Pass + +Now we know that all the tests pass individually, all of the module tests should +now be run to ensure that they all still pass and that there have been no +regressions due to any of the changes. + +``` +docker@cli:/var/www$ vendor/bin/phpunit -c core modules/custom/tdd_dublin/tests + +Testing modules/custom/tdd_dublin/tests +... + +Time: 1.27 minutes, Memory: 6.00MB + +OK (3 tests, 6 assertions) +``` + +They all pass, so we be confident that the code works as expected, we can +continue to refactor if needed, and if any changes are made to this module at a +later date, we have the tests to ensure that any regressions are caught and +fixed before deployment. + +## Next Steps + +I’ve started looking into whether some of the tests can be rewritten as kernel +tests, which should result in quicker test execution. I will post any updated +code to the [GitHub repository][3], and will also do another blog post +highlighting the differences between functional and kernel tests and the steps +taken to do the conversion. + +[0]: {{site.url}}/talks/tdd-test-driven-drupal +[1]: http://2017.drupal.ie +[2]: https://github.com/opdavies/tdd_dublin +[3]: https://packagist.org/packages/tightenco/collect +[4]: http://php.net/manual/en/function.array-column.php +[5]: https://gist.github.com/opdavies/dc5f0cea46ccd349b34a9f3a463c14bb diff --git a/source/_posts/zenophile.md b/source/_posts/zenophile.md new file mode 100644 index 000000000..b18e3114e --- /dev/null +++ b/source/_posts/zenophile.md @@ -0,0 +1,24 @@ +--- +title: Quickly Create Zen Subthemes Using Zenophile +date: 2010-05-10 +excerpt: How to use the Zenophile module to create a Zen subtheme. +tags: + - drupal-planet + - drupal-6 + - drupal-modules + - drupal-theming + - zen + - zenophile +--- + +If you use the [Zen](http://drupal.org/project/zen) theme, then you should also +be using the [Zenophile](http://drupal.org/project/zenophile) module! + +The Zenophile module allows you to very quickly create Zen subthemes from within +your web browser, as well as editing options such as the site directory where it +should be placed, the layout type (fixed or fluid), page wrapper and sidebar +widths, and the placement of the sidebars. + +For more information about the Zenophile module, check out +[this video](http://blip.tv/file/2427703) by +[Elliott Rothman](http://elliottrothman.com). From b8ad4d0d2fd71c3a0225ac1d2638616ab844c9aa Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Sun, 10 Mar 2024 23:27:18 +0000 Subject: [PATCH 273/501] Add daily email for 2024-03-08 Conventions over readability? --- source/_daily_emails/2024-03-08.md | 36 ++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 source/_daily_emails/2024-03-08.md diff --git a/source/_daily_emails/2024-03-08.md b/source/_daily_emails/2024-03-08.md new file mode 100644 index 000000000..52783249c --- /dev/null +++ b/source/_daily_emails/2024-03-08.md @@ -0,0 +1,36 @@ +--- +title: Conventions over readability? +date: 2024-03-08 +permalink: archive/2024/03/08/conventions-over-readability +tags: + - software-development + - clean-code + # - php + # - podcast +cta: ~ +snippet: | + Which is more important? To write readable code or following existing conventions? +--- + +I previously wrote about why you shouldn't use variable names like `$x` and `$y` in your code and why you should use more descriptive names. + +But what if there is an existing convention? + +For example, I use Lua to configure Neovim and noticed that it's common to use shortened variable names, such as `buffer` instead of `buffer_number` or `bufferNumber`. + +It's also common to use the variable `M` to declare a module. For example: + +```language-lua +local M = {} +M.find_files = function() + // ... +end + +return M +``` + +Whilst `Module` would be a more descriptive name, would deviating from the convention be more confusing for anyone reading the code? + +Do the benefits of following a convention outweigh the benefits of using more descriptive variable and function names? + +Which would be easier for newcomers to your project or team to understand and allow them to be productive sooner? From 95c13b05087bbfb8bdcaf7914d0b58c749023681 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Mon, 11 Mar 2024 22:01:45 +0000 Subject: [PATCH 274/501] Add daily email for 2024-03-09 Override Node Options is used on 40,624 Drupal websites --- source/_daily_emails/2024-03-09.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 source/_daily_emails/2024-03-09.md diff --git a/source/_daily_emails/2024-03-09.md b/source/_daily_emails/2024-03-09.md new file mode 100644 index 000000000..7ebd6ebc2 --- /dev/null +++ b/source/_daily_emails/2024-03-09.md @@ -0,0 +1,26 @@ +--- +title: Override Node Options is used on 40,624 Drupal websites +date: 2024-03-09 +permalink: archive/2024/03/09/override-node-options-40624-drupal-websites +tags: + - software-development + - drupal + - php +cta: testing_course +snippet: | + As of the 3rd of March, the Override Node Options module is used on 40,624 Drupal websites. +--- + +In my [Test-Driven Drupal talk slides][talk], I show a screenshot of a tweet from February 2012 by Tim Millwood asking if anyone wanted to maintain the Override Node Options module. + +I said yes and took over maintainership. + +In April 2012, the module was installed on 9,212 Drupal websites - just over 7,000 Drupal 6 websites and just over 2,000 Drupal 7 websites. + +Before DrupalCon Lille, the module was used on 38,096 websites and was 173rd in the most used modules. + +As of the 3rd of March, the Override Node Options module is used on 40,624 Drupal websites and is up to the 169th most used module. + +That's pretty cool! + +[talk]: {{site.url}}/talks/tdd-test-driven-drupal From 39afb3e82898ff8da6c44d4ee48ae6f37ffcb529 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Mon, 11 Mar 2024 23:57:50 +0000 Subject: [PATCH 275/501] Add Yuri Gerasymov episode --- .../14-yuri-gerasymov-diffy.md | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 source/_podcast_episodes/14-yuri-gerasymov-diffy.md diff --git a/source/_podcast_episodes/14-yuri-gerasymov-diffy.md b/source/_podcast_episodes/14-yuri-gerasymov-diffy.md new file mode 100644 index 000000000..0a929efbc --- /dev/null +++ b/source/_podcast_episodes/14-yuri-gerasymov-diffy.md @@ -0,0 +1,44 @@ +--- +date: 2024-03-11 +topic: Diffy and Visual Regression Testing +guests: + - Yuri Gerasymov +transistor: + id: 5940d69f +links: + - + - About Yuri + - https://ygerasimov.com/about-me + - + - Diffy + - https://diffy.website + - + - Yuri on Drupal.org + - https://www.drupal.org/u/ygerasimov +talking_points: + - What is visual regression testing? + - How do you deal with false positives? + - Different use cases for visual regression testing. + - Automatic updates. + - Scheduling content. + - Visual regression testing in CI. + - Diffy in WordPress. + - What's a baseline? + - Initial setup and onboarding feedback. + - Testing dark mode? + - Component testing with Storybook and Fractal? + - Testing local environments. + - Testing as authenticated users. + - The roadmap for Diffy. +quotes: + - We help development teams to have less visual bugs in their website. We take screenshots of the pages and compare them so you can see what changed and how. (YG) + - We built tools for you to mock the content. You provide selectors for the elements with the content of the article and we'll replace it with lorem ipsum text so it will be exactly the same across multiple environments. (YG) + - I can still write an assertion to check the text is on the page or not, but it won't confirm it's in the correct place. (OD) + - Having a tool checking for changes on a regular basis instead of only after a deployment would be very useful. (OD) + - So, you could have a tool like Violinst automatically creating pull requests and Diffy checking those PRs, so the two could work together? (OD) + - With visual testing, it's very easy to get started. (YG) + - Visual testing is great for showing your client your work. (YG) +chapters: [] +--- + +This week, Oliver discusses visual regression testing and Diffy with Yuri Gerasymov. From 09cab7bf7d54af0248f988ea25b7f26305d41a9a Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Tue, 12 Mar 2024 23:13:41 +0000 Subject: [PATCH 276/501] Add daily email for 2024-03-10 Visual testing and Diffy with Yuri Gerasymov --- source/_daily_emails/2024-03-10.md | 48 ++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 source/_daily_emails/2024-03-10.md diff --git a/source/_daily_emails/2024-03-10.md b/source/_daily_emails/2024-03-10.md new file mode 100644 index 000000000..e56ac7c05 --- /dev/null +++ b/source/_daily_emails/2024-03-10.md @@ -0,0 +1,48 @@ +--- +title: Visual testing and Diffy with Yuri Gerasymov +date: 2024-03-10 +permalink: archive/2024/03/10/visual-testing-and-diffy-with-yuri-gerasymov +tags: + - software-development + - drupal + - podcast + - automated-testing +cta: subscription +snippet: | + This week on Beyond Blocks, I'm joined by Yuri Gerasymov to discuss Diffy visual regression testing. +--- + +This week on the Beyond Blocks podcast, I'm joined by Yuri Gerasymov to discuss Diffy visual regression testing. + +## Talking Points + +- What is visual regression testing? +- How do you deal with false positives? +- Different use cases for visual regression testing. +- Automatic updates. +- Scheduling content. +- Visual regression testing in CI. +- Diffy in WordPress. +- What's a baseline? +- Initial setup and onboarding feedback. +- Testing dark mode? +- Component testing with Storybook and Fractal? +- Testing local environments. +- Testing as authenticated users. +- The roadmap for Diffy. + +## Quotable Quotes + +- We help development teams to have less visual bugs in their website. We take screenshots of the pages and compare them so you can see what changed and how. (YG) +- We built tools for you to mock the content. You provide selectors for the elements with the content of the article and we'll replace it with lorem ipsum text so it will be exactly the same across multiple environments. (YG) +- I can still write an assertion to check the text is on the page or not, but it won't confirm it's in the correct place. (OD) +- Having a tool checking for changes on a regular basis instead of only after a deployment would be very useful. (OD) +- So, you could have a tool like Violinst automatically creating pull requests and Diffy checking those PRs, so the two could work together? (OD) +- With visual testing, it's very easy to get started. (YG) +- Visual testing is great for showing your client your work. (YG) + +I learned a lot during this conversation and have added visual regression testing to my testing toolbox for working on projects. + +[Listen to the episode][episode] + +[episode]: {{site.url}}/podcast/14-yuri-gerasymov-diffy From f9329c6676ae092ecb8327755499d8b62ddc60b5 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Wed, 13 Mar 2024 22:46:09 +0000 Subject: [PATCH 277/501] Remove markdownlint line length warnings --- .markdownlint.yaml | 1 + build.yaml | 1 + 2 files changed, 2 insertions(+) create mode 100644 .markdownlint.yaml diff --git a/.markdownlint.yaml b/.markdownlint.yaml new file mode 100644 index 000000000..ff7d7cc82 --- /dev/null +++ b/.markdownlint.yaml @@ -0,0 +1 @@ +MD013: false diff --git a/build.yaml b/build.yaml index 48474bccd..e753f4624 100644 --- a/build.yaml +++ b/build.yaml @@ -13,6 +13,7 @@ flake: git: ignore: - '/source/build/' + - '!/.markdownlint.yaml' - '!/assets/' - '!/data/' - '!/package.json' From 57f15cba6e0142d0ba11888b7d075b257304d5d6 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Wed, 13 Mar 2024 22:48:38 +0000 Subject: [PATCH 278/501] Go back to a regular format .gitignore file --- .gitignore | 18 ++++-------------- build.yaml | 8 +------- 2 files changed, 5 insertions(+), 21 deletions(-) diff --git a/.gitignore b/.gitignore index 2619860fb..2c93fe1f9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,19 +1,9 @@ # Do not edit this file. It is automatically generated by https://www.oliverdavies.uk/build-configs. -/* +/output_*/ +/vendor/ -!/.gitignore -!/app/ -!/build.yaml -!/composer.{json,lock} -!/run.local -!/source/ - -!/flake.{nix,lock} +/.direnv/ +/node_modules/ /source/build/ -!/assets/ -!/data/ -!/package.json -!/pnpm-lock.yaml -!/stub.md diff --git a/build.yaml b/build.yaml index e753f4624..6d2f7bda5 100644 --- a/build.yaml +++ b/build.yaml @@ -13,13 +13,7 @@ flake: git: ignore: - '/source/build/' - - '!/.markdownlint.yaml' - - '!/assets/' - - '!/data/' - - '!/package.json' - - '!/pnpm-lock.yaml' - - '!/stub.md' + - '/node_modules/' experimental: - createInclusiveGitIgnoreFile: true createTmuxStartupFile: true From c473a2c1888c20cd531269d44ae39766993792ba Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Wed, 13 Mar 2024 22:40:41 +0000 Subject: [PATCH 279/501] Add daily email for 2024-03-11 Feature flags should be short-lived --- source/_daily_emails/2024-03-11.md | 50 ++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 source/_daily_emails/2024-03-11.md diff --git a/source/_daily_emails/2024-03-11.md b/source/_daily_emails/2024-03-11.md new file mode 100644 index 000000000..2dd253581 --- /dev/null +++ b/source/_daily_emails/2024-03-11.md @@ -0,0 +1,50 @@ +--- +title: Feature flags should be short-lived +date: 2024-03-11 +permalink: archive/2024/03/11/feature-flags-should-be-short-lived +tags: + - software-development + - feature-flags +cta: subscription +snippet: | + Feature flags should be short-lived. Once the change has been released, the flags should be removed. +--- + +Today, I [posted a tweet][tweet] with a screenshot of some code. + +When previously working on the [Versa CLI tool][versa], I had a TODO comment saying "What if there are multiple languages?". + +Versa is a tool for standardising commands between different languages and frameworks, but some projects, like my personal website, use multiple languages. + +The website is powered by Sculpin, a static site generator written in PHP (so there is a composer.json file) and node to compile the front-end assets (so there is also a package.json file). + +## What Problem Am I Trying to Solve? + +Depending on the language, commands like `versa install` will need to execute a different command - e.g., composer install` or `npm install` (or an equivalent node package manager). + +I added PHP support first, so if a composer.json file is found, the PHP command is run as the default. + +What I thought was for projects with multiple languages, to prompt the user to select the language when running the command if no explicit language is set. + +This led me to do a spike of using Symfony Console's `choice` method to see what that would look like so I could add a screenshot to the GitHub issue. + +Once I'd finished with the spike, rather than deleting the code, I wrapped it in an `if (false)` condition so it wouldn't be executed, but I could still re-enable it in the future. + +The screenshot in the tweet showed this, along with the text "Minimum viable feature flag." + +This is only supposed to be there for a short time until I revisit the code and implement the feature I was spiking on. + +If it would be a long time before I looked at it again, I'd take a different approach. + +## Here's the Thing + +One of the main rules of using feature flags is that they should be short-lived. + +It's less than ideal to read through a codebase and see it scattered with feature flags that are no longer needed and were used to release a feature several months ago, but the flag hasn't been removed. + +A feature flag is a temporary solution for separating the deployment of code from the release of a change. + +Once it's been released, the flag should be removed. + +[tweet]: https://twitter.com/opdavies/status/1767846980250714261 +[versa]: {{site.url}}/archive/2024/02/19/introducing-versa From 52c1bfd148d65ac8f1fd64f0c1ab3e62f518dbff Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Wed, 13 Mar 2024 23:16:05 +0000 Subject: [PATCH 280/501] Add introduction text to the daily email archive ...page --- source/_includes/macros.html.twig | 9 +++++++-- source/_pages/archive.html | 6 +++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/source/_includes/macros.html.twig b/source/_includes/macros.html.twig index b532d0987..3723faa75 100644 --- a/source/_includes/macros.html.twig +++ b/source/_includes/macros.html.twig @@ -1,3 +1,8 @@ -{% macro yearsExperience() %} - {{- today|date('Y') - 2007 -}} +{# Return a rounded count of daily emails I've sent (e.g. the actual number is 449, return 440+). #} +{% macro dailiesCount(dailies) %} + {{- dailies|length|round(-1, 'floor') -}}+ +{% endmacro %} + +{% macro yearsExperience() %} + {{- today|date('Y') - 2007 -}} {% endmacro %} diff --git a/source/_pages/archive.html b/source/_pages/archive.html index 9e845de87..58693350e 100644 --- a/source/_pages/archive.html +++ b/source/_pages/archive.html @@ -9,7 +9,11 @@ use: --- {% block content %} -
+ {% import 'macros' as macros %} + +

This is an archive of the {{ macros.dailiesCount(data.daily_emails) }} email messages I have sent to my daily mailing list since the 12th of August, 2022. Enjoy!

+ +
    {% for email in page.pagination.items %} From 8135cb35e0b8d46bb1e95c99a2c6387eea64eba3 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Wed, 13 Mar 2024 23:43:20 +0000 Subject: [PATCH 281/501] Add daily email for 2024-03-12 You should know when to remove a feature flag --- source/_daily_emails/2024-03-12.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 source/_daily_emails/2024-03-12.md diff --git a/source/_daily_emails/2024-03-12.md b/source/_daily_emails/2024-03-12.md new file mode 100644 index 000000000..dbf8ea7d8 --- /dev/null +++ b/source/_daily_emails/2024-03-12.md @@ -0,0 +1,26 @@ +--- +title: You should know when to remove a feature flag +date: 2024-03-12 +permalink: archive/2024/03/12/you-should-know-when-to-remove-a-feature-flag +tags: + - software-development + - feature-flags +cta: subscription +snippet: | + Yesterday, I said feature flags should be short-lived. But how do you know when a flag can be removed? +--- + +[In yesterday's email][yesterday], I mentioned my recent ["minimum viable feature flag" tweet][tweet] and that I think feature flags should be short-lived. + +But how do you know when a feature flag should be removed? + +My approach is to add a TODO comment above where I use a feature flag. + +In that comment, I added why the feature flag was added and when it can be removed. + +It can be something like "Remove this when x is deployed" and/or a targeted date when I'd expect to be able to remove the flag. + +Then, when reading through the code, anyone can see when it should be possible to remove each feature flag, and it's easy to find flags that can be removed by reviewing the TODO comments. + +[tweet]: https://twitter.com/opdavies/status/1767846980250714261 +[yesterday]: {{site.url}}/archive/2024/03/11/feature-flags-should-be-short-lived From 0a57fb05e3fae485eea326f646a1727bffe461e5 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Thu, 14 Mar 2024 23:09:34 +0000 Subject: [PATCH 282/501] Add daily email for 2024-03-13 80% of PHP? --- source/_daily_emails/2024-03-13.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 source/_daily_emails/2024-03-13.md diff --git a/source/_daily_emails/2024-03-13.md b/source/_daily_emails/2024-03-13.md new file mode 100644 index 000000000..78ddd696b --- /dev/null +++ b/source/_daily_emails/2024-03-13.md @@ -0,0 +1,20 @@ +--- +title: 80% of PHP? +date: 2024-03-13 +permalink: archive/2024/03/13/80--of-php +tags: + - software-development +cta: ~ +snippet: | + Why do people put things like "I know 80% of PHP on their CVs and websites? Is that what matters to prospective clients or employers? +--- + +Something I see on many Developers' websites and CVs is percentages or levels of how well they know certain tools and frameworks. + +Things like "80% of PHP" or "Advanced in HTML and CSS". + +But how do you quantify that? + +Do people alter their percentages accordingly if a new feature is added to a language or framework? + +Or, instead of trying to show how much you understand, focus on what problems you can solve with those tools and how you can provide value to customers or employers instead of what tools you'd use to do it. From 7930dc38a62b4d67924189e3d8d23c088b987f68 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Sat, 16 Mar 2024 00:26:15 +0000 Subject: [PATCH 283/501] Add daily email for 2024-03-14 Just say Drupal --- source/_daily_emails/2024-03-14.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 source/_daily_emails/2024-03-14.md diff --git a/source/_daily_emails/2024-03-14.md b/source/_daily_emails/2024-03-14.md new file mode 100644 index 000000000..3bb35a123 --- /dev/null +++ b/source/_daily_emails/2024-03-14.md @@ -0,0 +1,28 @@ +--- +title: Just say Drupal +date: 2024-03-14 +permalink: archive/2024/03/14/just-say-drupal +tags: + - software-development + - drupal + - php +cta: subscription +snippet: | + Should we remove the version number from Drupal's marketing materials and standardise how we refer to legacy and modern versions of Drupal? +--- + +Today, I've been following a conversation on Twitter and reading a blog post about Drupal naming and removing the version number from promotional materials. + +In the post, the author recommends stopping using the version number and "just say Drupal" and referring to Drupal 7 and older as "Legacy Drupal". + +Here's a quote from the article that stood out to me: + +> This is no longer true! We have successfully eliminated the need for organizations to leave Drupal because “it’s cheaper to just rebuild it in Wordpress than upgrade from 8 to 9 or from 9 to 10.” We’ve made it easier for our clients to spend less on major upgrades, we should be proud! + +Another suggestion was to also use the term "Modern Drupal" for anything since Drupal 8. + +I've been doing this or calling it "Drupal 8+" on streams and podcast episodes, but I like this approach and I'm going to standardise on this, help change the perception of Drupal and that large rebuilds are no longer needed to upgrade as they were between the older legacy versions. + +[Read the article][article] for more information about this. + +[article]: https://ten7.com/blog/post/just-say-drupal From 8f0222be76f6f1a5621d7996c1ac370bc59857b6 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Sat, 16 Mar 2024 11:53:21 +0000 Subject: [PATCH 284/501] Add daily email for 2024-03-15 Everything is a trade-off --- source/_daily_emails/2024-03-15.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 source/_daily_emails/2024-03-15.md diff --git a/source/_daily_emails/2024-03-15.md b/source/_daily_emails/2024-03-15.md new file mode 100644 index 000000000..e20d30b29 --- /dev/null +++ b/source/_daily_emails/2024-03-15.md @@ -0,0 +1,24 @@ +--- +title: Everything is a trade-off +date: 2024-03-15 +permalink: archive/2024/03/15/everything-is-a-trade-off +tags: + - software-development +cta: d7eol +snippet: | + All solutions have advantages and disadvantages. Which works best for you? +--- + +I recently added a custom `wrapper` class within a Tailwind CSS project. + +It combined the `max-w-6xl`, `mx-auto` and `px-4` classes using `@apply`, which I rarely use. + +I added it so we didn't have to add the same classes multiple times. + +The advantage was it removed some duplication, but people needed to switch from the Twig template to the CSS file to remember what the `wrapper` class did. + +This undoes some of the advantages of Tailwind CSS and utility classes - the ability to stay in one file without needing to context switching, and easily reading what classes are on an element and immediately knowing what styles are applied to it. + +Everything is a trade-off. + +You need to decide which option feels right for you. From f733868bdfab3dcb0e796945ec87f61541047249 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Sat, 16 Mar 2024 13:07:02 +0000 Subject: [PATCH 285/501] Add work-in-progress home page --- source/_pages/new.md | 72 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 source/_pages/new.md diff --git a/source/_pages/new.md b/source/_pages/new.md new file mode 100644 index 000000000..d37bb3c4e --- /dev/null +++ b/source/_pages/new.md @@ -0,0 +1,72 @@ +--- +title: Get Unlimited Drupal Consulting for a Fixed Monthly Price +meta: + title: Unlimited Drupal Consulting by Oliver Davies +draft: true +plans: + - + name: Standard + price: 5000 + tagline: One concurrent request. Cancel anytime. + features: + - One request at a time. + url: https://buy.stripe.com/8wM14OgBc2jg8Vy3cn + - + name: Pro + price: 9000 + tagline: Two concurrent requests. Cancel anytime. + features: + - Two requests at a time. + url: https://buy.stripe.com/9AQaFo0CeaPM3BecMY +features: + - Bug-free guarantee. + - Delivery in days, not weeks. + - Easy credit card or BACS payments. + - Cancel at any time. +--- + +{% block content %} + +
    + {% for plan in page.plans %} +
    +

    {{ plan.name }} - £{{ plan.price|number_format }}

    +
    +

    {{ plan.tagline }}

    + +
      + {% for feature in page.features|merge(plan.features) %} +
    • {{ feature }}
    • + {% endfor %} +
    + +
    + {% include 'button.html.twig' with { + text: 'Register now for the ' ~ plan.name|lower ~ ' plan', + url: plan.url, + withArrow: true, + } %} +
    +
    +
    + {% endfor %} +
    + +## What Does It Include? + +## Looking for Something Else? + +asdf +{% endblock %} + +{% block content_bottom %} +
    + {% include 'testimonials' with { + limit: 5, + tag: 'subscription', + title: 'Kind words from clients', + } %} + + {{ parent() }} +
    +{% endblock %} From 4c564b46b4ea1ea82c2826975bebb556caf7c001 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Sat, 16 Mar 2024 12:11:55 +0000 Subject: [PATCH 286/501] Add daily email for 2024-03-16 Adding tests to the Content Access by Path module --- source/_daily_emails/2024-03-16.md | 32 ++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 source/_daily_emails/2024-03-16.md diff --git a/source/_daily_emails/2024-03-16.md b/source/_daily_emails/2024-03-16.md new file mode 100644 index 000000000..0444e73e1 --- /dev/null +++ b/source/_daily_emails/2024-03-16.md @@ -0,0 +1,32 @@ +--- +title: Adding tests to the Content Access by Path module +date: 2024-03-16 +permalink: archive/2024/03/16/adding-tests-to-the-content-access-by-path-module +tags: + - software-development + - drupal + - php + - automated-testing +cta: subscription +snippet: | + As promised, I've added some tests to the Content Access by Path module. +--- + +Last month, I released the [Beyond Blocks podcast episode][episode] where I spoke with Mark Conroy. + +We spoke about a number of things, including the 'Content Access by Path' Drupal module he wrote, and I promised I'd write some automated tests for it as there weren't any at the time. + +[Yesterday on my Friday live stream][stream], I installed the module and learnt how it works whilst writing some automated tests. + +As is common, the first test took a little while to do as I got the setup steps working and learned how the module worked. Once that was passing, adding the others was fairly straight forward. + +After the stream, I [created an issue][issue] with a merge request to add the tests I wrote and enabling GitLab CI so they'll be automatically run for any other changes. + +Hopefully, it'll be reviewed soon, but I've done what I promised, contributed to more open-source software, learned more about a new module and, hopefully, others will have learned things from the stream too. + +[Subscribe to my YouTube channel][channel] to be notified of future live streams! + +[channel]: https://www.youtube.com/@opdavies +[episode]: {{site.url}}/podcast/11-mark-conroy +[issue]: https://www.drupal.org/project/content_access_by_path/issues/3428680 +[stream]: https://www.youtube.com/live/XTpliKd47Lg?si=o4-my-XHGvcM_mWS From 3aca7edf8cebd665a4e37e77fe03ed6b48921bfd Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Sat, 16 Mar 2024 23:08:30 +0000 Subject: [PATCH 287/501] Fix new home page bug --- source/_pages/new.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/source/_pages/new.md b/source/_pages/new.md index d37bb3c4e..dd716614c 100644 --- a/source/_pages/new.md +++ b/source/_pages/new.md @@ -66,7 +66,5 @@ asdf tag: 'subscription', title: 'Kind words from clients', } %} - - {{ parent() }}
{% endblock %} From ea6ab824a3cd389122c7687d139af1d7345f41e0 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Sun, 17 Mar 2024 22:26:23 +0000 Subject: [PATCH 288/501] Add daily email for 2024-03-17 Patches vs Merge Requests --- source/_daily_emails/2024-03-17.md | 31 ++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 source/_daily_emails/2024-03-17.md diff --git a/source/_daily_emails/2024-03-17.md b/source/_daily_emails/2024-03-17.md new file mode 100644 index 000000000..80a79bffd --- /dev/null +++ b/source/_daily_emails/2024-03-17.md @@ -0,0 +1,31 @@ +--- +title: Patches vs Merge Requests +date: 2024-03-17 +permalink: archive/2024/03/17/patches-vs-merge-requests +tags: + - software-development + - drupal + - php + - open-source +cta: d7eol +snippet: | + Which do you prefer? Patches or merge requests? +--- + +I'm aware of the ongoing changes to the issue queues on Drupal.org, I haven't contributed or committed as much recently, so I haven't had to use the new approaches, such as issue forks and GitLab merge requests. + +Yesterday, though, I was able to do that whilst submitting [the tests I wrote to the Content Access by Path module][yesterday]. + +I've made many contributions to projects on Drupal.org, including Drupal core and Drupal.org itself, so I'm very familiar with how the issue queues and the patch workflow work. + +When I first started contributing, we were using CVS, and before the "Great Git Migration". + +I'm also familiar with the pull or merge request approach from working on open-source and team projects on GitHub, GitLab and Bitbucket. + +I can see how a merge request-based workflow on Drupal.org will lower the barriers for new contributors, which seemed to be the case at DrupalCon Lille. + +I look forward to adapting and using it more now that the patch workflow is deprecated and will soon no longer work as everything will switch to merge requests and leverage more of GitLab's features. + +Another step forward for Drupal.org and the Drupal project. + +[yesterday]: {{site.url}}/archive/2024/03/16/adding-tests-to-the-content-access-by-path-module From 54c0e6f66b93ab2bc913729298ebe74737ccfb4b Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Mon, 18 Mar 2024 00:00:37 +0000 Subject: [PATCH 289/501] Add daily email for 2024-03-18 Automated Drupal 11 compatibility fixes --- source/_daily_emails/2024-03-18.md | 40 ++++++++++++++++++++++++++++++ source/_layouts/page.html.twig | 1 + 2 files changed, 41 insertions(+) create mode 100644 source/_daily_emails/2024-03-18.md diff --git a/source/_daily_emails/2024-03-18.md b/source/_daily_emails/2024-03-18.md new file mode 100644 index 000000000..8d0eac8e5 --- /dev/null +++ b/source/_daily_emails/2024-03-18.md @@ -0,0 +1,40 @@ +--- +title: Automated Drupal 11 compatibility fixes +date: 2024-03-18 +permalink: archive/2024/03/18/automated-drupal-11-compatibility-fixes +tags: + - software-development + - drupal + - php +cta: d7eol +snippet: | + First "Automated Drupal 11 compatibility fixes" email spotted... +--- + +Yesterday, I received the first "Automated Drupal 11 compatibility fixes" email from the Rector-powered Project Update Bot. + +It was for the [Feature Toggle Twig module](https://www.drupal.org/project/feature_toggle_twig) that adds a `featureIsEnabled()` function to Twig to check if a feature toggle is enabled. + +For example: + +```language-twig +{% verbatim -%} +{% if featureIsEnabled('foo') %} + {# ... #} +{% endif %} +{%- endverbatim %} +``` + +## What Changes Were Needed? + +The only change needed to make the module Drupal 11 compatible was updating the `core_version_requirement` to `^10 || ^11` - allowing the module to support Drupal 10 and 11 at the same time as it uses no deprecated code. + +That's a great thing about modern Drupal compared to legacy versions - no major changes or rewrites to support a new major version! + +## Here's the Thing + +I thought this was a great initiative in previous versions and I'm glad to see it again for Drupal 11, and it's great that it's being done with time before the Drupal 11 release as it gives maintainers the time to update their projects so as many modules as possible will be Drupal 11-compatible when it's released. + +I look forward to getting more of these emails for my [other contributed projects on Drupal.org][override node options]. + +[override node options]: {{site.url}}/archive/2024/03/09/override-node-options-40624-drupal-websites diff --git a/source/_layouts/page.html.twig b/source/_layouts/page.html.twig index 67aa7409e..d1570d4f5 100644 --- a/source/_layouts/page.html.twig +++ b/source/_layouts/page.html.twig @@ -17,6 +17,7 @@ {% block scripts %} + {% endblock %} From 776176e82cffa83124f7fcb063cbefac3531b8de Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Mon, 18 Mar 2024 23:55:11 +0000 Subject: [PATCH 290/501] Add Kevin Coyle testimonial --- app/config/sculpin_site.yml | 9 +++++++++ .../images/recommendations/kevin-coyle.jpg | Bin 0 -> 71347 bytes 2 files changed, 9 insertions(+) create mode 100644 source/assets/images/recommendations/kevin-coyle.jpg diff --git a/app/config/sculpin_site.yml b/app/config/sculpin_site.yml index 137b77742..e1029f3c2 100644 --- a/app/config/sculpin_site.yml +++ b/app/config/sculpin_site.yml @@ -51,6 +51,15 @@ meta: Oliver is an Acquia-certified Triple Drupal expert, core contributor, Developer, Consultant and multiple-time DrupalCon speaker. testimonials: + - + text: | + I'm liking your short emails. They're just the right length that isn't too distracting but I'm able to consume it in a single glance. + name: Kevin Coyle + title: Design System Engineering Consultant + url: https://www.coyledesign.co.uk + image: + url: '%site.assets.url%/assets/images/recommendations/kevin-coyle.jpg' + tags: [daily] - text: | I really love your daily posts. They are opinionated, and this gives room for thoughts, I appreciate this. diff --git a/source/assets/images/recommendations/kevin-coyle.jpg b/source/assets/images/recommendations/kevin-coyle.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2a95199bb309ebc70e512fd8bd6f4246052ae000 GIT binary patch literal 71347 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 Date: Mon, 18 Mar 2024 23:56:21 +0000 Subject: [PATCH 291/501] Use the non-S3 URL for assets --- app/config/sculpin_site_prod.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/config/sculpin_site_prod.yml b/app/config/sculpin_site_prod.yml index 59e2bccf0..dc229de17 100644 --- a/app/config/sculpin_site_prod.yml +++ b/app/config/sculpin_site_prod.yml @@ -1,9 +1,6 @@ imports: - sculpin_site.yml -assets: - url: https://oliverdavies-uk.s3.eu-west-2.amazonaws.com - plausible: domain: oliverdavies.uk From 40d76bde05e7e0e40cdbd5f5f2f30c40408493ee Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Tue, 19 Mar 2024 12:58:51 +0000 Subject: [PATCH 292/501] Format with nixfmt --- flake.nix | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/flake.nix b/flake.nix index d62b52dc9..0350d50fc 100644 --- a/flake.nix +++ b/flake.nix @@ -5,20 +5,15 @@ nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; }; - outputs = inputs @ {flake-parts, ...}: - flake-parts.lib.mkFlake {inherit inputs;} { - imports = [inputs.devshell.flakeModule]; + outputs = inputs@{ flake-parts, ... }: + flake-parts.lib.mkFlake { inherit inputs; } { + imports = [ inputs.devshell.flakeModule ]; - systems = ["x86_64-linux"]; + systems = [ "x86_64-linux" ]; + + perSystem = { config, self', inputs', pkgs, system, ... }: { + formatter = pkgs.nixfmt; - perSystem = { - config, - self', - inputs', - pkgs, - system, - ... - }: { devshells.default = { packages = with pkgs; [ "nodePackages.pnpm" @@ -27,8 +22,6 @@ "php82Packages.composer" ]; }; - - formatter = pkgs.alejandra; }; }; } From e76a6b58f45f7304b589c77e9f0cb9187ef196bf Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Tue, 19 Mar 2024 13:23:46 +0000 Subject: [PATCH 293/501] Move `/subscription` to the front page --- source/_pages/index.md | 240 ++++++++++++++++++++++++++-------- source/_pages/subscription.md | 205 ----------------------------- 2 files changed, 186 insertions(+), 259 deletions(-) delete mode 100644 source/_pages/subscription.md diff --git a/source/_pages/index.md b/source/_pages/index.md index 46c58ecff..b4af93ca6 100644 --- a/source/_pages/index.md +++ b/source/_pages/index.md @@ -1,76 +1,208 @@ --- -title: Do you need a Drupal expert to improve your website or development team? +title: Get Unlimited Drupal Consulting for a Fixed Monthly Price permalink: / -meta: - title: '%site.slogan% | %site.name%' ---- +supported_version: 10 +plans: + - + name: Standard + price: 5000 + tagline: One concurrent request. Cancel anytime. + features: + - One request at a time. + url: https://buy.stripe.com/8wM14OgBc2jg8Vy3cn + - + name: Pro + price: 9000 + tagline: Two concurrent requests. Cancel anytime. + features: + - Two requests at a time. + url: https://buy.stripe.com/9AQaFo0CeaPM3BecMY +features: + - Bug-free guarantee. + - Delivery in days, not weeks. + - Easy credit card or BACS payments. + - Cancel at any time. +faqs: + - + - Which versions of Drupal do you work on? + - |- + I only work on the latest supported version of Drupal core, which is currently Drupal %supported_version%. -{% block meta_title %}Drupal Strategist, Consultant and certified Drupal expert | {{ site.name }}{% endblock %} + If you have an application that uses an older version, I'm happy to give you a custom fixed-price proposal to upgrade to Drupal %supported_version%. + - + - Does this include maintenance tasks, such as updating Drupal core and contrib modules and themes? + - |- + Yes, updates are included and will need to be prioritised along with other requests. + + My suggestion is to do them regularly to reduce the risk of an update breaking your application. + - + - Is there a limit to how many requests I can have? + - |- + No. Once you're subscribed, you can add as many tasks to your queue as you'd like, and they'll be delivered one-by-one. + + You can move requests around and set your own project priority to ensure your most important tasks are finished first. + - + - How fast will I receive my completed requests? + - |- + Of course no two user stories or requests are the same, and some take longer than others. + + However, you'll typically start getting code back from me within days of submitting an active request. + + Software development is an iterative process, so I will break big projects into smaller tasks and start sending work-in-progress for review, feedback, and iteration. + - + - Who are the Developers? + - |- + I am! I won't pass your work to Junior Developers or offshore teams. You work directly with me. Always. + - + - How do I request tasks? + - |- + After subscribing, you'll need to give me access to your GitLab or GitHub repository and issues board. From there, assign as many tasks to me as you like, in priority order. + - + - What if I don't like how something turned out? + - |- + Development is an iterative process. Unlike an agency that will charge you extra for change orders, you get unlimited revisions until you’re happy with the work. + - + - What if I only have a single request? + - |- + Your subscription renews on a monthly basis. If you only have a single request, you are free to cancel your subscription after the first month. + + You can always renew again in the future, if you have a new request! + - + - How does the bug free guarantee work? + - |- + If you discover a bug in any code I delivered, for up to 6 months after the end of your subscription, I will fix it for free. + - + - Will you attend our daily stand-ups, or other recurring meetings? + - |- + No. To guard your time and mine, and to make your subscription as effective as possible, all communication is handled asynchronously via task requests, email, and/or Slack. If an occasional task requires some synchronous planning, we can schedule such calls on an as-needed basis. + - + - Do you have a refund policy? + - |- + Yes, of course. If you're unhappy with my work for any reason during your first month of service, just say the word, and I'll give you a full refund. No questions asked. +--- {% block content %} -{% import 'macros' as macros %} + In less time than it takes to post on a job board, and for a fraction of the cost, get unlimited access to a certified Drupal development expert, core contributor and multiple-time DrupalCon speaker for a fixed monthly fee. No surprises. Cancel anytime. -Hi, I'm Oliver - a certified Drupal Expert and Software Development Consultant with {{ macros.yearsExperience }} years of experience. +--- -I'm a Drupal core contributor, module and theme maintainer, former Developer for the Drupal Association, and multiple-time DrupalCon speaker. +## You're already running my code in production + +I've contributed code to Drupal core and written popular contributed Drupal modules and themes, PHP and JavaScript libraries, and Tailwind CSS plugins. + +--- + +## How it works + +### Make as many requests as you like + +Unlimited user stories. Unlimited tasks. Unlimited repos. Unlimited services. + +### You set the priority + +You decide what's most important. Change priorities at any time. + +### Get code in days, not weeks + +I work on one active request at a time, and start shipping code in days. Bigger projects will be broken down into smaller parts. + +### Satisfaction guaranteed + +Great software is an iterative process. I'll keep iterating with you until you're completely happy with the results. + +--- + +## Subscription benefits + +When you subscribe, you gain access to a number of unique benefits. + +### Fixed monthly rate + +No surprises. No missed quotes. No surprise invoices. Pay the same price each month. + +### Speedy delivery + +I work in small increments, so you'll begin seeing valuable code changes in mere days. + +### Quality guaranteed + +High quality code that just works. Or I fix it, for free! + +--- + +## Subscription plans + +{% for plan in page.plans %} +
+ +

+ {{ plan.name }}: + £{{ plan.price|number_format }} per month +

+
+ +

{{ plan.tagline }}

+ +
    + {% for feature in plan.features|merge(page.features) %} +
  • {{ feature }}
  • + {% endfor %} +
+ +
+ {% include 'button.html.twig' with { + text: 'Register now for the ' ~ plan.name|lower ~ ' plan', + url: plan.url, + withArrow: true, + } %} +
+
+{% endfor %} + +--- + +## Book a free call + +And we'll figure out what's best for you. {% include 'button' with { - position: 'centre', - text: 'Click here to email Oliver', - url: 'mailto:' ~ site.email, + text: 'Get in touch', + type: 'secondary', + url: 'https://savvycal.com/opdavies/drupal-consulting-exploratory-call', withArrow: true, } %} -## What I can help you with +--- -If you have a Drupal application, register for a [Drupal development subscription][subscription] and have unlimited access to an expert Drupal developer for a fixed monthly price. +{% include 'testimonials' with { + limit: 5, + tag: 'subscription', + title: 'Kind words from clients', +} %} -If you are stuck on an unsupported version of Drupal, such as 7, 8 or 9, and need help upgrading, [book a Drupal upgrade consultation call][call] or [roadmap for your website][roadmap]. +--- -If you need help or another pair of eyes on your code, book a [1-on-1 consultation call][call] or an [online pair programming session][pair] with me, with a 100% money-back guarantee. +## Frequently asked questions -If your team wants to write better software faster, I'm available for [development team coaching][team coaching]. +{% for faq in page.faqs %} +

{{ faq.0 }}

-## Looking for something else? + {{ faq.1|markdown }} +{% endfor %} + +--- + +{% include 'testimonials' with { + tag: 'subscription', + offset: 5, + title: 'More kind words from clients and colleagues', +} %} + +{# TODO: add daily subscription form #} -Here are [all my products and services][pricing]. If you still can't find what you need, [send me an email](mailto:oliver+website@oliverdavies.uk), and I'll see what I can do. {% endblock %} -{% block content_bottom %} - {% include 'testimonials' with { - tag: 'front', - title: 'Kind words from clients, subscribers, and past colleagues', - } %} - -
-

Get in touch

-
-
-

There's no reason to wait. Send me an email and I'll get back to you ASAP.

-
-
- {% include 'button' with { - position: 'centre', - text: 'Click here to email Oliver', - url: 'mailto:' ~ site.email, - withArrow: true, - } %} -
-
-
- - {% include 'daily-email-form.html.twig' with { - title: 'Register for daily software development emails', - intro: 'Sign up and get daily emails about Drupal, PHP and software development.', - } %} - - {{ parent() }} +{% block content_top %} + {% include 'message.html.twig' %} {% endblock %} - -[call]: {{site.url}}/call -[pair]: {{site.url}}/pair -[roadmap]: {{site.url}}/roadmap -[pricing]: {{site.url}}/pricing -[subscription]: {{site.url}}/subscription -[team coaching]: {{site.url}}/team-coaching diff --git a/source/_pages/subscription.md b/source/_pages/subscription.md deleted file mode 100644 index b0b9f19d2..000000000 --- a/source/_pages/subscription.md +++ /dev/null @@ -1,205 +0,0 @@ ---- -title: Drupal Development Subscription -supported_version: 10 -plans: - - - name: Standard - price: 5000 - tagline: One concurrent request. Cancel anytime. - features: - - One request at a time. - url: https://buy.stripe.com/8wM14OgBc2jg8Vy3cn - - - name: Pro - price: 9000 - tagline: Two concurrent requests. Cancel anytime. - features: - - Two requests at a time. - url: https://buy.stripe.com/9AQaFo0CeaPM3BecMY -features: - - Bug-free guarantee. - - Delivery in days, not weeks. - - Easy credit card or BACS payments. - - Cancel at any time. -faqs: - - - - Which versions of Drupal do you work on? - - |- - I only work on the latest supported version of Drupal core, which is currently Drupal %supported_version%. - - If you have an application that uses an older version, I'm happy to give you a custom fixed-price proposal to upgrade to Drupal %supported_version%. - - - - Does this include maintenance tasks, such as updating Drupal core and contrib modules and themes? - - |- - Yes, updates are included and will need to be prioritised along with other requests. - - My suggestion is to do them regularly to reduce the risk of an update breaking your application. - - - - Is there a limit to how many requests I can have? - - |- - No. Once you're subscribed, you can add as many tasks to your queue as you'd like, and they'll be delivered one-by-one. - - You can move requests around and set your own project priority to ensure your most important tasks are finished first. - - - - How fast will I receive my completed requests? - - |- - Of course no two user stories or requests are the same, and some take longer than others. - - However, you'll typically start getting code back from me within days of submitting an active request. - - Software development is an iterative process, so I will break big projects into smaller tasks and start sending work-in-progress for review, feedback, and iteration. - - - - Who are the Developers? - - |- - I am! I won't pass your work to Junior Developers or offshore teams. You work directly with me. Always. - - - - How do I request tasks? - - |- - After subscribing, you'll need to give me access to your GitLab or GitHub repository and issues board. From there, assign as many tasks to me as you like, in priority order. - - - - What if I don't like how something turned out? - - |- - Development is an iterative process. Unlike an agency that will charge you extra for change orders, you get unlimited revisions until you’re happy with the work. - - - - What if I only have a single request? - - |- - Your subscription renews on a monthly basis. If you only have a single request, you are free to cancel your subscription after the first month. - - You can always renew again in the future, if you have a new request! - - - - How does the bug free guarantee work? - - |- - If you discover a bug in any code I delivered, for up to 6 months after the end of your subscription, I will fix it for free. - - - - Will you attend our daily stand-ups, or other recurring meetings? - - |- - No. To guard your time and mine, and to make your subscription as effective as possible, all communication is handled asynchronously via task requests, email, and/or Slack. If an occasional task requires some synchronous planning, we can schedule such calls on an as-needed basis. - - - - Do you have a refund policy? - - |- - Yes, of course. If you're unhappy with my work for any reason during your first month of service, just say the word, and I'll give you a full refund. No questions asked. ---- - -{% block content %} - - In less time than it takes to post on a job board, and for a fraction of the cost, get unlimited access to a certified Drupal development expert, core contributor and multiple-time DrupalCon speaker for a fixed monthly fee. No surprises. Cancel anytime. - ---- - -## You're already running my code in production - -I've contributed code to Drupal core and written popular contributed Drupal modules and themes, PHP and JavaScript libraries, and Tailwind CSS plugins. - ---- - -## How it works - -### Make as many requests as you like - -Unlimited user stories. Unlimited tasks. Unlimited repos. Unlimited services. - -### You set the priority - -You decide what's most important. Change priorities at any time. - -### Get code in days, not weeks - -I work on one active request at a time, and start shipping code in days. Bigger projects will be broken down into smaller parts. - -### Satisfaction guaranteed - -Great software is an iterative process. I'll keep iterating with you until you're completely happy with the results. - ---- - -## Subscription benefits - -When you subscribe, you gain access to a number of unique benefits. - -### Fixed monthly rate - -No surprises. No missed quotes. No surprise invoices. Pay the same price each month. - -### Speedy delivery - -I work in small increments, so you'll begin seeing valuable code changes in mere days. - -### Quality guaranteed - -High quality code that just works. Or I fix it, for free! - ---- - -## Subscription plans - -{% for plan in page.plans %} -
- -

- {{ plan.name }}: - £{{ plan.price|number_format }} per month -

-
- -

{{ plan.tagline }}

- -
    - {% for feature in plan.features|merge(page.features) %} -
  • {{ feature }}
  • - {% endfor %} -
- -
- {% include 'button.html.twig' with { - text: 'Register now for the ' ~ plan.name|lower ~ ' plan', - url: plan.url, - withArrow: true, - } %} -
-
-{% endfor %} - ---- - -## Book a free call - -And we'll figure out what's best for you. - -{% include 'button' with { - text: 'Get in touch', - type: 'secondary', - url: 'https://savvycal.com/opdavies/subscription', - withArrow: true, -} %} - ---- - -{% include 'testimonials' with { - limit: 5, - tag: 'subscription', - title: 'Kind words from clients', -} %} - ---- - -## Frequently asked questions - -{% for faq in page.faqs %} -

{{ faq.0 }}

- - {{ faq.1|markdown }} -{% endfor %} - ---- - -{% include 'testimonials' with { - tag: 'subscription', - offset: 5, - title: 'More kind words from clients and colleagues', -} %} - -{% endblock %} - -{% block content_top %} - {% include 'message.html.twig' %} -{% endblock %} From ec1b678210bda051a9c67543d8463caed5b342e6 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Tue, 19 Mar 2024 13:28:55 +0000 Subject: [PATCH 294/501] Set meta title --- source/_pages/index.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/_pages/index.md b/source/_pages/index.md index b4af93ca6..c741b18a9 100644 --- a/source/_pages/index.md +++ b/source/_pages/index.md @@ -1,6 +1,8 @@ --- title: Get Unlimited Drupal Consulting for a Fixed Monthly Price permalink: / +meta: + title: Unlimited Drupal Consulting by Oliver Davies supported_version: 10 plans: - From e5b0f68ce32a8828b76c817543e12f1243555e83 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Tue, 19 Mar 2024 23:29:16 +0000 Subject: [PATCH 295/501] Remove team coaching page --- source/_pages/team-coaching.html.twig | 155 -------------------------- 1 file changed, 155 deletions(-) delete mode 100644 source/_pages/team-coaching.html.twig diff --git a/source/_pages/team-coaching.html.twig b/source/_pages/team-coaching.html.twig deleted file mode 100644 index cd8bcf485..000000000 --- a/source/_pages/team-coaching.html.twig +++ /dev/null @@ -1,155 +0,0 @@ ---- -title: Software Development Team Coaching -price: 10000 -form_url: https://forms.gle/zd3Lnwe2J1sF13qJ7 -faqs: - - - - Do you only work with Drupal development teams? - - No. While most of my background and experience is with PHP and Drupal, many things I can help with aren't specific to any language or framework. - - - - I have another question. - - No problem! Send me an email at
oliver@oliverdavies.uk. -meta: - description: |- - Upskill your development team with coaching by an Acquia-certified Drupal Triple Expert, core contributor and multiple-time DrupalCon speaker. ---- - -{% block content %} - {% import 'macros' as macros %} - -
-
- {# Pain #} -

Do you need an experienced Software Developer or Drupal expert to upskill your in-house team? Are they new to Drupal or want to make better use of best practices, such as automated testing and test-driven development?

-

Do you want to gain exposure by contributing to open-source software, like Drupal, but don't know where to start?

- - {# Dream #} - - {# Fix #} -

I offer team coaching on an ongoing monthly basis for a fixed price. You can sign up for as long as you need and cancel at any time.

- - {# 1st call to action #} -
- {% include 'button.html.twig' with { - position: 'centre', - text: 'Apply now', - url: page.form_url, - withArrow: true, - } %} -
-
- -
-

What is included?

- -
    -
  • Unlimited, 24/7 access to me via email or Slack, with responses within one business day.
  • -
  • Regular 1-on-1 check-in calls and pair/mob programming sessions.
  • -
  • Reviews and feedback of relevant code, flowcharts, technical design documents, etc.
  • -
- -

Some things I can help with

- -
    -
  • Developing custom Drupal modules and themes.
  • -
  • Drupal upgrades and migrations.
  • -
  • Automated testing and test-driven development.
  • -
  • CSS and front-end development.
  • -
  • Implementing and using component libraries, such as Fractal.
  • -
  • Continuous integration, delivery and deployment.
  • -
  • Local environment and CI pipeline setup.
  • -
  • Infrastructure automation, using tools like Terraform and Pulumi.
  • -
  • Automating and simplifying repetitive or complex tasks.
  • -
  • Contributing to open-source projects, such as Drupal.
  • -
-
- - {# Social proof #} - - {# Overcome objections #} - -
-

How does it work?

- -
    -
  • The cost of team coaching is £{{ page.price|number_format() }} per month, paid in advance.
  • -
  • You apply by clicking on the button below and submitting the application form, telling me about your team and what you want to achieve.
  • -
  • I'll review your application, usually within one working day.
  • -
  • If successful, you make your first payment and book your onboarding call where we can define some key objectives and metrics.
  • -
- -
- {% include 'button.html.twig' with { - position: 'centre', - text: 'Apply now', - url: page.form_url, - withArrow: true, - } %} -
- -

If you need lighter-touch guidance and advice, I also offer one-time consulting calls and pair programming sessions.

-
- - {% include 'testimonials' with { - tag: 'coaching', - limit: 5, - title: 'What others have said', - } %} - - {# Uniqueness #} - -
-

Frequently Asked Questions

- - {% for faq in page.faqs %} -
  • -

    {{ faq.0 }}

    - -

    {{ faq.1|raw }}

    -
  • - {% endfor %} -
    - -
    -

    Who am I?

    - -
      -
    • I'm an Acquia-certified Drupal expert with {{ macros.yearsExperience }} years of professional development experience.
    • -
    • I'm a former Drupal Association employee who was responsible for improving and maintaining Drupal.org.
    • -
    • I'm a Drupal core contributor and maintain numerous Drupal projects, including the Override Node Options module, which is used on over 38,000 websites.
    • -
    • I'm a multiple-time DrupalCon speaker who regularly presents talks and workshops at conferences and meetups.
    • -
    • I have experience working in in-house teams, such as Transport for Wales and the Drupal Association.
    • -
    • I have experience working for and with software development agencies.
    • -
    -
    - - {# 2nd CTA #} - -
    - {% include 'button.html.twig' with { - position: 'centre', - text: 'Apply now', - url: page.form_url, - } %} -
    - - {% include 'testimonials' with { - tag: 'coaching', - offset: 5, - title: 'More that others have said', - } %} - - {# Urgency #} -
    -

    Availability is limited

    - -

    I'm only available for a few team coaching engagements per year.

    -

    Scheduling is first come, first served, so the sooner you book your time slot, the sooner you will have the answers you need to upskill your team and deliver better software.

    -

    If you have any questions, feel free to send me an email.

    -
    -
    -{% endblock %} - -{% block content_top %} - {% include 'message.html.twig' %} -{% endblock %} From 8e2835d06b947cf2db489b310c2674193e784e11 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Tue, 19 Mar 2024 23:58:01 +0000 Subject: [PATCH 296/501] Add daily email for 2024-03-19 Drupal Commerce: not just for selling t-shirts and hats --- source/_daily_emails/2024-03-19.md | 37 ++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 source/_daily_emails/2024-03-19.md diff --git a/source/_daily_emails/2024-03-19.md b/source/_daily_emails/2024-03-19.md new file mode 100644 index 000000000..e8167ae5c --- /dev/null +++ b/source/_daily_emails/2024-03-19.md @@ -0,0 +1,37 @@ +--- +title: Drupal Commerce: not just for selling t-shirts and hats +date: 2024-03-19 +permalink: archive/2024/03/19/drupal-commerce-not-just-for-selling-t-shirts-and-hats +tags: + - software-development + - drupal + - drupal-commerce + - php +cta: subscription +snippet: | + Drupal Commerce can do so much more than just selling t-shirts, hats and furniture. +--- + +I recently had Ryan Szrama as a guest on the [Beyond Blocks podcast][podcast]. + +Ryan is the CEO of Centarro - the company behind Drupal Commerce, the eCommerce platform built on the Drupal CMS. + +I've used Drupal Commerce for a number of projects since it was released in 2011, as well as Ubercart before that. + +One of the major things I like about it is its flexibility. + +The Commerce Kickstart distribution is a great way to create a demo Drupal Commerce project that shows a typical eCommerce store selling everything from books to hats, furniture and inflatable flamingos. + +I've used Drupal Commerce for these typical scenarios but also for some non-typical ones. + +I created a multi-site Drupal Commerce store for a gadget insurance company, dealing with many products and product variations. I built a custom Vue.js form that created an order with the required items before passing customers to a Drupal Commerce checkout flow. + +I created a yearly photography competition website that photographers can enter by purchasing a product and uploading their photographs to the order. I built custom judging functionality, which allows jurors to score each entry and the site owner to see the totals and which submission won the competition. + +I created an events management and booking website where each event was a product with different variations based on the different prices - early bird, regular and last minute. Each event had a maximum number of places and, potentially, a waitlist. + +This website also included a loyalty scheme for event organisers and attendees, who received coupons after organising or attending a certain number of events. + +Drupal Commerce can do a lot and isn't just selling t-shirts, hats, books or furniture. + +[podcast]: {{site.url}}/podcast/13-ryan-szrama-centarro From b80cbd82e83c328b2f6ea73bdabdc2a050ad05ba Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Wed, 20 Mar 2024 00:05:43 +0000 Subject: [PATCH 297/501] Fix title --- source/_daily_emails/2024-03-19.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/_daily_emails/2024-03-19.md b/source/_daily_emails/2024-03-19.md index e8167ae5c..6ad88f89b 100644 --- a/source/_daily_emails/2024-03-19.md +++ b/source/_daily_emails/2024-03-19.md @@ -1,5 +1,5 @@ --- -title: Drupal Commerce: not just for selling t-shirts and hats +title: 'Drupal Commerce: not just for selling t-shirts and hats' date: 2024-03-19 permalink: archive/2024/03/19/drupal-commerce-not-just-for-selling-t-shirts-and-hats tags: From a80acfe8959410b5d3e8bcf2f56b3b143bdc78bb Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Wed, 20 Mar 2024 02:12:14 +0000 Subject: [PATCH 298/501] Update page title --- source/_pages/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/_pages/index.md b/source/_pages/index.md index c741b18a9..ed903684d 100644 --- a/source/_pages/index.md +++ b/source/_pages/index.md @@ -1,5 +1,5 @@ --- -title: Get Unlimited Drupal Consulting for a Fixed Monthly Price +title: Unlimited Drupal Consulting for a Fixed Monthly Price permalink: / meta: title: Unlimited Drupal Consulting by Oliver Davies From cf9490be1276270944cb17dca47fd1cbe028f584 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Wed, 20 Mar 2024 02:15:40 +0000 Subject: [PATCH 299/501] Bold text --- source/_pages/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/_pages/index.md b/source/_pages/index.md index ed903684d..80a8b8fca 100644 --- a/source/_pages/index.md +++ b/source/_pages/index.md @@ -85,7 +85,7 @@ faqs: {% block content %} - In less time than it takes to post on a job board, and for a fraction of the cost, get unlimited access to a certified Drupal development expert, core contributor and multiple-time DrupalCon speaker for a fixed monthly fee. No surprises. Cancel anytime. + In less time than it takes to post on a job board, and for a fraction of the cost, get **unlimited access to a certified Drupal development expert**, core contributor and multiple-time DrupalCon speaker for a fixed monthly fee. No surprises. Cancel anytime. --- From d6d48270519d90b9609c0bfe60a0570a5fbeb854 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Wed, 20 Mar 2024 16:44:16 +0000 Subject: [PATCH 300/501] Add Rob Allen episode --- .../15-rob-allen-domain-driven-design.md | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 source/_podcast_episodes/15-rob-allen-domain-driven-design.md diff --git a/source/_podcast_episodes/15-rob-allen-domain-driven-design.md b/source/_podcast_episodes/15-rob-allen-domain-driven-design.md new file mode 100644 index 000000000..b0a540b71 --- /dev/null +++ b/source/_podcast_episodes/15-rob-allen-domain-driven-design.md @@ -0,0 +1,49 @@ +--- +date: 2024-03-20 +topic: Domain-Driven Design +guests: + - Rob Allen +transistor: + id: 0de0b405 +links: + - + - Rob's blog + - https://akrabat.com + - + - Nineteen Feet + - https://19ft.com +talking_points: + - Rebuild vs. rewrite. + - Writing good commit messages. + - Are code comments useful? + - Technical Design Documents and ADRs. + - Ubiquitous language and Domain-Driven Design. + - PHP UK, PHP South West, conferences and user groups. + - DDD in Drupal? + - DRY and YAGNI. + - When to refactor? +quotes: + - I quite like legacy projects because i think they've already proved their worth in the marketplace. (RA) + - I general, I think that rewriting the wrong approach nearly every single time. (RA) + - Things are so impermanent. The only things you can trust are in the source code and what's in the revision history of that source code. + - We have the "what" but we don't have the "why". (OD) + - As you do this for longer, I think you start picking up on what you wish you'd written in the past. (RA) + - I think nearly everything related to software development that really matters is invariably about communication. (RA) + - Nearly everything that results in good quality software is because good communication works. (RA) + - Ubiquitous language is using the same language the specialists are using. (RA) + - An awful lot about DDD is trying to get the communication right. (RA) + - If you pretend it's not happening, it doesn't mean it's not happening - just that you're ignoring the problem. (RA) + - It's cheaper to fix things earlier in the process. (RA) + - User groups are such a good community resource. We get to try thing (talks) out. (RA) + - A conference gives you a focused block of time to learn something. (RA) + - I think the tenets of DDD can are important regardless (of the size of the project). (RA) + - We can refactor our way out, but now the overall time is longer. (RA) + - Time spent upfront is tangible effect on the time spent later. (RA) + - The biggest one [benefit of DDD] is that you end up with a project that's fit for purpose. + - You're way more likely to deliver a project that does what the customer needs if you have listened and understood what they said. (RA) + - It's in the customer's best interest for you to get it right the first time. (OD) + - You have to be proactive. It doesn't happen by default. (RA) +chapters: [] +--- + +This week, Oliver discusses Domain-Driven Design with PHP UK speaker, Rob Allen. From 61faa23c8fca4dca92ba0f8d6725eab68d565ead Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Thu, 21 Mar 2024 00:29:44 +0000 Subject: [PATCH 301/501] Add daily email for 2024-03-20 What is technical debt? --- source/_daily_emails/2024-03-20.md | 37 ++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 source/_daily_emails/2024-03-20.md diff --git a/source/_daily_emails/2024-03-20.md b/source/_daily_emails/2024-03-20.md new file mode 100644 index 000000000..ade534911 --- /dev/null +++ b/source/_daily_emails/2024-03-20.md @@ -0,0 +1,37 @@ +--- +title: What is technical debt? +date: 2024-03-20 +permalink: archive/2024/03/20/what-is-technical-debt +tags: + - software-development + - clean-code +cta: ~ +snippet: | + How do _you_ define technical debt? I can think of several potential definitions. +--- + +How do you define legacy code? + +Is it code written by previous Developers who worked on the codebase? + +Is it code you wrote last week or last month? + +Is it code for features everyone no longer uses? + +Is it the "old" part of the application that no one wants to work on? + +It is any code that's not nice to work on or difficult to change? + +Is it code written with different conventions to your current ones or in a different style? + +Is it any code that doesn't have automated tests or wasn't written with test-driven development? + +Is it code built with outdated tooling or frameworks (like CSS libraries) that were popular then but have since been replaced by something newer? + +## Here's the thing + +These are just some of the potential definitions I can think of. + +The term "legacy code" and others, such as "technical debt", often mean different things. + +What's your definition? Reply and let me know. From 0bdfee75a5a24baca48ea85c619ec0f50fe638cb Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Thu, 21 Mar 2024 00:37:29 +0000 Subject: [PATCH 302/501] Fix title --- source/_daily_emails/2024-03-20.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/_daily_emails/2024-03-20.md b/source/_daily_emails/2024-03-20.md index ade534911..d2f933caf 100644 --- a/source/_daily_emails/2024-03-20.md +++ b/source/_daily_emails/2024-03-20.md @@ -1,13 +1,13 @@ --- -title: What is technical debt? +title: What is legacy code? date: 2024-03-20 -permalink: archive/2024/03/20/what-is-technical-debt +permalink: archive/2024/03/20/what-is-legacy-code tags: - software-development - clean-code cta: ~ snippet: | - How do _you_ define technical debt? I can think of several potential definitions. + How do _you_ define legacy code? I can think of several potential definitions. --- How do you define legacy code? From 1c46fabec45f1b7d36c7f37c65681067c5c96fb6 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Thu, 21 Mar 2024 13:10:16 +0000 Subject: [PATCH 303/501] Add initial what's included in monthly consulting --- source/_pages/index.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/source/_pages/index.md b/source/_pages/index.md index 80a8b8fca..031a4c1c6 100644 --- a/source/_pages/index.md +++ b/source/_pages/index.md @@ -81,6 +81,8 @@ faqs: - Do you have a refund policy? - |- Yes, of course. If you're unhappy with my work for any reason during your first month of service, just say the word, and I'll give you a full refund. No questions asked. +urls: + exploratory_call: https://savvycal.com/opdavies/drupal-consulting-exploratory-call --- {% block content %} @@ -95,6 +97,20 @@ I've contributed code to Drupal core and written popular contributed Drupal modu --- +## What does it include? + +{# TODO: add more information about each of these. #} + +- Analysis and audit +- Roadmap and planning +- Implementation and maintenance +- Team training +- Ongoing advisory + +**Looking for something else?** I also offer fixed-scope consulting engagements and one-time advisory calls. [Book a short exploratory call]({{page.urls.exploratory_call}}) to see if we'd be a good match. + +--- + ## How it works ### Make as many requests as you like From d224d821140e064ed2e14c0596c91920ae430a5b Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Thu, 21 Mar 2024 13:12:49 +0000 Subject: [PATCH 304/501] Update to PHP 8.2 --- build.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.yaml b/build.yaml index 6d2f7bda5..847a92031 100644 --- a/build.yaml +++ b/build.yaml @@ -7,8 +7,8 @@ flake: packages: - nodePackages.pnpm - nodejs - - php81 - - php81Packages.composer + - php82 + - php82Packages.composer git: ignore: From cab143bd2588c2bcefaf61fcb36b9f51542d7651 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Fri, 22 Mar 2024 15:08:57 +0000 Subject: [PATCH 305/501] Add Override Node Options module text --- source/_pages/index.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/_pages/index.md b/source/_pages/index.md index 031a4c1c6..6be77060d 100644 --- a/source/_pages/index.md +++ b/source/_pages/index.md @@ -95,6 +95,8 @@ urls: I've contributed code to Drupal core and written popular contributed Drupal modules and themes, PHP and JavaScript libraries, and Tailwind CSS plugins. +For example, the Override Node Options module is used on around 40,000 active Drupal websites. + --- ## What does it include? From 6c7f1d87b549725c3ac8880bfcca6c50b07fd5e7 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Fri, 22 Mar 2024 15:15:53 +0000 Subject: [PATCH 306/501] Add more information --- source/_pages/index.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/source/_pages/index.md b/source/_pages/index.md index 6be77060d..39d6383af 100644 --- a/source/_pages/index.md +++ b/source/_pages/index.md @@ -62,7 +62,7 @@ faqs: - - What if I don't like how something turned out? - |- - Development is an iterative process. Unlike an agency that will charge you extra for change orders, you get unlimited revisions until you’re happy with the work. + Development is an iterative process. Unlike an agency that will charge you extra for change orders, you get unlimited revisions until you're happy with the work. - - What if I only have a single request? - |- @@ -103,11 +103,13 @@ For example, the Override Node Options module is used on around 40,000 active Dr {# TODO: add more information about each of these. #} -- Analysis and audit -- Roadmap and planning -- Implementation and maintenance -- Team training -- Ongoing advisory +- **Analysis & Audit**. I can help you identify expensive bottlenecks, hidden issues, and potential problems with your site. I'll put together a detailed report with actionable next-steps on how to fix any issues. +- **Roadmap & Planning**. We'll identify your goals and challenges, and I'll put together a custom roadmap to help you get there. +- **Implementation and Maintenance**. I can build your project for you, or work with your engineering team to accelerate your progress. +- **Team Training**. Give your team the skills they need to get more done. Grow, retain, and attract talented developers. +- **Ongoing Advisory**. Throughout the duration of your project, I'll be available to review progress, answer questions, recommend tools and processes, share emerging best practices, and keep your project on the right track. + +{# TODO: I don't want people to book an advisory call before an advisory call or pair programming session. #} **Looking for something else?** I also offer fixed-scope consulting engagements and one-time advisory calls. [Book a short exploratory call]({{page.urls.exploratory_call}}) to see if we'd be a good match. From d92d03debc8fc1a48695e1c2474f111dbd711d26 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Fri, 22 Mar 2024 15:17:09 +0000 Subject: [PATCH 307/501] Run `git push` whilst publishing changes --- run.local | 1 + 1 file changed, 1 insertion(+) diff --git a/run.local b/run.local index ed2e7cbe4..bea5706bd 100755 --- a/run.local +++ b/run.local @@ -68,6 +68,7 @@ function publish { export APP_ENV=production tag-release + git push git stash From 0b90bb11bba742f55b14ba84773f23b7d4a2a1c5 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Fri, 22 Mar 2024 15:23:43 +0000 Subject: [PATCH 308/501] Fix button hover styling --- source/_includes/button.html.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/_includes/button.html.twig b/source/_includes/button.html.twig index 850a51160..02afed38c 100644 --- a/source/_includes/button.html.twig +++ b/source/_includes/button.html.twig @@ -1,5 +1,5 @@
    - + {{ text|raw }} From 36360ae67567a880e5f4a06c01db68f7fd72847d Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Fri, 22 Mar 2024 15:30:47 +0000 Subject: [PATCH 309/501] Fix image ring colour --- source/_includes/about-me.html.twig | 2 +- source/_includes/testimonials.html.twig | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/_includes/about-me.html.twig b/source/_includes/about-me.html.twig index 641486751..9af1501b9 100644 --- a/source/_includes/about-me.html.twig +++ b/source/_includes/about-me.html.twig @@ -6,7 +6,7 @@
    - Picture of Oliver + Picture of Oliver
    diff --git a/source/_includes/testimonials.html.twig b/source/_includes/testimonials.html.twig index f8c638ed7..febd5f207 100644 --- a/source/_includes/testimonials.html.twig +++ b/source/_includes/testimonials.html.twig @@ -38,7 +38,7 @@ {% if testimonial.image %} - Photo of {{ testimonial.name }} + Photo of {{ testimonial.name }} {% endif %} From bfabedd1d560eb5fba8942400f54c519f465f507 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Fri, 22 Mar 2024 15:34:57 +0000 Subject: [PATCH 310/501] Remove space at the top of the page --- source/_pages/index.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/source/_pages/index.md b/source/_pages/index.md index 39d6383af..52bf27da7 100644 --- a/source/_pages/index.md +++ b/source/_pages/index.md @@ -224,7 +224,3 @@ And we'll figure out what's best for you. {# TODO: add daily subscription form #} {% endblock %} - -{% block content_top %} - {% include 'message.html.twig' %} -{% endblock %} From 9b48a3306d0e0ea8e9a95a1e9ed565231ccf82a7 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Fri, 22 Mar 2024 15:41:53 +0000 Subject: [PATCH 311/501] Re-add Drupal Association text --- source/_includes/drupal-association.html.twig | 7 +++++++ source/_pages/index.md | 1 + 2 files changed, 8 insertions(+) create mode 100644 source/_includes/drupal-association.html.twig diff --git a/source/_includes/drupal-association.html.twig b/source/_includes/drupal-association.html.twig new file mode 100644 index 000000000..e213d172a --- /dev/null +++ b/source/_includes/drupal-association.html.twig @@ -0,0 +1,7 @@ +
    +

    Work with me and support the Drupal project

    + +
    +
    diff --git a/source/_pages/index.md b/source/_pages/index.md index 52bf27da7..24c51fd48 100644 --- a/source/_pages/index.md +++ b/source/_pages/index.md @@ -223,4 +223,5 @@ And we'll figure out what's best for you. {# TODO: add daily subscription form #} +{% include 'drupal-association' %} {% endblock %} From cc2bd7da84008896b8e53911ed512f8a1736ee52 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Fri, 22 Mar 2024 15:43:19 +0000 Subject: [PATCH 312/501] Bump assets version --- app/config/sculpin_site.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/config/sculpin_site.yml b/app/config/sculpin_site.yml index e1029f3c2..c08a940c4 100644 --- a/app/config/sculpin_site.yml +++ b/app/config/sculpin_site.yml @@ -6,7 +6,7 @@ url: http://localhost:8000 assets: url: '%url%' - version: 3 + version: 4 banner_text: | In less than 12 months, Drupal 7 will be end-of-life and no longer supported. From 6948440877a30c33c251f319a773aa0c42b4923e Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Fri, 22 Mar 2024 15:47:21 +0000 Subject: [PATCH 313/501] Remove banner --- app/config/sculpin_site.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/config/sculpin_site.yml b/app/config/sculpin_site.yml index c08a940c4..42c762f25 100644 --- a/app/config/sculpin_site.yml +++ b/app/config/sculpin_site.yml @@ -8,9 +8,7 @@ assets: url: '%url%' version: 4 -banner_text: | - In less than 12 months, Drupal 7 will be end-of-life and no longer supported. - Plan your upgrade to Drupal 10 now. +banner_text: ~ ctas: call: | From 2ab7f0d05e4dcef84afeb38c44f48bdc62916ebf Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Sat, 23 Mar 2024 12:44:27 +0000 Subject: [PATCH 314/501] Add daily email for 2024-03-21 Git hooks - yay or nay? --- source/_daily_emails/2024-03-21.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 source/_daily_emails/2024-03-21.md diff --git a/source/_daily_emails/2024-03-21.md b/source/_daily_emails/2024-03-21.md new file mode 100644 index 000000000..2f0488e75 --- /dev/null +++ b/source/_daily_emails/2024-03-21.md @@ -0,0 +1,29 @@ +--- +title: Git hooks - yay or nay? +date: 2024-03-21 +permalink: archive/2024/03/21/git-hooks---yay-or-nay +tags: + - software-development + - git +cta: ~ +snippet: | + Are you in favour of Git hooks or not? +--- + +Many people are very for or against Git hooks - scripts that run automatically on events such as pre-commit and pre-push. + +Commonly, they are used for running tasks such as altering a commit message or running before committing automated tests and static analysis before pushing a commit. + +I'm on the fence. + +I've used them and added support for them to Build Configs, but I don't feel strongly about them. + +They are awkward to set up (you need to edit the configuration for them to work) and can be easily disabled or bypassed. + +Some people think it's the Developer's responsibility to run the tasks before pushing changes or that they'll be run in a CI pipeline, so why would they need to be run locally? + +As I write many small commits and push changes regularly, I can find hooks irritating and prefer to use watchers instead with tools like `watchexec` and `entr`. + +There are also tools like Captain Hook that are built to manage Git hooks. Maybe, I should investigate it more. + +What do you think? Are you yay or nay for Git hooks? From b5bfd7337066fd5279dc4534e9d6c640474ea5e9 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Sun, 24 Mar 2024 18:00:13 +0000 Subject: [PATCH 315/501] Add daily email for 2024-03-22 Watching all the things --- source/_daily_emails/2024-03-22.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 source/_daily_emails/2024-03-22.md diff --git a/source/_daily_emails/2024-03-22.md b/source/_daily_emails/2024-03-22.md new file mode 100644 index 000000000..c5eaca4ff --- /dev/null +++ b/source/_daily_emails/2024-03-22.md @@ -0,0 +1,21 @@ +--- +title: Watching all the things +date: 2024-03-22 +permalink: archive/2024/03/22/watching-all-the-things +tags: + - software-development + - php +cta: ~ +snippet: | + For some things, I use watch commands. For others, I have integrations in ny text editor. +--- + +[In yesterday's email][yesterday], I mentioned that I use watch commands such as `nodemon`, `watchexec` and `entr` whilst developing to run commands automatically when I change code. + +For example, running `find web/modules/custom | entr ./run test` will re-run my test suite when any custom module changes. + +This works well for tests, but for other checks, such as static analysis with PHPStan or coding standards with PHPCS, I have integrations in Neovim, and I get real-time feedback as I code. + +If a line fails static analysis or coding standards, a diagnostic message is shown so I can fix it immediately, and I don't need to use a watcher or wait for my CI pipeline to fail. + +[yesterday]: {{site.url}}/archive/2024/03/21/git-hooks---yay-or-nay From 43af028670eb89c3d906b11e178a1042c4cdda1a Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Sun, 24 Mar 2024 22:46:34 +0000 Subject: [PATCH 316/501] Add daily email for 2024-03-23 Write programs that do one thing and do it well --- source/_daily_emails/2024-03-23.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 source/_daily_emails/2024-03-23.md diff --git a/source/_daily_emails/2024-03-23.md b/source/_daily_emails/2024-03-23.md new file mode 100644 index 000000000..0065b648f --- /dev/null +++ b/source/_daily_emails/2024-03-23.md @@ -0,0 +1,30 @@ +--- +title: Write programs that do one thing and do it well +date: 2024-03-23 +permalink: archive/2024/03/23/write-programs-that-do-one-thing-and-do-it-well +tags: + - software-development + - unix + - linux +cta: ~ +snippet: | + Write programs that do one thing and do it well. +--- + +Over the last few days, I've written about watchers and running commands such as automated tests when files are changed. + +Some tools have this built in, whilst others don't. + +I've used different tools to do this and recently switched to `entr`. + +The previous one wasn't showing me the output from running Drupal automated tests, which `entr` does. + +I also like that it follows the UNIX philosophy of doing one thing well and working well with other programs. + +For example, to run my automated tests when I change a file, I need to run `find web/modules/custom | entr ./run test`. + +`entr` isn't concerned with how to find the list of files to watch - only what to do with them. + +To get the list of files, I use the `find` command and provide the files to `entr`. + +I also like to do this with my application code. I like to write small modules and libraries with clear boundaries and responsibilities, do their tasks well, and work well with other parts of the application. From b448a91e2d7d648853bd2aaba5a3a5f2fdf6b415 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Tue, 26 Mar 2024 00:00:14 +0000 Subject: [PATCH 317/501] Add daily email for 2024-03-24 Why I don't use a GUI for Git --- source/_daily_emails/2024-03-24.md | 31 ++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 source/_daily_emails/2024-03-24.md diff --git a/source/_daily_emails/2024-03-24.md b/source/_daily_emails/2024-03-24.md new file mode 100644 index 000000000..9cba12388 --- /dev/null +++ b/source/_daily_emails/2024-03-24.md @@ -0,0 +1,31 @@ +--- +title: Why I don't use a GUI for Git +date: 2024-03-24 +permalink: archive/2024/03/24/why-i-dont-use-a-gui-for-git +tags: + - software-development + - git +cta: ~ +snippet: | + Do you use a GUI when working with Git? This is why I don't. +--- + +I've been a Git user since my first full-time Developer position in 2010. + +I've used other version control systems, too, early in my career, but I settled on Git and haven't looked back. + +I've used GUI tools for Git, such as Sourcetree and GitHub Desktop, but I prefer to use Git on the command line instead of a GUI or TUI. + +As a Developer who uses a command-line-focused workflow and works mainly in a terminal, there is less context switching, but I want to focus on learning the tool itself rather than a wrapper around it. + +Some GUIs add their own terminology or functionality, making it difficult for people to debug something on the command line if they experience an issue. + +It's easier to solve problems if you understand the tool itself. + +What if I had a favourite Git GUI that became no longer supported or maintained? + +Would any time spent learning that GUI have been wasted? + +This was also a reason why I switched to using Docker and Docker Compose instead of pre-built wrappers. + +I want to better understand and be efficient with the underlying tool, not only someone else's implementation of it. From b96c5fded823364b581d9935b6d6b7434569bf98 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Tue, 26 Mar 2024 00:06:59 +0000 Subject: [PATCH 318/501] Add Drupal Commerce FAQ --- source/_pages/index.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source/_pages/index.md b/source/_pages/index.md index 24c51fd48..e62049aec 100644 --- a/source/_pages/index.md +++ b/source/_pages/index.md @@ -37,6 +37,10 @@ faqs: Yes, updates are included and will need to be prioritised along with other requests. My suggestion is to do them regularly to reduce the risk of an update breaking your application. + - + - Do you work with Drupal Commerce? + - |- + Yes, I have a lot of experience with Drupal Commerce and have used it for [various types of e-commerce applications](/archive/2024/03/19/drupal-commerce-not-just-for-selling-t-shirts-and-hats). - - Is there a limit to how many requests I can have? - |- From b6120c31dc52c97261128e9576635f8432cef92a Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Tue, 26 Mar 2024 08:56:15 +0000 Subject: [PATCH 319/501] Add daily email for 2024-03-25 Newport City Council running LocalGov Drupal --- source/_daily_emails/2024-03-25.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 source/_daily_emails/2024-03-25.md diff --git a/source/_daily_emails/2024-03-25.md b/source/_daily_emails/2024-03-25.md new file mode 100644 index 000000000..2c52fd496 --- /dev/null +++ b/source/_daily_emails/2024-03-25.md @@ -0,0 +1,23 @@ +--- +title: Newport City Council running LocalGov Drupal +date: 2024-03-25 +permalink: archive/2024/03/25/newport-city-council-running-localgov-drupal +tags: + - software-development + - drupal + - php + - localgov +cta: subscription +snippet: | + Newport City Council is now the first Welsh council to use LocalGov Drupal. +--- + +Last week, it was announced that Newport City Council's (my local council) website is now running on LocalGov Drupal - a distribution for council websites. + +This increases the total to 44 councils in the UK and Ireland. + +Newport is the first Welsh council to use it and the first LocalGov website to be bilingual, serving content in both English and Welsh. + +It's great to see Drupal adoption continue to grow in Wales and within the Welsh public sector, and LocalGov being adopted by more councils across the UK and Ireland. + +I think LocalGov Drupal is a great project and one [I've contributed to previously](https://github.com/localgovdrupal/localgov_alert_banner/pull/225), and I plan to do more in the future. From 5a2008c5a544a3418b394a0b22f00da6d4e61634 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Tue, 26 Mar 2024 19:08:57 +0000 Subject: [PATCH 320/501] Add daily email for 2024-03-26 Let someone else do the work --- source/_daily_emails/2024-03-26.md | 37 ++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 source/_daily_emails/2024-03-26.md diff --git a/source/_daily_emails/2024-03-26.md b/source/_daily_emails/2024-03-26.md new file mode 100644 index 000000000..6d8338a2e --- /dev/null +++ b/source/_daily_emails/2024-03-26.md @@ -0,0 +1,37 @@ +--- +title: Let someone else do the work +date: 2024-03-26 +permalink: archive/2024/03/26/let-someone-else-do-the-work +tags: + - software-development + - drupal + - php + - open-source +cta: ~ +snippet: | + Why would I write something myself if there's already code available that solves my problem? +--- + +Yesterday, I was investigating a CSS issue. + +It's a known issue due to some legacy code in the website's CSS that I'd fixed before but now needed to extend. + +Instead of blindly following the same path and extending my previous fix, I decided to rethink the problem and my approach. + +I re-read my documentation and re-reviewed potential solutions I'd evaluated previously. + +I decided to take a different approach to solving the problem and found an open-source plugin that someone else had written that gave me the functionality I needed instead of writing it from scratch. + +I read the code and did a short spike to see if it worked with the existing configuration. + +After some experimentation, I got it to work and added it to the project. + +## Here's the Thing + +The plugin I used was only 34 lines of code, but these are lines I didn't need to write or will need to maintain. + +If it works, why would I write it myself? + +I'd rather use what someone else has written and contribute if I find a bug or need a new feature. + +That's the benefit of open-source software, and why I use Drupal, PHP, Sculpin, Tailwind CSS, Nix, and others. From 356ce143bc4756bd17940fa63d3ef7197b9cf3f5 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Tue, 26 Mar 2024 23:33:50 +0000 Subject: [PATCH 321/501] Configure tmuxinator --- .tmux | 27 --------------------------- .tmuxinator.yml | 10 ++++++++++ 2 files changed, 10 insertions(+), 27 deletions(-) delete mode 100755 .tmux create mode 100644 .tmuxinator.yml diff --git a/.tmux b/.tmux deleted file mode 100755 index ca108368a..000000000 --- a/.tmux +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash - -set -o errexit -set -o nounset - -session_name="${1:-oliverdavies-uk}" -session_path="${2:-$(pwd)}" - -if tmux has-session -t="${session_name}" 2> /dev/null; then - tmux attach -t "${session_name}" || - tmux switch-client -t "${session_name}" -fi - -tmux new-session -d -s "${session_name}" -n vim -c "${session_path}" - -# 1. Main window: Vim. -tmux send-keys -t "${session_name}:vim" "nvim" Enter -tmux split-pane -t "${session_name}:vim" -h -c "${session_path}" -p 40 -tmux send-keys -t "${session_name}:vim.right" "./run start" Enter - -# 2. General shell use. -tmux new-window -t "${session_name}" -c "${session_path}" - -tmux switch-client -t "${session_name}:vim.left" || - tmux attach -t "${session_name}:vim.left" - -# vim: ft=bash diff --git a/.tmuxinator.yml b/.tmuxinator.yml new file mode 100644 index 000000000..93087edcc --- /dev/null +++ b/.tmuxinator.yml @@ -0,0 +1,10 @@ +name: oliverdavies-uk +root: . + +windows: + - vim: nvim + - workers: + panes: + - ./run start + - ./run npm:build:css + - shell: From 28cd5a45d81d090ef4c6e8527162a4d2d9923df0 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Thu, 28 Mar 2024 22:58:47 +0000 Subject: [PATCH 322/501] Add pain points --- source/_pages/index.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/source/_pages/index.md b/source/_pages/index.md index e62049aec..474f0340e 100644 --- a/source/_pages/index.md +++ b/source/_pages/index.md @@ -91,7 +91,25 @@ urls: {% block content %} - In less time than it takes to post on a job board, and for a fraction of the cost, get **unlimited access to a certified Drupal development expert**, core contributor and multiple-time DrupalCon speaker for a fixed monthly fee. No surprises. Cancel anytime. +{# Pain #} + +- Are bugs and errors on your Drupal website losing you customers? +- Are you stuck on an outdated or unsupported version of Drupal? +- Are you unable to efficiently change your website and spend your time searching for workarounds? +- Does it take too long to release new features and bug fixes? +- Are you considering switching to Drupal from your current CMS and wondering if it's the right choice? + + + +--- + +{# Solution #} + +In less time than it takes to post on a job board, and for a fraction of the cost, get **unlimited access to a certified Drupal development expert**, core contributor and multiple-time DrupalCon speaker for a fixed monthly fee. No surprises. Cancel anytime. --- From 64208660483fb02cc499462e74723165bf03366c Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Thu, 28 Mar 2024 23:19:51 +0000 Subject: [PATCH 323/501] Add daily email for 2024-03-27 Hotfixing without branches --- source/_daily_emails/2024-03-27.md | 62 ++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 source/_daily_emails/2024-03-27.md diff --git a/source/_daily_emails/2024-03-27.md b/source/_daily_emails/2024-03-27.md new file mode 100644 index 000000000..a6a70cb03 --- /dev/null +++ b/source/_daily_emails/2024-03-27.md @@ -0,0 +1,62 @@ +--- +title: Hotfixing without branches +date: 2024-03-27 +permalink: archive/2024/03/27/hotfixing-without-branches +tags: + - software-development + - git +cta: subscription +snippet: | + How do I deal with hotfixes if I don't branch? +--- + +{% block content %} +Last month, I wrote an email explaining [why I don't create branches][post]. + +After sending that email, I received this question from a reader (shared with permission): + +> I'm trying to work at one feature at a time, so I usually don't need feature branches. +> +> There's one thing that's difficult for me, maybe you could tell us something about this in one of your upcoming posts: +> +> How do you deal with hot fixes? +> +> I know, there's keeping features small, commit often and early. But sometimes this isn't possible, and a feature will take e.g. 3 days of work. Now, if there's an urgent bug in production that should be fixed asap, what are you doing? +> +> Git stash probably won't help here, as there might be commits already that would deliver an incomplete feature. + +## Option 1: Worktrees + +Whilst I don't create branches, I do use [Git worktrees][], which allows me to have multiple versions of the code checked out at the same time - similar to having multiple clones of a repository. + +Having multiple worktrees, you don't need to stash the code for your incomplete feature or worry about commits you haven't pushed yet. + +You can create a new worktree, fix the urgent bug and switch back to your `main` worktree when you're finished. + +You'll still need to update the original worktree with your new changes which may result in conflicts - the same as merging or rebasing onto a branch. + +## Option 2: Feature Flags + +My preferred approach is to use [feature flags], a.k.a. feature toggles. + +My wrapping the incomplete feature in a feature flag, it can be deployed but won't be active until the flag is enabled - similar to writing a new Drupal module but not enabling it. + +This is a technique I use often as it works well with trunk-based development and continuous integration and delivery. + +When the incomplete feature is feature-flagged, you can fix the bug and deploy the hotfix without stashing or rebasing any changes, and you can leave the flag disabled. +When the feature is ready, enable the feature flag to activate it and, if you need to turn it off again (maybe that's causing the next bug), you can easily disable it without needing to revert and deploy the code again. + +You can just turn the feature flag back off. + +I hope that helps! + +[git worktrees]: {{site.url}}/archive/2022/08/12/git-worktrees-docker-compose +[feature flags]: {{site.url}}/archive/2022/12/07/separating-releases-from-deployments-with-feature-flags +[post]: {{site.url}}/archive/2024/02/28/why-i-dont-branch +{% endblock %} + +{% block cta %} +P.S. If you want to learn how to use feature flags and continuous integration and delivery, register for team coaching and training as part of my [unlimited monthly Drupal consulting subscription][subscription]. + +[subscription]: {{site.url}}/subscription +{% endblock %} From 7a156200d1eb3d3c1421ec47644eeac42f78b0a7 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Fri, 29 Mar 2024 00:18:35 +0000 Subject: [PATCH 324/501] Add the name for the daily email list --- source/_pages/archive.html | 2 +- source/_pages/daily.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/source/_pages/archive.html b/source/_pages/archive.html index 58693350e..8267ada24 100644 --- a/source/_pages/archive.html +++ b/source/_pages/archive.html @@ -1,5 +1,5 @@ --- -title: Daily Email Archive +title: 'The Daily Drupaler: Email Archive' generator: pagination pagination: max_per_page: 30 diff --git a/source/_pages/daily.md b/source/_pages/daily.md index 9246cd982..18eefffce 100644 --- a/source/_pages/daily.md +++ b/source/_pages/daily.md @@ -1,9 +1,9 @@ --- -title: Register for daily software development emails +title: Sign up for the Daily Drupaler Email List --- {% block content %} - Subscribe to my daily newsletter for software professionals on software development and delivery, DevOps, community, and open-source. + Subscribe to my daily newsletter for software professionals on software development and delivery, Drupal, DevOps, community, and open-source. {% include 'daily-email-form.html.twig' %} {% endblock %} From cb429930b0713852b2687c0b307327e8462400ef Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Sat, 30 Mar 2024 00:24:05 +0000 Subject: [PATCH 325/501] Add daily email for 2024-03-28 Starting to sprinkle JavaScript with Simulus --- source/_daily_emails/2024-03-28.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 source/_daily_emails/2024-03-28.md diff --git a/source/_daily_emails/2024-03-28.md b/source/_daily_emails/2024-03-28.md new file mode 100644 index 000000000..bc7eb86a1 --- /dev/null +++ b/source/_daily_emails/2024-03-28.md @@ -0,0 +1,28 @@ +--- +title: Starting to sprinkle JavaScript with Simulus +date: 2024-03-28 +permalink: archive/2024/03/28/starting-to-sprinkle-javascript-with-simulus +tags: + - software-development + - symfony + - stimulus +cta: ~ +snippet: | + Have you used Stimulus? I took it for a test drivet this week. +--- + +I've been watching the new "Cosmic Coding with Symfony 7" series on SymfonyCasts, and today's video was about Stimulus - a "modest JavaScript framework for the HTML you already have". + +As I'm comfortable with other frameworks, such as Vue.js and Alpine.js, I've usually skipped videos about Stimulus, but today, it caught my eye. + +I was intrigued by it and experimented with it by refactoring a component from a project I am working on. + +I like the organisation Stimulus provides by using JavaScript classes and controllers while keeping things simple in the HTML code. + +I initially did this in Fractal but then created [an example project][repo] using Stimulus with esbuild. It's now on my GitHub profile and includes other tools such as Nix, just, and Tmuxinator. + +After this short evaluation, I like Stimulus and will use it on other components in this project, maybe using third-party Stimulus controllers - either directly or for inspiration. + +Hat tip to Ryan Weaver and SymfonyCasts for showing Stimulus and Nick Janetakis for showing me esbuild. + +[repo]: https://github.com/opdavies/stimulus-esbuild-example From 1084aa502ea6f7ccdb26028ad9eb024b0c404968 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Sun, 31 Mar 2024 10:27:06 +0100 Subject: [PATCH 326/501] Update build configuration files --- build.yaml | 3 -- flake.lock | 95 +++--------------------------------------------------- flake.nix | 30 +++++++---------- 3 files changed, 15 insertions(+), 113 deletions(-) diff --git a/build.yaml b/build.yaml index 847a92031..33fc8f31b 100644 --- a/build.yaml +++ b/build.yaml @@ -14,6 +14,3 @@ git: ignore: - '/source/build/' - '/node_modules/' - -experimental: - createTmuxStartupFile: true diff --git a/flake.lock b/flake.lock index 58815101b..f8efa105a 100644 --- a/flake.lock +++ b/flake.lock @@ -1,82 +1,12 @@ { "nodes": { - "devshell": { - "inputs": { - "nixpkgs": "nixpkgs", - "systems": "systems" - }, - "locked": { - "lastModified": 1695195896, - "narHash": "sha256-pq9q7YsGXnQzJFkR5284TmxrLNFc0wo4NQ/a5E93CQU=", - "owner": "numtide", - "repo": "devshell", - "rev": "05d40d17bf3459606316e3e9ec683b784ff28f16", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "devshell", - "type": "github" - } - }, - "flake-parts": { - "inputs": { - "nixpkgs-lib": "nixpkgs-lib" - }, - "locked": { - "lastModified": 1693611461, - "narHash": "sha256-aPODl8vAgGQ0ZYFIRisxYG5MOGSkIczvu2Cd8Gb9+1Y=", - "owner": "hercules-ci", - "repo": "flake-parts", - "rev": "7f53fdb7bdc5bb237da7fefef12d099e4fd611ca", - "type": "github" - }, - "original": { - "id": "flake-parts", - "type": "indirect" - } - }, "nixpkgs": { "locked": { - "lastModified": 1677383253, - "narHash": "sha256-UfpzWfSxkfXHnb4boXZNaKsAcUrZT9Hw+tao1oZxd08=", + "lastModified": 1711703276, + "narHash": "sha256-iMUFArF0WCatKK6RzfUJknjem0H9m4KgorO/p3Dopkk=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "9952d6bc395f5841262b006fbace8dd7e143b634", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixpkgs-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs-lib": { - "locked": { - "dir": "lib", - "lastModified": 1693471703, - "narHash": "sha256-0l03ZBL8P1P6z8MaSDS/MvuU8E75rVxe5eE1N6gxeTo=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "3e52e76b70d5508f3cec70b882a29199f4d1ee85", - "type": "github" - }, - "original": { - "dir": "lib", - "owner": "NixOS", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs_2": { - "locked": { - "lastModified": 1705856552, - "narHash": "sha256-JXfnuEf5Yd6bhMs/uvM67/joxYKoysyE3M2k6T3eWbg=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "612f97239e2cc474c13c9dafa0df378058c5ad8d", + "rev": "d8fe5e6c92d0d190646fb9f1056741a229980089", "type": "github" }, "original": { @@ -88,24 +18,7 @@ }, "root": { "inputs": { - "devshell": "devshell", - "flake-parts": "flake-parts", - "nixpkgs": "nixpkgs_2" - } - }, - "systems": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" + "nixpkgs": "nixpkgs" } } }, diff --git a/flake.nix b/flake.nix index 0350d50fc..b2bf3657a 100644 --- a/flake.nix +++ b/flake.nix @@ -1,27 +1,19 @@ # Do not edit this file. It is automatically generated by https://www.oliverdavies.uk/build-configs. { - inputs = { - devshell.url = "github:numtide/devshell"; - nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; - }; + description = "A Nix Flake for oliverdavies-uk"; - outputs = inputs@{ flake-parts, ... }: - flake-parts.lib.mkFlake { inherit inputs; } { - imports = [ inputs.devshell.flakeModule ]; + inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; - systems = [ "x86_64-linux" ]; + outputs = { nixpkgs, ... }: + let + system = "x86_64-linux"; + pkgs = nixpkgs.legacyPackages.${system}; - perSystem = { config, self', inputs', pkgs, system, ... }: { - formatter = pkgs.nixfmt; + inherit (pkgs) mkShell; + in { + devShells.${system}.default = + mkShell { buildInputs = with pkgs; [ nodePackages.pnpm nodejs php82 php82Packages.composer ]; }; - devshells.default = { - packages = with pkgs; [ - "nodePackages.pnpm" - "nodejs" - "php82" - "php82Packages.composer" - ]; - }; - }; + formatter.${system} = pkgs.nixfmt; }; } From 999090cb19a4f32ce5f01825b9c6dc939d04d3fb Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Sun, 31 Mar 2024 10:28:05 +0100 Subject: [PATCH 327/501] Rename .tmuxinator.yml --- .tmuxinator.yml => .tmuxinator.yaml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .tmuxinator.yml => .tmuxinator.yaml (100%) diff --git a/.tmuxinator.yml b/.tmuxinator.yaml similarity index 100% rename from .tmuxinator.yml rename to .tmuxinator.yaml From edb27c723b033d552e40031ad7ac04a220857b50 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Sun, 31 Mar 2024 21:52:11 +0100 Subject: [PATCH 328/501] Add daily email for 2024-03-29 How I Git --- source/_daily_emails/2024-03-29.md | 44 ++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 source/_daily_emails/2024-03-29.md diff --git a/source/_daily_emails/2024-03-29.md b/source/_daily_emails/2024-03-29.md new file mode 100644 index 000000000..54ee75520 --- /dev/null +++ b/source/_daily_emails/2024-03-29.md @@ -0,0 +1,44 @@ +--- +title: How I Git +date: 2024-03-29 +permalink: archive/2024/03/29/how-i-git +tags: + - software-development + - git +cta: ~ +snippet: | + Here's more about how I use Git. +--- + + +After [Wednesday's email][wednesday], someone said, "It sounds like you and I use git very differently." So, I wanted to explain what my typical Git workflow is. + +I used to use Git Flow, but now, I almost never create a new branch when starting a new task. + +I keep my workflow as simple as possible by using trunk-based development and working on a single branch as much as I can. + +Before I start, I make sure any uncommitted changes are committed or reset and that the automated tests, static analysis, coding standards checks, etc., are passing so I know I'm starting from a good place. + +Then, I start working on the task. + +I like to work in small steps and make small, regular commits, but I don't always push each individual commit to the remote repository. + +Sometimes, I'll make a number of "work in progress" commits and squash them into one before pushing them. + +I want the time between making and pushing the commit to be as short as possible, and I want each commit to be deployable. + +If I'm doing test-driven development, I'll typically commit each time a test is passing - whether it's adding a new test or extending one. + +I run any tests often whilst writing code to ensure they pass, either using a watch command or a keybinding in Neovim. + +I won't push a commit that would cause the code to not work, a test to fail, or block any other (potentially more urgent) changes from being pushed to production. + +If I push a commit that breaks the CI pipeline, I'll fix it quickly, which is usually possible as the changes are small. + +If not, I'll revert the commit to get back to a deployable state as quickly as possible. + +If I'm going to add a feature flag, I'll usually know that in advance and avoid rushing to add one later if a more urgent task comes in. + +By keeping each commit in a working and deployable state, a change can be feature flagged and deployed but not activated until the feature flag is enabled. + +[wednesday]: {{site.url}}/archive/2024/03/27/hotfixing-without-branches From b612d0229cca3a0b1ded2ebe900eb9c80b74190d Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Mon, 1 Apr 2024 23:24:36 +0100 Subject: [PATCH 329/501] Add daily email for 2024-03-30 Leaving a trail of breadcrumbs --- source/_daily_emails/2024-03-30.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 source/_daily_emails/2024-03-30.md diff --git a/source/_daily_emails/2024-03-30.md b/source/_daily_emails/2024-03-30.md new file mode 100644 index 000000000..aab1be7f8 --- /dev/null +++ b/source/_daily_emails/2024-03-30.md @@ -0,0 +1,23 @@ +--- +title: Leaving a trail of breadcrumbs +date: 2024-03-30 +permalink: archive/2024/03/30/leaving-a-trail-of-breadcrumbs +tags: + - software-development + - git +cta: ~ +snippet: | + A commit log is like leaving a trail of breadcrumbs for yourself as you work on a task. +--- + +A great thing about committing often is that you leave a trail of breadcrumbs for yourself, and you can easily find your way back. + +If you are trying to get a test to pass or fix a bug and you get a bit lost, you can see what you've changed since your last commit instead of everything since you started working on your task. + +If needed, you can discard your changes and reset to your last working commit to try again, similar to using the "test and commit or revert" approach. + +If I commit too often, I can squash them before pushing. + +If I don't commit often enough, I can regret not committing more regularly if I get stuck or lost. + +I'd rather do the former. From 0495aa608b866d723b2ce872bed4ab179ffb8400 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Tue, 2 Apr 2024 23:50:36 +0100 Subject: [PATCH 330/501] Add daily email for 2024-03-31 Making Git work the way you want --- source/_daily_emails/2024-03-31.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 source/_daily_emails/2024-03-31.md diff --git a/source/_daily_emails/2024-03-31.md b/source/_daily_emails/2024-03-31.md new file mode 100644 index 000000000..a2ffadfa3 --- /dev/null +++ b/source/_daily_emails/2024-03-31.md @@ -0,0 +1,29 @@ +--- +title: Making Git work the way you want +date: 2024-03-31 +permalink: archive/2024/03/31/making-git-work-the-way-you-want +tags: + - software-development + - git +cta: ~ +snippet: | + Merge or rebase - which do you use? +--- + +Another question that followed my recent Git emails was, " I assume you use rebase over merge?" + +The short answer is "yes". I like to keep the history of my repositories clean and simple to read by keeping the logs linear and not full of merge commits. + +The longer answer is that I do merges, but only fast-forward merges, at least by default. + +If, when merging, Git can fast-forward my branch to the latest commit without creating a merge commit, it will do so. + +If not, I can then rebase my changes to make them linear and fast-forwardable. Alternatively, if the commits have already been pushed and cannot be overwritten, I can explicitly allow a non-fast-forward merge in that situation. + +I have Git configured to work this way as that's how I want it to work, and that configurability is something I like about Git. + +If you want to see how I have Git configured, my settings are in [my dotfiles repository][dotfiles] (note this file is written in the Nix language as I use Nix to manage my configuration). + +If you're working in a team, I'd suggest having a common configuration for everyone and defined rules for how you're going to use Git (branch names, merge or rebase, etc) to avoid inconsistencies. + +[dotfiles]: https://github.com/opdavies/dotfiles.nix/blob/462eff64f227332d58c7c3652eeaa88b692c064d/lib/shared/modules/git.nix#L95-L135 From d78e7a8001c3dd40178e456bddc23460e9fca34d Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Tue, 2 Apr 2024 23:53:28 +0100 Subject: [PATCH 331/501] Add daily email for 2024-04-01 I'm attending LocalGov Drupal Camp --- source/_daily_emails/2024-04-01.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 source/_daily_emails/2024-04-01.md diff --git a/source/_daily_emails/2024-04-01.md b/source/_daily_emails/2024-04-01.md new file mode 100644 index 000000000..0696fa042 --- /dev/null +++ b/source/_daily_emails/2024-04-01.md @@ -0,0 +1,22 @@ +--- +title: I'm attending LocalGov Drupal Camp +date: 2024-04-01 +permalink: archive/2024/04/01/i-m-attending-localgov-drupal-camp +tags: + - software-development + - drupal + - localgov-drupal + - drupalcamp +cta: ~ +snippet: | + In a few weeks, I'll be attending a LocalGov Drupal Camp in Birmingham, UK. +--- + +On the 24th of April, I'll be attending a LocalGov Drupal Camp in Birmingham, UK. + +As someone keen to get more involved and contribute more to the project, and who's [local council recently switched to LocalGov][newport], I'm looking forward to the chance to learn and contribute on the day and afterwards. + +If you want to attend also, there are a few remaining tickets available and you can [register on Eventbrite][event]. + +[event]: https://www.eventbrite.co.uk/e/localgov-drupal-camp-2024-tickets-847025314517 +[newport]: {{site.url}}/archive/2024/03/25/newport-city-council-running-localgov-drupal From e3ccde801fb59f71cdb4fffcc1a51355aa753485 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Thu, 4 Apr 2024 22:27:04 +0100 Subject: [PATCH 332/501] Add daily email for 2024-04-02 Releasing a new project one page at a time --- source/_daily_emails/2024-04-02.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 source/_daily_emails/2024-04-02.md diff --git a/source/_daily_emails/2024-04-02.md b/source/_daily_emails/2024-04-02.md new file mode 100644 index 000000000..e6a3b9e32 --- /dev/null +++ b/source/_daily_emails/2024-04-02.md @@ -0,0 +1,27 @@ +--- +title: Releasing a new project one page at a time +date: 2024-04-02 +permalink: archive/2024/04/02/releasing-a-new-project-one-page-at-a-time +tags: + - software-development + - drupal +cta: ~ +snippet: | + How do you release a new website? One page at a time. +--- + +How do you release a new project? + +Do you build everything and release everything at once? + +I've used the strategy of building and releasing it a page at a time and running two versions simultaneously. + +The main live version stays running, and you use a tool like NGINX or Cloudflare as a gatekeeper to direct traffic to the correct application - either the current one or the new one - based on the requested page. + +When a page is ready, you add it to the list of pages to serve from the new application to put it live. + +If there's an issue, it is also easy to revert to the original page. + +I've used this approach with my website and for client Drupal upgrade projects, where some pages are on Drupal 7 and some on Drupal 10. + +It's not the right approach for every situation, but it's a useful one to have in the toolkit. From 3e1bb141c5e52084f29a25204f06f468ca98b1ef Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Sun, 7 Apr 2024 00:18:31 +0100 Subject: [PATCH 333/501] Add daily email for 2024-04-03 Switching web servers using Build Configs --- source/_daily_emails/2024-04-03.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 source/_daily_emails/2024-04-03.md diff --git a/source/_daily_emails/2024-04-03.md b/source/_daily_emails/2024-04-03.md new file mode 100644 index 000000000..931428227 --- /dev/null +++ b/source/_daily_emails/2024-04-03.md @@ -0,0 +1,25 @@ +--- +title: Switching web servers using Build Configs +date: 2024-04-03 +permalink: archive/2024/04/03/switching-web-servers-using-build-configs +tags: + - software-development + - build-configs +cta: ~ +snippet: | + Today, I used my Build Configs tool to easily switch a project from one web server to another. +--- + +Have you been in a situation where you needed to switch something in a project, like the type of database or a payment provider? + +Today, I decided to switch a project from NGINX to Apache. + +Usually, this would involve using a different base Docker image, creating new configuration files, and changing things like the root directory for my project. + +But, because I'd built this into [Build Configs], I was able to change a few lines in one file, and when I re-generated the configuration files, this project was running Apache. + +This is an excellent example of why I built this tool: to save time and reduce duplication across my projects. + +For this change, it did both. + +[build configs]: {{site.url}}/talks/building-build-configs From 06466b6db7d7bc51e468a38aede1b3a70f359dca Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Mon, 8 Apr 2024 00:07:21 +0100 Subject: [PATCH 334/501] Add daily email for 2024-04-04 PHP attributes: coming soon to a Drupal version near you --- source/_daily_emails/2024-04-04.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 source/_daily_emails/2024-04-04.md diff --git a/source/_daily_emails/2024-04-04.md b/source/_daily_emails/2024-04-04.md new file mode 100644 index 000000000..483d06617 --- /dev/null +++ b/source/_daily_emails/2024-04-04.md @@ -0,0 +1,24 @@ +--- +title: "PHP attributes: coming soon to a Drupal version near you" +date: 2024-04-04 +permalink: archive/2024/04/04/php-attributes--coming-soon-to-a-drupal-version-near-you +tags: + - software-development + - drupal + - php +cta: ~ +snippet: | + Have you used PHP attributes in Drupal yet? They've started to be available for some plugin types since version 10.2. +--- + +Since Drupal 10.2, the framework has started to adopt PHP attributes - a new feature since PHP 8 - as an alternative to annotations when defining plugins, such as blocks and queues. + +From the 10.2 release notes: + +> Drupal core has started adopting PHP attributes, a modern PHP language feature, to provide better developer experience for plugin annotations. Contributed and custom code can begin adopting this improved API for their plugins, and Block and Action plugins can all be converted to the new API. + +It seems that what's been added so far is the first phase of converting the core's plugins, with more to follow in future versions. + +There are also discussions about supporting both attributes and annotations in Drupal 11. + +I love to see Drupal continuing to evolve and adopt new features from the PHP language and Symfony framework (such as autowiring and autoconfiguration), and I'm looking forward to using attributes in my projects. From 0768ec449876138d64a59c1b1d6fbf4220c0073d Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Mon, 8 Apr 2024 23:57:29 +0100 Subject: [PATCH 335/501] Add daily email for 2024-04-05 One Drupal fits all --- source/_daily_emails/2024-04-05.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 source/_daily_emails/2024-04-05.md diff --git a/source/_daily_emails/2024-04-05.md b/source/_daily_emails/2024-04-05.md new file mode 100644 index 000000000..43f455f16 --- /dev/null +++ b/source/_daily_emails/2024-04-05.md @@ -0,0 +1,20 @@ +--- +title: One Drupal fits all +date: 2024-04-05 +permalink: archive/2024/04/05/one-drupal-fits-all +tags: + - software-development + - drupal + - php +cta: d7eol +snippet: | + Drupal works well for small and simple applications, as well large and compex applications. +--- + +Two of the main things I like about Drupal are its flexibility and scalability. + +It works well for basic applications, such as personal websites and blogs (like mine) and for large, complex applications such as Drupal.org itself and many others. + +I don't need to learn one tool for small or simple projects and another for large or complex projects. + +Drupal is one size fits all. From 54f6b9fa28e75ac9b6a9535ea31be328085d07fb Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Tue, 9 Apr 2024 09:20:20 +0100 Subject: [PATCH 336/501] Add daily email for 2024-04-06 Drupal is a content management framework --- source/_daily_emails/2024-04-06.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 source/_daily_emails/2024-04-06.md diff --git a/source/_daily_emails/2024-04-06.md b/source/_daily_emails/2024-04-06.md new file mode 100644 index 000000000..115c0be89 --- /dev/null +++ b/source/_daily_emails/2024-04-06.md @@ -0,0 +1,28 @@ +--- +title: Drupal is a content management framework +date: 2024-04-06 +permalink: archive/2024/04/06/drupal-is-a-content-management-framework +tags: + - software-development + - drupal + - php +cta: d7eol +snippet: | + Drupal isn't just a content management system. It's a content management framework. +--- + +There used to be a saying: + +If you want to build a website, use . If you want to build , use Drupal. + +I think I've heard it used with all the other content management systems, and it isn't about saying one is better. + +It highlights that Drupal isn't just a content management system - it's a content management framework. + +A framework you use to build your content management system to meet your needs. + +Straight away after installing Drupal, you can create as many content types as you want with as many fields as you want to build as simple or complex a content model as you need. + +You aren't restricted to the default page and article content types or a fixed set of fields. + +That, along with Views - a built-in query builder to build pages and blocks of your content, user login and registration, and JSON:API to allow other applications, such as mobile apps, to access your data, to name a few things, makes Drupal a very powerful option. From 18a63f82aafb84fe2c43f9cedb3c34d890812051 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Wed, 10 Apr 2024 23:52:50 +0100 Subject: [PATCH 337/501] Add daily email for 2024-04-07 Avoiding nesting --- source/_daily_emails/2024-04-07.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 source/_daily_emails/2024-04-07.md diff --git a/source/_daily_emails/2024-04-07.md b/source/_daily_emails/2024-04-07.md new file mode 100644 index 000000000..4e1037b09 --- /dev/null +++ b/source/_daily_emails/2024-04-07.md @@ -0,0 +1,22 @@ +--- +title: Avoiding nesting +date: 2024-04-07 +permalink: archive/2024/04/07/avoiding-nesting +tags: + - software-development + - clean-code +cta: ~ +snippet: | + How many levels of nesting do you have in your code? It's something I try to keep to a minimum. +--- +One of my goals when coding is to reduce the amount of nesting in the code I write. + +I mean both in my PHP code where if conditions and foreach loops can be nested within each other, and CSS and Sass files, which support nesting CSS rules. + +My aim is to have a maximum of two or three levels of indentation, though sometimes this isn't possible. + +Doing so where I can, though, makes my code easier to read and understand and encourages other clean code approaches, such as having small and well-named functions. + +In CSS or Sass, avoiding nesting makes it easier to find a rule I'm looking for instead of having to find how rules have been nested or names have been concatenated - making it hard to search or grep for a string. + +This approach is part of "object callisthenics", which was introduced by Jeff Bay and includes other approaches that I like to follow, such as not using the `else` keyword and other good practices that I like to try and implement when possible.-- From 5939f58741ed5901cccce80339bb52c3ce83b974 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Thu, 11 Apr 2024 23:28:35 +0100 Subject: [PATCH 338/501] Add daily email for 2024-04-08 Come for the software, stay for the community --- source/_daily_emails/2024-04-08.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 source/_daily_emails/2024-04-08.md diff --git a/source/_daily_emails/2024-04-08.md b/source/_daily_emails/2024-04-08.md new file mode 100644 index 000000000..debc116af --- /dev/null +++ b/source/_daily_emails/2024-04-08.md @@ -0,0 +1,23 @@ +--- +title: Come for the software, stay for the community +date: 2024-04-08 +permalink: archive/2024/04/08/come-for-the-software--stay-for-the-community +tags: + - software-development + - drupal + - php +cta: ~ +snippet: Come for the software, stay for the community +--- + +One of my favourite Drupal phrases is "Come for the software, stay for the community". + +I started using Drupal so I could build a website for the Tae Kwon-Do school I was training at, and then started to get involved with the community online and at events, such as local meetups and conferences, such as DrupalCamps and DrupalCon. + +I started to contribute code to Drupal core and contrib projects and was a contribution mentor at my first DrupalCon in Prague in 2017 as well as others since, helping first time Drupal contributors. + +I've got jobs and projects from my involvement with the community, including working for the Drupal Association itself. + +I've met people and travelled to places I otherwise wouldn't have because of the Drupal community, as well as the wider PHP, open source and software development communities, + +I came to Drupal for the code, and stayed for the code and the community. From b7e4de79132fdd6648552c8cd7984ea9f3f02c34 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Fri, 12 Apr 2024 23:29:50 +0100 Subject: [PATCH 339/501] Add daily email for 2024-04-09 Paying it forward --- source/_daily_emails/2024-04-09.md | 36 ++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 source/_daily_emails/2024-04-09.md diff --git a/source/_daily_emails/2024-04-09.md b/source/_daily_emails/2024-04-09.md new file mode 100644 index 000000000..730ff1426 --- /dev/null +++ b/source/_daily_emails/2024-04-09.md @@ -0,0 +1,36 @@ +--- +title: Paying it forward +date: 2024-04-09 +permalink: archive/2024/04/09/paying-it-forward +tags: + - software-development + - drupal + - php +cta: ~ +snippet: | + Now, it's my turn to pay it forward. +--- + +As well as building applications with PHP and Drupal, I spend a lot of time helping others and "paying it forward". + +I write and contribute to open-source software and offer free online pair programming sessions to work on open-source projects. + +I speak at conferences and meetups. + +I create content, including videos and coding live streams. + +I mentor at in-person events, such as DrupalCon, and for bootcamps, such as School of Code. + +I've organised events, such as PHP South Wales and DrupalCamp Bristol, and reviewed session submissions for DrupalCon. + +But I wouldn't have been able to do this without others doing the same when I was learning and getting into software development. + +In July 2008, when I was evaluating technologies, I posted a question on a forum (I was learning HTML, PHP and MySQL then). + +Someone replied and recommended using Drupal, which is how I learned about the project. + +If they hadn't done that, I may not be in the position I am now. + +I might not have found Drupal, contributed to it, worked for the Drupal Association, or spoken at DrupalCon. + +Now, it's my turn to pay it forward. From 07b1a148508a8bfa9fd57c19c08a5b799c6a46bc Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Sat, 13 Apr 2024 23:04:22 +0100 Subject: [PATCH 340/501] Add daily email for 2024-04-10 Resurrecting the Speakerdeck Field module --- source/_daily_emails/2024-04-10.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 source/_daily_emails/2024-04-10.md diff --git a/source/_daily_emails/2024-04-10.md b/source/_daily_emails/2024-04-10.md new file mode 100644 index 000000000..76976ae69 --- /dev/null +++ b/source/_daily_emails/2024-04-10.md @@ -0,0 +1,23 @@ +--- +title: Resurrecting the Speakerdeck Field module +date: 2024-04-10 +permalink: archive/2024/04/10/resurrecting-the-speakerdeck-field-module +tags: + - software-development + - drupal + - php + - open-source +cta: ~ +snippet: | + Resurrecting the Speakerdeck Field Drupal module +--- + +This week, I've resurrected the [Speakerdeck field Drupal module][module]. + +It's a module I wrote in 2017 for the Drupal 8 version of my website to embed Speakerdeck slides on my talk pages. + +My website now runs on Drupal 10, but the great thing is that the same code that worked for Drupal 8 also works for Drupal 9 and 10. + +There was no rewrite for each major version, and as I'm not using any deprecated code, the same code works for all the modern versions mentioned, and it looks like it will for Drupal 11, too. + +[module]: https://www.drupal.org/project/speakerdeck_field From 5d6f775fcd6a3223f0e0d4d658b81909445ecfa8 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Sun, 14 Apr 2024 00:29:35 +0100 Subject: [PATCH 341/501] Add daily email for 2024-04-11 Over 100 ATDC subscribers --- source/_daily_emails/2024-04-11.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 source/_daily_emails/2024-04-11.md diff --git a/source/_daily_emails/2024-04-11.md b/source/_daily_emails/2024-04-11.md new file mode 100644 index 000000000..2da59bf82 --- /dev/null +++ b/source/_daily_emails/2024-04-11.md @@ -0,0 +1,24 @@ +--- +title: Over 100 ATDC subscribers +date: 2024-04-11 +permalink: archive/2024/04/11/over-100-atdc-subscribers +tags: + - software-development + - drupal + - php + - automated-testing + - test-driven-development +cta: ~ +snippet: | + Over 100 people have taken my automated testing in Drupal email course! +--- + +Since launching my Automated Testing in Drupal email course, over 100 people have subscribed and received the free ten daily lessons where I explain how to start from scratch to build a Drupal module with automated tests and test-driven development. + +Thanks to everyone who has taken the course so far and those who have provided feedback. + +Automated testing and test-driven development were game changers for me and enabled me to deliver better projects. + +If you'd like to take the course and learn how to do automated testing in Drupal, you can [register for free][register] and get them direct to your inbox. + +[register]: {{site.url}}/atdc From 5074cc34a97cc4007e5b5c2cff2dead0eb4f9224 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Mon, 15 Apr 2024 22:23:22 +0100 Subject: [PATCH 342/501] Add daily email for 2024-04-12 Drupal Rector and the Project Update Bot --- source/_daily_emails/2024-04-12.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 source/_daily_emails/2024-04-12.md diff --git a/source/_daily_emails/2024-04-12.md b/source/_daily_emails/2024-04-12.md new file mode 100644 index 000000000..76f369373 --- /dev/null +++ b/source/_daily_emails/2024-04-12.md @@ -0,0 +1,24 @@ +--- +title: Drupal Rector and the Project Update Bot +date: 2024-04-12 +permalink: archive/2024/04/12/drupal-rector-and-the-project-update-bot +tags: + - software-development + - drupal + - php +cta: d7eol +snippet: | + How do I know the SpeakerDeck Field module still works on Drupal 10 and 11? +--- + +[In Wednesday's email][wednesday], I said I was resurrecting the Speakerdeck Field module, and the same version works on Drupal 8, 9, 10 and 11. + +How do I know this? + +One of my favourite PHP libraries is Rector - a tool for upgrading code to the newest versions of PHP or, in this case, Drupal using Drupal Rector. + +It runs automatically on modules, including Speakerdeck Field, via the Project Upgrade Bot and generates a set of updates to apply. + +CI pipelines with GitLab CI and a reliable test suite make it much easier to upgrade modules to the latest Drupal version and ensure they still work. + +[wednesday]: {{site.url}}/archive/2024/04/10/resurrecting-the-speakerdeck-field-module From 88d5d58cf04a3107260ad467e5b7abc660d60dbe Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Tue, 16 Apr 2024 00:25:40 +0100 Subject: [PATCH 343/501] Add daily email for 2024-04-13 Rector is not just for Drupal --- source/_daily_emails/2024-04-13.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 source/_daily_emails/2024-04-13.md diff --git a/source/_daily_emails/2024-04-13.md b/source/_daily_emails/2024-04-13.md new file mode 100644 index 000000000..9c6f8f0eb --- /dev/null +++ b/source/_daily_emails/2024-04-13.md @@ -0,0 +1,28 @@ +--- +title: Rector is not just for Drupal +date: 2024-04-13 +permalink: archive/2024/04/13/rector-is-not-just-for-drupal +tags: + - software-development + - drupal + - php +cta: ~ +snippet: | + Why spend time doing manual tasks that can be automated? Instead, save time by using tools like Rector. +--- + +I like framework-agnostic tools. + +I like to reuse knowledge and tools across projects, whether I'm working with Drupal, Symfony, Laravel or Sculpin. + +Rector is one of those tools. + +[Yesterday] I said I use it to create automatic updates to my Drupal module code, but it can be used for other PHP projects, too. + +If you're upgrading a PHP library and want to use promoted constructor properties, for example, Rector can do that for you - and a lot more. + +You define which rules or presets you want to use, run Rector on the code, and it will make those changes. + +Having Rector do this work leaves me free to stay focused on other tasks. + +[yesterday]: {{site.url}}/archive/2024/04/12/drupal-rector-and-the-project-update-bot From 697aeff142336194f5e1d290e6cd99a982a44a40 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Wed, 17 Apr 2024 17:57:17 +0100 Subject: [PATCH 344/501] Add daily email for 2024-04-14 What about updating custom modules and themes? --- source/_daily_emails/2024-04-14.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 source/_daily_emails/2024-04-14.md diff --git a/source/_daily_emails/2024-04-14.md b/source/_daily_emails/2024-04-14.md new file mode 100644 index 000000000..c903593ed --- /dev/null +++ b/source/_daily_emails/2024-04-14.md @@ -0,0 +1,26 @@ +--- +title: What about updating custom modules and themes? +date: 2024-04-14 +permalink: archive/2024/04/14/what-about-updating-custom-modules-and-themes +tags: + - software-development + - drupal + - php +cta: d7eol +snippet: | + But, how do you update custom modules and themes between major modern versions of Drupal? +--- + +[Yesterday's email][yesterday] was about using Drupal Rector and the Automated Project Update bot to update contributed modules. + +But what about custom modules within your application? + +To do this, I use the `drupal-check` tool, which is built on PHPStan, and the Upgrade Status module. + +They scan your custom modules and themes and report any deprecated code within your custom projects - i.e. code that will be removed in a future major version - and tell you what new code to use instead. + +Once you've removed any deprecations, your module or theme will be ready for the next major version of Drupal. + +This is the approach I've used to upgrade numerous websites between major modern versions of Drupal, making small updates to existing code instead of having to rewrite it from scratch. + +[yesterday]: {{site.url}}/archive/2024/04/12/drupal-rector-and-the-project-update-bot From 8063d2220c4f9e839d7f53e991acb2519dae6844 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Thu, 18 Apr 2024 22:13:52 +0100 Subject: [PATCH 345/501] Add daily email for 2024-04-15 A note to open-source software maintainers --- source/_daily_emails/2024-04-15.md | 31 ++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 source/_daily_emails/2024-04-15.md diff --git a/source/_daily_emails/2024-04-15.md b/source/_daily_emails/2024-04-15.md new file mode 100644 index 000000000..248058ca5 --- /dev/null +++ b/source/_daily_emails/2024-04-15.md @@ -0,0 +1,31 @@ +--- +title: A note to open-source software maintainers +date: 2024-04-15 +permalink: archive/2024/04/15/a-note-to-open-source-software-maintainers +tags: + - software-development + - drupal + - php + - open-source +cta: ~ +snippet: | + If you want people to use your open-source software, make it easy to use and update. +--- + +I recently updated a website to the latest version of Drupal. + +Doing so introduced a bug in a contributed module I was using. + +A fix was committed to the 2.x branch of the module, which is still in an unstable alpha phase, and the issue was closed. + +There was no fix for the stable 1.x branch. + +There was no fix backported to the 1.x branch. + +There are breaking changes between the 1.x and 2.x branches that require me to update custom code that uses the module, which I don't want to do yet, and I don't want to update to an unstable version. + +If you maintain open-source software, don't force people to update too quickly. + +If you can, make it easy for people to update to the next major version by keeping breaking changes to a minimum and providing time and clear instructions for doing so. + +If these are too difficult, it could discourage or prevent people from using your software. From 3f8c5a25c0d964adcd7d47b84cba61b1c5e98900 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Thu, 18 Apr 2024 23:26:32 +0100 Subject: [PATCH 346/501] Add daily email for 2024-04-16 Regularly releasing open-source software --- source/_daily_emails/2024-04-16.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 source/_daily_emails/2024-04-16.md diff --git a/source/_daily_emails/2024-04-16.md b/source/_daily_emails/2024-04-16.md new file mode 100644 index 000000000..67b3fba86 --- /dev/null +++ b/source/_daily_emails/2024-04-16.md @@ -0,0 +1,25 @@ +--- +title: Regularly releasing open-source software +date: 2024-04-16 +permalink: archive/2024/04/16/regularly-releasing-open-source-software +tags: + - software-development + - open-source +cta: ~ +snippet: | + As with client applications, I recommend releasing new versions of open-source software as often as possible. +--- + +As with client applications, I recommend releasing new versions of open-source software as often as possible. + +This makes it easier for users to update to newer versions as they can easily see the changes, and less risky as it's easier to roll back or diagnose the problem if there is an issue. + +If you've added a feature or fixed a bug, fewer people will likely use it if it's not within a released version of your project and sat on a development branch or waiting for a tagged release. This means they're not benefiting from the changes and getting value from them. + +You can add feature flags to hide work-in-progress features that you don't want people to use yet, but you don't want to block yourself from releasing a new version until it's complete. + +Regular releases encourage smaller and simpler changes rather than larger and more complex - and potentially breaking - changes. + +I like to have a release day each month to release new versions of my open-source projects, which works well for me. + +I have a recurring task on my To Do list to review my projects and tag and release any new versions that are needed. From 504589b213be3b41f0bd61a5ef6bcc4fc7fb94f9 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Fri, 19 Apr 2024 21:58:57 +0100 Subject: [PATCH 347/501] Add daily email for 2024-04-17 Regular releases encourage contribution --- source/_daily_emails/2024-04-17.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 source/_daily_emails/2024-04-17.md diff --git a/source/_daily_emails/2024-04-17.md b/source/_daily_emails/2024-04-17.md new file mode 100644 index 000000000..05f775cdd --- /dev/null +++ b/source/_daily_emails/2024-04-17.md @@ -0,0 +1,19 @@ +--- +title: Regular releases encourage contribution +date: 2024-04-17 +permalink: archive/2024/04/17/regular-releases-encourage-contribution +tags: + - software-development + - open-source +cta: d7eol +snippet: | + Regular open-source releases encourage contribution to your project. +--- + +As well as writing and maintaining my open-source software projects, I regularly contribute to other people's - including PHP libraries, Drupal modules and Tailwind CSS plugins. + +I'm more likely to contribute to your project if you release new versions regularly. + +Why would I if my changes may sit on a development branch for a long time or not be released at all? + +If I can see that my changes are likely to be released soon, I'm more likely to spend the time and effort to contribute instead of looking for a different solution. From b402989b38a9201591490a75cb390b1163481d60 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Sat, 20 Apr 2024 23:42:31 +0100 Subject: [PATCH 348/501] Add daily email for 2024-04-18 First commit to LocalGov Drupal Microsites --- source/_daily_emails/2024-04-18.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 source/_daily_emails/2024-04-18.md diff --git a/source/_daily_emails/2024-04-18.md b/source/_daily_emails/2024-04-18.md new file mode 100644 index 000000000..12d9aca74 --- /dev/null +++ b/source/_daily_emails/2024-04-18.md @@ -0,0 +1,23 @@ +--- +title: I made my first commit to LocalGov Drupal Microsites +date: 2024-04-18 +permalink: archive/2024/04/18/first-commit-localgov-drupal-microsites +tags: + - software-development + - drupal + - localgov-drupal + - open-source +cta: subscription +snippet: | + This week, I submitted my first change to the LocalGov Microsites distribution. +--- + +This week, I submitted [my first change][pr] to the LocalGov Microsites project. + +It was a small change - correcting a typo that I spotted within the README file. + +It's OK to start small, especially when first working on a new codebase. + +It was a good introduction to the project and the first of what I hope to be numerous contributions to the Microsites distribution and LocalGov Drupal in general. + +[pr]: https://github.com/localgovdrupal/localgov_microsites_project/pull/43 From 65a13b21b60dd2fd7b9926344a39f3b734e09fa0 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Mon, 22 Apr 2024 13:00:00 +0100 Subject: [PATCH 349/501] Update tmux session name --- .tmuxinator.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tmuxinator.yaml b/.tmuxinator.yaml index 93087edcc..ec81cbb0b 100644 --- a/.tmuxinator.yaml +++ b/.tmuxinator.yaml @@ -1,4 +1,4 @@ -name: oliverdavies-uk +name: oliverdavies-uk-sculpin root: . windows: From 3b5c6e429a7a178b758b566a3c9f028152d2c90e Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Mon, 22 Apr 2024 13:00:00 +0100 Subject: [PATCH 350/501] Add Adam's testimonial for the email course --- app/config/sculpin_site.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/config/sculpin_site.yml b/app/config/sculpin_site.yml index 42c762f25..71263cd60 100644 --- a/app/config/sculpin_site.yml +++ b/app/config/sculpin_site.yml @@ -49,6 +49,14 @@ meta: Oliver is an Acquia-certified Triple Drupal expert, core contributor, Developer, Consultant and multiple-time DrupalCon speaker. testimonials: + - + text: | + Well done. You've created a really excellent resource here that has the potential to bring Drupal development forward a huge leap. You’ve managed to simplify and share some often complex seeming issues. + name: Adam Nuttall + title: Drupal Engineer + image: + url: '%site.assets.url%/assets/images/recommendations/adam-nuttall.jpg' + tags: [testing] - text: | I'm liking your short emails. They're just the right length that isn't too distracting but I'm able to consume it in a single glance. From ebab457dea726a7eafe8a9f92cd38977362e112a Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Mon, 22 Apr 2024 19:28:55 +0100 Subject: [PATCH 351/501] Add daily email for 2024-04-19 When should you tag 1.0? --- source/_daily_emails/2024-04-19.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 source/_daily_emails/2024-04-19.md diff --git a/source/_daily_emails/2024-04-19.md b/source/_daily_emails/2024-04-19.md new file mode 100644 index 000000000..649fa0a7d --- /dev/null +++ b/source/_daily_emails/2024-04-19.md @@ -0,0 +1,25 @@ +--- +title: When should you tag 1.0? +date: 2024-04-19 +permalink: archive/2024/04/19/when-should-you-tag-1-0 +tags: + - software-development + - open-source +cta: ~ +snippet: | + When should you tag version 1.0 of your project? +--- + +Something I've seen, both with contributed Drupal modules and other open-source projects, over the past few years is they spend a lot of time in the 0.x versions or releasing alpha and beta versions rather than releasing a 1.0 or stable version. + +I presume it's a concern around backward compatibility and maintaining that once a stable version is released. + +But, if you want people to use your module or upgrade it to the latest version, that's much easier to do once there's a stable version. + +Some organisations prohibit using alpha or unstable versions of projects so, if there isn't a stable version, they wouldn't be able to use it. + +Personally, if I'm using one of my open-source modules, plugins or libraries in production, there should be a stable 1.0 version tagged. + +Once it's in production, I'm already making an implied commitment that it's going to be stable and I won't break everything in the next release, so why not make that explicit and tag a stable release? + +Version numbers are free and nothing is stopping you from deprecating code and releasing a new major version with breaking changes in the future, so go ahead and tag that stable version. From c0d2a7a946992ec74ff9f1032504bf76c82b1197 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Mon, 22 Apr 2024 22:35:57 +0100 Subject: [PATCH 352/501] Add daily email for 2024-04-20 Speaking at DrupalCamp Belgium --- source/_daily_emails/2024-04-20.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 source/_daily_emails/2024-04-20.md diff --git a/source/_daily_emails/2024-04-20.md b/source/_daily_emails/2024-04-20.md new file mode 100644 index 000000000..1c1cfe433 --- /dev/null +++ b/source/_daily_emails/2024-04-20.md @@ -0,0 +1,24 @@ +--- +title: Speaking at DrupalCamp Belgium +date: 2024-04-20 +permalink: archive/2024/04/20/speaking-at-drupalcamp-belgium +tags: + - drupal + - drupalcamp + - public-speaking +cta: ~ +snippet: | + I'm happy to be speaking at DrupalCamp Belgium next month. +--- + +I'm happy to be speaking at DrupalCamp Belgium next month, on the 10th and 11th of May in Ghent. + +I'll be presenting two sessions on [automated testing in Drupal][testing] (Friday) and [Tailwind CSS][tailwind] (Saturday). + +I spoke about testing most recently at DrupalCon Lille, and I'm looking forward to updating my Tailwind talk again to include the latest features within the framework. + +[Tickets are still available][tickets] and it would be great to see you there. + +[testing]: https://www.drupalcamp.be/en/drupalcamp-ghent-2024/schedule/friday +[tailwind]: https://www.drupalcamp.be/en/drupalcamp-ghent-2024/schedule/saturday +[tickets]: https://www.drupalcamp.be/en/drupalcamp-ghent-2024/tickets From de44de5c44dab75666115417898155008d9da1a2 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Wed, 24 Apr 2024 22:47:40 +0100 Subject: [PATCH 353/501] Show the filepath for the new daily email ```shell nvim $(daily "Almost at 100 talks and workshops") ``` --- run.local | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run.local b/run.local index bea5706bd..dcbd163e6 100755 --- a/run.local +++ b/run.local @@ -46,7 +46,7 @@ function create-daily { ${title}" - git log --stat -1 + echo "${filepath}" } function npm:build:css { From b507b2dcb0aa3fd319457ad6d89b1175e5238a1c Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Wed, 24 Apr 2024 22:49:21 +0100 Subject: [PATCH 354/501] Add daily email for 2024-04-21 Almost at 100 talks and workshops --- source/_daily_emails/2024-04-21.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 source/_daily_emails/2024-04-21.md diff --git a/source/_daily_emails/2024-04-21.md b/source/_daily_emails/2024-04-21.md new file mode 100644 index 000000000..3c43cf6a3 --- /dev/null +++ b/source/_daily_emails/2024-04-21.md @@ -0,0 +1,28 @@ +--- +title: Almost at 100 talks and workshops +date: 2024-04-21 +permalink: archive/2024/04/21/almost-at-100-talks-and-workshops +tags: + - software-development + - drupal + - drupalcamp + - php + - public-speaking +cta: ~ +snippet: | + I'm almost at 100 talks and presentations given! +--- + +[My talks page][talks] on my website says I've presented 98 talks and workshops at different events since September 2012. + +Next month, I'm giving two talks at DrupalCamp Ghent. + +One on [automated testing and test-driven development][testing] and another on [Tailwind CSS]. + +This means that these will be my 99th and 100th presentations! + +I enjoy speaking at events such as meetups and conferences, and I hope people have learned from them as well. + +[tailwind css]: {{site.url}}/talks/taking-flight-with-tailwind-css +[testing]: {{site.url}}/talks/tdd-test-driven-drupal +[talks]: {{site.url}}/talks From 50fac6657c0b471b7e30e17331aa62354d3880b6 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Fri, 26 Apr 2024 00:04:16 +0100 Subject: [PATCH 355/501] Add daily email for 2024-04-22 Building websites with PHP and Sculpin --- source/_daily_emails/2024-04-22.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 source/_daily_emails/2024-04-22.md diff --git a/source/_daily_emails/2024-04-22.md b/source/_daily_emails/2024-04-22.md new file mode 100644 index 000000000..120462e6d --- /dev/null +++ b/source/_daily_emails/2024-04-22.md @@ -0,0 +1,22 @@ +--- +title: Building websites with PHP and Sculpin +date: 2024-04-22 +permalink: archive/2024/04/22/building-websites-with-php-and-sculpin +tags: + - software-development + - php + - public-speaking +cta: ~ +snippet: | + The video of my most recent talk at PHPSW has been released. +--- + + +The video of the most recent version of my [Building static websites with PHP and Sculpin][talk] from February's PHP South West meetup [has been released][video]. + +In the talk, I explain what Sculpin is and how it works before a live demo where I rebuild part of the PHPSW website (which I worked on originally). + +If you have any feedback or questions, reply to this email and let me know. + +[talk]: {{site.url}}/talks/building-static-websites-sculpin +[video]: https://youtu.be/axy6ltc9meA?si=FtR4DZ5BVi_Se60J From a7fa9e41835c9f7d72d777a3ce7b906e5b07e474 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Fri, 26 Apr 2024 23:53:51 +0100 Subject: [PATCH 356/501] Add daily email for 2024-04-23 Why use a static site generator --- source/_daily_emails/2024-04-23.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 source/_daily_emails/2024-04-23.md diff --git a/source/_daily_emails/2024-04-23.md b/source/_daily_emails/2024-04-23.md new file mode 100644 index 000000000..1f66c10a1 --- /dev/null +++ b/source/_daily_emails/2024-04-23.md @@ -0,0 +1,28 @@ +--- +title: Why use a static site generator +date: 2024-04-23 +permalink: archive/2024/04/23/why-use-a-static-site-generator +tags: + - software-development + - php + - sculpin +cta: ~ +snippet: | + Why use a static site generator instead of a CMS? +--- + +[In February][yesterday], I spoke at the PHP South West meetup about Sculpin - a static site generator written in PHP. + +Sculpin uses Twig, HTML and Markdown to generate static HTML files that you can upload to any web server. + +You don't need PHP or a database server - making it simpler and cheaper to host compared to a CMS-powered site. + +There's also no database for people to hack into and, as they're just static HTML pages, they are very quick to load. + +The downside is that files need to be created and edited locally or editing Git files on GitHub, etc, as there's no CMS to log into. + +Still, for some projects, static site generators are a great option. + +For Drupal, there's Tome - a module that creates a static website from a Drupal installation, and something I plan to investigate. + +[yesterday]: {{site.url}}/archive/2024/04/22/building-websites-with-php-and-sculpin From 4f318c33f6834c43753087ca5c13716b7ae2be13 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Sat, 27 Apr 2024 13:52:09 +0100 Subject: [PATCH 357/501] Add daily email for 2024-04-24 Testing topic branches in isolation --- source/_daily_emails/2024-04-24.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 source/_daily_emails/2024-04-24.md diff --git a/source/_daily_emails/2024-04-24.md b/source/_daily_emails/2024-04-24.md new file mode 100644 index 000000000..1867cb5cb --- /dev/null +++ b/source/_daily_emails/2024-04-24.md @@ -0,0 +1,27 @@ +--- +title: Testing topic branches in isolation +date: 2024-04-24 +permalink: archive/2024/04/26/testing-topic-branches-in-isolation +tags: + - software-development + - git +cta: ~ +snippet: | + Do you test topic branches in isolation? +--- + +I was recently asked about setting up different testing environments based on topic (a.k.a. feature) branches. + +When a feature or bug fix was finished, it would create a new environment for it to be tested. + +If it passed testing, the topic branch was merged and it would be included in the next release. + +But, there's no guarantee it still works once it's been merged. + +It could conflict with changes from a different topic branch and no longer work - even if it worked when it was tested in isolation on its own branch. + +To ensure it still works, it will need to be tested again. + +So, what is the benefit of testing it in isolation if it needs to be tested again once it's merged? + +This is why I prefer continuous integration and delivery (CI/CD), as I always know all changes work together and not just in isolation. From ab406a87aed9ef9d00fc65b1d1ff500014f6eb62 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Sat, 27 Apr 2024 15:56:06 +0100 Subject: [PATCH 358/501] Add daily email for 2024-04-25 If everyone branches, no-one gets the updates --- source/_daily_emails/2024-04-25.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 source/_daily_emails/2024-04-25.md diff --git a/source/_daily_emails/2024-04-25.md b/source/_daily_emails/2024-04-25.md new file mode 100644 index 000000000..525281f18 --- /dev/null +++ b/source/_daily_emails/2024-04-25.md @@ -0,0 +1,15 @@ +--- +title: If everyone branches, no-one gets the updates +date: 2024-04-25 +permalink: archive/2024/04/25/if-everyone-branches-no-one-gets-updates +tags: + - software-development + - git +cta: ~ +snippet: | + If everyone works on their own branch, no-one gets any changes. +--- + +A common response I get when debating topic branches is "I regularly pull `develop` or `main` and get the latest changes. + +But if everyone is working on individual topic branches instead of the mainline branch, where do the changes come from? From c95636c4bce59de31a7b035d8bd1b5304f65b142 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Sun, 28 Apr 2024 23:49:01 +0100 Subject: [PATCH 359/501] Add daily email for 2024-04-26 Don't cherry-pick features from a branch to deploy --- source/_daily_emails/2024-04-26.md | 31 ++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 source/_daily_emails/2024-04-26.md diff --git a/source/_daily_emails/2024-04-26.md b/source/_daily_emails/2024-04-26.md new file mode 100644 index 000000000..483c78a20 --- /dev/null +++ b/source/_daily_emails/2024-04-26.md @@ -0,0 +1,31 @@ +--- +title: Don't cherry-pick features from a branch to deploy +date: 2024-04-26 +permalink: archive/2024/04/26/don-t-cherry-pick-features-from-a-branch-to-deploy +tags: + - software-development + - git +cta: ~ +snippet: | + Don't cherry-pick features from a branch to deploy +--- + +I previously worked on a project where, after a code change had been reviewed and merged, it was pushed to a UAT environment for the client to test. + +This usually resulted in a group of changes pushed to the UAT environment, waiting for the client to test them. + +They would, and then decide which changes they wanted to be moved to production. + +Maybe changes 1, 2 and 4 would be asked to be deployed, but not 3 or 5. + +Someone would then cherry pick the relevant commits onto the mainline branch and deploy them to production. + +But, if the code isn't the same as on that UAT environment, how do you know it still works? + +Could a commit have been missed or could not including a non-selected commit have caused a regression or unintended side effects? + +`git cherry-pick` isn't a command I use often, and definitely not in this scenario. + +If you want to select which changes go live, feature flags are a better option as you don't need to change the commits or code you're pushing. + +You push all the commits from UAT to production and enable the feature flags for the things you want to release. From 0ab2ade96b7ca979f61f046fcad3cf9ae06f2504 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Mon, 29 Apr 2024 13:00:00 +0100 Subject: [PATCH 360/501] Change heading --- source/_layouts/daily_email.html.twig | 2 +- source/_layouts/post.html.twig | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/_layouts/daily_email.html.twig b/source/_layouts/daily_email.html.twig index 1b922f36c..62d00cfd8 100644 --- a/source/_layouts/daily_email.html.twig +++ b/source/_layouts/daily_email.html.twig @@ -19,7 +19,7 @@ {% block content_bottom %} {% include 'daily-email-form.html.twig' with { intro: 'Sign up here and get more like this delivered straight to your inbox every day.', - title: 'Was this useful?', + title: 'Was this interesting?', } %} {% include 'about-me.html.twig' %} diff --git a/source/_layouts/post.html.twig b/source/_layouts/post.html.twig index 7ffcbe531..37149837d 100644 --- a/source/_layouts/post.html.twig +++ b/source/_layouts/post.html.twig @@ -9,7 +9,7 @@ {% block content_bottom %} {% include 'daily-email-form.html.twig' with { intro: 'Sign up here and get more like this delivered straight to your inbox every day.', - title: 'Was this useful?', + title: 'Was this interesting?', } %} {% include 'about-me.html.twig' %} From fbadfb345dd9f6393cf2d0dd156ae745c8910676 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Mon, 29 Apr 2024 23:44:02 +0100 Subject: [PATCH 361/501] Add daily email for 2024-04-27 Can you make a test fail? --- source/_daily_emails/2024-04-27.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 source/_daily_emails/2024-04-27.md diff --git a/source/_daily_emails/2024-04-27.md b/source/_daily_emails/2024-04-27.md new file mode 100644 index 000000000..ede4afd5d --- /dev/null +++ b/source/_daily_emails/2024-04-27.md @@ -0,0 +1,15 @@ +--- +title: Can you make a test fail? +date: 2024-04-27 +permalink: archive/2024/04/27/can-you-make-a-test-fail +tags: + - software-development + - automated-testing +cta: ~ +snippet: | + Can you make an automated test fail when you want? +--- + +Can you make an automated test fail when you want it to? + +If not, how do you know it's testing the correct thing? From 306b62a5421f6cdb1510198904a7e87d6636292d Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Tue, 30 Apr 2024 18:24:05 +0100 Subject: [PATCH 362/501] Update ATDC testimonials --- app/config/sculpin_site.yml | 16 ++++++++++++---- source/_pages/atdc.md | 2 +- .../images/recommendations/frank-landry.jpg | Bin 0 -> 193455 bytes .../recommendations/matthieu-scarset.jpg | Bin 0 -> 5483 bytes 4 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 source/assets/images/recommendations/frank-landry.jpg create mode 100644 source/assets/images/recommendations/matthieu-scarset.jpg diff --git a/app/config/sculpin_site.yml b/app/config/sculpin_site.yml index 71263cd60..38580b1c6 100644 --- a/app/config/sculpin_site.yml +++ b/app/config/sculpin_site.yml @@ -49,6 +49,14 @@ meta: Oliver is an Acquia-certified Triple Drupal expert, core contributor, Developer, Consultant and multiple-time DrupalCon speaker. testimonials: + - + text: | + The course was very informative. One of the biggest pain points with Drupal testing was that there was no clear and definitive guide on setting up the php unit XML file to get functional and kernel tests working right away. Your guide was fantastic and I will definitely be using it going forward in my module development for work. + name: Frank Landry + title: ~ + image: + url: '%site.assets.url%/assets/images/recommendations/frank-landry.jpg' + tags: [testing, atdc] - text: | Well done. You've created a really excellent resource here that has the potential to bring Drupal development forward a huge leap. You’ve managed to simplify and share some often complex seeming issues. @@ -56,7 +64,7 @@ testimonials: title: Drupal Engineer image: url: '%site.assets.url%/assets/images/recommendations/adam-nuttall.jpg' - tags: [testing] + tags: [testing, atdc] - text: | I'm liking your short emails. They're just the right length that isn't too distracting but I'm able to consume it in a single glance. @@ -85,7 +93,7 @@ testimonials: url: https://matthieuscarset.com image: url: '%site.assets.url%/assets/images/recommendations/matthieu-scarset.jpg' - tags: [testing] + tags: [testing, atdc] - text: | Hi Oliver, we met briefly at the Tech Connect event in London last month. Been reading through a few of your latest posts and have found the messages valuable, especially as we spent the week learning about unit, integration and e2e testing. I have signed up to your mailing list to keep the good advice flowing! @@ -109,7 +117,7 @@ testimonials: title: Senior Software Engineer image: url: '%site.assets.url%/assets/images/recommendations/mike-karthauser.jpg' - tags: [daily, testing, coaching] + tags: [daily, testing, coaching, atdc] - text: | I had the opportunity and good fortune to work with Oliver solving two problems that I was having on a Drupal Commerce site. I have done several Drupal sites using UberCart, but since it is deprecated, I chose to use Commerce. I had searched, posted to forums, and other normal means to find answers to my problems, to no response and to no avail. @@ -164,7 +172,7 @@ testimonials: url: https://www.playingwithpixels.co.uk image: url: '%site.assets.url%/assets/images/recommendations/tawny.jpg' - tags: [testing, coaching, call] + tags: [testing, coaching, call, atdc] - text: | I've worked with Oliver for a number of years on B2C and B2B web projects and he has always demonstrated himself to be an expert in his field. diff --git a/source/_pages/atdc.md b/source/_pages/atdc.md index 3d30afece..1d03c50af 100644 --- a/source/_pages/atdc.md +++ b/source/_pages/atdc.md @@ -20,7 +20,7 @@ Learn to test things like: {% block content_bottom %}
    - {% include 'testimonials' with { tag: 'testing' } %} + {% include 'testimonials' with { tag: 'atdc' } %} {{ parent() }}
    diff --git a/source/assets/images/recommendations/frank-landry.jpg b/source/assets/images/recommendations/frank-landry.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0ec744fcf447457f26c4180033cd28bc8322a409 GIT binary patch literal 193455 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 K;{KfZe-i*9hhmce literal 0 HcmV?d00001 diff --git a/source/assets/images/recommendations/matthieu-scarset.jpg b/source/assets/images/recommendations/matthieu-scarset.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bc6ce770998df7ff372752e1a20f1d207572a763 GIT binary patch literal 5483 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 literal 0 HcmV?d00001 From b7cfed428eb8b52297e72473d7da37bea94b5c3c Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Wed, 1 May 2024 00:32:11 +0100 Subject: [PATCH 363/501] Add daily email for 2024-04-28 --- source/_daily_emails/2024-04-28.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 source/_daily_emails/2024-04-28.md diff --git a/source/_daily_emails/2024-04-28.md b/source/_daily_emails/2024-04-28.md new file mode 100644 index 000000000..cd7090aa4 --- /dev/null +++ b/source/_daily_emails/2024-04-28.md @@ -0,0 +1,15 @@ +--- +title: Replicating a bug with a test +date: 2024-04-28 +permalink: archive/2024/04/28/replicating-a-bug-with-a-test +tags: + - software-development + - automated-testing +cta: ~ +snippet: | + When fixing a bug, do you try to replicate it with a test? +--- + +When fixing a bug, can you write a new failing test to replicate the bug? + +If so, you verify the bug exists, you know when it's fixed (the test passes), and you know you won't introduce the same bug again - otherwise, the new test will fail. From e3793693a70cdada042da6db23de3f0b97348628 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Wed, 1 May 2024 07:12:15 +0100 Subject: [PATCH 364/501] Add daily email for 2024-04-29 Some kind words --- source/_daily_emails/2024-04-29.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 source/_daily_emails/2024-04-29.md diff --git a/source/_daily_emails/2024-04-29.md b/source/_daily_emails/2024-04-29.md new file mode 100644 index 000000000..c492301c2 --- /dev/null +++ b/source/_daily_emails/2024-04-29.md @@ -0,0 +1,27 @@ +--- +title: Some kind words +date: 2024-04-29 +permalink: archive/2024/04/29/some-kind-words +tags: + - software-development + - drupal + - automated-testing + - test-driven-development +cta: atdc +snippet: | + I recently received these kind words someone who recently completed my Drupal automated testing email course. +--- + +I recently received these kind words from Frank Landry, who recently completed my [Drupal automated testing email course][course]: + +> The course was very informative. +> +> One of the biggest pain points with Drupal testing was that there was no clear and definitive guide on setting up the php unit XML file to get functional and kernel tests working right away. +> +> Your guide was fantastic and I will definitely be using it going forward in my module development for work. + +Thanks, Frank, for your feedback. + +It's great to get feedback on my content and to know it's helping and providing value to people. + +[course]: {{site.url}}/atdc From 96f11c7496382bc8d6780c473047d66096cea386 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Wed, 1 May 2024 22:55:47 +0100 Subject: [PATCH 365/501] Add Simon Graham episode --- .../16-simon-graham-weight-loss.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 source/_podcast_episodes/16-simon-graham-weight-loss.md diff --git a/source/_podcast_episodes/16-simon-graham-weight-loss.md b/source/_podcast_episodes/16-simon-graham-weight-loss.md new file mode 100644 index 000000000..70faf7e43 --- /dev/null +++ b/source/_podcast_episodes/16-simon-graham-weight-loss.md @@ -0,0 +1,17 @@ +--- +date: 2024-05-01 +topic: Health, fitness and weight loss +guests: + - Simon Graham +transistor: + id: da3e6309 +links: + - + - simongpt.co.uk + - https://www.simongpt.co.uk +talking_points: [] +quotes: [] +chapters: [] +--- + +This week, Oliver discusses health, fitness and weight loss with Personal Trainer and Weight Loss Coach, Simon Graham. From cc83a4779790e9b4b3d9a1f8bb1539aab199a363 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Thu, 2 May 2024 00:32:31 +0100 Subject: [PATCH 366/501] Add daily email for 2024-04-30 Stepping back into debugging --- source/_daily_emails/2024-04-30.md | 36 ++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 source/_daily_emails/2024-04-30.md diff --git a/source/_daily_emails/2024-04-30.md b/source/_daily_emails/2024-04-30.md new file mode 100644 index 000000000..74068a128 --- /dev/null +++ b/source/_daily_emails/2024-04-30.md @@ -0,0 +1,36 @@ +--- +title: Stepping back into debugging +date: 2024-04-30 +permalink: archive/2024/04/30/stepping-back-into-debugging +tags: + - software-development + - php +cta: ~ +snippet: | + This week, I've set up Xdebug on a new project to help with some complicated debugging. +--- + +In PHP, we have functions like `var_dump()`, `dump()` and `dd()` that are used to debug code and print output to the screen. + +In Drupal, we have functions like `dpm()` and `kint()`, too. + +These functions are great for simple debugging but sometimes I need more, which is when I reach for a step debugger - namely, Xdebug. + +This is common when working in complex legacy code, where you need to be able to see a breakpoint and step through the code to see what path it takes and what the state is at each step. + +## Enter Xdebug + +Xdebug is a tool I use fairly often and something I have configured on an individual project basis. + +This week, I spent some time adding it to a new project and ensured my notes and documentation still worked. + +I use Docker and Docker Compose on Linux, so there are slight changes compared to running PHP natively, so I wanted to make sure it still works. + +I've added my latest setup to my [Drupal Docker Example repository][repo] and plan to add it to my standard [Build Configs] setup for Drupal projects. + +If you use Docker Compose on Linux, it may be useful for you. + +If you haven't tried Xdebug before, I suggest giving it a try and see if improves your debugging. + +[build configs]: {{site.url}}/build-configs +[repo]: https://github.com/opdavies/docker-example-drupal From 4e8c28c9f377d527105e149cd1f8ec86a7119cd2 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Fri, 3 May 2024 00:42:15 +0100 Subject: [PATCH 367/501] Add daily email for 2024-05-01 Broken pipeline? Fix or revert it. --- source/_daily_emails/2024-05-01.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 source/_daily_emails/2024-05-01.md diff --git a/source/_daily_emails/2024-05-01.md b/source/_daily_emails/2024-05-01.md new file mode 100644 index 000000000..406eee04d --- /dev/null +++ b/source/_daily_emails/2024-05-01.md @@ -0,0 +1,30 @@ +--- +title: Broken pipeline? Fix or revert it. +date: 2024-05-01 +permalink: archive/2024/05/01/broken-pipeline-fix-or-revert-it +tags: + - software-development + - continuous-integration + - trunk-based-development +cta: ~ +snippet: | + What do you do if someone's pushed a failing commit and broken the CI pipeline? +--- + +If you're doing trunk-based development where multiple people are committing and pushing work to the same branch, what do you do if you've pushed a commit that fails the checks and breaks the pipeline? + +This is a bad state and needs to be solved as soon as possible as it's causing a problem for everyone else. + +If the pipeline is failing, someone else could push a change and have it fail for a different reason, but wouldn't know. + +The responsibility is on the person who pushed the failing commit to resolve it and get the pipeline passing again as soon as possible. + +You break it, you bought it. + +Hopefully, the changes in the failing commit are small and it's something you can resolve quickly and push a fixed commit. + +If you can't fix it within a few minutes, revert the failing commit and try again. + +What if someone's pushed a failing commit and hasn't fixed it in a timely manner? + +Revert it for them, fix the pipeline and unblock the rest of the team. From 4d954c75b3e4039bc0bde6dbd17268bea666cdf8 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Fri, 3 May 2024 07:44:47 +0100 Subject: [PATCH 368/501] Delete old draft posts One of these was causing Sculpin to keep reloading and re-generating the site. --- ...uilding-oliverdavies-uk-1-initial-setup.md | 19 -- .../configuring-the-reroute-email-module.md | 70 ----- .../debugging-php-docker-xdebug-neovim-dap.md | 102 -------- ...the-last-commit-that-a-patch-applies-to.md | 49 ---- .../neovim-database-plugin-vim-dadbod-ui.md | 5 - ...cquia-dashboard-with-vuejs-tailwind-css.md | 163 ------------ .../updating-override-node-options-tests.md | 239 ------------------ ...ing-feature-flags-in-drupal-development.md | 13 - .../using-traefik-local-proxy-sculpin.md | 64 ----- source/_posts/weeknotes-2021-08-06.md | 32 --- 10 files changed, 756 deletions(-) delete mode 100644 source/_posts/building-oliverdavies-uk-1-initial-setup.md delete mode 100644 source/_posts/configuring-the-reroute-email-module.md delete mode 100755 source/_posts/debugging-php-docker-xdebug-neovim-dap.md delete mode 100644 source/_posts/finding-the-last-commit-that-a-patch-applies-to.md delete mode 100644 source/_posts/neovim-database-plugin-vim-dadbod-ui.md delete mode 100644 source/_posts/rebuilding-acquia-dashboard-with-vuejs-tailwind-css.md delete mode 100644 source/_posts/updating-override-node-options-tests.md delete mode 100644 source/_posts/using-feature-flags-in-drupal-development.md delete mode 100644 source/_posts/using-traefik-local-proxy-sculpin.md delete mode 100644 source/_posts/weeknotes-2021-08-06.md diff --git a/source/_posts/building-oliverdavies-uk-1-initial-setup.md b/source/_posts/building-oliverdavies-uk-1-initial-setup.md deleted file mode 100644 index f9fcf680a..000000000 --- a/source/_posts/building-oliverdavies-uk-1-initial-setup.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: 'Building oliverdavies.uk with Sculpin: Part 1 - initial setup and configuration' -excerpt: | - First part of the "Building oliverdavies.uk" series, covering the initial - Sculpin setup and configuration. -tags: [sculpin] -draft: true -date: ~ ---- - -Based on . - -Uses . - -`app/config/sculpin_kernel.yml`: - -`app/config/sculpin_site.yml`: - -`app/config/sculpin_site_prod.yml`: diff --git a/source/_posts/configuring-the-reroute-email-module.md b/source/_posts/configuring-the-reroute-email-module.md deleted file mode 100644 index 27ece0a8e..000000000 --- a/source/_posts/configuring-the-reroute-email-module.md +++ /dev/null @@ -1,70 +0,0 @@ ---- -title: Configuring the Reroute Email Module -date: 2014-12-22 -excerpt: - How to configure the Reroute Email module, to prevent sending emails to real - users from your pre-production sites! -tags: - - drupal - - drupal-6 - - drupal-7 - - drupal-planet - - email -draft: true ---- - -[Reroute Email](https://www.drupal.org/project/reroute_email) module uses -`hook_mail_alter()` to prevent emails from being sent to users from -non-production sites. It allows you to enter one or more email addresses that -will receive the emails instead of delivering them to the original user. - -> This is useful in case where you do not want email sent from a Drupal site to -> reach the users. For example, if you copy a live site to a test site for the -> purpose of development, and you do not want any email sent to real users of -> the original site. Or you want to check the emails sent for uniform -> formatting, footers, ...etc. - -As we don't need the module configured on production (we don't need to reroute -any emails there), it's best to do this in code using settings.local.php (if you -have one) or the standard settings.php file. - -The first thing that we need to do is to enable rerouting. Without doing this, -nothing will happen. - -```php -$conf['reroute_email_enable'] = TRUE; -``` - -The next option is to whether to show rerouting description in mail body. I -usually have this enabled. Set this to TRUE or FALSE depending on your -preference. - -```php -$conf['reroute_email_enable_message'] = TRUE; -``` - -The last setting is the email address to use. If you're entering a single -address, you can add it as a simple string. - -```php -$conf['reroute_email_address'] = 'person1@example.com'; -``` - -In this example, all emails from the site will be rerouted to -person1@example.com. - -If you want to add multiple addresses, these should be added in a -semicolon-delimited list. Whilst you could add these also as a string, I prefer -to use an array of addresses and the `implode()` function. - -```php -$conf['reroute_email_address'] = implode(';', array( - 'person1@example.com', - 'person2@example.com', - 'person3@example.com', -)); -``` - -In this example, person2@example.com and person3@example.com would receive their -emails from the site as normal. Any emails to addresses not in the array would -continue to be redirected to person1@example.com. diff --git a/source/_posts/debugging-php-docker-xdebug-neovim-dap.md b/source/_posts/debugging-php-docker-xdebug-neovim-dap.md deleted file mode 100755 index a6a0fe3ba..000000000 --- a/source/_posts/debugging-php-docker-xdebug-neovim-dap.md +++ /dev/null @@ -1,102 +0,0 @@ ---- -title: Debugging PHP in Docker with Xdebug, Neovim and DAP -date: ~ -tags: - - docker - - neovim - - dap - - xdebug - - php - - drupal -draft: true ---- - -I've been a full-time Neovim user for a year at the time of writing this post and whilst I was a semi-regular Xdebug user, it's something that I've managed to work around and have mostly resorted to `var_dump()`, `dump()`, or `dd()` instead for debugging. - -This week though, whilst working on some particularly tricky PHP code, I decided to spend some time and get Xdebug working and be able to use a step debugger within Neovim. - -https://gist.githubusercontent.com/opdavies/688a3c8917893bf34a3da32ff69c1837/raw/112e16634930d312cd04c525de42a198c8a32bb9/dap.lua - -## Installing Xdebug - -Installing Xdebug itself within Docker was straight forward. I was able to add two lines to my existing `RUN` command - `pecl install xdebug` to install the extension and `docker-php-ext-enable xdebug` to enable it. - -Now when I run `php -v` inside my container, I can see that it mentions Xdebug. - -## Configuring Xdebug - -https://www.youtube.com/watch?v=ZIGdBSD6zvU - -``` -xdebug.mode=develop,debug -xdebug.client_host=host.docker.internal -xdebug.discover_client_host=0 -xdebug.output_dir=/tmp/xdebug -xdebug.log=/tmp/xdebug/xdebug-example.log -xdebug.start_with_request=yes -``` -## Installing DAP plugins - -I use [Packer](https://github.com/wbthomason/packer.nvim) for managing my Neovim plugins so I needed to install some additional ones to add the DAP (debug adapter protocol) functionality. - -```lua -use "mfussenegger/nvim-dap" -use "rcarriga/nvim-dap-ui" -use "theHamsta/nvim-dap-virtual-text" -use "nvim-telescope/telescope-dap.nvim" -``` - -## Installing DAP dependencies - -[https://github.com/mfussenegger/nvim-dap/wiki/Debug-Adapter-installation#PHP](https://github.com/mfussenegger/nvim-dap/wiki/Debug-Adapter-installation#PHP) - -There's also a prerequisite for install the `vscode-php-debug` adapter. - -I configure my laptop with Ansible, so I added a new `debugger` role that is responsible for cloning this repository and installing its contents: - -[https://github.com/opdavies/dotfiles/blob/7681c535269049556736f1f857c8c9fd800857a3/roles/debugger/tasks/php.yaml](https://github.com/opdavies/dotfiles/blob/7681c535269049556736f1f857c8c9fd800857a3/roles/debugger/tasks/php.yaml) - -## Configuring DAP for Xdebug - -```lua -dap.adapters.php = { - type = "executable", - command = "node", - args = { os.getenv("HOME") .. "/build/vscode-php-debug/out/phpDebug.js" } -} - -dap.configurations.php = { - { - type = "php", - request = "launch", - name = "Listen for Xdebug", - port = 9003, - pathMappings = { - ["/var/www/html"] = "${workspaceFolder}" - } - } -} -``` - -I first needed to configure the adapter to use `vscode-php-debug` and then add a DAP configuration. - -The default port for the step debugger is now 9003 rather than 9000 so I changed this from the default, and as I'm working with PHP inside a container, I also added a path mapping so that my code could be found. - -## Testing the connection - -> [Step Debug] Creating socket for 'host.docker.internal:9003', getaddrinfo: Invalid argument. - -```yaml -services: - php: - volumes: - - "/tmp/xdebug:/tmp/xdebug" - extra_hosts: - - "host.docker.internal:host-gateway" -``` - ---- - -keymaps: - -https://github.com/opdavies/docker-drupal-example diff --git a/source/_posts/finding-the-last-commit-that-a-patch-applies-to.md b/source/_posts/finding-the-last-commit-that-a-patch-applies-to.md deleted file mode 100644 index bf9bbca7d..000000000 --- a/source/_posts/finding-the-last-commit-that-a-patch-applies-to.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -title: Finding the last commit that a patch applies to -excerpt: How to find the last commit in a Git repository that a patch applies to. -date: 2020-03-26 -tags: - - bash - - git -draft: true ---- - -```bash -#!/usr/bin/env bash - -# https://www.drupal.org/files/issues/2018-08-28/group-configurable-entities-as-group-content-2797793-58.patch - -patch_filename=group-configurable-entities-as-group-content-2797793-58.patch -first_commit=6e8c22a -last_commit=8.x-1.x - -find_commits_between() { - first_commit=$1 - last_commit=$2 - - git rev-list --reverse --ancestry-path $first_commit^...$last_commit -} - -reset_repo() { - git reset --hard $1 >& /dev/null -} - -apply_patch() { - git apply --check $patch_filename >& /dev/null -} - -for sha1 in $(find_commits_between $first_commit $last_commit); do - echo "Trying ${sha1}..." - - reset_repo $sha1 - apply_patch - - if [[ $? -eq 0 ]]; then - echo "Patch applies" - continue - fi - - echo "Patch does not apply" - exit 1 -done -``` diff --git a/source/_posts/neovim-database-plugin-vim-dadbod-ui.md b/source/_posts/neovim-database-plugin-vim-dadbod-ui.md deleted file mode 100644 index ab2e5078b..000000000 --- a/source/_posts/neovim-database-plugin-vim-dadbod-ui.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: test -draft: true -date: ~ ---- diff --git a/source/_posts/rebuilding-acquia-dashboard-with-vuejs-tailwind-css.md b/source/_posts/rebuilding-acquia-dashboard-with-vuejs-tailwind-css.md deleted file mode 100644 index 799d442e4..000000000 --- a/source/_posts/rebuilding-acquia-dashboard-with-vuejs-tailwind-css.md +++ /dev/null @@ -1,163 +0,0 @@ ---- -title: Rebuilding Acquia’s Dashboard with Vue.js and Tailwind CSS -excerpt: How I rebuilt Acquia’s dashboard using Vue.js and Tailwind CSS. -tags: - - drupal - - tailwind-css - - tweet - - vuejs -draft: true -date: ~ -promoted: true ---- - -After -[rebuilding Drupal’s Bartik theme](/blog/rebuilding-bartik-with-vuejs-tailwind-css), -I’ve now used [Vue.js][vue] and [Tailwind CSS][tailwind] to rebuild another -Drupal related UI - this time it’s [Acquia’s](https://www.acquia.com) web -hosting dashboard. Again, you can [view the site on Netlify][netlify] and [the -code on GitHub][github]. - -## Why? - -The same as the Bartik rebuild, this was a good opportunity for me to gain more -experience with new technologies - Vue in particular - and to provide another -demonstration of how Tailwind CSS can be used. - -Like the Bartik clone, this was originally going to be another single page -rebuild, however after completing the first page I decided to expand it to -include three pages which also gave me the opportunity to use -[Vue Router](https://router.vuejs.org) - something that I had not used -previously - and to organise a multi-page Vue application. - -## Configuring Vue Router - -`src/router/index.js`: - -```js -import Vue from 'vue'; -import Router from 'vue-router'; -import Applications from '@/views/Applications'; -import Environment from '@/views/Environment'; -import Environments from '@/views/Environments'; - -Vue.use(Router); - -export default new Router({ - routes: [ - { - path: '/', - name: 'applications', - component: Applications, - }, - { - path: '/:id/environments', - name: 'environments', - component: Environments, - props: true, - }, - { - path: '/:id/environments/:environmentName', - name: 'environment', - component: Environment, - props: true, - }, - ], -}); -``` - -## Passing in data - -`src/data.json` - -```json -{ - "applications": { - "1": { - "id": 1, - "name": "Rebuilding Acquia", - "machineName": "rebuildingacquia", - "type": "Drupal", - "level": "Enterprise", - "environments": { - "dev": { - "name": "Dev", - "url": "dev.rebuilding-acquia.com", - "label": "develop" - }, - "stage": { - "name": "Stage", - "url": "stg.rebuilding-acquia.com", - "label": "master" - }, - "prod": { - "name": "Prod", - "url": "rebuilding-acquia.com", - "label": "tags/2018-12-21" - } - }, - "tasks": [ - { - "text": "Commit: fdac923 Merge branch 'update-password-policy' refs/heads/master", - "user": "system", - "times": { - "display": "Dec 19, 2018 3:48:29 PM UTC +0000", - "started": "Dec 19, 2018 3:48:29 PM UTC +0000", - "completed": "Dec 19, 2018 3:48:29 PM UTC +0000" - }, - "loading": false, - "success": true - } - ] - } - } -} -``` - -## The Environments page - -This was the first page that I rebuilt - the Environments page for an -application that shows the information of the different configured environments. - -Vue Router is configured to show the - -{% include 'figure' with { - image: { - src: '/images/blog/rebuilding-acquia-vue-tailwind/3-environments.png', - alt: 'A screenshot of the rebuilt Environments page.', - }, - caption: 'The rebuilt Environments page for an application.', -} %} - -## The applications page - -{% include 'figure' with { - image: { - src: '/images/blog/rebuilding-acquia-vue-tailwind/1-applications-grid.png', - alt: 'The rebuild Applications page, with applications displayed in a grid.', - }, - caption: 'The rebuilt Applications page - grid view', -} %} - -{% include 'figure' with { - image: { - src: '/images/blog/rebuilding-acquia-vue-tailwind/2-applications-list.png', - alt: 'The rebuild Applications page, with applications displayed as a list.', - }, - caption: 'The rebuilt Applications page - list view', -} %} - -## An environment page - -{% include 'figure' with { - image: { - src: '/images/blog/rebuilding-acquia-vue-tailwind/4-environment.png', - alt: 'A screenshot of the rebuilt Environment page.', - }, - caption: 'The rebuilt page for an environment within an application.', -} %} - -[github]: https://github.com/opdavies/rebuilding-acquia -[netlify]: https://rebuilding-acquia.oliverdavies.uk -[tailwind]: https://tailwindcss.com -[vue]: https://vuejs.org diff --git a/source/_posts/updating-override-node-options-tests.md b/source/_posts/updating-override-node-options-tests.md deleted file mode 100644 index dbf62193d..000000000 --- a/source/_posts/updating-override-node-options-tests.md +++ /dev/null @@ -1,239 +0,0 @@ ---- -title: Updating Override Node Options Tests -date: 2017-05-05 -excerpt: ~ -tags: - - drupal - - drupal-modules - - drupal-planet - - testing -draft: true ---- - -Recently, I reviewed [a patch][1] in the [Override Node Options][2] module issue -queue. For those not familiar with it, the module adds extra permissions for -node options like "authored by" and "published on" which are normally only -available to users with the `administer nodes` permission. What the patch does -is to optionally add another set of permissions that enable options for all -content types - e.g. "override published option for all node types", in addition -to or instead of the content type specific ones. - -It was quite an old issue and the latest patch needed to be re-rolled due to -merge conflicts, but the existing tests still passed. Though as no new tests -were added for the new functionality, these needed to be added before I -committed it. - -## Reviewing the Existing Tests - -The first thing to do was to run the existing tests and check that they still -passed. I do this on the command line by typing -`php scripts/run-tests.sh --class OverrideNodeOptionsTestCase`. - -``` -Drupal test run ---------------- - -Tests to be run: - - Override node options (OverrideNodeOptionsTestCase) - -Test run started: - Saturday, April 29, 2017 - 14:44 - -Test summary ------------- - -Override node options 142 passes, 0 fails, 0 exceptions, and 38 debug messages - -Test run duration: 32 sec -``` - -After confirming that the existing tests still passed, I reviewed them to see -what could be re-used. - -This is one of the original tests: - -```php -/** - * Test the 'Authoring information' fieldset. - */ -protected function testNodeOptions() { - $this->adminUser = $this->drupalCreateUser(array( - 'create page content', - 'edit any page content', - 'override page published option', - 'override page promote to front page option', - 'override page sticky option', - 'override page comment setting option', - )); - $this->drupalLogin($this->adminUser); - - $fields = array( - 'status' => (bool) !$this->node->status, - 'promote' => (bool) !$this->node->promote, - 'sticky' => (bool) !$this->node->sticky, - 'comment' => COMMENT_NODE_OPEN, - ); - $this->drupalPost('node/' . $this->node->nid . '/edit', $fields, t('Save')); - $this->assertNodeFieldsUpdated($this->node, $fields); - - $this->drupalLogin($this->normalUser); - $this->assertNodeFieldsNoAccess($this->node, array_keys($fields)); -} -``` - -The first part of the test is creating and logging in a user with some content -type specific override permissions (`$this->adminUser`), and then testing that -the fields were updated when the node is saved. The second part is testing that -the fields are not visible for a normal user without the extra permissions -(`$this->normalUser`), which is created in the `setUp()` class' method. - -To test the new "all types" permissions, I created another user to test against -called `$generalUser` and run the first part of the tests in a loop. - -## Beginning to Refactor the Tests - -With the tests passing, I was able to start refactoring. - -```php -// Create a new user with content type specific permissions. -$specificUser = $this->drupalCreateUser(array( - 'create page content', - 'edit any page content', - 'override page published option', - 'override page promote to front page option', - 'override page sticky option', - 'override page comment setting option', -)); - -foreach (array($specificUser) as $account) { - $this->drupalLogin($account); - - // Test all the things. - ... -} -``` - -I started with a small change, renaming `$this->adminUser` to `$specificUser` to -make it clearer what permissions it had, and moving the tests into a loop so -that the tests can be repeated for both users. - -After that change, I ran the tests again to check that everything still worked. - -## Adding Failing Tests - -The next step is to start testing the new permissions. - -```php -... - -$generalUser = $this->drupalCreateUser(array()); - -foreach (array($specificUser, $generalUser) as $account) { - $this->drupalLogin($account); - - // Test all the things. -} -``` - -I added a new `$generalUser` to test the general permissions and added to the -loop, but in order to see the tests failing intially I assigned it no -permissions. When running the tests again, 6 tests have failed. - -``` -Test summary ------------- - -Override node options 183 passes, 6 fails, 0 exceptions, and 49 debug messages - -Test run duration: 28 sec -``` - -Then it was a case of re-adding more permissions to the user and seeing the -number of failures decrease, confirming that the functionality was working -correctly. - -TODO: Add another example. - -## Gotchas - -There was a bug that I found where a permission was added, but wasn't used -within the implementation code. After initially expecting the test to pass after -adding the permission to `$generalUser` and the test still failed, I noticed -that the - -This was fixed by adding the extra code into `override_node_options.module`. - -```diff -- $form['comment_settings']['#access'] |= user_access('override ' . $node->type . ' comment setting option'); -+ $form['comment_settings']['#access'] |= user_access('override ' . $node->type . ' comment setting option') || user_access('override all comment setting option'); -``` - -The other issue that I found was within `testNodeRevisions`. -`assertNodeFieldsUpdated()` was failing after being put in a loop as the `vid` -was not the same as what was expected. - -Note: You can get more verbose output from `run-tests.sh` by adding the -`--verbose` option. - -> Node vid was updated to '3', expected 2. - -```diff -- $fields = array( -- 'revision' => TRUE, -- ); -- $this->drupalPost('node/' . $this->node->nid . '/edit', $fields, t('Save')); -- $this->assertNodeFieldsUpdated($this->node, array('vid' => $this->node->vid + 1)); -+ $generalUser = $this->drupalCreateUser(array( -+ 'create page content', -+ 'edit any page content', -+ 'override all revision option', -+ )); -+ -+ foreach (array($specificUser, $generalUser) as $account) { -+ $this->drupalLogin($account); -+ -+ // Ensure that we have the latest node data. -+ $node = node_load($this->node->nid, NULL, TRUE); -+ -+ $fields = array( -+ 'revision' => TRUE, -+ ); -+ $this->drupalPost('node/' . $node->nid . '/edit', $fields, t('Save')); -+ $this->assertNodeFieldsUpdated($node, array('vid' => $node->vid + 1)); -+ } -``` - -The crucial part of this change was the addition of -`$node = node_load($this->node->nid, NULL, TRUE);` to ensure that the latest -version of the node was loaded during each loop. - -## Conclusion - -- Ensure that the existing tests were passing before starting to refactor. -- Start with small changes and continue to run the tests to ensure that nothing - has broken. -- After the first change, I committed it as `WIP: Refactoring tests`, and used - `git commit --amend --no-edit` to amend that commit each time I had refactored - another test. After the last refactor, I updated the commit message. -- It’s important to see tests failing before making them pass. This was achieved - by initially assigning no permissions to `$generalUser` so that the fails - failed and then added permissions and re-run the tests to ensure that the - failure count decreased with each new permission. - -With the refactoring complete, the number of passing tests increased from 142 -to 213. - -``` -Override node options 213 passes, 0 fails, 0 exceptions, and 60 debug messages - -Test run duration: 25 sec -``` - - - -[Here][3] are my full changes from the previous patch, where I added the new -tests as well as some small refactors. - -[1]: https://www.drupal.org/node/974730 -[2]: https://www.drupal.org/project/override_node_options -[3]: https://www.drupal.org/files/issues/interdiff_25712.txt diff --git a/source/_posts/using-feature-flags-in-drupal-development.md b/source/_posts/using-feature-flags-in-drupal-development.md deleted file mode 100644 index b57ac762c..000000000 --- a/source/_posts/using-feature-flags-in-drupal-development.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -title: Using feature flags in Drupal development -excerpt: Different ways of using feature flags witin Drupal development -date: 2020-03-31 -tags: - - drupal - - drupal-7 - - drupal-8 - - php -draft: true ---- - -TODO. diff --git a/source/_posts/using-traefik-local-proxy-sculpin.md b/source/_posts/using-traefik-local-proxy-sculpin.md deleted file mode 100644 index 0f663c723..000000000 --- a/source/_posts/using-traefik-local-proxy-sculpin.md +++ /dev/null @@ -1,64 +0,0 @@ ---- -title: Using Traefik as a local proxy with Sculpin -tags: - - docker - - sculpin -draft: true -date: ~ ---- - - - -## Before - -```yaml -services: - app: - build: - context: . - dockerfile: tools/docker/images/Dockerfile - target: app - volumes: - - assets:/app/source/build - - /app/output_dev - - .:/app - ports: - - 8000:8000 -``` - -## Adding the proxy service - -```yaml -services: - proxy: - image: traefik:v2.0-alpine - command: - - --api.insecure=true - - --providers.docker - volumes: - - /var/run/docker.sock:/var/run/docker.sock - ports: - - 80:80 - - 8080:8080 - labels: - - "traefik.enable=false" -``` - -## Updating the app service - -```yaml -app: - build: - context: . - dockerfile: tools/docker/images/Dockerfile - target: app - expose: - - 80 - command: [generate, --server, --watch, --port, '80', --url, http://oliverdavies.localhost] - volumes: - - assets:/app/source/build - - /app/output_dev - - .:/app - labels: - - "traefik.http.routers.oliverdavies.rule=Host(`oliverdavies.localhost`)" -``` diff --git a/source/_posts/weeknotes-2021-08-06.md b/source/_posts/weeknotes-2021-08-06.md deleted file mode 100644 index d08f0a779..000000000 --- a/source/_posts/weeknotes-2021-08-06.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -title: 'Weeknotes: August 6th' -excerpt: TODO -tags: - - personal - - week-notes -draft: true -date: ~ ---- - -## Vim - -- https://gist.github.com/opdavies/f944261b54f70b43f2297cab6779cf59 -- surround.vim - https://github.com/tpope/vim-surround -- https://towardsdatascience.com/how-i-learned-to-enjoy-vim-e310e53e8d56 - -## Re-watching invoice.space streams - -https://www.youtube.com/playlist?list=PLasJXc7CbyYfsdXu6t0406-kGwDN8aUG9 - -## Trialing Conventional Commits - -https://nitayneeman.com/posts/understanding-semantic-commit-messages-using-git-and-angular - -https://www.conventionalcommits.org/en/v1.0.0-beta.2 - -https://github.com/angular/angular/blob/22b96b9/CONTRIBUTING.md#-commit-message-guidelines - -https://github.com/vuejs/vue/commits/dev -https://github.com/vuejs/vue-cli/commits/dev - -https://github.com/pestphp/pest-intellij/commits/main From 51d4368d1f415e444f98e816455eb5712188bcbd Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Fri, 3 May 2024 08:22:06 +0100 Subject: [PATCH 369/501] Update lock file --- composer.lock | 140 ++++++++++++++------------------------------------ 1 file changed, 39 insertions(+), 101 deletions(-) diff --git a/composer.lock b/composer.lock index 8d2552d53..a8c9f6537 100644 --- a/composer.lock +++ b/composer.lock @@ -1095,16 +1095,16 @@ }, { "name": "react/http", - "version": "v1.9.0", + "version": "v1.10.0", "source": { "type": "git", "url": "https://github.com/reactphp/http.git", - "reference": "bb3154dbaf2dfe3f0467f956a05f614a69d5f1d0" + "reference": "8111281ee57f22b7194f5dba225e609ba7ce4d20" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/http/zipball/bb3154dbaf2dfe3f0467f956a05f614a69d5f1d0", - "reference": "bb3154dbaf2dfe3f0467f956a05f614a69d5f1d0", + "url": "https://api.github.com/repos/reactphp/http/zipball/8111281ee57f22b7194f5dba225e609ba7ce4d20", + "reference": "8111281ee57f22b7194f5dba225e609ba7ce4d20", "shasum": "" }, "require": { @@ -1115,14 +1115,13 @@ "react/event-loop": "^1.2", "react/promise": "^3 || ^2.3 || ^1.2.1", "react/socket": "^1.12", - "react/stream": "^1.2", - "ringcentral/psr7": "^1.2" + "react/stream": "^1.2" }, "require-dev": { "clue/http-proxy-react": "^1.8", "clue/reactphp-ssh-proxy": "^1.4", "clue/socks-react": "^1.4", - "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35", + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", "react/async": "^4 || ^3 || ^2", "react/promise-stream": "^1.4", "react/promise-timer": "^1.9" @@ -1175,7 +1174,7 @@ ], "support": { "issues": "https://github.com/reactphp/http/issues", - "source": "https://github.com/reactphp/http/tree/v1.9.0" + "source": "https://github.com/reactphp/http/tree/v1.10.0" }, "funding": [ { @@ -1183,7 +1182,7 @@ "type": "open_collective" } ], - "time": "2023-04-26T10:29:24+00:00" + "time": "2024-03-27T17:20:46+00:00" }, { "name": "react/promise", @@ -1416,67 +1415,6 @@ ], "time": "2023-06-16T10:52:11+00:00" }, - { - "name": "ringcentral/psr7", - "version": "1.3.0", - "source": { - "type": "git", - "url": "https://github.com/ringcentral/psr7.git", - "reference": "360faaec4b563958b673fb52bbe94e37f14bc686" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ringcentral/psr7/zipball/360faaec4b563958b673fb52bbe94e37f14bc686", - "reference": "360faaec4b563958b673fb52bbe94e37f14bc686", - "shasum": "" - }, - "require": { - "php": ">=5.3", - "psr/http-message": "~1.0" - }, - "provide": { - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "phpunit/phpunit": "~4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "files": [ - "src/functions_include.php" - ], - "psr-4": { - "RingCentral\\Psr7\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - } - ], - "description": "PSR-7 message implementation", - "keywords": [ - "http", - "message", - "stream", - "uri" - ], - "support": { - "source": "https://github.com/ringcentral/psr7/tree/master" - }, - "time": "2018-05-29T20:21:04+00:00" - }, { "name": "sculpin/sculpin", "version": "3.2.0", @@ -1951,16 +1889,16 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v3.4.0", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", "shasum": "" }, "require": { @@ -1969,7 +1907,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.4-dev" + "dev-main": "3.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -1998,7 +1936,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" }, "funding": [ { @@ -2014,7 +1952,7 @@ "type": "tidelift" } ], - "time": "2023-05-23T14:45:45+00:00" + "time": "2024-04-18T09:32:20+00:00" }, { "name": "symfony/error-handler", @@ -2374,16 +2312,16 @@ }, { "name": "symfony/http-client-contracts", - "version": "v2.5.2", + "version": "v2.5.3", "source": { "type": "git", "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "ba6a9f0e8f3edd190520ee3b9a958596b6ca2e70" + "reference": "e5cc97c2b4a4db0ba26bebc154f1426e3fd1d2f1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/ba6a9f0e8f3edd190520ee3b9a958596b6ca2e70", - "reference": "ba6a9f0e8f3edd190520ee3b9a958596b6ca2e70", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/e5cc97c2b4a4db0ba26bebc154f1426e3fd1d2f1", + "reference": "e5cc97c2b4a4db0ba26bebc154f1426e3fd1d2f1", "shasum": "" }, "require": { @@ -2432,7 +2370,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v2.5.2" + "source": "https://github.com/symfony/http-client-contracts/tree/v2.5.3" }, "funding": [ { @@ -2448,20 +2386,20 @@ "type": "tidelift" } ], - "time": "2022-04-12T15:48:08+00:00" + "time": "2024-03-26T19:42:53+00:00" }, { "name": "symfony/http-foundation", - "version": "v5.4.35", + "version": "v5.4.39", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "f2ab692a22aef1cd54beb893aa0068bdfb093928" + "reference": "3356c93efc30b0c85a37606bdfef16b813faec0e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/f2ab692a22aef1cd54beb893aa0068bdfb093928", - "reference": "f2ab692a22aef1cd54beb893aa0068bdfb093928", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/3356c93efc30b0c85a37606bdfef16b813faec0e", + "reference": "3356c93efc30b0c85a37606bdfef16b813faec0e", "shasum": "" }, "require": { @@ -2508,7 +2446,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v5.4.35" + "source": "https://github.com/symfony/http-foundation/tree/v5.4.39" }, "funding": [ { @@ -2524,7 +2462,7 @@ "type": "tidelift" } ], - "time": "2024-01-23T13:51:25+00:00" + "time": "2024-04-18T08:26:06+00:00" }, { "name": "symfony/http-kernel", @@ -3096,16 +3034,16 @@ }, { "name": "symfony/service-contracts", - "version": "v2.5.2", + "version": "v2.5.3", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c" + "reference": "a2329596ddc8fd568900e3fc76cba42489ecc7f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/4b426aac47d6427cc1a1d0f7e2ac724627f5966c", - "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/a2329596ddc8fd568900e3fc76cba42489ecc7f3", + "reference": "a2329596ddc8fd568900e3fc76cba42489ecc7f3", "shasum": "" }, "require": { @@ -3159,7 +3097,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v2.5.2" + "source": "https://github.com/symfony/service-contracts/tree/v2.5.3" }, "funding": [ { @@ -3175,20 +3113,20 @@ "type": "tidelift" } ], - "time": "2022-05-30T19:17:29+00:00" + "time": "2023-04-21T15:04:16+00:00" }, { "name": "symfony/var-dumper", - "version": "v5.4.36", + "version": "v5.4.39", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "2e9c2b11267119d9c90d6b3fdce5e4e9f15e2e90" + "reference": "1987f86ad7f339fe3d3e8e6cf3b7ce4d4b8e547e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/2e9c2b11267119d9c90d6b3fdce5e4e9f15e2e90", - "reference": "2e9c2b11267119d9c90d6b3fdce5e4e9f15e2e90", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/1987f86ad7f339fe3d3e8e6cf3b7ce4d4b8e547e", + "reference": "1987f86ad7f339fe3d3e8e6cf3b7ce4d4b8e547e", "shasum": "" }, "require": { @@ -3248,7 +3186,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v5.4.36" + "source": "https://github.com/symfony/var-dumper/tree/v5.4.39" }, "funding": [ { @@ -3264,7 +3202,7 @@ "type": "tidelift" } ], - "time": "2024-02-15T11:19:14+00:00" + "time": "2024-04-18T08:26:06+00:00" }, { "name": "symfony/yaml", From d8cab58f8320bf9c9599c451ee62b0e8410dab9b Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Fri, 3 May 2024 09:09:48 +0100 Subject: [PATCH 370/501] Add talks --- source/_talks/building-static-websites-sculpin.md | 5 +++++ source/_talks/taking-flight-with-tailwind-css.md | 5 +++++ source/_talks/tdd-test-driven-drupal.md | 5 +++++ source/_talks/upgrading-your-site-drupal-9.md | 5 +++++ 4 files changed, 20 insertions(+) diff --git a/source/_talks/building-static-websites-sculpin.md b/source/_talks/building-static-websites-sculpin.md index 3323773ae..64ad6d149 100644 --- a/source/_talks/building-static-websites-sculpin.md +++ b/source/_talks/building-static-websites-sculpin.md @@ -12,6 +12,11 @@ video: type: youtube id: xRTiWR9nBSA events: + - + name: BrumPHP + date: 2024-05-23 + location: Birmingham, UK + url: https://www.eventbrite.com/e/brumphp-23rd-may-2024-tickets-803037766577 - name: PHP South West date: 2024-02-14 diff --git a/source/_talks/taking-flight-with-tailwind-css.md b/source/_talks/taking-flight-with-tailwind-css.md index d4f819739..b71d16a52 100644 --- a/source/_talks/taking-flight-with-tailwind-css.md +++ b/source/_talks/taking-flight-with-tailwind-css.md @@ -11,6 +11,11 @@ video: type: youtube tags: [css, tailwind, meetup] events: + - + name: DrupalCamp Belgium + location: Ghent, Belgium + date: 2024-05-11 + url: https://www.drupalcamp.be/en/drupalcamp-ghent-2024/session/taking-flight-tailwi%E2%80%A6 - name: Norfolk Developers Conference location: Norwich, UK diff --git a/source/_talks/tdd-test-driven-drupal.md b/source/_talks/tdd-test-driven-drupal.md index de9ee8c3a..14747c8e4 100644 --- a/source/_talks/tdd-test-driven-drupal.md +++ b/source/_talks/tdd-test-driven-drupal.md @@ -15,6 +15,11 @@ image: type: image/png use: [talks] events: + - + name: DrupalCamp Belgium + location: Ghent, Belgium + date: 2024-05-10 + url: https://www.drupalcamp.be/en/drupalcamp-ghent-2024/session/tdd-test-driven-drup%E2%80%A6 - name: DrupalCon Lille 2023 location: Lille, France diff --git a/source/_talks/upgrading-your-site-drupal-9.md b/source/_talks/upgrading-your-site-drupal-9.md index 344eeab0f..b73620cee 100644 --- a/source/_talks/upgrading-your-site-drupal-9.md +++ b/source/_talks/upgrading-your-site-drupal-9.md @@ -22,6 +22,11 @@ events: url: https://www.meetup.com/leedsphp/events/272504993 date: 2020-09-23 online: true + - + name: Drupal NYC meetup + url: https://midwestphp.org/talks/7C0m4I87vq72cDoXvsHFRp/Upgrading_your_site_to_Drupal_9 + date: 2020-09-02 + online: true --- For most Drupal Developers and users, the idea of moving a project from one From 8fc2bdca0e46a904a62071149bc1061883f5b85c Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Fri, 3 May 2024 09:28:26 +0100 Subject: [PATCH 371/501] Update video --- source/_talks/building-static-websites-sculpin.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/_talks/building-static-websites-sculpin.md b/source/_talks/building-static-websites-sculpin.md index 64ad6d149..783d92236 100644 --- a/source/_talks/building-static-websites-sculpin.md +++ b/source/_talks/building-static-websites-sculpin.md @@ -10,7 +10,7 @@ tags: [meetups, phpsw, sculpin] tweets: yes video: type: youtube - id: xRTiWR9nBSA + id: axy6ltc9meA events: - name: BrumPHP From d06560dd9a35a8248bc8201b508e77d2e278727d Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Fri, 3 May 2024 09:55:57 +0100 Subject: [PATCH 372/501] Remove tags from talks --- source/_talks/building-static-websites-sculpin.md | 1 - source/_talks/dancing-for-drupal.md | 1 - source/_talks/deploying-php-fabric.md | 1 - source/_talks/drupal-8-module-development.md | 1 - source/_talks/drupal-8-rejoining-the-herd.md | 1 - source/_talks/drupal-vm-generator.md | 1 - source/_talks/drupalorg-2015.md | 1 - source/_talks/getting-your-data-into-drupal-8.md | 1 - source/_talks/goodbye-drush-make-hello-composer.md | 1 - source/_talks/modern-drupal-development-with-composer.md | 1 - source/_talks/taking-flight-with-tailwind-css.md | 1 - source/_talks/using-illuminate-collections-outside-laravel.md | 1 - 12 files changed, 12 deletions(-) diff --git a/source/_talks/building-static-websites-sculpin.md b/source/_talks/building-static-websites-sculpin.md index 783d92236..6b0c915c5 100644 --- a/source/_talks/building-static-websites-sculpin.md +++ b/source/_talks/building-static-websites-sculpin.md @@ -6,7 +6,6 @@ speakerdeck: ratio: "1.77777777777778" url: https://speakerdeck.com/opdavies/building-static-websites-with-sculpin code: https://github.com/opdavies/sculpin-demo -tags: [meetups, phpsw, sculpin] tweets: yes video: type: youtube diff --git a/source/_talks/dancing-for-drupal.md b/source/_talks/dancing-for-drupal.md index 40469b8f8..4cd754512 100644 --- a/source/_talks/dancing-for-drupal.md +++ b/source/_talks/dancing-for-drupal.md @@ -1,7 +1,6 @@ --- title: Dancing for Drupal description: A talk on Drupal, presented alongside others representing Umbraco, Sitecore and Episerver. -tags: [meetup, umbristol, drupal] tweets: yes speakerdeck: id: ffa9b6dea6dc4a8eb207b9982ed6e1bd diff --git a/source/_talks/deploying-php-fabric.md b/source/_talks/deploying-php-fabric.md index dfce37aa9..d8b13b404 100644 --- a/source/_talks/deploying-php-fabric.md +++ b/source/_talks/deploying-php-fabric.md @@ -5,7 +5,6 @@ speakerdeck: id: c147618ce07546ca92f92983c52d6a41 ratio: "1.77777777777778" url: https://speakerdeck.com/opdavies/deploying-php-applications-with-fabric -tags: [meetup, conference, php, fabric] meta: og: title: Deploying PHP Applcations with Fabric diff --git a/source/_talks/drupal-8-module-development.md b/source/_talks/drupal-8-module-development.md index 8518de089..b4ebd1b5f 100644 --- a/source/_talks/drupal-8-module-development.md +++ b/source/_talks/drupal-8-module-development.md @@ -1,7 +1,6 @@ --- title: Getting Started with Drupal 8 Module Development description: How to build your first module for Drupal 8. -tags: [conference, php, drupal, drupalcamp, drupal-8] tweets: yes code: https://github.com/opdavies/dclondon16-d8-module speakerdeck: diff --git a/source/_talks/drupal-8-rejoining-the-herd.md b/source/_talks/drupal-8-rejoining-the-herd.md index 3299245d3..3145fb45e 100644 --- a/source/_talks/drupal-8-rejoining-the-herd.md +++ b/source/_talks/drupal-8-rejoining-the-herd.md @@ -1,7 +1,6 @@ --- title: "Drupal 8: Rejoining the Herd" description: A talk highlighting some of the recent technical and non-technical changes in Drupal 8. -tags: [conference, php, drupal, drupal-8] speakerdeck: id: 440fd6592f474741bc606c96bc32c104 ratio: "1.37081659973226" diff --git a/source/_talks/drupal-vm-generator.md b/source/_talks/drupal-vm-generator.md index 3fd6f39a7..50b14c023 100644 --- a/source/_talks/drupal-vm-generator.md +++ b/source/_talks/drupal-vm-generator.md @@ -3,7 +3,6 @@ title: Drupal VM Generator description: Announcing the Drupal VM Generator CLI tool. type: Lightning talk code: https://github.com/opdavies/drupal-vm-generator -tags: [drupal-vm, drupal-vm-generator, meetup, symfony] speakerdeck: id: a27ee1d2bfed4a209dc395fa455acb41 ratio: "1.37081659973226" diff --git a/source/_talks/drupalorg-2015.md b/source/_talks/drupalorg-2015.md index a7c3144f7..6d2577d27 100644 --- a/source/_talks/drupalorg-2015.md +++ b/source/_talks/drupalorg-2015.md @@ -1,7 +1,6 @@ --- title: "Drupal.org in 2015: What's Coming Next" description: A retrospective of the Drupal Association’s work in 2014 and a look forward to what we’ll be working on in 2015. -tags: [conference, drupalcamp, drupalcamp-london, drupal-association] speakerdeck: id: 0cf8d7b647c94ae289e9db2b46a9e8f2 ratio: "1.77777777777778" diff --git a/source/_talks/getting-your-data-into-drupal-8.md b/source/_talks/getting-your-data-into-drupal-8.md index 0318d7fc1..014ad4a8d 100644 --- a/source/_talks/getting-your-data-into-drupal-8.md +++ b/source/_talks/getting-your-data-into-drupal-8.md @@ -8,7 +8,6 @@ speakerdeck: video: type: youtube id: jtmARTuYhp8 -tags: [drupalcamp, migration, drupal-8] meta: og: title: Getting (Your Data) into Drupal 8 diff --git a/source/_talks/goodbye-drush-make-hello-composer.md b/source/_talks/goodbye-drush-make-hello-composer.md index 5db4e21ea..23c10cf1c 100644 --- a/source/_talks/goodbye-drush-make-hello-composer.md +++ b/source/_talks/goodbye-drush-make-hello-composer.md @@ -1,7 +1,6 @@ --- title: Goodbye Drush Make. Hello Composer! description: How to use Composer to manage your Drupal applications. -tags: ["meetup", "drupal", "composer"] speakerdeck: id: 1c1e0e129ab34816bd4c4edb5f6642c2 ratio: "1.37081659973226" diff --git a/source/_talks/modern-drupal-development-with-composer.md b/source/_talks/modern-drupal-development-with-composer.md index ce4a7163e..0ae5d3aa6 100644 --- a/source/_talks/modern-drupal-development-with-composer.md +++ b/source/_talks/modern-drupal-development-with-composer.md @@ -2,7 +2,6 @@ title: Modern Drupal Development with Composer description: A lightning talk on how to use Composer to manage your Drupal projects. type: Lightning talk -tags: ["meetups", "phpsw", "drupal", "composer"] speakerdeck: id: 7a1358502526425a9cfd288f85fb32f3 ratio: "1.37081659973226" diff --git a/source/_talks/taking-flight-with-tailwind-css.md b/source/_talks/taking-flight-with-tailwind-css.md index b71d16a52..bf3f00bec 100644 --- a/source/_talks/taking-flight-with-tailwind-css.md +++ b/source/_talks/taking-flight-with-tailwind-css.md @@ -9,7 +9,6 @@ speakerdeck: video: id: C20QZbGlmZ8 type: youtube -tags: [css, tailwind, meetup] events: - name: DrupalCamp Belgium diff --git a/source/_talks/using-illuminate-collections-outside-laravel.md b/source/_talks/using-illuminate-collections-outside-laravel.md index e8bc01cfc..f594a2467 100644 --- a/source/_talks/using-illuminate-collections-outside-laravel.md +++ b/source/_talks/using-illuminate-collections-outside-laravel.md @@ -8,7 +8,6 @@ speakerdeck: video: type: youtube id: 1l0kO-iaN_o -tags: [nomad-php, lightning-talk, laravel, collections] type: Lightning talk events: - From a516fa11e1efcc56dfff174334d35177a48bac69 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Fri, 3 May 2024 11:54:38 +0100 Subject: [PATCH 373/501] Remove `tweets` from talks --- source/_talks/building-static-websites-sculpin.md | 1 - source/_talks/dancing-for-drupal.md | 1 - source/_talks/drupal-8-module-development.md | 1 - source/_talks/drupal-8.md | 1 - source/_talks/git-flow.md | 1 - source/_talks/test-drive-twig-with-sculpin.md | 1 - 6 files changed, 6 deletions(-) diff --git a/source/_talks/building-static-websites-sculpin.md b/source/_talks/building-static-websites-sculpin.md index 6b0c915c5..9f2c43c10 100644 --- a/source/_talks/building-static-websites-sculpin.md +++ b/source/_talks/building-static-websites-sculpin.md @@ -6,7 +6,6 @@ speakerdeck: ratio: "1.77777777777778" url: https://speakerdeck.com/opdavies/building-static-websites-with-sculpin code: https://github.com/opdavies/sculpin-demo -tweets: yes video: type: youtube id: axy6ltc9meA diff --git a/source/_talks/dancing-for-drupal.md b/source/_talks/dancing-for-drupal.md index 4cd754512..cc72c3c73 100644 --- a/source/_talks/dancing-for-drupal.md +++ b/source/_talks/dancing-for-drupal.md @@ -1,7 +1,6 @@ --- title: Dancing for Drupal description: A talk on Drupal, presented alongside others representing Umbraco, Sitecore and Episerver. -tweets: yes speakerdeck: id: ffa9b6dea6dc4a8eb207b9982ed6e1bd ratio: "1.33333333333333" diff --git a/source/_talks/drupal-8-module-development.md b/source/_talks/drupal-8-module-development.md index b4ebd1b5f..8420a93a2 100644 --- a/source/_talks/drupal-8-module-development.md +++ b/source/_talks/drupal-8-module-development.md @@ -1,7 +1,6 @@ --- title: Getting Started with Drupal 8 Module Development description: How to build your first module for Drupal 8. -tweets: yes code: https://github.com/opdavies/dclondon16-d8-module speakerdeck: id: 0041804e52664d12a8e31cd118264813 diff --git a/source/_talks/drupal-8.md b/source/_talks/drupal-8.md index 3853f8e9c..509cf8dd1 100644 --- a/source/_talks/drupal-8.md +++ b/source/_talks/drupal-8.md @@ -10,7 +10,6 @@ speakerdeck: video: type: youtube id: 36zCxPrOOzM -tweets: yes events: - name: PHP South West diff --git a/source/_talks/git-flow.md b/source/_talks/git-flow.md index c41a66672..c034b63ac 100644 --- a/source/_talks/git-flow.md +++ b/source/_talks/git-flow.md @@ -8,7 +8,6 @@ speakerdeck: video: type: youtube id: T-miCpHxfds -tweets: yes events: - name: DrupalCamp London 2014 diff --git a/source/_talks/test-drive-twig-with-sculpin.md b/source/_talks/test-drive-twig-with-sculpin.md index 5bbdf9e41..494dc806d 100644 --- a/source/_talks/test-drive-twig-with-sculpin.md +++ b/source/_talks/test-drive-twig-with-sculpin.md @@ -6,7 +6,6 @@ speakerdeck: ratio: "1.77777777777778" url: https://speakerdeck.com/opdavies/test-drive-twig-with-sculpin code: https://github.com/opdavies/sculpin-demo -tweets: yes events: - name: DrupalCamp North 2015 From 9b19d7a03681ebd3cb58e213049bcfe2051894a8 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Fri, 3 May 2024 12:30:24 +0100 Subject: [PATCH 374/501] Use a Twig extension to count the years of experience --- app/config/sculpin_kernel.yml | 25 +++++++++++------- composer.json | 8 ++++-- source/_includes/about-me.html.twig | 2 +- source/_includes/macros.html.twig | 4 --- source/_pages/call.html.twig | 4 +-- source/_pages/drupal-upgrade.md | 2 +- source/_pages/pair.html.twig | 2 +- source/_pages/press.md | 2 +- source/_pages/speaker.md | 2 +- .../TwigExtension/OpdaviesTwigExtension.php | 26 +++++++++++++++++++ 10 files changed, 54 insertions(+), 23 deletions(-) create mode 100644 src/Opdavies/TwigExtension/OpdaviesTwigExtension.php diff --git a/app/config/sculpin_kernel.yml b/app/config/sculpin_kernel.yml index 7b459e5c4..7856fedc3 100644 --- a/app/config/sculpin_kernel.yml +++ b/app/config/sculpin_kernel.yml @@ -1,11 +1,16 @@ sculpin_content_types: - daily_emails: - permalink: /archive/:year/:month/:day/:basename/ - pages: - permalink: /:basename/ - podcast_episodes: - permalink: /podcast/:basename/ - posts: - permalink: /blog/:basename/ - talks: - permalink: /talks/:basename/ + daily_emails: + permalink: /archive/:year/:month/:day/:basename/ + pages: + permalink: /:basename/ + podcast_episodes: + permalink: /podcast/:basename/ + posts: + permalink: /blog/:basename/ + talks: + permalink: /talks/:basename/ + +services: + App\Opdavies\TwigExtension\OpdaviesTwigExtension: + tags: + - { name: twig.extension } diff --git a/composer.json b/composer.json index ca66df3ed..1d5c25429 100644 --- a/composer.json +++ b/composer.json @@ -6,6 +6,10 @@ "config": { "allow-plugins": { "sculpin/sculpin-theme-composer-plugin": true - } - } + } + }, + "autoload": { + "psr-4": { + "App\\": "src" } + } } diff --git a/source/_includes/about-me.html.twig b/source/_includes/about-me.html.twig index 9af1501b9..283a55cd0 100644 --- a/source/_includes/about-me.html.twig +++ b/source/_includes/about-me.html.twig @@ -10,7 +10,7 @@

    -

    I'm an Acquia-certified Drupal Triple Expert with {{ macros.yearsExperience }} years of experience, an open-source software maintainer and Drupal core contributor, public speaker, live streamer, and host of the Beyond Blocks podcast.

    +

    I'm an Acquia-certified Drupal Triple Expert with {{ get_years_of_experience() }} years of experience, an open-source software maintainer and Drupal core contributor, public speaker, live streamer, and host of the Beyond Blocks podcast.

    diff --git a/source/_includes/macros.html.twig b/source/_includes/macros.html.twig index 3723faa75..802cfcdf0 100644 --- a/source/_includes/macros.html.twig +++ b/source/_includes/macros.html.twig @@ -2,7 +2,3 @@ {% macro dailiesCount(dailies) %} {{- dailies|length|round(-1, 'floor') -}}+ {% endmacro %} - -{% macro yearsExperience() %} - {{- today|date('Y') - 2007 -}} -{% endmacro %} diff --git a/source/_pages/call.html.twig b/source/_pages/call.html.twig index 9e36e9b48..0acf42962 100644 --- a/source/_pages/call.html.twig +++ b/source/_pages/call.html.twig @@ -32,7 +32,7 @@ faqs: {# Fix #} -

    As a professional Software Developer and Consultant with {{ macros.yearsExperience }} years of Drupal and PHP experience, I have a lot of knowledge that I use to help customers and their projects.

    +

    As a professional Software Developer and Consultant with {{ get_years_of_experience() }} years of Drupal and PHP experience, I have a lot of knowledge that I use to help customers and their projects.

    {# 1st call to action #} @@ -92,7 +92,7 @@ faqs:

    Who am I?

      -
    • I'm an Acquia-certified Drupal expert with {{ macros.yearsExperience }} years of professional development experience.
    • +
    • I'm an Acquia-certified Drupal expert with {{ get_years_of_experience() }} years of professional development experience.
    • I'm a former Drupal Association employee who was responsible for improving and maintaining Drupal.org.
    • I'm a Drupal core contributor and maintain numerous Drupal projects, including the Override Node Options module, which is used on over 38,000 websites.
    • I'm a multiple-time DrupalCon speaker who regularly presents talks and workshops at conferences and meetups.
    • diff --git a/source/_pages/drupal-upgrade.md b/source/_pages/drupal-upgrade.md index 271bb0167..5e37aa7e8 100644 --- a/source/_pages/drupal-upgrade.md +++ b/source/_pages/drupal-upgrade.md @@ -70,7 +70,7 @@ An upgrade roadmap is a personalised audit of your Drupal website and includes d ## Who am I? -* I'm an Acquia-certified Drupal expert with {{ macros.yearsExperience }} years of professional development experience. +* I'm an Acquia-certified Drupal expert with {{ get_years_of_experience() }} years of professional development experience. * I'm a former Drupal Association employee who was responsible for improving and maintaining Drupal.org. * I'm a Drupal core contributor and maintain numerous Drupal projects, including the Override Node Options module, which is used on over 38,000 websites. * I'm a multiple-time DrupalCon speaker who regularly presents talks and workshops at conferences and meetups. diff --git a/source/_pages/pair.html.twig b/source/_pages/pair.html.twig index 0facfaca5..5d63a4444 100644 --- a/source/_pages/pair.html.twig +++ b/source/_pages/pair.html.twig @@ -50,7 +50,7 @@ link: https://savvycal.com/opdavies/pair

      Who am I?

        -
      • I'm an Acquia-certified Drupal expert with {{ macros.yearsExperience }} years of professional development experience.
      • +
      • I'm an Acquia-certified Drupal expert with {{ get_years_of_experience() }} years of professional development experience.
      • I'm a former Drupal Association employee who was responsible for improving and maintaining Drupal.org.
      • I'm a Drupal core contributor and maintain numerous Drupal projects, including the Override Node Options module, which is used on over 38,000 websites.
      • I'm a multiple-time DrupalCon speaker who regularly presents talks and workshops at conferences and meetups.
      • diff --git a/source/_pages/press.md b/source/_pages/press.md index 77fadc332..dafb10bee 100644 --- a/source/_pages/press.md +++ b/source/_pages/press.md @@ -10,7 +10,7 @@ Please feel free to use anything here as-is without checking with me first. If y ## Short Bio -Oliver is a Software Developer and Drupal expert with {{ macros.yearsExperience }} years experience. He specialises in code quality, automated testing and test-driven development. +Oliver is a Software Developer and Drupal expert with {{ get_years_of_experience() }} years experience. He specialises in code quality, automated testing and test-driven development. ## Sample Topics diff --git a/source/_pages/speaker.md b/source/_pages/speaker.md index aa4f84f54..d7db1fa3e 100644 --- a/source/_pages/speaker.md +++ b/source/_pages/speaker.md @@ -6,7 +6,7 @@ title: Speaker Information ## Bio -Oliver is a Software Developer and Drupal Expert with {{ macros.yearsExperience }} years of experience. As well as consulting on large Drupal projects, Oliver helps Drupal Developers learn automated testing and test-driven development via a free email course and paid coaching and workshops. He regularly contributes to open-source software projects, including Drupal core. +Oliver is a Software Developer and Drupal Expert with {{ get_years_of_experience() }} years of experience. As well as consulting on large Drupal projects, Oliver helps Drupal Developers learn automated testing and test-driven development via a free email course and paid coaching and workshops. He regularly contributes to open-source software projects, including Drupal core. ## Photos diff --git a/src/Opdavies/TwigExtension/OpdaviesTwigExtension.php b/src/Opdavies/TwigExtension/OpdaviesTwigExtension.php new file mode 100644 index 000000000..f86d87524 --- /dev/null +++ b/src/Opdavies/TwigExtension/OpdaviesTwigExtension.php @@ -0,0 +1,26 @@ +format('Y') - 2007; + } +} From 688867525280aa84821e3213859c55de10251a71 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Fri, 3 May 2024 12:53:05 +0100 Subject: [PATCH 375/501] Use a Twig extension for counting past talks --- composer.json | 5 +- composer.lock | 248 +++++++++++++++++- source/_pages/talks.md | 2 +- .../_talks/introduction-to-mob-programming.md | 2 +- .../TwigExtension/OpdaviesTwigExtension.php | 18 ++ 5 files changed, 270 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index 1d5c25429..cba133e12 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,8 @@ { "require": { - "sculpin/sculpin": "^3.2", - "opdavies/sculpin-twig-markdown-bundle": "^0.2.0" + "illuminate/collections": "^11.6", + "opdavies/sculpin-twig-markdown-bundle": "^0.2.0", + "sculpin/sculpin": "^3.2" }, "config": { "allow-plugins": { diff --git a/composer.lock b/composer.lock index a8c9f6537..369b716c7 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b02a47aa3f7b8cb842ee39beec85b6b1", + "content-hash": "910019ed15f62fccc7241d065627ac72", "packages": [ { "name": "dflydev/ant-path-matcher", @@ -562,6 +562,201 @@ }, "time": "2020-11-24T22:02:12+00:00" }, + { + "name": "illuminate/collections", + "version": "v11.6.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/collections.git", + "reference": "19c6554c7eba0efabc3f8aa4c434815b7f6b4b7d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/collections/zipball/19c6554c7eba0efabc3f8aa4c434815b7f6b4b7d", + "reference": "19c6554c7eba0efabc3f8aa4c434815b7f6b4b7d", + "shasum": "" + }, + "require": { + "illuminate/conditionable": "^11.0", + "illuminate/contracts": "^11.0", + "illuminate/macroable": "^11.0", + "php": "^8.2" + }, + "suggest": { + "symfony/var-dumper": "Required to use the dump method (^7.0)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "11.x-dev" + } + }, + "autoload": { + "files": [ + "helpers.php" + ], + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Collections package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2024-04-15T15:26:05+00:00" + }, + { + "name": "illuminate/conditionable", + "version": "v11.6.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/conditionable.git", + "reference": "8a558fec063b6a63da1c3af1d219c0f998edffeb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/conditionable/zipball/8a558fec063b6a63da1c3af1d219c0f998edffeb", + "reference": "8a558fec063b6a63da1c3af1d219c0f998edffeb", + "shasum": "" + }, + "require": { + "php": "^8.0.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "11.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Conditionable package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2024-04-04T17:36:49+00:00" + }, + { + "name": "illuminate/contracts", + "version": "v11.6.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/contracts.git", + "reference": "8782f75e80ab3e6036842d24dbeead34a16f3a79" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/contracts/zipball/8782f75e80ab3e6036842d24dbeead34a16f3a79", + "reference": "8782f75e80ab3e6036842d24dbeead34a16f3a79", + "shasum": "" + }, + "require": { + "php": "^8.2", + "psr/container": "^1.1.1|^2.0.1", + "psr/simple-cache": "^1.0|^2.0|^3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "11.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Contracts\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Contracts package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2024-04-17T14:09:55+00:00" + }, + { + "name": "illuminate/macroable", + "version": "v11.6.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/macroable.git", + "reference": "e1be58f9b2af73f242dc6a9add1f376b3ec89eef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/macroable/zipball/e1be58f9b2af73f242dc6a9add1f376b3ec89eef", + "reference": "e1be58f9b2af73f242dc6a9add1f376b3ec89eef", + "shasum": "" + }, + "require": { + "php": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "11.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Macroable package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2023-06-08T14:08:27+00:00" + }, { "name": "michelf/php-markdown", "version": "1.9.1", @@ -873,6 +1068,57 @@ }, "time": "2021-07-14T16:41:46+00:00" }, + { + "name": "psr/simple-cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" + }, + "time": "2021-10-29T13:26:27+00:00" + }, { "name": "react/cache", "version": "v1.2.0", diff --git a/source/_pages/talks.md b/source/_pages/talks.md index 1b1f9795a..cb962ef58 100644 --- a/source/_pages/talks.md +++ b/source/_pages/talks.md @@ -10,7 +10,7 @@ use: [talks] {% endfor %} {% endfor %} -Since September 2012, I have given {{ talkCount }} public presentations and workshops at various conferences and meetups, in-person and remotely, on topics including PHP, Drupal, automated testing, Git, CSS, and systems administration. +Since September 2012, I have given {{ get_past_talk_count(data.talks) }} public presentations and workshops at various conferences and meetups, in-person and remotely, on topics including PHP, Drupal, automated testing, Git, CSS, and systems administration. {% for talk in data.talks|sort((a, b) => a.events|first.date|date('U') > b.events|first.date|date('U') ? -1 : 1) %}
        diff --git a/source/_talks/introduction-to-mob-programming.md b/source/_talks/introduction-to-mob-programming.md index 96c6535bc..4595d2270 100644 --- a/source/_talks/introduction-to-mob-programming.md +++ b/source/_talks/introduction-to-mob-programming.md @@ -10,7 +10,7 @@ events: - name: PHP South Wales location: Cardiff, Wales - date: '2022-09-28' + date: 2022-09-28 url: https://www.meetup.com/php-south-wales/events/288359737 --- diff --git a/src/Opdavies/TwigExtension/OpdaviesTwigExtension.php b/src/Opdavies/TwigExtension/OpdaviesTwigExtension.php index f86d87524..9c9fc41d4 100644 --- a/src/Opdavies/TwigExtension/OpdaviesTwigExtension.php +++ b/src/Opdavies/TwigExtension/OpdaviesTwigExtension.php @@ -2,6 +2,7 @@ namespace App\Opdavies\TwigExtension; +use Sculpin\Contrib\ProxySourceCollection\ProxySourceItem; use Twig\Extension\AbstractExtension; use Twig\TwigFunction; @@ -10,6 +11,7 @@ class OpdaviesTwigExtension extends AbstractExtension public function getFunctions(): array { return [ + new TwigFunction('get_past_talk_count', [$this, 'getPastTalkCount']), new TwigFunction('get_years_of_experience', [$this, 'getYearsOfExperience']), ]; } @@ -19,6 +21,22 @@ class OpdaviesTwigExtension extends AbstractExtension return 'app.opdavies_twig_extension'; } + public function getPastTalkCount(array $talks): int + { + $today = (new \DateTime())->getTimestamp(); + + return collect($talks) + ->flatMap(fn (ProxySourceItem $talk) => $talk->data()->get('events')) + ->filter( + function (array $event) use ($today): bool { + assert(array_key_exists(array: $event, key: 'date')); + + return $event['date'] < $today; + } + ) + ->count(); + } + public function getYearsOfExperience(): int { return (new \DateTimeImmutable())->format('Y') - 2007; From 6ee3e91363d71e9037114f6b14693afea92936e3 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Fri, 3 May 2024 14:18:05 +0100 Subject: [PATCH 376/501] Add OpdaviesTwigExtensionTest --- .gitignore | 4 + composer.json | 9 +- composer.lock | 1636 ++++++++++++++++- phpunit.xml.dist | 8 + .../OpdaviesTwigExtensionTest.php | 78 + 5 files changed, 1732 insertions(+), 3 deletions(-) create mode 100644 phpunit.xml.dist create mode 100644 tests/Opdavies/TwigExtension/OpdaviesTwigExtensionTest.php diff --git a/.gitignore b/.gitignore index 2c93fe1f9..733ceba1b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,7 @@ /node_modules/ /source/build/ + +# PHPUnit. +/.phpunit.cache/ +/.phpunit.result.cache diff --git a/composer.json b/composer.json index cba133e12..6e87e2fad 100644 --- a/composer.json +++ b/composer.json @@ -12,5 +12,12 @@ "autoload": { "psr-4": { "App\\": "src" } - } + }, + "autoload-dev": { + "psr-4": { + "App\\Tests\\": "tests" } + }, + "require-dev": { + "phpunit/phpunit": "^11.1" + } } diff --git a/composer.lock b/composer.lock index 369b716c7..5c098171a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "910019ed15f62fccc7241d065627ac72", + "content-hash": "bd500bef7c5bb79c78f157cd47833196", "packages": [ { "name": "dflydev/ant-path-matcher", @@ -3809,7 +3809,1639 @@ "time": "2017-05-11T10:04:12+00:00" } ], - "packages-dev": [], + "packages-dev": [ + { + "name": "myclabs/deep-copy", + "version": "1.11.1", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "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", + "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.11.1" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2023-03-08T13:26:56+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.0.2", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/139676794dc1e9231bf7bcd123cfc0c99182cb13", + "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^7.0 || ^8.0 || ^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.0.2" + }, + "time": "2024-03-05T20:51:40+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": "phpunit/php-code-coverage", + "version": "11.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "7e35a2cbcabac0e6865fd373742ea432a3c34f92" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e35a2cbcabac0e6865fd373742ea432a3c34f92", + "reference": "7e35a2cbcabac0e6865fd373742ea432a3c34f92", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^5.0", + "php": ">=8.2", + "phpunit/php-file-iterator": "^5.0", + "phpunit/php-text-template": "^4.0", + "sebastian/code-unit-reverse-lookup": "^4.0", + "sebastian/complexity": "^4.0", + "sebastian/environment": "^7.0", + "sebastian/lines-of-code": "^3.0", + "sebastian/version": "^5.0", + "theseer/tokenizer": "^1.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "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": "11.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 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/11.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-12T15:35:40+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "99e95c94ad9500daca992354fa09d7b99abe2210" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/99e95c94ad9500daca992354fa09d7b99abe2210", + "reference": "99e95c94ad9500daca992354fa09d7b99abe2210", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.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", + "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/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-02-02T06:05:04+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5d8d9355a16d8cc5a1305b0a85342cfa420612be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5d8d9355a16d8cc5a1305b0a85342cfa420612be", + "reference": "5d8d9355a16d8cc5a1305b0a85342cfa420612be", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^11.0" + }, + "suggest": { + "ext-pcntl": "*" + }, + "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", + "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", + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-02-02T06:05:50+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "d38f6cbff1cdb6f40b03c9811421561668cc133e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/d38f6cbff1cdb6f40b03c9811421561668cc133e", + "reference": "d38f6cbff1cdb6f40b03c9811421561668cc133e", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.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": "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/4.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-02-02T06:06:56+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "7.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "8a59d9e25720482ee7fcdf296595e08795b84dc5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/8a59d9e25720482ee7fcdf296595e08795b84dc5", + "reference": "8a59d9e25720482ee7fcdf296595e08795b84dc5", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.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", + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-02-02T06:08:01+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "11.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "d475be032238173ca3b0a516f5cc291d174708ae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d475be032238173ca3b0a516f5cc291d174708ae", + "reference": "d475be032238173ca3b0a516f5cc291d174708ae", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.10.1", + "phar-io/manifest": "^2.0.3", + "phar-io/version": "^3.0.2", + "php": ">=8.2", + "phpunit/php-code-coverage": "^11.0", + "phpunit/php-file-iterator": "^5.0", + "phpunit/php-invoker": "^5.0", + "phpunit/php-text-template": "^4.0", + "phpunit/php-timer": "^7.0", + "sebastian/cli-parser": "^3.0", + "sebastian/code-unit": "^3.0", + "sebastian/comparator": "^6.0", + "sebastian/diff": "^6.0", + "sebastian/environment": "^7.0", + "sebastian/exporter": "^6.0", + "sebastian/global-state": "^7.0", + "sebastian/object-enumerator": "^6.0", + "sebastian/type": "^5.0", + "sebastian/version": "^5.0" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "11.1-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/11.1.3" + }, + "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": "2024-04-24T06:34:25+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "00a74d5568694711f0222e54fb281e1d15fdf04a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/00a74d5568694711f0222e54fb281e1d15fdf04a", + "reference": "00a74d5568694711f0222e54fb281e1d15fdf04a", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.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": "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/3.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:26:58+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "6634549cb8d702282a04a774e36a7477d2bd9015" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/6634549cb8d702282a04a774e36a7477d2bd9015", + "reference": "6634549cb8d702282a04a774e36a7477d2bd9015", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.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": "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", + "security": "https://github.com/sebastianbergmann/code-unit/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-02-02T05:50:41+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "df80c875d3e459b45c6039e4d9b71d4fbccae25d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/df80c875d3e459b45c6039e4d9b71d4fbccae25d", + "reference": "df80c875d3e459b45c6039e4d9b71d4fbccae25d", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.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" + } + ], + "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", + "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/4.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-02-02T05:52:17+00:00" + }, + { + "name": "sebastian/comparator", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "bd0f2fa5b9257c69903537b266ccb80fcf940db8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/bd0f2fa5b9257c69903537b266ccb80fcf940db8", + "reference": "bd0f2fa5b9257c69903537b266ccb80fcf940db8", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/diff": "^6.0", + "sebastian/exporter": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.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" + }, + { + "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/6.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-02-02T05:53:45+00:00" + }, + { + "name": "sebastian/complexity", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "88a434ad86150e11a606ac4866b09130712671f0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/88a434ad86150e11a606ac4866b09130712671f0", + "reference": "88a434ad86150e11a606ac4866b09130712671f0", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.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": "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/4.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-02-02T05:55:19+00:00" + }, + { + "name": "sebastian/diff", + "version": "6.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "ab83243ecc233de5655b76f577711de9f842e712" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ab83243ecc233de5655b76f577711de9f842e712", + "reference": "ab83243ecc233de5655b76f577711de9f842e712", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0", + "symfony/process": "^4.2 || ^5" + }, + "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" + }, + { + "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/6.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:30:33+00:00" + }, + { + "name": "sebastian/environment", + "version": "7.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "4eb3a442574d0e9d141aab209cd4aaf25701b09a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/4eb3a442574d0e9d141aab209cd4aaf25701b09a", + "reference": "4eb3a442574d0e9d141aab209cd4aaf25701b09a", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.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/7.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-23T08:56:34+00:00" + }, + { + "name": "sebastian/exporter", + "version": "6.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "f291e5a317c321c0381fa9ecc796fa2d21b186da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/f291e5a317c321c0381fa9ecc796fa2d21b186da", + "reference": "f291e5a317c321c0381fa9ecc796fa2d21b186da", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.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" + }, + { + "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/6.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:28:20+00:00" + }, + { + "name": "sebastian/global-state", + "version": "7.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "c3a307e832f2e69c7ef869e31fc644fde0e7cb3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/c3a307e832f2e69c7ef869e31fc644fde0e7cb3e", + "reference": "c3a307e832f2e69c7ef869e31fc644fde0e7cb3e", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.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/7.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:32:10+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "376c5b3f6b43c78fdc049740bca76a7c846706c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/376c5b3f6b43c78fdc049740bca76a7c846706c0", + "reference": "376c5b3f6b43c78fdc049740bca76a7c846706c0", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.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": "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/3.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-02-02T06:00:36+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "f75f6c460da0bbd9668f43a3dde0ec0ba7faa678" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f75f6c460da0bbd9668f43a3dde0ec0ba7faa678", + "reference": "f75f6c460da0bbd9668f43a3dde0ec0ba7faa678", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.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": "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", + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/6.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-02-02T06:01:29+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "bb2a6255d30853425fd38f032eb64ced9f7f132d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/bb2a6255d30853425fd38f032eb64ced9f7f132d", + "reference": "bb2a6255d30853425fd38f032eb64ced9f7f132d", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.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" + } + ], + "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", + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/4.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-02-02T06:02:18+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "b75224967b5a466925c6d54e68edd0edf8dd4ed4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/b75224967b5a466925c6d54e68edd0edf8dd4ed4", + "reference": "b75224967b5a466925c6d54e68edd0edf8dd4ed4", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.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" + }, + { + "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", + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-02-02T06:08:48+00:00" + }, + { + "name": "sebastian/type", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "b8502785eb3523ca0dd4afe9ca62235590020f3f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b8502785eb3523ca0dd4afe9ca62235590020f3f", + "reference": "b8502785eb3523ca0dd4afe9ca62235590020f3f", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.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", + "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", + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-02-02T06:09:34+00:00" + }, + { + "name": "sebastian/version", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "13999475d2cb1ab33cb73403ba356a814fdbb001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/13999475d2cb1ab33cb73403ba356a814fdbb001", + "reference": "13999475d2cb1ab33cb73403ba356a814fdbb001", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "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", + "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", + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-02-02T06:10:47+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": [], diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 000000000..eecbdc3c1 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,8 @@ + + + + + tests + + + diff --git a/tests/Opdavies/TwigExtension/OpdaviesTwigExtensionTest.php b/tests/Opdavies/TwigExtension/OpdaviesTwigExtensionTest.php new file mode 100644 index 000000000..4a043ef63 --- /dev/null +++ b/tests/Opdavies/TwigExtension/OpdaviesTwigExtensionTest.php @@ -0,0 +1,78 @@ +extension = new OpdaviesTwigExtension(); + } + + public function testNoPastEvents(): void + { + $talk = $this->createTalk( + events: [ + ['date' => (new \DateTime('+1 days'))->getTimestamp()], + ], + ); + + self::assertSame(0, $this->extension->getPastTalkCount([$talk])); + } + + public function testSinglePastEvent(): void + { + $talkA = $this->createTalk( + events: [ + ['date' => (new \DateTime('+1 days'))->getTimestamp()], + ], + ); + + $talkB = $this->createTalk( + events: [ + ['date' => (new \DateTime('-3 days'))->getTimestamp()], + ], + ); + + self::assertSame(1, $this->extension->getPastTalkCount([$talkA, $talkB])); + } + + public function testMultiplePastEvents(): void + { + $talkA = $this->createTalk( + events: [ + ['date' => (new \DateTime('-1 days'))->getTimestamp()], + ['date' => (new \DateTime('+1 days'))->getTimestamp()], + ], + ); + + $talkB = $this->createTalk( + events: [ + ['date' => (new \DateTime('-3 days'))->getTimestamp()], + ], + ); + + self::assertSame(2, $this->extension->getPastTalkCount([$talkA, $talkB])); + } + + /** + * Create a mock talk with a list of events. + */ + private function createTalk(array $events): ProxySourceItem + { + $configuration = $this->createMock(Configuration::class); + $configuration->method('get')->with($this->identicalTo('events'))->willReturn($events); + + $talk = $this->createMock(ProxySourceItem::class); + $talk->method('data')->willReturn($configuration); + + return $talk; + } +} From b4612c3da3ddbc1ddcd067d708c346f549f918f7 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Fri, 3 May 2024 14:22:39 +0100 Subject: [PATCH 377/501] Fix indentation --- .../TwigExtension/OpdaviesTwigExtensionTest.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/Opdavies/TwigExtension/OpdaviesTwigExtensionTest.php b/tests/Opdavies/TwigExtension/OpdaviesTwigExtensionTest.php index 4a043ef63..5690af2e6 100644 --- a/tests/Opdavies/TwigExtension/OpdaviesTwigExtensionTest.php +++ b/tests/Opdavies/TwigExtension/OpdaviesTwigExtensionTest.php @@ -20,18 +20,18 @@ class OpdaviesTwigExtensionTest extends TestCase { $talk = $this->createTalk( events: [ - ['date' => (new \DateTime('+1 days'))->getTimestamp()], + ['date' => (new \DateTime('+1 days'))->getTimestamp()], ], ); - self::assertSame(0, $this->extension->getPastTalkCount([$talk])); + self::assertSame(0, $this->extension->getPastTalkCount([$talk])); } public function testSinglePastEvent(): void { $talkA = $this->createTalk( events: [ - ['date' => (new \DateTime('+1 days'))->getTimestamp()], + ['date' => (new \DateTime('+1 days'))->getTimestamp()], ], ); @@ -48,8 +48,8 @@ class OpdaviesTwigExtensionTest extends TestCase { $talkA = $this->createTalk( events: [ - ['date' => (new \DateTime('-1 days'))->getTimestamp()], - ['date' => (new \DateTime('+1 days'))->getTimestamp()], + ['date' => (new \DateTime('-1 days'))->getTimestamp()], + ['date' => (new \DateTime('+1 days'))->getTimestamp()], ], ); From 4eb34fbbb12fe4575a9f087771018fc80ed3a833 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Fri, 3 May 2024 14:45:58 +0100 Subject: [PATCH 378/501] Add more tests --- .../OpdaviesTwigExtensionTest.php | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/Opdavies/TwigExtension/OpdaviesTwigExtensionTest.php b/tests/Opdavies/TwigExtension/OpdaviesTwigExtensionTest.php index 5690af2e6..0b23e8e63 100644 --- a/tests/Opdavies/TwigExtension/OpdaviesTwigExtensionTest.php +++ b/tests/Opdavies/TwigExtension/OpdaviesTwigExtensionTest.php @@ -44,6 +44,34 @@ class OpdaviesTwigExtensionTest extends TestCase self::assertSame(1, $this->extension->getPastTalkCount([$talkA, $talkB])); } + public function testSingleTalkWithMultiplePastEvents(): void + { + $talk = $this->createTalk( + events: [ + ['date' => (new \DateTime('-1 days'))->getTimestamp()], + ['date' => (new \DateTime('-1 week'))->getTimestamp()], + ['date' => (new \DateTime('-1 year'))->getTimestamp()], + ], + ); + + self::assertSame(3, $this->extension->getPastTalkCount([$talk])); + } + + public function testSingleTalkWithMultiplePastAndFutureEvents(): void + { + $talk = $this->createTalk( + events: [ + ['date' => (new \DateTime('+1 day'))->getTimestamp()], + ['date' => (new \DateTime('-1 day'))->getTimestamp()], + ['date' => (new \DateTime('-1 week'))->getTimestamp()], + ['date' => (new \DateTime('+1 year'))->getTimestamp()], + ['date' => (new \DateTime('-1 year'))->getTimestamp()], + ], + ); + + self::assertSame(3, $this->extension->getPastTalkCount([$talk])); + } + public function testMultiplePastEvents(): void { $talkA = $this->createTalk( From 216fe30bf533379021478108a985cb3cb83cf596 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Fri, 3 May 2024 14:55:43 +0100 Subject: [PATCH 379/501] Add custom assertion for the talk count --- .../OpdaviesTwigExtensionTest.php | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/tests/Opdavies/TwigExtension/OpdaviesTwigExtensionTest.php b/tests/Opdavies/TwigExtension/OpdaviesTwigExtensionTest.php index 0b23e8e63..42852d50a 100644 --- a/tests/Opdavies/TwigExtension/OpdaviesTwigExtensionTest.php +++ b/tests/Opdavies/TwigExtension/OpdaviesTwigExtensionTest.php @@ -24,7 +24,7 @@ class OpdaviesTwigExtensionTest extends TestCase ], ); - self::assertSame(0, $this->extension->getPastTalkCount([$talk])); + $this->assertTalkCount(expectedCount: 0, talks: [$talk]); } public function testSinglePastEvent(): void @@ -41,7 +41,7 @@ class OpdaviesTwigExtensionTest extends TestCase ], ); - self::assertSame(1, $this->extension->getPastTalkCount([$talkA, $talkB])); + $this->assertTalkCount(expectedCount: 1, talks: [$talkA, $talkB]); } public function testSingleTalkWithMultiplePastEvents(): void @@ -54,7 +54,7 @@ class OpdaviesTwigExtensionTest extends TestCase ], ); - self::assertSame(3, $this->extension->getPastTalkCount([$talk])); + $this->assertTalkCount(expectedCount: 3, talks: [$talk]); } public function testSingleTalkWithMultiplePastAndFutureEvents(): void @@ -69,7 +69,7 @@ class OpdaviesTwigExtensionTest extends TestCase ], ); - self::assertSame(3, $this->extension->getPastTalkCount([$talk])); + $this->assertTalkCount(expectedCount: 3, talks: [$talk]); } public function testMultiplePastEvents(): void @@ -87,7 +87,18 @@ class OpdaviesTwigExtensionTest extends TestCase ], ); - self::assertSame(2, $this->extension->getPastTalkCount([$talkA, $talkB])); + $this->assertTalkCount(expectedCount: 2, talks: [$talkA, $talkB]); + } + + /** + * Assert the extension uses the correct number of talks. + */ + private function assertTalkCount(int $expectedCount, array $talks): void + { + self::assertSame( + actual: $this->extension->getPastTalkCount($talks), + expected: $expectedCount, + ); } /** From 94415f04bab452c9a9cdbab3644359db27cdb55f Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Fri, 3 May 2024 15:21:56 +0100 Subject: [PATCH 380/501] Add GitHub Actions CI configuration --- .github/workflows/test.yaml | 22 ++++++++++++++++++++++ run.local | 4 ++++ 2 files changed, 26 insertions(+) create mode 100644 .github/workflows/test.yaml diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 000000000..7cd1db1f3 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,22 @@ +name: Run tests + +on: + pull_request: + push: + branches: + - main + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - uses: cachix/install-nix-action@v26 + with: + nix_path: nixpkgs=channel:nixos-unstable + + - uses: actions/checkout@v4 + + - run: | + nix develop -c composer install + nix develop -c ./run test diff --git a/run.local b/run.local index dcbd163e6..1dd224428 100755 --- a/run.local +++ b/run.local @@ -82,4 +82,8 @@ function publish { git stash pop } +function test { + ./vendor/bin/phpunit "${@}" +} + # vim: ft=bash From 45e8206df81ace279b6ca3c6bff8f4c6dc732757 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Fri, 3 May 2024 15:24:56 +0100 Subject: [PATCH 381/501] Use `--testdox` in CI --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 7cd1db1f3..ba559213a 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -19,4 +19,4 @@ jobs: - run: | nix develop -c composer install - nix develop -c ./run test + nix develop -c ./run test --testdox --colors=always From cd6575c6fcc091a0b7c98b6985b3a92b85e279e3 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Fri, 3 May 2024 21:40:02 +0100 Subject: [PATCH 382/501] Use Ansible for uploading files and managing NGINX --- build.yaml | 1 + flake.nix | 2 +- run.local | 5 +- tools/ansible/deploy.yaml | 18 ++ tools/ansible/hosts.ini | 1 + tools/ansible/reload-nginx.yaml | 16 + tools/nginx/www.oliverdavies.uk.conf | 449 +++++++++++++++++++++++++++ 7 files changed, 489 insertions(+), 3 deletions(-) create mode 100644 tools/ansible/deploy.yaml create mode 100644 tools/ansible/hosts.ini create mode 100644 tools/ansible/reload-nginx.yaml create mode 100644 tools/nginx/www.oliverdavies.uk.conf diff --git a/build.yaml b/build.yaml index 33fc8f31b..8e2f835c8 100644 --- a/build.yaml +++ b/build.yaml @@ -5,6 +5,7 @@ language: php flake: devshell: packages: + - ansible - nodePackages.pnpm - nodejs - php82 diff --git a/flake.nix b/flake.nix index b2bf3657a..a8cc2f592 100644 --- a/flake.nix +++ b/flake.nix @@ -12,7 +12,7 @@ inherit (pkgs) mkShell; in { devShells.${system}.default = - mkShell { buildInputs = with pkgs; [ nodePackages.pnpm nodejs php82 php82Packages.composer ]; }; + mkShell { buildInputs = with pkgs; [ ansible nodePackages.pnpm nodejs php82 php82Packages.composer ]; }; formatter.${system} = pkgs.nixfmt; }; diff --git a/run.local b/run.local index 1dd224428..a2de2c6ba 100755 --- a/run.local +++ b/run.local @@ -76,8 +76,9 @@ function publish { npm:build:css generate - rsync --archive --verbose --compress --update --delete \ - output_prod/ ssh.oliverdavies.uk:/srv/oliverdavies.uk-sculpin + ansible-playbook tools/ansible/deploy.yaml \ + -i tools/ansible/hosts.ini \ + --ask-become-pass git stash pop } diff --git a/tools/ansible/deploy.yaml b/tools/ansible/deploy.yaml new file mode 100644 index 000000000..2ca5cce01 --- /dev/null +++ b/tools/ansible/deploy.yaml @@ -0,0 +1,18 @@ +--- +- hosts: all + + vars: + project_dir: /srv/www.oliverdavies.uk + + tasks: + - ansible.builtin.file: + path: "{{ project_dir }}" + state: directory + owner: opdavies + group: opdavies + become: true + + - ansible.builtin.synchronize: + src: ../../output_prod/ + dest: "{{ project_dir }}" + delete: true diff --git a/tools/ansible/hosts.ini b/tools/ansible/hosts.ini new file mode 100644 index 000000000..52f304027 --- /dev/null +++ b/tools/ansible/hosts.ini @@ -0,0 +1 @@ +ssh.oliverdavies.uk diff --git a/tools/ansible/reload-nginx.yaml b/tools/ansible/reload-nginx.yaml new file mode 100644 index 000000000..f84dec2ea --- /dev/null +++ b/tools/ansible/reload-nginx.yaml @@ -0,0 +1,16 @@ +--- +- hosts: all + become: true + + vars: + domain: www.oliverdavies.uk + + tasks: + - ansible.builtin.copy: + src: ../nginx/{{ domain }}.conf + dest: /etc/nginx/sites-available/{{ domain }}.conf + + - ansible.builtin.file: + src: /etc/nginx/sites-available/{{ domain }}.conf + dest: /etc/nginx/sites-enabled/{{ domain }}.conf + state: link diff --git a/tools/nginx/www.oliverdavies.uk.conf b/tools/nginx/www.oliverdavies.uk.conf new file mode 100644 index 000000000..d9617a4df --- /dev/null +++ b/tools/nginx/www.oliverdavies.uk.conf @@ -0,0 +1,449 @@ +map $uri $new_uri { + ~^/10-useful-drupal-6-modules-i-use-every-project/?$ /blog/10-useful-drupal-6-modules; + ~^/2010/04/05/styling-drupal-6s-taxonomy-lists-with-php-css-and-jquery/?$ /blog/style-drupal-6s-taxonomy-lists-php-css-jquery; + ~^/2010/04/28/using-imagecache-and-imagecrop-for-my-portfolio/?$ /blog/using-imagecache-imagecrop-my-portfolio; + ~^/2010/05/29/importing-images-using-the-imagefieldimport-module/?$ /blog/quickly-import-multiples-images-using-imagefieldimport-module; + ~^/2010/06/23/creating-a-block-of-social-media-icons-using-cck-views-and-nodequeue/?$ /blog/create-block-social-media-icons-using-cck-views-nodequeue; + ~^/2010/07/05/thanks/?$ /blog/thanks; + ~^/2010/08/17/create-a-better-photo-gallery-in-drupal-part-2/?$ /blog/create-better-photo-gallery-drupal-part-2; + ~^/2014/05/21/git-format-patch/?$ /blog/git-format-patch-your-friend; + ~^/2PxmyqP/?$ /articles/examples-of-laravel-collections-in-drupal; + ~^/39CoG/?$ /articles/drupalcamp-london-testing-workshop; + ~^/3eGQr/?$ https://github.com/howToCodeWell/howToCodeWellFM/blob/c927e0b3589f1d7375002f7fd70f0bfc9fc90449/composer.json#L17; + ~^/6UhLN/?$ https://github.com/opdavies/sculpin-twig-markdown-bundle/pull/1; + ~^/6i3YZ/?$ https://www.youtube.com/watch?v=vUK5sEbd-dk; + ~^/9rv0Z/?$ https://www.drupal.org/project/override_node_options/issues/3109852; + ~^/BhMZi/?$ https://git.drupalcode.org/search?utf8=%E2%9C%93&snippets=&scope=&repository_ref=8.x-1.x&search=baz&project_id=23203; + ~^/NBi5h/?$ https://git.drupalcode.org/search?utf8=%E2%9C%93&search=bar&group_id=&project_id=23203&search_code=true&repository_ref=8.x-1.x&nav_source=navbar; + ~^/P5KQ5/?$ https://www.npmjs.com/package/tailwindcss-skip-link; + ~^/S8ZDA/?$ /articles/rebuilding-bartik-with-vuejs-tailwind-css-part-2; + ~^/Wh48P/?$ https://github.com/opdavies/oliverdavies.uk/blob/master/source/_partials/talk/video.html.twig; + ~^/XbzS2/?$ https://github.com/opdavies/gmail-filter-builder; + ~^/YK1VH/?$ /articles/psr4-autoloading-test-cases-drupal-7; + ~^/YilTZ$ https://drupalcamp.london/tickets/training; + ~^/about/?$ /; + ~^/about/cv/?$ /cv; + ~^/about/speaker/?$ /speaker; + ~^/about/speaker-information/?$ /speaker-information; + ~^/acquia-certifications/?$ https://certification.acquia.com/registry?fname=Oliver&lname=Davies&city=&state=&country=United+Kingdom&org=&exam=All; + ~^/acquia-certified/?$ https://certification.acquia.com/?fname=Oliver&lname=Davies; + ~^/ansible-molecule/?$ /articles/test-driven-ansible-role-development-molecule; + ~^/ansible/?$ https://galaxy.ansible.com/opdavies; + ~^/ansistrano-code/?$ https://github.com/opdavies/dransible; + ~^/ansistrano-demo/?$ https://www.youtube.com/watch?v=PLS4ET7FAcU; + ~^/ansistrano-slides/?$ /talks/deploying-php-ansible-ansistrano; + ~^/archive/2022/10/20/run-vs-task-runners/? /archive/2022/10/19/run-vs-task-runners; + ~^/articles/(.*)/?$ /blog/$1; + ~^/atNOQ/?$ https://youtu.be/r41dkD2EOo8; + ~^/automatically-updating-talk-created-date/?$ https://gist.github.com/opdavies/4e75e1753d8603113f07f8264bb783d6; + ~^/blog.xml/?$ /rss.xml; + ~^/blog/10-useful-drupal-6-modules/?$ /blog/useful-drupal-6-modules; + ~^/blog/10-years-working-full-time-drupal/?$ /blog/10-years-working-full-time-drupal-php; + ~^/blog/2010/04/05/style-drupal-6s-taxonomy-lists-php-css-and-jquery/?$ /blog/style-drupal-6s-taxonomy-lists-php-css-and-jquery; + ~^/blog/2010/04/05/styling-drupal-6s-taxonomy-lists-with-php-css-and-jquery/?$ /blog/style-drupal-6s-taxonomy-lists-php-css-jquery; + ~^/blog/2010/04/28/using-imagecache-and-imagecrop-my-portfolio/?$ /blog/using-imagecache-and-imagecrop-my-portfolio; + ~^/blog/2010/05/06/conditional-email-addresses-webform/?$ /blog/conditional-email-addresses-webform; + ~^/blog/2010/05/10/quickly-create-zen-subthemes-using-zenophile/?$ /blog/quickly-create-zen-subthemes-using-zenophile; + ~^/blog/2010/05/25/create-slideshow-multiple-images-using-fancy-slide/?$ /blog/create-slideshow-multiple-images-using-fancy-slide; + ~^/blog/2010/05/29/quickly-import-multiples-images-using-imagefieldimport-module/?$ /blog/quickly-import-multiples-images-using-imagefieldimport-module; + ~^/blog/2010/06/02/improve-jpg-quality-imagecache-and-imageapi/?$ /blog/improve-jpg-quality-imagecache-and-imageapi; + ~^/blog/2010/06/23/create-block-social-media-icons-using-cck-views-and-nodequeue/?$ /blog/create-block-social-media-icons-using-cck-views-and-nodequeue; + ~^/blog/2010/06/25/10-useful-drupal-6-modules/?$ /blog/10-useful-drupal-6-modules; + ~^/blog/2010/06/28/create-flickr-photo-gallery-using-feeds-cck-and-views/?$ /blog/create-flickr-photo-gallery-using-feeds-cck-and-views; + ~^/blog/2010/07/01/change-content-type-multiple-nodes-using-sql/?$ /blog/change-content-type-multiple-nodes-using-sql; + ~^/blog/2010/07/02/create-virtual-hosts-mac-os-x-using-virtualhostx/?$ /blog/create-virtual-hosts-mac-os-x-using-virtualhostx; + ~^/blog/2010/07/07/add-taxonomy-term-multiple-nodes-using-sql/?$ /blog/add-taxonomy-term-multiple-nodes-using-sql; + ~^/blog/2010/07/07/quickly-adding-taxonomy-term-multiple-nodes-using-sql/?$ /blog/add-taxonomy-term-multiple-nodes-using-sql; + ~^/blog/2010/07/12/overview-teleport-module/?$ /blog/review-teleport-module; + ~^/blog/2010/07/12/review-teleport-module/?$ /blog/review-teleport-module; + ~^/blog/2010/08/10/review-adminhover-module/?$ /blog/review-adminhover-module; + ~^/blog/2010/08/11/create-better-photo-gallery-drupal-part-1/?$ /blog/create-better-photo-gallery-drupal-part-1; + ~^/blog/2010/08/11/how-create-better-photo-gallery-drupal-part-1/?$ /blog/create-better-photo-gallery-drupal-part-1; + ~^/blog/2010/08/17/create-better-photo-gallery-drupal-part-2/?$ /blog/create-better-photo-gallery-drupal-part-2; + ~^/blog/2010/08/20/review-image-caption-module/?$ /blog/review-image-caption-module; + ~^/blog/2010/09/26/south-wales-drupal-user-group/?$ /blog/south-wales-drupal-user-group; + ~^/blog/2010/10/10/create-and-apply-patches/?$ /blog/create-and-apply-patches; + ~^/blog/2010/10/13/create-better-photo-gallery-drupal-part-3/?$ /blog/create-better-photo-gallery-drupal-part-3; + ~^/blog/2010/10/22/create-better-photo-gallery-drupal-part-21/?$ /blog/create-better-photo-gallery-drupal-part-21; + ~^/blog/2010/11/04/use-regular-expressions-search-and-replace-coda-or-textmate/?$ /blog/use-regular-expressions-search-and-replace-coda-or-textmate; + ~^/blog/2011/02/14/easily-embed-typekit-fonts-your-drupal-website/?$ /blog/easily-embed-typekit-fonts-your-drupal-website; + ~^/blog/2011/03/15/display-number-facebook-fans-php/?$ /blog/display-number-facebook-fans-php; + ~^/blog/2011/03/31/proctor-stevenson/?$ /blog/proctor-stevenson; + ~^/blog/2011/05/20/proctors-hosting-next-drupal-meetup/?$ /blog/proctors-hosting-next-drupal-meetup; + ~^/blog/2011/05/23/imagefield-import-archive/?$ /blog/imagefield-import-archive; + ~^/blog/2011/08/28/create-multigroups-drupal-7-using-field-collections/?$ /blog/create-multigroups-drupal-7-using-field-collections; + ~^/blog/2011/10/19/install-and-configure-subversion-svn-server-ubuntu/?$ /blog/install-and-configure-subversion-svn-server-ubuntu; + ~^/blog/2011/10/install-and-configure-subversion-svn-server-ubuntu/?$ /blog/how-install-configure-subversion-svn-server-ubuntu; + ~^/blog/2012/01/04/site-upgraded-drupal-7/?$ /blog/site-upgraded-drupal-7; + ~^/blog/2012/02/01/use-authorize-keys-create-passwordless-ssh-connection/?$ /blog/use-authorized-keys-create-passwordless-ssh-connection; + ~^/blog/2012/04/16/create-omega-subtheme-less-css-preprocessor-using-omega-tools-and-drush/?$ /blog/create-omega-subtheme-less-css-preprocessor-using-omega-tools-and-drush; + ~^/blog/2012/04/17/installing-nagios-centos/?$ /blog/installing-nagios-centos; + ~^/blog/2012/04/19/adding-custom-theme-templates-drupal-7/?$ /blog/adding-custom-theme-templates-drupal-7; + ~^/blog/2012/04/adding-custom-theme-templates-drupal-7/?$ /blog/adding-custom-theme-templates-drupal-7; + ~^/blog/2012/05/23/add-date-popup-calendar-custom-form/?$ /blog/add-date-popup-calendar-custom-form; + ~^/blog/2012/05/23/checkout-specific-revision-svn-command-line/?$ /blog/checkout-specific-revision-svn-command-line; + ~^/blog/2012/05/23/forward-one-domain-another-using-mod-rewrite-and-htaccess/?$ /blog/forward-one-domain-another-using-mod-rewrite-and-htaccess; + ~^/blog/2012/05/23/forward-one-domain-another-using-modrewrite-and-htaccess/?$ /blog/forward-one-domain-another-using-modrewrite-htaccess; + ~^/blog/2012/05/23/prevent-apache-displaying-text-files-within-web-browser/?$ /blog/prevent-apache-displaying-text-files-within-web-browser; + ~^/blog/2012/05/23/writing-info-file-drupal-7-theme/?$ /blog/writing-info-file-drupal-7-theme; + ~^/blog/2012/05/24/dividing-drupals-process-and-preprocess-functions-separate-files/?$ /blog/dividing-drupals-process-and-preprocess-functions-separate-files; + ~^/blog/2012/05/forward-one-domain-another-using-modrewrite-and-htaccess/?$ /blog/forward-one-domain-another-using-modrewrite-htaccess; + ~^/blog/2012/07/12/my-new-drupal-modules/?$ /blog/my-new-drupal-modules; + ~^/blog/2012/07/14/install-nomensa-media-player-drupal/?$ /blog/install-nomensa-media-player-drupal; + ~^/blog/2012/07/27/writing-article-linux-journal/?$ /blog/writing-article-linux-journal; + ~^/blog/2012/07/install-and-configure-nomensa-accessible-media-player-drupal/?$ /blog/install-configure-nomensa-accessible-media-player-drupal; + ~^/blog/2012/07/nomensa-accessible-media-player-drupal/?$ /blog/install-configure-nomensa-accessible-media-player-drupal; + ~^/blog/2012/08/18/display-custom-menu-drupal-7-theme-template-file/?$ /blog/display-custom-menu-drupal-7-theme-template-file; + ~^/blog/2012/09/06/reflections-speaking-unifieddiff/?$ /blog/reflections-speaking-unifieddiff; + ~^/blog/2012/10/25/my-sublime-text-2-settings/?$ /blog/my-sublime-text-2-settings; + ~^/blog/2012/11/15/accessible-bristol-site-launched/?$ /blog/accessible-bristol-site-launched; + ~^/blog/2012/11/17/open-sublime-text-2-mac-os-x-command-line/?$ /blog/open-sublime-text-2-mac-os-x-command-line; + ~^/blog/2012/12/06/use-sass-and-compass-drupal-7-using-sassy/?$ /blog/use-sass-and-compass-drupal-7-using-sassy; + ~^/blog/2012/12/use-sass-and-compass-drupal-7-using-sassy/?$ /blog/use-sass-and-compass-drupal-7-using-sassy; + ~^/blog/2013/01/09/checking-if-user-logged-drupal-right-way/?$ /blog/checking-if-user-logged-drupal-right-way; + ~^/blog/2013/02/16/creating-and-using-custom-tokens-drupal-7/?$ /blog/creating-and-using-custom-tokens-drupal-7; + ~^/blog/2013/02/creating-and-using-custom-tokens-drupal-7/?$ /blog/creating-using-custom-tokens-drupal-7; + ~^/blog/2013/03/02/quickest-way-install-sublime-text-2-ubuntu/?$ /blog/quickest-way-install-sublime-text-2-ubuntu; + ~^/blog/2013/04/20/leaving-nomensa-joining-precedent/?$ /blog/leaving-nomensa-joining-precedent; + ~^/blog/2013/04/27/display-git-branch-or-tag-names-your-bash-prompt/?$ /blog/display-git-branch-or-tag-names-your-bash-prompt; + ~^/blog/2013/04/display-git-branch-or-tag-names-your-bash-prompt/?$ /blog/display-git-branch-or-tag-names-your-bash-prompt; + ~^/blog/2013/06/13/some-useful-links-using-simpletest-drupal/?$ /blog/some-useful-links-using-simpletest-drupal; + ~^/blog/2013/07/17/creating-local-and-staging-sites-drupals-domain-module-enabled/?$ /blog/creating-local-and-staging-sites-drupals-domain-module-enabled; + ~^/blog/2013/07/26/going-drupalcon/?$ /blog/going-drupalcon; + ~^/blog/2013/09/06/create-a-zen-sub-theme-using-drush/?$ /blog/create-a-zen-sub-theme-using-drush; + ~^/blog/2013/09/create-zen-sub-theme-using-drush/?$ /blog/create-zen-sub-theme-using-drush; + ~^/blog/2013/11/19/dont-bootstrap-drupal-use-drush/?$ /blog/dont-bootstrap-drupal-use-drush; + ~^/blog/2013/11/27/useful-vagrant-commands/?$ /blog/useful-vagrant-commands; + ~^/blog/2013/11/dont-bootstrap-drupal-use-drush/?$ /blog/dont-bootstrap-drupal-use-drush; + ~^/blog/2013/12/24/quickly-apply-patches-using-git-and-curl-or-wget/?$ /blog/quickly-apply-patches-using-git-and-curl-or-wget; + ~^/blog/2013/12/31/download-different-versions-drupal-drush/?$ /blog/download-different-versions-drupal-drush; + ~^/blog/2013/12/quickly-apply-patches-using-git-and-curl-or-wget /blog/quickly-apply-patches-using-git-curl-or-wget; + ~^/blog/2014/01/15/some-useful-git-aliases/?$ /blog/some-useful-git-aliases; + ~^/blog/2014/02/09/drupalcamp-london-2014/?$ /blog/drupalcamp-london-2014; + ~^/blog/2014/03/03/what-git-flow/?$ /blog/what-git-flow; + ~^/blog/2014/05/03/drupal-association/?$ /blog/drupal-association; + ~^/blog/2014/05/06/thanks/?$ /blog/thanks; + ~^/blog/2014/05/21/git-format-patch/?$ /blog/git-format-patch; + ~^/blog/2014/07/02/drush-make-drupalbristol/?$ /blog/drush-make-drupalbristol; + ~^/blog/2014/10/06/fix-vagrant-loading-wrong-virtual-machine/?$ /blog/fix-vagrant-loading-wrong-virtual-machine; + ~^/blog/2014/10/21/updating-features-and-adding-components-using-drush/?$ /blog/updating-features-and-adding-components-using-drush; + ~^/blog/2014/11/18/include-css-fonts-using-sass-each-loop/?$ /blog/include-css-fonts-using-sass-each-loop; + ~^/blog/2014/11/20/using-remote-files-when-developing-locally-with-stage-file-proxy-module/?$ /blog/using-remote-files-when-developing-locally-with-stage-file-proxy-module; + ~^/blog/2014/11/27/pantheon-settings-files/?$ /blog/pantheon-settings-files; + ~^/blog/2014/12/20/include-local-drupal-settings-file-environment-configuration-and-overrides/?$ /blog/include-local-drupal-settings-file-environment-configuration-and-overrides; + ~^/blog/2015/04/03/how-to-define-a-minimum-drupal-core-version/?$ /blog/how-to-define-a-minimum-drupal-core-version; + ~^/blog/2015/06/18/updating-forked-repositories-on-github/?$ /blog/updating-forked-repositories-on-github; + ~^/blog/2015/07/19/sculpin-twig-resources/?$ /blog/sculpin-twig-resources; + ~^/blog/2015/07/21/automating-sculpin-jenkins/?$ /blog/automating-sculpin-jenkins; + ~^/blog/2015/12/22/programmatically-load-an-entityform-in-drupal-7/?$ /blog/programmatically-load-an-entityform-in-drupal-7; + ~^/blog/2016/02/15/announcing-the-drupal-vm-generator/?$ /blog/announcing-the-drupal-vm-generator; + ~^/blog/2016/05/03/simplifying-drupal-migrations-with-xautoload/?$ /blog/simplifying-drupal-migrations-with-xautoload; + ~^/blog/2016/07/15/building-gmail-filters-with-php/?$ /blog/building-gmail-filters-with-php; + ~^/blog/2016/12/30/drupal-vm-generator-291-released/?$ /blog/drupal-vm-generator-291-released; + ~^/blog/2017/01/07/easier-sculpin-commands-with-composer-and-npm-scripts/?$ /blog/easier-sculpin-commands-with-composer-and-npm-scripts; + ~^/blog/2017/01/31/nginx-redirects-with-query-string-arguments/?$ /blog/nginx-redirects-with-query-string-arguments; + ~^/blog/2017/05/05/fixing-drupal-simpletest-docker/?$ /blog/2017/05/05/fixing-drupal-simpletest-issues-inside-docker-containers; + ~^/blog/2017/05/05/fixing-drupal-simpletest-issues-inside-docker-containers/?$ /blog/fixing-drupal-simpletest-issues-inside-docker-containers; + ~^/blog/2017/05/20/turning-drupal-module-into-feature/?$ /blog/turning-your-custom-drupal-module-feature; + ~^/blog/2017/06/09/introducing-the-drupal-meetups-twitterbot/?$ /blog/introducing-the-drupal-meetups-twitterbot; + ~^/blog/2017/07/13/publishing-sculpin-sites-with-github-pages/?$ /blog/publishing-sculpin-sites-github-pages; + ~^/blog/2017/11/07/tdd-test-driven-drupal/?$ /blog/tdd-test-driven-drupal; + ~^/blog/2017/11/07/writing-drupal-module-test-driven-development-tdd/?$ /blog/2017/11/07/tdd-test-driven-drupal; + ~^/blog/2018/01/30/drupalcamp-bristol-2018/?$ /blog/drupalcamp-bristol-2018; + ~^/blog/2018/02/05/using-tailwind-css-in-your-drupal-theme/?$ /blog/using-tailwind-css-in-your-drupal-theme; + ~^/blog/2018/02/27/looking-forward-to-drupalcamp-london/?$ /blog/looking-forward-to-drupalcamp-london; + ~^/blog/2018/02/27/queuing-private-messages-in-drupal-8/?$ /blog/queuing-private-messages-in-drupal-8; + ~^/blog/2018/02/28/building-the-new-phpsw-website/?$ /blog/building-the-new-phpsw-website; + ~^/blog/2018/03/02/yay-the-mediacurrent-contrib-half-hour-is-back/?$ /blog/yay-the-mediacurrent-contrib-half-hour-is-back; + ~^/blog/2018/03/04/tweets-from-drupalcamp-london/?$ /blog/tweets-from-drupalcamp-london; + ~^/blog/2018/04/23/back-to-the-future-git-diff-apply/?$ /blog/back-future-gits-diff-apply-commands; + ~^/blog/2018/05/06/creating-a-custom-phpunit-command-for-docksal/?$ /blog/creating-a-custom-phpunit-command-for-docksal; + ~^/blog/add-date-popup-calendar-custom-form/?$ /blog/how-add-date-popup-calendar-custom-form; + ~^/blog/adding-methods-decorating-entity-metadata-wrapper/?$ /blog/decorating-entity-metadata-wrapper-add-refactor-methods; + ~^/blog/announcing-drupal-vm-generator/?$ /blog/announcing-the-drupal-vm-generator; + ~^/blog/announcing-the-drupal-vm-config-generator/?$ /blog/announcing-the-drupal-vm-generator; + ~^/blog/back-to-the-future-git-diff-apply/?$ /blog/back-future-gits-diff-apply-commands; + ~^/blog/building-gmail-filters-in-php/?$ /blog/building-gmail-filters-php; + ~^/blog/building-new-phpsw-website/?$ /blog/building-the-new-phpsw-website; + ~^/blog/building-presentation-slides-reveal-js-tailwind-css/?$ /blog/building-presentation-slides-rst2pdf; + ~^/blog/building-speaker-leaderboard-php-south-wales-using-drupal-symfony/?$ /blog/building-speaker-leaderboard-php-south-wales-drupal-symfony; + ~^/blog/create-and-apply-patches/?$ /blog/how-create-apply-patches; + ~^/blog/create-flickr-photo-gallery-using-feeds-cck-and-views/?$ /blog/create-flickr-photo-gallery-using-feeds-cck-views; + ~^/blog/creating-and-using-custom-tokens-drupal-7/?$ /blog/creating-using-custom-tokens-drupal-7; + ~^/blog/creating-custom-docksal-commands/?$ /blog/creating-custom-phpunit-command-docksal; + ~^/blog/debugging-drupal-commerce-promotions-illiminate-collections/?$ /blog/debugging-drupal-commerce-illuminate-collections; + ~^/blog/decorating-entity-metadata-wrapper-add-add-refactor-methods/?$ /blog/decorating-entity-metadata-wrapper-add-refactor-methods; + ~^/blog/dev-book-club-refactoring-chapter-1/?$ /blog/dev-book-club-notes-refactoring-chapter-1; + ~^/blog/dividing-drupals-process-preprocess-functions-separate-files/?$ /blog/dividing-drupals-process-and-preprocess-functions-separate-files; + ~^/blog/drupal-8-commerce-fixing-no-such-customer-error-(on-)?checkout/?$ /blog/drupal-8-commerce-fixing-no-such-customer-error-checkout; + ~^/blog/drupal-vm-generator-291-released/?$ /blog/drupal-vm-generator-updates; + ~^/blog/drupalcamp-london-2019-tickets/?$ /blog/drupalcamp-london-2019-tickets-available-call-sessions; + ~^/blog/drush-make-drupalbristol/?$ /talks/drush-make-drupalbristol; + ~^/blog/easier-git-repository-cloning-with-insteadof/?$ /blog/easier-git-repository-cloning-insteadof; + ~^/blog/easier-sculpin-commands-with-composer-and-npm-scripts/?$ /blog/easier-sculpin-commands-composer-npm-scripts; + ~^/blog/editing-meetup-videos-kdenlive/?$ /blog/editing-meetup-videos-linux-kdenlive; + ~^/blog/examples-of-laravel-collections-in-drupal/?$ /blog/using-laravel-collections-drupal; + ~^/blog/experimenting-with-events-in-drupal-8/?$ /blog/experimenting-events-drupal-8; + ~^/blog/fix-vagrant-loading-wrong-virtual-machine/?$ /blog/how-fix-vagrant-loading-wrong-virtual-machine; + ~^/blog/fixing-drupal-simpletest-docker/?$ /blog/fixing-drupal-simpletest-issues-inside-docker-containers; + ~^/blog/forward-one-domain-another-using-modrewrite-and-htaccess/?$ /blog/forward-one-domain-another-using-modrewrite-htaccess; + ~^/blog/forward-one-domain-another-using-modrewrite-and-htaccess/?$ /blog/forward-one-domain-another-using-modrewrite-htaccess; + ~^/blog/git-format-patch/?$ /blog/git-format-patch-your-friend; + ~^/blog/how-easily-embed-typekit-fonts-your-drupal-website/?$ /blog/easily-embed-typekit-fonts-your-drupal-website; + ~^/blog/how-split-new-drupal-contrib-project-within-another-repository/?$ /blog/splitting-new-drupal-project-from-repo; + ~^/blog/how-style-drupal-6s-taxonomy-lists-php-css-and-jquery/?$ /blog/style-drupal-6s-taxonomy-lists-php-css-jquery; + ~^/blog/include-local-drupal-settings-file-environment-configuration-overrides/?$ /blog/include-local-drupal-settings-file-environment-configuration-and-overrides; + ~^/blog/install-and-configure-subversion-svn-server-ubuntu/?$ /blog/how-install-configure-subversion-svn-server-ubuntu; + ~^/blog/live-blogging-symfonylive-london/?$ /blog/live-blogging-symfonylive-london-2019; + ~^/blog/minimum-core-version/?$ /blog/how-define-minimum-drupal-core-version; + ~^/blog/nginx-redirects-with-query-string-arguments/?$ /blog/nginx-redirects-query-string-arguments; + ~^/blog/null-users-and-system-users-in-drupal/?$ /blog/null-users-system-users-drupal; + ~^/blog/overridding-phpcs-configuration-drupal-ci/?$ /blog/overriding-phpcs-configuration-drupal-ci; + ~^/blog/pantheon-settings-files/?$ /blog/include-environment-specific-settings-files-pantheon; + ~^/blog/pdfpc-pdf-presenter-console-notes/?$ /blog/presenting-pdf-slides-using-pdfpc-pdf-presenter-console; + ~^/blog/php-apps-subdirectory-nginx/?$ /blog/how-put-your-php-application-subdirectory-another-site-nginx; + ~^/blog/presenting-tailwind-css-ansible-cms-philly/?$ /blog/presenting-on-tailwind-css-and-ansible-at-cms-philly; + ~^/blog/programmatically-load-(an-)?entityform-(in-)?drupal-7/?$ /blog/entityform; + ~^/blog/published-my-first-docker-images-docker-hub/?$ /blog/published-my-first-docker-images-docker-hub-adr-tools-sculpin-rst2pdf; + ~^/blog/publishing-sculpin-sites-(with-)?github-pages/?$ /blog/publishing-sculpin-sites-github-pages; + ~^/blog/queuing-private-messages-in-drupal-8/?$ /blog/queuing-private-messages-drupal-8; + ~^/blog/quick-project-switching-in-phpstorm/?$ /blog/quick-project-switching-phpstorm; + ~^/blog/quickly-apply-patches-using-git-and-curl-or-wget/?$ /blog/quickly-apply-patches-using-git-curl-or-wget; + ~^/blog/rebuilding-bartik-with-vuejs-tailwind-css-part-2/?$ /blog/rebuilding-bartik-drupals-default-theme-vuejs-tailwind-css-part-2; + ~^/blog/rebuilding-bartik-with-vuejs-tailwind-css/?$ /blog/rebuilding-bartik-drupals-default-theme-vuejs-tailwind-css; + ~^/blog/rebuilding-uis-tailwind-css/?$ /blog/uis-ive-rebuilt-tailwind-css; + ~^/blog/restructuring-my-tailwindjs-config-files/?$ /blog/restructuring-my-tailwindjs-configuration-files; + ~^/blog/retrieving-profile-data-user-using-entity-metadata-wrapper/?$ /blog/cleanly-retrieving-user-profile-data-using-entity-metadata-wrapper; + ~^/blog/running-drupal-with-symfony-local-server/?$ /blog/running-drupal-88-symfony-local-server; + ~^/blog/running-phpunit-tests-docksal-phpstorm/?$ /blog/how-run-drupal-8-phpunit-tests-within-docksal-phpstorm; + ~^/blog/simplifying-drupal-migrations-with-xautoload/?$ /blog/simplifying-drupal-migrations-xautoload; + ~^/blog/speaking-drupalcon-barcelona-2020/?$ /blog/speaking-drupalcon-europe-2020; + ~^/blog/speaking-during-lockdown/?$ /blog/speaking-remotely-during-covid-19; + ~^/blog/speaking-remotely-during-lockdown/?$ /blog/speaking-remotely-during-covid-19; + ~^/blog/style-drupal-6s-taxonomy-lists-php-css-jquery/?$ /blog/style-drupal-6s-taxonomy-lists-php-css-and-jquery; + ~^/blog/survey-results-my-drupalcon-europe-session/?$ /blog/survey-results-my-drupalcon-europe-session-test-driven-drupal; + ~^/blog/system-users-null-users/?$ /blog/null-users-and-system-users-in-drupal; + ~^/blog/test-driven-drupal-gitstore-leanpub/?$ /blog/test-driven-drupal-on-gitstore-leanpub; + ~^/blog/test-driven-drupal-presentation-drupalcon-europe-0/?$ /blog/test-driven-drupal-presentation-drupalcon-europe; + ~^/blog/test-driven-drupal-session-drupalcon-europe/?$ /blog/test-driven-drupal-presentation-drupalcon-europe; + ~^/blog/test-driven-drupal-session-video-drupalcon-europe/?$ /blog/test-driven-drupal-presentation-drupalcon-europe; + ~^/blog/testing-tailwind-css-plugins-with-jest/?$ /blog/testing-tailwind-css-plugins-jest; + ~^/blog/testing-tailwindcss-plugins-with-jest/?$ /blog/testing-tailwind-css-plugins-jest; + ~^/blog/tweets-from-drupalcamp-london/?$ /blog/tweets-drupalcamp-london; + ~^/blog/updating-features-and-adding-components-using-drush/?$ /blog/updating-features-adding-components-using-drush; + ~^/blog/updating-forked-repositories-github/?$ /blog/updating-forked-github-repos; + ~^/blog/use-regular-expressions-search-and-replace-coda-or-textmate/?$ /blog/use-regular-expressions-search-replace-coda-or-textmate; + ~^/blog/using-environment-variables-settings-docksal/?$ /blog/how-use-environment-variables-your-drupal-settings-docksal; + ~^/blog/using-psr-4-autoloading-your-drupal-7-test-cases/?$ /blog/psr4-autoloading-test-cases-drupal-7; + ~^/blog/using-tailwind-css-in-your-drupal-theme/?$ /blog/using-tailwind-css-your-drupal-theme; + ~^/blog/using-the-pcss-extension-postcss-webpack-encore/?$ /blog/using-pcss-extension-postcss-webpack-encore; + ~^/blog/weeknotes-june-5th/?$ /blog/weeknotes-2021-06-05; + ~^/blog/writing-drupal-module-test-driven-development-tdd/?$ /blog/writing-new-drupal-8-module-using-test-driven-development-tdd; + ~^/book/?$ /test-driven-drupal; + ~^/calendars?/?$ https://savvycal.com/opdavies; + ~^/cms-philly/?$ /articles/presenting-on-tailwind-css-and-ansible-at-cms-philly; + ~^/code-enigma-interview/?$ https://blog.codeenigma.com/interview-with-a-drupal-expert-9fcd8e0fad28; + ~^/consulting/?$ /; + ~^/contrib-half-hour/?$ https://www.youtube.com/playlist?list=PLu-MxhbnjI9rHroPvZO5LEUhr58Yl0j_F; + ~^/d0P5z/?$ /talks/drupal-8-php-libraries-drupalorg-api; + ~^/d7/?$ /drupal7; + ~^/dcbristol-cfp/?$ https://www.papercall.io/drupalcamp-bristol-2019; + ~^/dcbristol17-videos/?$ https://www.youtube.com/playlist?list=PLOwPvExSyLLngtd6R4PUD9MCXa6QL_obA; + ~^/dcbristol19-announced/?$ /articles/drupalcamp-bristol-2019-speakers-sessions-announced; + ~^/dclondon-sat/?$ https://drupalcamp.london/schedule/saturday; + ~^/dclondon-sun/?$ https://drupalcamp.london/schedule/sunday; + ~^/dclondon20/?$ /articles/drupalcamp-london-testing-workshop; + ~^/ddev-phpunit-command/?$ /blog/creating-custom-phpunit-command-ddev; + ~^/deploying-php-ansible/?$ /talks/deploying-php-ansible-ansistrano; + ~^/dks7E/?$ https://www.youtube.com/watch?v=PLS4ET7FAcU; + ~^/do-library/?$ https://github.com/opdavies/drupalorg-api-php; + ~^/do-projects/?$ https://github.com/opdavies/drupal-module-drupalorg-projects; + ~^/docksal-phpunit-phpstorm/?$ /articles/running-phpunit-tests-docksal-phpstorm; + ~^/docksal-posts/?$ /articles/tags/docksal; + ~^/dransible-drupal-9/?$ /blog/upgrading-dransible-project-drupal-9; + ~^/dransible/?$ https://github.com/opdavies/dransible; + ~^/drupal-bristol-march-19/?$ https://docs.google.com/presentation/d/1pk9LIN-hHX73kvDdo-lzgmKlAeH33_K_uvI0t7A-rvY/edit?usp=sharing; + ~^/drupal-consultant/?$ /drupal-consulting; + ~^/drupal-consulting/?$ /; + ~^/drupal-core-live-stream/?$ https://www.youtube.com/watch?v=OK4FWwh1gQU; + ~^/drupal-core-testing-gate/?$ https://www.drupal.org/core/gates#testing; + ~^/drupal-first-time-issues/?$ https://www.drupal.org/project/issues/search?text=&projects=&assigned=&submitted=&project_issue_followers=&status%5B%5D=Open&issue_tags_op=%3D&issue_tags=Novice; + ~^/drupal-forum-post/?$ http://www.webmaster-forums.net/webmasters-corner/developing-my-website-using-php-and-mysql#comment-1231537; + ~^/drupal-marketplace-uk/?$ https://www.drupal.org/drupal-services?offices%5B%5D=24460; + ~^/drupal-meetups-twitterbot/?$ /articles/introducing-the-drupal-meetups-twitterbot; + ~^/drupal-novice-issues/?$ https://www.drupal.org/project/issues/search?text=&projects=&assigned=&submitted=&project_issue_followers=&status%5B%5D=Open&issue_tags_op=%3D&issue_tags=Novice; + ~^/drupal-php-developer-consultant-uk/?$ /drupal-php-developer; + ~^/drupal-php-developer/?$ /drupal-consultant; + ~^/drupal-tailwind-demo/?$ https://www.youtube.com/watch?v=1eM-Gw6GI4g; + ~^/drupal-tailwindcss/?$ https://www.drupal.org/project/tailwindcss; + ~^/drupal-vuejs/?$ /talks/decoupling-drupal-vuejs/; + ~^/drupal7/?$ /drupal-upgrade; + ~^/drupalcamp-london-2019-tickets/?$ /articles/drupalcamp-london-2019-tickets; + ~^/drupalcamp-nyc-training/?$ https://www.youtube.com/watch?v=3M9c4UUzKm0; + ~^/drupalorg-project-issues/?$ https://www.drupal.org/project/issues/search?projects=Override+Node+Options%2C+Tailwind+CSS+Starter+Kit%2C+Block+ARIA+Landmark+Roles%2C+Copyright+Block+module%2C+System+User%2C+Null+User%2C+Collection+class%2C+Pathauto+Menu+Link%2C+Webform+ARIA&project_issue_followers=&status%5B%5D=1&status%5B%5D=13&status%5B%5D=8&status%5B%5D=14&status%5B%5D=15&issue_tags_op=%3D; + ~^/drupalorg/?$ https://www.drupal.org/u/opdavies; + ~^/drupalversary/?$ https://github.com/opdavies/drupal-module-drupalversary; + ~^/elewant/?$ https://elewant.com/shepherd/admire/opdavies; + ~^/feed/?$ /rss.xml; + ~^/first-drupal-core-issue/?$ https://www.drupal.org/project/drupal/issues/753898; + ~^/first-npm-package/?$ https://www.npmjs.com/package/tailwindcss-vuejs; + ~^/freeagent/?$ https://opdavies.freeagent.com; + ~^/git-flow/?$ /talks/git-flow; + ~^/gitlab/?$ https://gitlab.com/opdavies; + ~^/gitstore/?$ https://enjoy.gitstore.app/maintainers/opdavies; + ~^/gmail-filters/?$ https://gitlab.com/opdavies/gmail-filters; + ~^/images/me-precedent.jpg/?$ /sites/default/files/images/social-avatar.jpg; + ~^/inviqa-tailwind-demo/?$ https://play.tailwindcss.com/Yfmw8O5UNN; + ~^/inviqa-tailwind-notes/?$ https://gist.github.com/opdavies/e6f0f4938506a6859acf1aca8b4e1a74; + ~^/join-php-south-wales-slack/?$ https://join.slack.com/t/phpsouthwales/shared_invite/zt-4vuetc43-AvtEK1WqNzp5k1w4yWKOJA; + ~^/jy6rW/?$ https://www.meetup.com/PHP-South-Wales/events/264731393; + ~^/kB6Jd/?$ /articles/running-drupal-with-symfony-local-server/; + ~^/kmDRA/?$ https://www.bbc.co.uk/news/uk-46561779; + ~^/leeds-php-drupal-9/?$ https://www.meetup.com/leedsphp/events/272504993; + ~^/live/?$ https://www.youtube.com/channel/UCkeK0qF9HHUPQH_fvn4ghqQ; + ~^/npm/?$ https://www.npmjs.com/~opdavies; + ~^/oFlkS/?$ /articles/test-driven-drupal-on-gitstore-leanpub; + ~^/oliver-davies-uk-based-drupal-symfony-developer/?$ /oliver-davies-uk-based-drupal-php-developer; + ~^/pair-programming/?$ /pair; + ~^/pair-with-me/?$ /pair; + ~^/pairing/?$ /pair; + ~^/php-ansible/?$ /talks/deploying-php-ansible-ansistrano; + ~^/qSHAl/?$ /articles/published-my-first-npm-package/; + ~^/qT1Rb/?$ https://github.com/opdavies/drupal-meetups-twitterbot; + ~^/rebuilding-acquia/?$ https://rebuilding-acquia.oliverdavies.uk; + ~^/rebuilding-bartik/?$ /articles/rebuilding-bartik-with-vuejs-tailwind-css; + ~^/rebuilding-bristol-js/?$ https://github.com/opdavies/rebuilding-bristol-js; + ~^/rebuilding-pantheon/?$ https://play.tailwindcss.com/LND98XihGI?layout=horizontal; + ~^/rebuilding-platformsh/?$ https://rebuilding-platformsh.oliverdavies.uk; + ~^/rebuilding-symfony/?$ https://github.com/opdavies/rebuilding-symfony; + ~^/rk29B/?$ https://www.meetup.com/PHP-South-Wales/events/268422525; + ~^/rss/?$ /rss.xml; + ~^/roadmap/?$ /drupal-upgrade; + ~^/s9MjJ/?$ https://symfonycasts.com/screencast/symfony; + ~^/sculpin-encore-versioning/?$ https://github.com/opdavies/oliverdavies.uk/commit/d192b04aefa6e7a21bfc1f2e0fe0a16111e0e8a2; + ~^/sites/default/files/images/social-avatar.jpg /images/social-avatar.jpg; + ~^/skills/?$ https://opdavies-skills-tailwindcss.netlify.com/; + ~^/slides-drupal-9/?$ https://slides-upgrading-to-drupal-9.oliverdavies.uk; + ~^/slides-upgrading-to-drupal-9/?$ https://slides-upgrading-to-drupal-9.oliverdavies.uk; + ~^/slides-upgrading-to-drupal-9/index.html/?$ https://slides-upgrading-to-drupal-9.oliverdavies.uk; + ~^/slides-working-with-workspace/?$ https://slides-working-with-workspace.oliverdavies.uk; + ~^/speaker-info/?$ /speaker; + ~^/speaker-information/?$ /speaker; + ~^/speaker/?$ /press; + ~^/speaking-videos/?$ https://www.youtube.com/playlist?list=PLHn41Ay7w7kfAzczswrANch5oHAPZBlvu; + ~^/stream/?$ https://www.youtube.com/channel/UCkeK0qF9HHUPQH_fvn4ghqQ/live; + ~^/subscription/?$ /; + ~^/swap-markdown-parser/?$ https://github.com/opdavies/sculpin-twig-markdown-bundle-example/tree/swap-markdown-parser; + ~^/symfony-server/?$ /articles/running-drupal-with-symfony-local-server; + ~^/symfony/?$ https://connect.symfony.com/profile/opdavies; + ~^/symfonylive/?$ /articles/live-blogging-symfonylive-london; + ~^/symposium/?$ https://symposiumapp.com/u/opdavies; + ~^/tailwind-css-talk/?$ /talks/taking-flight-tailwind-css; + ~^/tailwind-repos/?$ https://github.com/opdavies?utf8=%E2%9C%93&tab=repositories&q=tailwindcss; + ~^/tailwind-talk/?$ /talks/taking-flight-with-tailwind-css; + ~^/tailwindcss-demo/?$ http://tailwindcss-demo.oliverdavies.uk/; + ~^/talks-offer-tweet/?$ https://twitter.com/opdavies/status/1250870367712935938; + ~^/talks/2012/09/05/what-is-this-drupal-thing-unified-diff/?$ /talks/what-is-this-drupal-thing; + ~^/talks/2013/07/10/drupal-ldap-swdug/?$ /talks/drupal-ldap; + ~^/talks/2014/03/01/git-flow-drupalcamp-london-2014/?$ /talks/git-flow; + ~^/talks/2014/07/02/drush-make-drupalbristol-drupal-bristol/?$ /talks/drush-make-drupalbristol; + ~^/talks/2014/08/19/drupal-association-swdug/?$ /talks/drupal-association; + ~^/talks/2015/01/18/drupalorg-2015-drupalcamp-brighton-2015/?$ /talks/drupalorg-in-2015-whats-coming-next; + ~^/talks/2015/02/28/drupalorg-2015-drupalcamp-london-2015/?$ /talks/drupalorg-in-2015-whats-coming-next; + ~^/talks/2015/04/08/drupal-8-phpsw/?$ /talks/drupal-8; + ~^/talks/2015/07/25/test-drive-twig-with-sculpin-drupalcamp-north-2015/?$ /talks/test-drive-twig-with-sculpin; + ~^/talks/2015/08/25/dancing-for-drupal-umbristol/?$ /talks/dancing-for-drupal; + ~^/talks/2015/10/14/sculpin-phpsw/?$ /talks/sculpin; + ~^/talks/2016/03/05/drupal-8-module-development-drupalcamp-london-2016/?$ /talks/getting-started-with-drupal-8-module-development; + ~^/talks/2016/03/09/drupal-vm-generator-nwdug/?$ /talks/drupal-vm-generator; + ~^/talks/2016/04/02/drupal-vm-generator-drupal-bristol/?$ /talks/drupal-vm-generator; + ~^/talks/2016/06/11/drupal-8-rejoining-the-herd-php-south-coast-2016/?$ /talks/drupal-8-rejoining-the-herd; + ~^/talks/2016/07/23/drupal-vm-meet-symfony-console-drupalcamp-bristol-2016/?$ /talks/drupal-vm-meet-symfony-console; + ~^/talks/2016/11/09/drupal-development-with-composer-phpsw/?$ /talks/drupal-development-with-composer; + ~^/talks/2016/11/17/goodbye-drush-make-hello-composer-drupal-bristol/?$ /talks/goodbye-drush-make-hello-composer; + ~^/talks/2017/01/18/getting-your-data-into-drupal-8-drupal-bristol/?$ /talks/getting-your-data-into-drupal-8; + ~^/talks/2017/03/04/getting-your-data-into-drupal-8-drupalcamp-london-2017/?$ /talks/getting-your-data-into-drupal-8; + ~^/talks/ansible-ansistrano/?$ https://www.oliverdavies.uk/talks/deploying-php-ansible-ansistrano; + ~^/talks/archive/?$ /talks; + ~^/talks/deploying-php-applications-fabric/?$ /talks/deploying-php-fabric; + ~^/talks/deploying-php-applications-with-fabric/?$ /talks/deploying-php-fabric; + ~^/talks/drupal-vm-generator-2/?$ /talks/drupal-vm-generator; + ~^/talks/drupalorg-2015-2/?$ /talks/drupalorg-2015; + ~^/talks/drupalorg-in-2015-whats-coming-next/?$ /talks/drupalorg-2015; + ~^/talks/getting-started-with-drupal-8-module-development/?$ /drupal-8-module-development; + ~^/talks/having-fun-drupal-8-php-libraries-drupalorg-api/?$ /talks/drupal-8-php-libraries-drupalorg-api; + ~^/talks/never-commit-master-introduction-git-flow/?$ /talks/git-flow; + ~^/talks/sculpin/?$ /talks/building-static-websites-sculpin; + ~^/talks/tailwind/?$ /talks/taking-flight-with-tailwind-css/; + ~^/talks/taking-flight-tailwind-css/?$ /talks/taking-flight-with-tailwind-css; + ~^/talks/using-laravel-collections-outside-laravel/?$ /talks/using-illuminate-collections-outside-laravel; + ~^/talks/working-workspace/?$ /talks/working-with-workspace; + ~^/tdd-blog/?$ https://github.com/opdavies/drupal-module-tdd-blog; + ~^/tdd-test-driven-drupal/?$ /talks/tdd-test-driven-drupal/; + ~^/team-coaching/?$ /; + ~^/test-driven-drupal-book/?$ /test-driven-drupal; + ~^/testing-drupal-intro/?$ https://inviqa.com/blog/drupal-automated-testing-introduction; + ~^/testing-drupal/?$ https://www.oliverdavies.uk/talks/tdd-test-driven-drupal; + ~^/testing-tailwind-plugins/?$ /articles/testing-tailwindcss-plugins-with-jest; + ~^/testing-workshop-code/?$ https://github.com/opdavies/workshop-drupal-automated-testing-code; + ~^/testing-workshop/?$ https://github.com/opdavies/workshop-drupal-automated-testing; + ~^/todoist-filters/?$ https://gist.github.com/opdavies/6709fbdac5c3babbd94137bcc8b8e3c2; + ~^/twitter-tweaks/?$ https://github.com/opdavies/chrome-extension-twitter-tweaks; + ~^/upgrading-to-drupal-9/?$ /talks/upgrading-your-site-drupal-9; + ~^/uxbjV/?$ https://www.drupal.org/project/copyright_block; + ~^/vyTEF/?$ https://www.npmjs.com/package/tailwindcss-vuejs; + ~^/webpack-encore-pcss-regex/?$ https://regexr.com/51iaf; + ~^/wordcamp-bristol-tailwindcss/?$ https://2019.bristol.wordcamp.org/session/taking-flight-with-tailwind-css; + ~^/wordpress-tailwind/?$ https://github.com/opdavies/wordcamp-bristol-2019; + ~^/work/?$ /drupal-php-developer; + ~^/working-with-workspace/?$ /talks/working-with-workspace; + ~^/workshop-drupal-testing/?$ https://github.com/opdavies/workshop-drupal-automated-testing; + ~^/workspace-demo/?$ https://github.com/opdavies/working-with-workspace-demo; + ~^/wp-tailwind-repo/?$ https://github.com/opdavies/wordcamp-bristol-2019; + ~^/wp-tailwind-starter/?$ https://github.com/opdavies/wordpress-tailwindcss-startker-kit; + ~^/wp-tailwind-static/?$ https://wp-tailwind.oliverdavies.uk; + ~^/wp-tailwind/?$ https://wp-tailwind.oliverdavies.uk; + ~^/yXhoS/?$ /talks/things-you-should-know-about-php; +} + +server { + listen 80 default_server; + + server_name _; + + return 301 https://$host$request_uri; +} + +server { + listen 443 ssl; + + server_name oliverdavies.uk; + + ssl_certificate /home/opdavies/.acme.sh/www.oliverdavies.uk/www.oliverdavies.uk.cer; + ssl_certificate_key /home/opdavies/.acme.sh/www.oliverdavies.uk/www.oliverdavies.uk.key; + + return 301 $scheme://www.oliverdavies.uk$request_uri; +} + +server { + listen 443 ssl; + + server_name www.oliverdavies.uk; + root /srv/www.oliverdavies.uk; + index index.html; + + error_page 404 /404; + + ssl_certificate /home/opdavies/.acme.sh/www.oliverdavies.uk/www.oliverdavies.uk.cer; + ssl_certificate_key /home/opdavies/.acme.sh/www.oliverdavies.uk/www.oliverdavies.uk.key; + + location / { + try_files $uri $uri.html $uri/index.html =404; + } + + rewrite ^/(.*)/$ /$1 permanent; + + if ($new_uri) { + return 301 $new_uri; + } +} From 77fb00b47d85c231d9c94fb0b32628c97d453ebb Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Fri, 3 May 2024 23:51:15 +0100 Subject: [PATCH 383/501] Add daily email for 2024-05-02 Re-evaluating old tools --- source/_daily_emails/2024-05-02.md | 31 ++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 source/_daily_emails/2024-05-02.md diff --git a/source/_daily_emails/2024-05-02.md b/source/_daily_emails/2024-05-02.md new file mode 100644 index 000000000..07dc698f9 --- /dev/null +++ b/source/_daily_emails/2024-05-02.md @@ -0,0 +1,31 @@ +--- +title: Re-evaluating old tools +date: 2024-05-02 +permalink: archive/2024/05/02/re-evaluating-old-tools +tags: + - software-development + - devops + - ansible +cta: ~ +snippet: | + Today, I gave Ansible a spin again after not using it for a while. +--- + +It's been a while since I used Ansible. + +I used it for automating infrastructure and deploying applications. + +[I gave a talk about it][talk] on several occasions, including remotely for the Ansible London meetup during COVID. + +I haven't had to use it recently but, today, I [brought it back][commit] out of my toolkit. + +I was reviewing how I manage my website's configuration and files and Ansible seemed to be a perfect choice. + +The files need to be uploaded to the server and, to handle redirects, I also have a custom NGINX virtual host configuration that needs to be uploaded if I change it and the NGINX service reloaded. + +Ansible is a great tool for this type of task and, just because it's a tool I haven't used it for a while, that doesn't mean I can't re-evaluate it and see if fits my current requirements. + +It's still in my toolkit, just in case I need it. + +[commit]: https://github.com/opdavies/oliverdavies.uk/commit/cd6575c6fcc091a0b7c98b6985b3a92b85e279e3 +[talk]: {{site.url}}/talks/deploying-php-ansible-ansistrano From e309e4fe33659f5de1fe7a486cd09ded19f8a9da Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Sat, 4 May 2024 00:03:36 +0100 Subject: [PATCH 384/501] Update project name --- .tmuxinator.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tmuxinator.yaml b/.tmuxinator.yaml index ec81cbb0b..93087edcc 100644 --- a/.tmuxinator.yaml +++ b/.tmuxinator.yaml @@ -1,4 +1,4 @@ -name: oliverdavies-uk-sculpin +name: oliverdavies-uk root: . windows: From e3b6a8feeceeb5b5a3893a41d736fada6eb18334 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Sat, 4 May 2024 23:54:06 +0100 Subject: [PATCH 385/501] Add daily email for 2024-05-03 Don't add boolean arguments --- source/_daily_emails/2024-05-03.md | 38 ++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 source/_daily_emails/2024-05-03.md diff --git a/source/_daily_emails/2024-05-03.md b/source/_daily_emails/2024-05-03.md new file mode 100644 index 000000000..9a7fe6fcb --- /dev/null +++ b/source/_daily_emails/2024-05-03.md @@ -0,0 +1,38 @@ +--- +title: Don't add boolean arguments +date: 2024-05-03 +permalink: archive/2024/05/03/dont-add-boolean-arguments +tags: + - software-development + - drupal + - php +cta: ~ +snippet: | + Don't add boolean arguments to your methods. +--- + +A convention I like from the Laravel framework is to avoid adding boolean arguments to methods. + +For example, if I have this function: + +```php +public function getPosts() { ... } +``` +If I wanted to only get published posts, one way would be to add a boolean argument: + +```php +public function getPosts(boolean $onlyPublished) { ... } +``` + +Then, I'd need to use that within the method body to add another condition (this is referred to as control coupling, where one method affects another). + +The non-boolean approach would be to create a separate method with its own distinct name. + +For example, `getPosts()` could be named `getAllPosts()` and there could be a separate `getPublishedPosts()` method for only getting published posts: + +```php +public function getAllPosts() { ... } + +public function getPublishedPosts() { ... } +``` +Whilst we have two methods now instead of one, it's much clearer what each does and there aren't any random `true` or `false`s wherever the method is used. From d3789edb27d6de039a3f604ac61114b6abfa070d Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Sun, 5 May 2024 23:59:05 +0100 Subject: [PATCH 386/501] Add daily email for 2024-05-04 Strict typing in PHP --- source/_daily_emails/2024-05-04.md | 38 ++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 source/_daily_emails/2024-05-04.md diff --git a/source/_daily_emails/2024-05-04.md b/source/_daily_emails/2024-05-04.md new file mode 100644 index 000000000..7385354ff --- /dev/null +++ b/source/_daily_emails/2024-05-04.md @@ -0,0 +1,38 @@ +--- +title: Strict typing in PHP +date: 2024-05-04 +permalink: archive/2024/05/04/strict-typing-in-php +tags: + - software-development + - php +cta: ~ +snippet: | + Do you enable strict types in your PHP code? +--- + +I prefer writing and working with strictly typed code. + +One of the major improvements in PHP has been the option to enable strict types. + +For example, this code will usually not error and give the result: + +```php +function add(int $a, int $b): void +{ + var_dump($a + $b); +} + +add(1, '1'); +``` + +However, I'd prefer if it failed as I'm passing the function an integer and a string, but specifying they should both be integers. + +Fixing this is simple, by adding this line to the top of the file: + +```php +declare(strict_types=1); +``` + +I add this to every PHP file by default. + +I want my code to be as strict and predictable as possible, and to error when I want it to and make any bugs more explicit and easier to find and fix. From 3a5f60be83191fb4e72097d50f7dd7dced3c7124 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Mon, 6 May 2024 00:24:20 +0100 Subject: [PATCH 387/501] Add daily email for 2024-05-05 Making PHPStan stricter --- source/_daily_emails/2024-05-05.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 source/_daily_emails/2024-05-05.md diff --git a/source/_daily_emails/2024-05-05.md b/source/_daily_emails/2024-05-05.md new file mode 100644 index 000000000..157c7b21d --- /dev/null +++ b/source/_daily_emails/2024-05-05.md @@ -0,0 +1,30 @@ +--- +title: Making PHPStan stricter +date: 2024-05-05 +permalink: archive/2024/05/05/making-phpstan-stricter +tags: + - software-development + - php + - phpstan +cta: ~ +snippet: | + As well as PHP, you can make PHPStan stricter. +--- + +Continuing yesterday's thought on [strictness in PHP][yesterday], today I want to talk about adding more strictness to PHPStan. + +Adding the [PHPStan Strict Rules extension][extension] makes PHPStan stricter by adding new, more opinionated rules. + +For example: + +* Require booleans in if, elseif, ternary operator, after !, and on both sides of && and ||. +* Use the `$strict` parameter with `in_array`, `array_search`, `array_keys` and `base64_decode`. +* Disallow empty(). +* Require calling parent constructor. + +You can enable and disable rules as needed but, like setting the PHPStan level, ideally I like to enable them all by default and see how strict I go. + +It depends on the code being tested and the preference of the team, though I find the stricter the rules, the less bugs there are. + +[extension]: https://github.com/phpstan/phpstan-strict-rules +[yesterday]: {{site.url}}/archive/2024/05/04/strict-typing-in-php From 44ae1e91a1faf5966422cda01d73f301611be97f Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Tue, 7 May 2024 15:41:49 +0100 Subject: [PATCH 388/501] Add daily email for 2024-05-06 Interactive staging --- source/_daily_emails/2024-05-06.md | 50 ++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 source/_daily_emails/2024-05-06.md diff --git a/source/_daily_emails/2024-05-06.md b/source/_daily_emails/2024-05-06.md new file mode 100644 index 000000000..0069f3b74 --- /dev/null +++ b/source/_daily_emails/2024-05-06.md @@ -0,0 +1,50 @@ +--- +title: Interactive staging +date: 2024-05-06 +permalink: archive/2024/05/06/interactive-staging +tags: + - software-development + - git +cta: ~ +snippet: | + TODO +--- + +A major addition to my Git workflow has been the ability to interactively add hunks of code to be committed. + +There's `git add -i` to interactively add, though I usually go straight to `git add -p` to use `patch`. + +This will ask you to confirm if you want to add each hunk to the commit (a.k.a. the staging area) or not. + +For example, here's the prompt I get whilst working on the post for this email: + +```shell +diff --git a/source/_daily_emails/2024-05-06.md b/source/_daily_emails/2024-05-06.md +index 42fe48f..ef36a2b 100644 +--- a/source/_daily_emails/2024-05-06.md ++++ b/source/_daily_emails/2024-05-06.md +@@ -4,10 +4,12 @@ date: 2024-05-06 + permalink: archive/2024/05/06/interactive-staging + tags: + - software-development +- # - drupal +- # - php +- # - podcast ++ - git + cta: ~ + snippet: | + TODO + --- ++ ++A major addition to my Git workflow has been the ability to interactively add hunks of code to be committed. ++ ++There's `git add -i` to interactively add, though I usually go straight to `git add -p` to use `patch`. ++There's `git add -i` to interactively add, though I usually go straight to `git add -p` to use `patch`. +(1/1) Stage this hunk [y,n,q,a,d,s,e,?]? +``` + +I can add the whole hunk, split it into smaller hunks, add all the hunks in the file or ignore this hunk and later hunks in the file. + +Then the process is repeated for any following hunks. + +This means I can add the relevant hunks to craft the commit I want and I can keep my commits small and meaningful, and easy to revert if there is an issue. From 1045e5c85ed995a7c488e9610e569fc5326e1c15 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Wed, 8 May 2024 07:34:25 +0100 Subject: [PATCH 389/501] Fix HTML validation errors --- source/_includes/button.html.twig | 2 +- source/_includes/testimonials.html.twig | 2 +- source/_layouts/base.html.twig | 40 ++++++++++++------------- source/_layouts/default.html.twig | 2 +- source/_layouts/page.html.twig | 2 +- 5 files changed, 24 insertions(+), 24 deletions(-) diff --git a/source/_includes/button.html.twig b/source/_includes/button.html.twig index 02afed38c..7cbeb63f6 100644 --- a/source/_includes/button.html.twig +++ b/source/_includes/button.html.twig @@ -5,7 +5,7 @@ - + diff --git a/source/_includes/testimonials.html.twig b/source/_includes/testimonials.html.twig index febd5f207..ceb6560f5 100644 --- a/source/_includes/testimonials.html.twig +++ b/source/_includes/testimonials.html.twig @@ -38,7 +38,7 @@ {% if testimonial.image %} - Photo of {{ testimonial.name }} + Photo of {{ testimonial.name }} {% endif %} diff --git a/source/_layouts/base.html.twig b/source/_layouts/base.html.twig index e7f9b65c7..b9a7ece00 100644 --- a/source/_layouts/base.html.twig +++ b/source/_layouts/base.html.twig @@ -1,40 +1,40 @@ - - - - - + + + + + - - - - + + + + - + - - - - + + + + {% if page.meta.description %} - + {% else %} - + {% endif %} {% block head_title %} {% if page.meta.title %} {{ page.meta.title }} - - + + {% else %} {{ page.title }} | {{ site.name }} - - + + {% endif %} {% endblock %} diff --git a/source/_layouts/default.html.twig b/source/_layouts/default.html.twig index 9e2be7751..1d32f9566 100644 --- a/source/_layouts/default.html.twig +++ b/source/_layouts/default.html.twig @@ -32,6 +32,6 @@
        {% include 'main-menu.html.twig' %}
        -
        +
    {% endblock %} diff --git a/source/_layouts/page.html.twig b/source/_layouts/page.html.twig index d1570d4f5..7731e968b 100644 --- a/source/_layouts/page.html.twig +++ b/source/_layouts/page.html.twig @@ -7,7 +7,7 @@ {% block styles %} -