From 3fb558dd701e8039748c674141a0f7e75d591aa5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 28 Jul 2026 21:49:51 +0000 Subject: [PATCH 01/16] Update uselagoon/valkey-8 Docker tag to v26.8.0 | datasource | package | from | to | | ---------- | ------------------ | ------ | ------ | | docker | uselagoon/valkey-8 | 26.7.0 | 26.8.0 | (cherry picked from commit 759754103cbd9e4c32c9b489b62e30918fd11bf0) --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 952635538..b10d04ca9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -218,7 +218,7 @@ services: #;< SERVICE_CACHE cache: - image: uselagoon/valkey-8:26.7.0 + image: uselagoon/valkey-8:26.8.0 #;< HOSTING_LAGOON labels: lagoon.type: redis # Change to 'none' if dedicated Redis service is used. See https://docs.lagoon.sh/using-lagoon-advanced/service-types/ From e5065c9c4a395d3bfca20668b5b1944a19b11f8d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 28 Jul 2026 21:49:44 +0000 Subject: [PATCH 02/16] Update uselagoon/mysql-8.4 Docker tag to v26.8.0 | datasource | package | from | to | | ---------- | ------------------- | ------ | ------ | | docker | uselagoon/mysql-8.4 | 26.7.0 | 26.8.0 | (cherry picked from commit 3ca7c572d9b3a66ac1e9116327d0e70f0849eddf) --- docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index b10d04ca9..236ed66d8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -190,7 +190,7 @@ services: context: . dockerfile: .docker/database.dockerfile args: - IMAGE: "${VORTEX_DB_IMAGE:-uselagoon/mysql-8.4:26.7.0}" # Use custom database image (if defined) or fallback to standard database image. + IMAGE: "${VORTEX_DB_IMAGE:-uselagoon/mysql-8.4:26.8.0}" # Use custom database image (if defined) or fallback to standard database image. environment: <<: *default-environment <<: *default-user @@ -203,7 +203,7 @@ services: #;< MIGRATION database2: - image: "${VORTEX_DB2_IMAGE:-uselagoon/mysql-8.4:26.7.0}" # Use custom database image (if defined) or fallback to standard database image. + image: "${VORTEX_DB2_IMAGE:-uselagoon/mysql-8.4:26.8.0}" # Use custom database image (if defined) or fallback to standard database image. environment: <<: *default-environment MYSQL_DATABASE: drupal From ae5f6562ba9761950c5b4f785f2828e2dd312f79 Mon Sep 17 00:00:00 2001 From: Alex Skrypnyk Date: Fri, 31 Jul 2026 15:33:45 +1000 Subject: [PATCH 03/16] Made 'dclint' and 'hadolint' optional development tools. Forward-ported from main f9415793247dd4d6070bc1a4d0e62c4986e9c20d. --- .circleci/config.yml | 4 + .github/workflows/build-test-deploy.yml | 4 + .vortex/cli/src/Prompts/Handlers/Tools.php | 28 +++++- .../Handlers/ToolsHandlerProcessTest.php | 91 +++++++++++++++++++ .../AbstractHandlerDiscoveryTestCase.php | 2 +- .../Handlers/ToolsHandlerDiscoveryTest.php | 67 +++++++++++++- .vortex/docs/content/tools/dclint.mdx | 4 + .vortex/docs/content/tools/hadolint.mdx | 5 + .vortex/docs/cspell.json | 1 + 9 files changed, 202 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 55635678b..987551e4e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -147,16 +147,20 @@ jobs: name: Login to container registry command: ./vendor/bin/vortex-login-container-registry + #;< TOOL_HADOLINT - run: name: Lint Dockerfiles with Hadolint command: | for file in $(find .docker \( -name 'Dockerfile' -o -name '*.dockerfile' \)); do echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] done + #;> TOOL_HADOLINT + #;< TOOL_DCLINT - run: name: Lint Docker Compose files with DCLint command: docker run --rm -v "${PWD}":/app zavoloklom/dclint:3.1.0 . || [ "${VORTEX_CI_DCLINT_IGNORE_FAILURE:-0}" -eq 1 ] + #;> TOOL_DCLINT #;< CI_GITLEAKS - run: diff --git a/.github/workflows/build-test-deploy.yml b/.github/workflows/build-test-deploy.yml index 720326a16..1616ac744 100644 --- a/.github/workflows/build-test-deploy.yml +++ b/.github/workflows/build-test-deploy.yml @@ -117,16 +117,20 @@ jobs: - name: Login to container registry run: ./vendor/bin/vortex-login-container-registry + #;< TOOL_HADOLINT - name: Lint Dockerfiles with Hadolint run: | find .docker \( -name 'Dockerfile' -o -name '*.dockerfile' \) | while read -r file; do echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint done continue-on-error: ${{ vars.VORTEX_CI_HADOLINT_IGNORE_FAILURE == '1' }} + #;> TOOL_HADOLINT + #;< TOOL_DCLINT - name: Lint Docker Compose files with DCLint run: docker run --rm -v "${PWD}":/app zavoloklom/dclint:3.1.0 . continue-on-error: ${{ vars.VORTEX_CI_DCLINT_IGNORE_FAILURE == '1' }} + #;> TOOL_DCLINT #;< CI_GITLEAKS - name: Scan for committed secrets with Gitleaks diff --git a/.vortex/cli/src/Prompts/Handlers/Tools.php b/.vortex/cli/src/Prompts/Handlers/Tools.php index 178f6c7ff..45311b332 100644 --- a/.vortex/cli/src/Prompts/Handlers/Tools.php +++ b/.vortex/cli/src/Prompts/Handlers/Tools.php @@ -32,6 +32,10 @@ class Tools extends AbstractHandler { const TWIG_CS_FIXER = 'twig_cs_fixer'; + const DCLINT = 'dclint'; + + const HADOLINT = 'hadolint'; + /** * {@inheritdoc} */ @@ -63,7 +67,9 @@ public function options(array $responses): ?array { public function default(array $responses): null|string|bool|array { return [ self::BEHAT, + self::DCLINT, self::ESLINT, + self::HADOLINT, self::JEST, self::PHPCS, self::PHPSTAN, @@ -211,7 +217,7 @@ function (string $content, ContentFile $file) use ($tool): string { } protected function processGroup(string $name): void { - $config = static::getToolDefinitions('goups')[$name]; + $config = static::getToolDefinitions('groups')[$name]; $selected_tools = $this->getResponseAsArray(); if (!isset($config['tools']) || array_intersect($config['tools'], $selected_tools)) { @@ -463,6 +469,26 @@ public static function getToolDefinitions(string $filter = 'all'): array { ], ], + self::DCLINT => [ + 'title' => 'DCLint', + 'present' => fn(): mixed => File::exists($this->dstDir . '/.dclintrc') || + File::contains($this->dstDir . '/.github/workflows/build-test-deploy.yml', 'dclint') || + File::contains($this->dstDir . '/.circleci/config.yml', 'dclint'), + 'files' => ['.dclintrc'], + 'strings' => ['/^.*dclint.*\n?/m'], + ], + + // The 'hadolint ignore=' directives in Dockerfiles are deliberately not + // a detection signal: they survive deselection, so treating them as one + // would make the choice impossible to reverse. + self::HADOLINT => [ + 'title' => 'Hadolint', + 'present' => fn(): mixed => File::exists($this->dstDir . '/.hadolint.yaml') || + File::exists($this->dstDir . '/.hadolint.yml') || + File::contains($this->dstDir . '/.github/workflows/build-test-deploy.yml', 'hadolint') || + File::contains($this->dstDir . '/.circleci/config.yml', 'hadolint'), + ], + // Tool groups with shared resources. 'backend_linting' => [ 'tools' => [self::PHPCS, self::PHPSTAN, self::RECTOR], diff --git a/.vortex/cli/tests/Functional/Handlers/ToolsHandlerProcessTest.php b/.vortex/cli/tests/Functional/Handlers/ToolsHandlerProcessTest.php index 740cc376d..bf247c9bb 100644 --- a/.vortex/cli/tests/Functional/Handlers/ToolsHandlerProcessTest.php +++ b/.vortex/cli/tests/Functional/Handlers/ToolsHandlerProcessTest.php @@ -29,6 +29,7 @@ public static function dataProviderHandlerProcess(): \Iterator { 'phpunit', 'behat', 'gherkinlint', + 'dclint', 'bdd', '/\blint-be:/', '/\blint-be-fix:/', @@ -47,6 +48,8 @@ public static function dataProviderHandlerProcess(): \Iterator { $test->assertFileDoesNotExist(static::$sut . '/.prettierignore'); $test->assertFileDoesNotExist(static::$sut . '/.stylelintrc.js'); $test->assertFileDoesNotExist(static::$sut . '/jest.config.js'); + $test->assertFileDoesNotExist(static::$sut . '/.dclintrc'); + $test->assertFileNotContainsString(static::$sut . '/.github/workflows/build-test-deploy.yml', 'hadolint'); $test->assertSutContains([ '/\blint-fe:/', @@ -150,6 +153,94 @@ public static function dataProviderHandlerProcess(): \Iterator { 'vincentlanglet/twig-cs-fixer', ])), ]; + yield 'tools_no_dclint' => [ + static::cw(function ($test): void { + $tools = array_keys(Tools::getToolDefinitions('tools')); + $test->prompts[Tools::id()] = array_values(array_diff($tools, [Tools::DCLINT])); + $test->prompts[CiProvider::id()] = CiProvider::GITHUB_ACTIONS; + }), + static::cw(function (AbstractHandlerProcessTestCase $test): void { + $test->assertSutNotContains(['dclint']); + $test->assertFileDoesNotExist(static::$sut . '/.dclintrc'); + $test->assertFileNotContainsString(static::$sut . '/.github/workflows/build-test-deploy.yml', 'VORTEX_CI_DCLINT_IGNORE_FAILURE'); + $test->assertFileContainsString(static::$sut . '/.github/workflows/build-test-deploy.yml', 'hadolint'); + }), + ]; + yield 'tools_no_dclint_circleci' => [ + static::cw(function ($test): void { + $tools = array_keys(Tools::getToolDefinitions('tools')); + $test->prompts[Tools::id()] = array_values(array_diff($tools, [Tools::DCLINT])); + $test->prompts[CiProvider::id()] = CiProvider::CIRCLECI; + }), + static::cw(function (AbstractHandlerProcessTestCase $test): void { + $test->assertSutNotContains(['dclint']); + $test->assertFileDoesNotExist(static::$sut . '/.dclintrc'); + $test->assertFileNotContainsString(static::$sut . '/.circleci/config.yml', 'VORTEX_CI_DCLINT_IGNORE_FAILURE'); + $test->assertFileContainsString(static::$sut . '/.circleci/config.yml', 'hadolint'); + }), + ]; + yield 'tools_no_hadolint' => [ + static::cw(function ($test): void { + $tools = array_keys(Tools::getToolDefinitions('tools')); + $test->prompts[Tools::id()] = array_values(array_diff($tools, [Tools::HADOLINT])); + $test->prompts[CiProvider::id()] = CiProvider::GITHUB_ACTIONS; + }), + static::cw(function (AbstractHandlerProcessTestCase $test): void { + $ci = static::$sut . '/.github/workflows/build-test-deploy.yml'; + $test->assertFileNotContainsString($ci, 'hadolint'); + $test->assertFileNotContainsString($ci, 'VORTEX_CI_HADOLINT_IGNORE_FAILURE'); + $test->assertFileContainsString($ci, 'dclint'); + $test->assertFileExists(static::$sut . '/.dclintrc'); + // Dockerfile directives are inert comments that remain useful when + // the tool is run by hand, so they survive deselection. + $test->assertFileContainsString(static::$sut . '/.docker/cli.dockerfile', '# hadolint global ignore='); + }), + ]; + yield 'tools_no_hadolint_circleci' => [ + static::cw(function ($test): void { + $tools = array_keys(Tools::getToolDefinitions('tools')); + $test->prompts[Tools::id()] = array_values(array_diff($tools, [Tools::HADOLINT])); + $test->prompts[CiProvider::id()] = CiProvider::CIRCLECI; + }), + static::cw(function (AbstractHandlerProcessTestCase $test): void { + $ci = static::$sut . '/.circleci/config.yml'; + $test->assertFileNotContainsString($ci, 'hadolint'); + $test->assertFileNotContainsString($ci, 'VORTEX_CI_HADOLINT_IGNORE_FAILURE'); + $test->assertFileContainsString($ci, 'dclint'); + $test->assertFileExists(static::$sut . '/.dclintrc'); + $test->assertFileContainsString(static::$sut . '/.docker/cli.dockerfile', '# hadolint global ignore='); + }), + ]; + yield 'tools_no_docker_linters' => [ + static::cw(function ($test): void { + $tools = array_keys(Tools::getToolDefinitions('tools')); + $test->prompts[Tools::id()] = array_values(array_diff($tools, [Tools::DCLINT, Tools::HADOLINT])); + $test->prompts[CiProvider::id()] = CiProvider::GITHUB_ACTIONS; + }), + static::cw(function (AbstractHandlerProcessTestCase $test): void { + $ci = static::$sut . '/.github/workflows/build-test-deploy.yml'; + $test->assertSutNotContains(['dclint']); + $test->assertFileDoesNotExist(static::$sut . '/.dclintrc'); + $test->assertFileNotContainsString($ci, 'hadolint'); + $test->assertFileNotContainsString($ci, 'VORTEX_CI_HADOLINT_IGNORE_FAILURE'); + $test->assertFileNotContainsString($ci, 'VORTEX_CI_DCLINT_IGNORE_FAILURE'); + }), + ]; + yield 'tools_no_docker_linters_circleci' => [ + static::cw(function ($test): void { + $tools = array_keys(Tools::getToolDefinitions('tools')); + $test->prompts[Tools::id()] = array_values(array_diff($tools, [Tools::DCLINT, Tools::HADOLINT])); + $test->prompts[CiProvider::id()] = CiProvider::CIRCLECI; + }), + static::cw(function (AbstractHandlerProcessTestCase $test): void { + $ci = static::$sut . '/.circleci/config.yml'; + $test->assertSutNotContains(['dclint']); + $test->assertFileDoesNotExist(static::$sut . '/.dclintrc'); + $test->assertFileNotContainsString($ci, 'hadolint'); + $test->assertFileNotContainsString($ci, 'VORTEX_CI_HADOLINT_IGNORE_FAILURE'); + $test->assertFileNotContainsString($ci, 'VORTEX_CI_DCLINT_IGNORE_FAILURE'); + }), + ]; yield 'tools_no_eslint' => [ static::cw(function ($test): void { $tools = array_keys(Tools::getToolDefinitions('tools')); diff --git a/.vortex/cli/tests/Unit/Handlers/AbstractHandlerDiscoveryTestCase.php b/.vortex/cli/tests/Unit/Handlers/AbstractHandlerDiscoveryTestCase.php index 5722ab2e2..bab60f4c3 100644 --- a/.vortex/cli/tests/Unit/Handlers/AbstractHandlerDiscoveryTestCase.php +++ b/.vortex/cli/tests/Unit/Handlers/AbstractHandlerDiscoveryTestCase.php @@ -145,7 +145,7 @@ protected static function getExpectedDefaults(): array { VersionScheme::id() => VersionScheme::CALVER, Timezone::id() => 'UTC', Services::id() => [Services::CLAMAV, Services::REDIS, Services::SOLR], - Tools::id() => [Tools::BEHAT, Tools::ESLINT, Tools::JEST, Tools::PHPCS, Tools::PHPSTAN, Tools::PHPUNIT, Tools::RECTOR, Tools::STYLELINT, Tools::TWIG_CS_FIXER], + Tools::id() => [Tools::BEHAT, Tools::DCLINT, Tools::ESLINT, Tools::HADOLINT, Tools::JEST, Tools::PHPCS, Tools::PHPSTAN, Tools::PHPUNIT, Tools::RECTOR, Tools::STYLELINT, Tools::TWIG_CS_FIXER], HostingProvider::id() => HostingProvider::NONE, HostingProjectName::id() => NULL, Webroot::id() => Webroot::WEB, diff --git a/.vortex/cli/tests/Unit/Handlers/ToolsHandlerDiscoveryTest.php b/.vortex/cli/tests/Unit/Handlers/ToolsHandlerDiscoveryTest.php index 35e3bed78..dbf23e413 100644 --- a/.vortex/cli/tests/Unit/Handlers/ToolsHandlerDiscoveryTest.php +++ b/.vortex/cli/tests/Unit/Handlers/ToolsHandlerDiscoveryTest.php @@ -4,6 +4,7 @@ namespace DrevOps\VortexCli\Tests\Unit\Handlers; +use DrevOps\VortexCli\Prompts\Handlers\CiProvider; use DrevOps\VortexCli\Prompts\Handlers\Tools; use DrevOps\VortexCli\Utils\Config; use DrevOps\VortexCli\Utils\File; @@ -18,11 +19,14 @@ public static function dataProviderRunPrompts(): \Iterator { $expected_installed = static::getExpectedInstalled(); yield 'tools - prompt - defaults' => [ [Tools::id() => Key::ENTER], - [Tools::id() => [Tools::BEHAT, Tools::ESLINT, Tools::JEST, Tools::PHPCS, Tools::PHPSTAN, Tools::PHPUNIT, Tools::RECTOR, Tools::STYLELINT, Tools::TWIG_CS_FIXER]] + $expected_defaults, + [Tools::id() => [Tools::BEHAT, Tools::DCLINT, Tools::ESLINT, Tools::HADOLINT, Tools::JEST, Tools::PHPCS, Tools::PHPSTAN, Tools::PHPUNIT, Tools::RECTOR, Tools::STYLELINT, Tools::TWIG_CS_FIXER]] + $expected_defaults, ]; yield 'tools - discovery - all tools' => [ [], - [Tools::id() => [Tools::BEHAT, Tools::ESLINT, Tools::JEST, Tools::PHPCS, Tools::PHPSTAN, Tools::PHPUNIT, Tools::RECTOR, Tools::STYLELINT, Tools::TWIG_CS_FIXER]] + $expected_installed, + [ + Tools::id() => [Tools::BEHAT, Tools::DCLINT, Tools::ESLINT, Tools::HADOLINT, Tools::JEST, Tools::PHPCS, Tools::PHPSTAN, Tools::PHPUNIT, Tools::RECTOR, Tools::STYLELINT, Tools::TWIG_CS_FIXER], + CiProvider::id() => CiProvider::CIRCLECI, + ] + $expected_installed, function (AbstractHandlerDiscoveryTestCase $test, Config $config): void { $test->stubVortexProject($config); $dependencies = [ @@ -35,6 +39,8 @@ function (AbstractHandlerDiscoveryTestCase $test, Config $config): void { ]; $test->stubComposerJsonDependencies($dependencies, TRUE); file_put_contents(static::$sut . '/package.json', json_encode(['devDependencies' => ['eslint' => '*', 'jest' => '*', 'stylelint' => '*']], JSON_PRETTY_PRINT)); + File::dump(static::$sut . '/.dclintrc'); + File::dump(static::$sut . '/.circleci/config.yml', 'docker run --rm -i hadolint/hadolint'); }, ]; yield 'tools - discovery - none' => [ @@ -235,6 +241,63 @@ function (AbstractHandlerDiscoveryTestCase $test, Config $config): void { File::dump(static::$sut . '/.twig-cs-fixer.php'); }, ]; + yield 'tools - discovery - dclint' => [ + [], + [Tools::id() => [Tools::DCLINT]] + $expected_installed, + function (AbstractHandlerDiscoveryTestCase $test, Config $config): void { + $test->stubVortexProject($config); + File::dump(static::$sut . '/.dclintrc'); + }, + ]; + yield 'tools - discovery - dclint, alt' => [ + [], + [ + Tools::id() => [Tools::DCLINT], + CiProvider::id() => CiProvider::GITHUB_ACTIONS, + ] + $expected_installed, + function (AbstractHandlerDiscoveryTestCase $test, Config $config): void { + $test->stubVortexProject($config); + File::dump(static::$sut . '/.github/workflows/build-test-deploy.yml', 'docker run --rm -v "${PWD}":/app zavoloklom/dclint:3.1.0 .'); + }, + ]; + yield 'tools - discovery - hadolint' => [ + [], + [ + Tools::id() => [Tools::HADOLINT], + CiProvider::id() => CiProvider::GITHUB_ACTIONS, + ] + $expected_installed, + function (AbstractHandlerDiscoveryTestCase $test, Config $config): void { + $test->stubVortexProject($config); + File::dump(static::$sut . '/.github/workflows/build-test-deploy.yml', 'docker run --rm -i hadolint/hadolint'); + }, + ]; + yield 'tools - discovery - hadolint, alt' => [ + [], + [ + Tools::id() => [Tools::HADOLINT], + CiProvider::id() => CiProvider::CIRCLECI, + ] + $expected_installed, + function (AbstractHandlerDiscoveryTestCase $test, Config $config): void { + $test->stubVortexProject($config); + File::dump(static::$sut . '/.circleci/config.yml', 'docker run --rm -i hadolint/hadolint'); + }, + ]; + yield 'tools - discovery - hadolint, alt2' => [ + [], + [Tools::id() => [Tools::HADOLINT]] + $expected_installed, + function (AbstractHandlerDiscoveryTestCase $test, Config $config): void { + $test->stubVortexProject($config); + File::dump(static::$sut . '/.hadolint.yaml'); + }, + ]; + yield 'tools - discovery - hadolint, alt3' => [ + [], + [Tools::id() => [Tools::HADOLINT]] + $expected_installed, + function (AbstractHandlerDiscoveryTestCase $test, Config $config): void { + $test->stubVortexProject($config); + File::dump(static::$sut . '/.hadolint.yml'); + }, + ]; } } diff --git a/.vortex/docs/content/tools/dclint.mdx b/.vortex/docs/content/tools/dclint.mdx index ae8c6630f..b144c1b1a 100644 --- a/.vortex/docs/content/tools/dclint.mdx +++ b/.vortex/docs/content/tools/dclint.mdx @@ -6,6 +6,10 @@ https://github.com/zavoloklom/docker-compose-linter **Vortex** does not install DCLint. Please follow the [instructions](https://github.com/zavoloklom/docker-compose-linter#installation) to install it on your system. +DCLint is selected by default during installation. Deselecting it in the +`Development tools` question removes the `.dclintrc` configuration file and the +continuous integration step from every supported provider. + ## Usage ```shell diff --git a/.vortex/docs/content/tools/hadolint.mdx b/.vortex/docs/content/tools/hadolint.mdx index 567a99410..bb981f4b7 100644 --- a/.vortex/docs/content/tools/hadolint.mdx +++ b/.vortex/docs/content/tools/hadolint.mdx @@ -12,6 +12,11 @@ In CI, Hadolint is installed automatically as part of the pipeline. ::: +Hadolint is selected by default during installation. Deselecting it in the +`Development tools` question removes the continuous integration step from every +supported provider. The `hadolint ignore=` directives in the Dockerfiles are +kept, so the tool still works when run by hand. + ## Usage ```shell diff --git a/.vortex/docs/cspell.json b/.vortex/docs/cspell.json index 4c6c05a30..bb0403549 100644 --- a/.vortex/docs/cspell.json +++ b/.vortex/docs/cspell.json @@ -39,6 +39,7 @@ "cweagans", "dantleech", "dclint", + "dclintrc", "ddev", "dealerdirect", "devel", From f393e3655203726f6f770e35c9be72618c21184b Mon Sep 17 00:00:00 2001 From: Alex Skrypnyk Date: Wed, 29 Jul 2026 12:27:17 +1000 Subject: [PATCH 04/16] Update Container images - All - Major, minor and patch to v26.8.0 (#2853) Co-authored-by: Renovate Self Hosted (cherry picked from commit f5d99606ec0168a86f031a7c0bbc0631d8c196d4) --- .docker/clamav.dockerfile | 2 +- .docker/database.dockerfile | 2 +- .docker/solr.dockerfile | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.docker/clamav.dockerfile b/.docker/clamav.dockerfile index 17ccf5c00..e80ced3c7 100644 --- a/.docker/clamav.dockerfile +++ b/.docker/clamav.dockerfile @@ -10,7 +10,7 @@ # @see https://hub.docker.com/r/uselagoon/commons/tags # @see https://github.com/uselagoon/lagoon-images/tree/main/images/commons -FROM uselagoon/commons:26.7.0 AS commons +FROM uselagoon/commons:26.8.0 AS commons FROM clamav/clamav-debian:1.5.3 diff --git a/.docker/database.dockerfile b/.docker/database.dockerfile index b7a439fe9..1a0929ff7 100644 --- a/.docker/database.dockerfile +++ b/.docker/database.dockerfile @@ -5,7 +5,7 @@ # # The ARG value will be updated with a value passed from docker-compose.yml -ARG IMAGE=uselagoon/mysql-8.4:26.7.0 +ARG IMAGE=uselagoon/mysql-8.4:26.8.0 # hadolint ignore=DL3006 FROM ${IMAGE} diff --git a/.docker/solr.dockerfile b/.docker/solr.dockerfile index 894793e1d..6bc87c6a5 100644 --- a/.docker/solr.dockerfile +++ b/.docker/solr.dockerfile @@ -5,7 +5,7 @@ # @see https://hub.docker.com/r/uselagoon/solr-9-drupal/tags # @see https://github.com/uselagoon/lagoon-images/blob/main/images/solr-drupal/9.Dockerfile -FROM uselagoon/solr-9-drupal:26.7.0 +FROM uselagoon/solr-9-drupal:26.8.0 # Solr jump-start config needs to be manually copied from the search_api_solr # Drupal module to .docker/config/solr/config-set. From 3d91902b904e51ad2cfc47e231c0623f04983642 Mon Sep 17 00:00:00 2001 From: Alex Skrypnyk Date: Fri, 31 Jul 2026 15:36:17 +1000 Subject: [PATCH 05/16] Suppressed Composer output of the initial tooling install and adopted the standard progress helpers. Forward-ported from main fa098d09ae4329c8914272bb37cbb3773241904a. --- scripts/vortex-tooling.sh | 50 ++++++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/scripts/vortex-tooling.sh b/scripts/vortex-tooling.sh index e2362b72e..2a8501dcb 100755 --- a/scripts/vortex-tooling.sh +++ b/scripts/vortex-tooling.sh @@ -20,6 +20,40 @@ set -eu [ "${VORTEX_DEBUG-}" = "1" ] && set -x +# ------------------------------------------------------------------------------ + +# @formatter:off +info() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[36m[INFO] %s\033[0m\n" "${1}" || printf "[INFO] %s\n" "${1}"; } +note() { printf " %s\n" "${1}"; } +task() { _TASK_START=$(date +%s); [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[34m[TASK] %s\033[0m\n" "${1}" || printf "[TASK] %s\n" "${1}"; } +pass() { _d=""; [ -n "${_TASK_START:-}" ] && _d=" ($(($(date +%s) - _TASK_START))s)" && unset _TASK_START; [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[32m[ OK ] %s%s\033[0m\n" "${1}" "${_d}" || printf "[ OK ] %s%s\n" "${1}" "${_d}"; } +fail() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[31m[FAIL] %s\033[0m\n" "${1}" || printf "[FAIL] %s\n" "${1}"; } +# @formatter:on + +# Run Composer without exposing its progress output, which is an internal detail +# of this bootstrap rather than something that was asked for. The captured +# output is replayed on stderr when the command fails, so failures remain +# diagnosable. Debug mode streams the output as it happens. +composer_run() { + if [ "${VORTEX_DEBUG-}" = "1" ]; then + composer "$@" + return + fi + + local output status=0 + output=$(composer "$@" 2>&1) || status=$? + + if [ "${status}" -ne 0 ]; then + fail "Composer command failed." + + if [ -n "${output}" ]; then + printf "%s\n" "${output}" >&2 + fi + + return "${status}" + fi +} + # Already installed - nothing to do. If the package is present but its # 'vendor/bin/' proxies are missing (a workspace bootstrapped before the # binaries were surfaced), remove it so the install below re-creates both. @@ -30,6 +64,8 @@ if [ -d ./vendor/drevops/vortex-tooling ]; then rm -rf ./vendor/drevops/vortex-tooling fi +info "Started Vortex tooling installation." + mkdir -p vendor-temp vendor/drevops # Always remove the throwaway project on exit - including when an intermediate @@ -55,27 +91,27 @@ echo "{\"require\":{\"drevops/vortex-tooling\":\"${version}\"}}" >vendor-temp/co # In dev mode the package is not yet on Packagist, so add a path repository # pointing at the in-tree copy. The CLI install command strips this VORTEX_DEV-fenced # block from consumer sites. -composer --working-dir=vendor-temp config repositories.vortex-tooling --json '{"type":"path","url":"../.vortex/tooling","options":{"symlink":false,"versions":{"drevops/vortex-tooling":"2.0.0-alpha1"}}}' +composer_run --working-dir=vendor-temp config repositories.vortex-tooling --json '{"type":"path","url":"../.vortex/tooling","options":{"symlink":false,"versions":{"drevops/vortex-tooling":"2.0.0-alpha1"}}}' #;> VORTEX_DEV # Carry over inline patches declared for our package, if any. patches=$(composer config extra.patches.drevops/vortex-tooling --json 2>/dev/null) || patches= if [ -n "${patches}" ] && [ "${patches}" != "[]" ] && [ "${patches}" != "{}" ]; then - composer --working-dir=vendor-temp config extra.patches.drevops/vortex-tooling --json "${patches}" + composer_run --working-dir=vendor-temp config extra.patches.drevops/vortex-tooling --json "${patches}" fi # Carry over the patches-file pointer, if defined. Prefix with '..' so the # path resolves from inside 'vendor-temp/' back to the project root. patches_file=$(composer config extra.patches-file 2>/dev/null) || patches_file= if [ -n "${patches_file}" ]; then - composer --working-dir=vendor-temp config extra.patches-file "../${patches_file}" + composer_run --working-dir=vendor-temp config extra.patches-file "../${patches_file}" fi # When any patches were registered, pull in the composer-patches plugin and # allow it to run during install. if [ -n "${patches}" ] || [ -n "${patches_file}" ]; then - composer --working-dir=vendor-temp require --no-update cweagans/composer-patches:^2 - composer --working-dir=vendor-temp config allow-plugins.cweagans/composer-patches true + composer_run --working-dir=vendor-temp require --no-update cweagans/composer-patches:^2 + composer_run --working-dir=vendor-temp config allow-plugins.cweagans/composer-patches true # Inline 'extra.patches' paths (and paths inside a 'patches-file') are # relative to the project root. Copy the project 'patches/' directory into # the throwaway project so those paths resolve from inside 'vendor-temp/'. @@ -84,7 +120,7 @@ if [ -n "${patches}" ] || [ -n "${patches_file}" ]; then fi fi -composer --working-dir=vendor-temp install --no-dev --no-interaction +composer_run --working-dir=vendor-temp install --no-dev --no-interaction #;< VORTEX_DEV # The 2.x tooling marks itself as "Vortex 2.x tooling" in its composer.json @@ -108,3 +144,5 @@ if [ -d vendor-temp/vendor/bin ]; then [ -e "${bin}" ] && mv "${bin}" vendor/bin/ done fi + +pass "Finished Vortex tooling installation." From ff6ae68cdf3e35084dff1637598495b9314374df Mon Sep 17 00:00:00 2001 From: Alex Skrypnyk Date: Fri, 31 Jul 2026 15:38:38 +1000 Subject: [PATCH 06/16] Removed the 'page' content model when the demo and search modules are deselected. Forward-ported from main ece81600140c32b9f3630db5cfb7c07759b21305. --- .gitignore | 2 ++ .vortex/cli/src/Prompts/Handlers/CustomModules.php | 8 ++++++++ .../Handlers/CustomModulesHandlerProcessTest.php | 12 ++++++++++-- .vortex/docs/content/architecture.mdx | 2 +- 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 2b290fecb..e02e81705 100644 --- a/.gitignore +++ b/.gitignore @@ -34,7 +34,9 @@ web/sites/simpletest # Composer installs contrib recipes into 'recipes/', next to custom recipes, # so recipes stay an allow list: un-ignore custom recipes explicitly. recipes/* +#;< CONTENT_MODEL !recipes/page +#;> CONTENT_MODEL #;< AI_CODE_INSTRUCTIONS # Ignore all Claude files by default. Custom files should be added explicitly. diff --git a/.vortex/cli/src/Prompts/Handlers/CustomModules.php b/.vortex/cli/src/Prompts/Handlers/CustomModules.php index fd68d27f9..888482496 100644 --- a/.vortex/cli/src/Prompts/Handlers/CustomModules.php +++ b/.vortex/cli/src/Prompts/Handlers/CustomModules.php @@ -182,6 +182,14 @@ public function process(): void { File::remove($path); } } + + // The 'page' content model is shared: the demo module attaches behaviour to + // the content type and the search tests index content of that type. It is + // only removed once neither of them remains. + if (!in_array(self::DEMO, $selected) && !in_array(self::SEARCH, $selected)) { + File::removeTokenAsync('CONTENT_MODEL'); + File::remove($t . '/recipes/page'); + } } /** diff --git a/.vortex/cli/tests/Functional/Handlers/CustomModulesHandlerProcessTest.php b/.vortex/cli/tests/Functional/Handlers/CustomModulesHandlerProcessTest.php index 3c9072008..4ad4e1d95 100644 --- a/.vortex/cli/tests/Functional/Handlers/CustomModulesHandlerProcessTest.php +++ b/.vortex/cli/tests/Functional/Handlers/CustomModulesHandlerProcessTest.php @@ -29,6 +29,7 @@ public static function dataProviderHandlerProcess(): \Iterator { static::cw(function (AbstractHandlerProcessTestCase $test): void { $test->assertSutNotContains('_demo'); $test->assertSutNotContains('counter_block'); + $test->assertDirectoryExists(static::$sut . '/recipes/page'); }), ]; yield 'custom_modules_no_search' => [ @@ -36,7 +37,10 @@ public static function dataProviderHandlerProcess(): \Iterator { $test->prompts[CustomModules::id()] = [CustomModules::BASE, CustomModules::DEMO]; $test->prompts[AiCodeInstructions::id()] = TRUE; }), - static::cw(fn(FunctionalTestCase $test) => $test->assertSutNotContains('_search')), + static::cw(function (AbstractHandlerProcessTestCase $test): void { + $test->assertSutNotContains('_search'); + $test->assertDirectoryExists(static::$sut . '/recipes/page'); + }), ]; yield 'custom_modules_none' => [ static::cw(fn($test): array => $test->prompts[CustomModules::id()] = []), @@ -44,6 +48,7 @@ public static function dataProviderHandlerProcess(): \Iterator { $test->assertSutNotContains('_base'); $test->assertSutNotContains('_demo'); $test->assertSutNotContains('_search'); + $test->assertDirectoryDoesNotExist(static::$sut . '/recipes/page'); }), ]; yield 'custom_modules_search_without_solr' => [ @@ -54,7 +59,10 @@ public static function dataProviderHandlerProcess(): \Iterator { $test->prompts[Services::id()] = [Services::CLAMAV, Services::REDIS]; $test->prompts[AiCodeInstructions::id()] = TRUE; }), - static::cw(fn(FunctionalTestCase $test) => $test->assertSutNotContains('_search')), + static::cw(function (AbstractHandlerProcessTestCase $test): void { + $test->assertSutNotContains('_search'); + $test->assertDirectoryExists(static::$sut . '/recipes/page'); + }), ]; } diff --git a/.vortex/docs/content/architecture.mdx b/.vortex/docs/content/architecture.mdx index b6b205f47..dae4ac358 100644 --- a/.vortex/docs/content/architecture.mdx +++ b/.vortex/docs/content/architecture.mdx @@ -88,7 +88,7 @@ caches, and content files. Two sections stay as small allow lists because a deny rule cannot express them: - `recipes/` - Composer installs contributed recipes next to your custom ones, - so custom recipes are un-ignored explicitly (`!recipes/page`). + so each custom recipe is un-ignored explicitly. - `.claude/` - only `.claude/settings.json` is tracked by default. The other two ignore files apply the same deny-list model to different targets: From e72b30782db066cc9b9d5cba6f1e80f0de029afe Mon Sep 17 00:00:00 2001 From: Alex Skrypnyk Date: Fri, 31 Jul 2026 15:40:20 +1000 Subject: [PATCH 07/16] Added the visual regression report to the GitHub Actions run summary. Forward-ported from main ab2ae94296cf805eadcc501b9acd548cd76d2648. --- .github/workflows/test-vr.yml | 46 +++++++++++++++---- .../content/development/visual-regression.mdx | 16 +++++-- 2 files changed, 47 insertions(+), 15 deletions(-) diff --git a/.github/workflows/test-vr.yml b/.github/workflows/test-vr.yml index ac002426f..0c3ca0884 100644 --- a/.github/workflows/test-vr.yml +++ b/.github/workflows/test-vr.yml @@ -64,6 +64,7 @@ jobs: shared_url: ${{ steps.result.outputs.shared_url }} pages_changed: ${{ steps.result.outputs.pages_changed }} pages_total: ${{ steps.result.outputs.pages_total }} + summary: ${{ steps.summary.outputs.summary }} steps: - name: Resolve PR number from target URL @@ -253,7 +254,7 @@ jobs: result_json="$(/tmp/diffy.phar diff:get-result "${diff_id}" --format=json)" shared_url="$(printf '%s' "${result_json}" | jq -r '.diffSharedUrl // empty')" - pages_changed="$(printf '%s' "${result_json}" | jq -r '[.diffs | to_entries[] | select(.value | to_entries | map(.value.percentageChanges // 0) | add > 0)] | length')" + pages_changed="$(printf '%s' "${result_json}" | jq -r '[.diffs[] | select([.[].percentageChanges | tonumber? // 0] | add > 0)] | length')" pages_total="$(printf '%s' "${result_json}" | jq -r '.diffs | length')" { @@ -268,6 +269,39 @@ jobs: env: STEPS_COMPARE_OUTPUTS_DIFF_ID: ${{ steps.compare.outputs.diff_id }} + - name: Write run summary + id: summary + if: steps.gate.outputs.skipped != 'true' + run: | + set -euo pipefail + + summary="$(printf '%s\n' \ + "### Visual regression report" \ + "" \ + "**${PAGES_CHANGED} of ${PAGES_TOTAL}** pages changed, **${CHANGES_PERCENT}%** overall difference." \ + "" \ + "| Setting | Value |" \ + "| --- | --- |" \ + "| Source environment | \`${SOURCE_ENV}\` |" \ + "| Target environment | \`${TARGET_URL}\` |" \ + "| Diff ID | \`${DIFF_ID}\` |" \ + "" \ + "[View full Diffy report](${SHARED_URL})")" + + printf '%s\n' "${summary}" >> "$GITHUB_STEP_SUMMARY" + + { + echo "summary<> "$GITHUB_OUTPUT" + env: + CHANGES_PERCENT: ${{ steps.result.outputs.changes_percent }} + SHARED_URL: ${{ steps.result.outputs.shared_url }} + PAGES_CHANGED: ${{ steps.result.outputs.pages_changed }} + PAGES_TOTAL: ${{ steps.result.outputs.pages_total }} + DIFF_ID: ${{ steps.compare.outputs.diff_id }} + vr-report: runs-on: ubuntu-latest needs: vr-compare @@ -282,13 +316,5 @@ jobs: with: number: ${{ needs.vr-compare.outputs.pr_number }} header: vr-diffy - message: | - ### Visual regression report - - - **Pages changed**: ${{ needs.vr-compare.outputs.pages_changed }} of ${{ needs.vr-compare.outputs.pages_total }} - - **Overall difference**: ${{ needs.vr-compare.outputs.changes_percent }}% - - **Target environment**: ${{ env.TARGET_URL }} - - **Source environment**: ${{ env.SOURCE_ENV }} - - [View full Diffy report](${{ needs.vr-compare.outputs.shared_url }}) + message: ${{ needs.vr-compare.outputs.summary }} hide_and_recreate: true diff --git a/.vortex/docs/content/development/visual-regression.mdx b/.vortex/docs/content/development/visual-regression.mdx index 2e87a2b6c..ec804883a 100644 --- a/.vortex/docs/content/development/visual-regression.mdx +++ b/.vortex/docs/content/development/visual-regression.mdx @@ -141,6 +141,9 @@ GitHub branch protection rules. │ vr-compare: parse PR from URL, gate │ │ │ │ │ ▼ │ +│ Report in workflow run summary │ +│ │ │ +│ ▼ │ │ vr-report │ │ │ │ │ ▼ │ @@ -179,7 +182,7 @@ them do. │ vr-compare job │ │ │ │ │ ▼ │ -│ vr-report job │ +│ Report in workflow run summary │ │ │ └────────────────────┬────────────────────────┘ │ @@ -192,8 +195,8 @@ them do. ``` Use this entry point for ad-hoc comparisons against a known environment -URL. No PR is involved, so the result is visible only in the Diffy UI -(and in the workflow run log). +URL. No PR is involved, so there is no comment to post - the result is +shown in the workflow run summary and in the Diffy UI. ## Limiting which branches dispatch @@ -293,11 +296,14 @@ entry point. `VR_DIFFY_POLL_INTERVAL` seconds. 5. Fetches the diff result and exposes the diff ID, PR number, change percentage, page counts, and shared report URL as job outputs. +6. Renders the report into the workflow run summary, so the result is + readable in the GitHub Actions UI without opening the job log. `vr-report`: -1. Posts a sticky comment on the PR with the summary and a link to the - Diffy report. Re-deploys edit the same comment rather than stacking. +1. Posts the same report as a sticky comment on the PR, with a link to + the Diffy report. Re-deploys edit the same comment rather than + stacking. ## Making it blocking From be0b6dc15d142d291307aa9263f504eb5b7959d0 Mon Sep 17 00:00:00 2001 From: Alex Skrypnyk Date: Fri, 31 Jul 2026 15:41:24 +1000 Subject: [PATCH 08/16] Added opt-in for visual regression on non-PR deployments. Forward-ported from main dc399b62666e693d493c5c631bf4cdc831734a8d. --- .github/workflows/test-vr.yml | 97 +++++++++--------- .../content/development/visual-regression.mdx | 98 +++++++++++++++---- 2 files changed, 125 insertions(+), 70 deletions(-) diff --git a/.github/workflows/test-vr.yml b/.github/workflows/test-vr.yml index 0c3ca0884..e1d91eccf 100644 --- a/.github/workflows/test-vr.yml +++ b/.github/workflows/test-vr.yml @@ -1,9 +1,9 @@ # GitHub Actions visual regression testing workflow. # # Runs a Diffy visual regression comparison after a deployment lands. -# Triggered automatically by 'notify-diffy' (via repository_dispatch) -# when the deployed PR has the configured label, or manually via -# 'workflow_dispatch' against any URL. +# Triggered automatically by 'notify-diffy' (via repository_dispatch) when +# the deployed PR has the configured label or the deployed branch is opted +# in, or manually via 'workflow_dispatch' against any URL. name: Test - Visual regression on: @@ -43,10 +43,12 @@ env: DIFFY_CLI_VERSION: ${{ vars.VR_DIFFY_CLI_VERSION || '0.1.53' }} DIFFY_MAX_WAIT: ${{ vars.VR_DIFFY_MAX_WAIT || '2700' }} DIFFY_PR_LABEL: ${{ vars.VR_DIFFY_PR_LABEL || 'VR' }} - DIFFY_AUTO_BRANCHES: ${{ vars.VR_DIFFY_AUTO_BRANCHES || 'deps/*' }} + DIFFY_PR_SKIP_BRANCHES: ${{ vars.VR_DIFFY_PR_SKIP_BRANCHES || 'deps/*' }} + DIFFY_BRANCHES: ${{ vars.VR_DIFFY_BRANCHES }} DIFFY_POLL_INTERVAL: ${{ vars.VR_DIFFY_POLL_INTERVAL || '30' }} SOURCE_ENV: ${{ github.event.client_payload.source_env || inputs.source_env || 'production' }} TARGET_URL: ${{ github.event.client_payload.target_url || inputs.target_url }} + BRANCH: ${{ github.event.client_payload.branch }} LABEL: ${{ github.event.client_payload.label || inputs.label || 'manual' }} jobs: @@ -74,18 +76,18 @@ jobs: # Hosting providers expose PR environments via URLs that contain # a 'pr-' segment (e.g. 'app.pr-123.example.lagoon.cloud'). - # Extract the number from the URL; if no match, this is not a PR - # deployment and visual regression should not run. + # Extract the number from the URL; a URL without one is not a PR + # deployment and is gated on the deployed branch instead. pr_number="$(printf '%s' "${TARGET_URL}" | sed -n 's|.*pr-\([0-9]\{1,\}\).*|\1|p')" if [ -n "${pr_number}" ]; then echo "PR #${pr_number} resolved from target URL ${TARGET_URL}." else - echo "::notice::No PR pattern found in target URL ${TARGET_URL}. Visual regression will not run." + echo "No PR pattern found in target URL ${TARGET_URL}." fi echo "pr_number=${pr_number}" >> "$GITHUB_OUTPUT" - - name: Gate run on PR label (or skip for non-PR contexts on dispatch) + - name: Gate run on PR label or opted-in branch id: gate env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -95,55 +97,50 @@ jobs: run: | set -euo pipefail - # Manual workflow_dispatch always proceeds - the operator - # explicitly asked for a comparison. - if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ]; then - echo "Manual dispatch, proceeding." - echo "skipped=false" >> "$GITHUB_OUTPUT" - exit 0 - fi + # Matches a git ref against a comma-separated glob list. Splitting + # on whitespace as well as commas absorbs padding around each + # pattern. + ref_matches() { + local ref="${1}" pattern patterns + [ -n "${ref}" ] || return 1 + [ -n "${2}" ] || return 1 + IFS=$', \t' read -ra patterns <<<"${2}" + for pattern in "${patterns[@]}"; do + # shellcheck disable=SC2254 + case "${ref}" in ${pattern}) return 0 ;; esac + done + return 1 + } - # repository_dispatch with no PR resolved - nothing to gate on. + # Record the gate decision and end the step. + proceed() { echo "${1}"; echo "skipped=false" >> "$GITHUB_OUTPUT"; exit 0; } + skip() { echo "::notice::${1}"; echo "skipped=true" >> "$GITHUB_OUTPUT"; exit 0; } + + # The operator explicitly asked for a comparison. + [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ] && proceed "Manual dispatch." + + # No PR resolved - the deployment targets a permanent environment + # such as a release branch, 'develop', 'dev' or 'stage'. Those run + # only when the deployed branch is explicitly opted in. if [ -z "${PR_NUMBER}" ]; then - echo "::notice::No PR associated with this deployment, skipping visual regression." - echo "skipped=true" >> "$GITHUB_OUTPUT" - exit 0 + ref_matches "${BRANCH}" "${DIFFY_BRANCHES}" && proceed "Deployed branch '${BRANCH}' matches VR_DIFFY_BRANCHES." + skip "No PR associated with this deployment and branch '${BRANCH}' does not match VR_DIFFY_BRANCHES, skipping visual regression." fi - # PR resolved - fetch labels and head branch in one call. - pr_data="$(gh pr view "${PR_NUMBER}" --repo "${GITHUB_REPOSITORY}" --json labels,headRefName)" + # A failed lookup means the 'pr-' segment in the URL did + # not come from a pull request in this repository. + pr_data="$(gh pr view "${PR_NUMBER}" --repo "${GITHUB_REPOSITORY}" --json labels,headRefName)" || skip "PR #${PR_NUMBER} could not be read, skipping visual regression." + head_ref="$(printf '%s' "${pr_data}" | jq -r '.headRefName')" - labels_lower="$(printf '%s' "${pr_data}" | jq -r '.labels[].name' | tr '[:upper:]' '[:lower:]')" + labels="$(printf '%s' "${pr_data}" | jq -r '.labels[].name')" - # Auto-run bypass: PRs from configured branches (e.g. Renovate's - # `deps/*`) skip the label check. - if [ -n "${DIFFY_AUTO_BRANCHES}" ]; then - IFS=',' read -ra patterns <<<"${DIFFY_AUTO_BRANCHES}" - for pattern in "${patterns[@]}"; do - # Trim surrounding whitespace. - pattern="${pattern# }" - pattern="${pattern% }" - [ -z "${pattern}" ] && continue - # shellcheck disable=SC2254 - case "${head_ref}" in - ${pattern}) - echo "PR #${PR_NUMBER} head branch '${head_ref}' matches auto-run pattern '${pattern}', proceeding." - echo "skipped=false" >> "$GITHUB_OUTPUT" - exit 0 - ;; - esac - done - fi + # PRs from configured branches (e.g. Renovate's `deps/*`) skip the + # label check. + ref_matches "${head_ref}" "${DIFFY_PR_SKIP_BRANCHES}" && proceed "PR #${PR_NUMBER} head branch '${head_ref}' matches VR_DIFFY_PR_SKIP_BRANCHES." - # Label check (case-insensitive). - needle="$(printf '%s' "${DIFFY_PR_LABEL}" | tr '[:upper:]' '[:lower:]')" - if echo "${labels_lower}" | grep -qx "${needle}"; then - echo "PR #${PR_NUMBER} has the '${DIFFY_PR_LABEL}' label, proceeding." - echo "skipped=false" >> "$GITHUB_OUTPUT" - else - echo "::notice::PR #${PR_NUMBER} does not have the '${DIFFY_PR_LABEL}' label and head branch '${head_ref}' does not match VR_DIFFY_AUTO_BRANCHES, skipping visual regression." - echo "skipped=true" >> "$GITHUB_OUTPUT" - fi + echo "${labels}" | grep -qixF "${DIFFY_PR_LABEL}" && proceed "PR #${PR_NUMBER} has the '${DIFFY_PR_LABEL}' label." + + skip "PR #${PR_NUMBER} does not have the '${DIFFY_PR_LABEL}' label and head branch '${head_ref}' does not match VR_DIFFY_PR_SKIP_BRANCHES, skipping visual regression." - name: Validate target URL if: steps.gate.outputs.skipped != 'true' diff --git a/.vortex/docs/content/development/visual-regression.mdx b/.vortex/docs/content/development/visual-regression.mdx index ec804883a..f3676eafe 100644 --- a/.vortex/docs/content/development/visual-regression.mdx +++ b/.vortex/docs/content/development/visual-regression.mdx @@ -7,7 +7,8 @@ sidebar_label: Visual regression **Vortex** ships an optional visual regression workflow powered by [Diffy](../tools/diffy.mdx). It compares the just-deployed environment against a baseline (typically `production`) and posts the result back to -the related pull request. +the related pull request, or to the workflow run summary when the +deployment has no pull request. ## Account setup @@ -74,7 +75,8 @@ the site. In Cloudflare, add an IP Access Rule for that address with the | `VR_DIFFY_MAX_WAIT` | `2700` | Maximum seconds to wait for a comparison to complete (45 minutes) | | `VR_DIFFY_POLL_INTERVAL` | `30` | Polling interval in seconds | | `VR_DIFFY_PR_LABEL` | `VR` | PR label that opts a deployment into visual regression. Case-insensitive | -| `VR_DIFFY_AUTO_BRANCHES` | `deps/*` | Comma-separated glob list of PR head branches that bypass the label gate (matches Renovate's `branchPrefix`). Set to empty to require the label on every PR. | +| `VR_DIFFY_PR_SKIP_BRANCHES` | `deps/*` | Comma-separated glob list of PR head branches that skip the label gate (matches Renovate's `branchPrefix`). Set to empty to require the label on every PR. | +| `VR_DIFFY_BRANCHES` | (empty) | Comma-separated glob list of deployed branches that run visual regression when the deployment has no pull request (for example `release/*`). Empty means such deployments never run. | | `VR_DIFFY_SOURCE_ENV` | `production` | Default Diffy source environment for comparisons | Add these under *Settings > Secrets and variables > Actions > Repository @@ -106,7 +108,7 @@ GitHub branch protection rules. ```text ┌─ GitHub ────────────────────────────────────┐ │ │ -│ PR opened with `VR` label │ +│ PR opened or branch pushed │ │ │ │ │ ▼ │ │ build-test-deploy.yml │ @@ -140,11 +142,20 @@ GitHub branch protection rules. │ ▼ │ │ vr-compare: parse PR from URL, gate │ │ │ │ +│ ┌────────┴────────┐ │ +│ ▼ ▼ │ +│ PR deployment no PR resolved │ +│ │ │ │ +│ ▼ ▼ │ +│ VR label or branch in │ +│ PR skip list branch list │ +│ │ │ │ +│ └────────┬────────┘ │ │ ▼ │ │ Report in workflow run summary │ │ │ │ │ ▼ │ -│ vr-report │ +│ vr-report (PR only) │ │ │ │ │ ▼ │ │ PR comment + Diffy report link │ @@ -158,8 +169,9 @@ commit SHA**. The workflow itself extracts the PR number from the deployed environment URL by matching the `pr-` segment (e.g. `https://pr-123.example.com/` resolves to PR #123) and verifies the `VR` label is present (case-insensitive). If the target URL has no -`pr-` segment, the deployment is not a PR environment and the -run is skipped. +`pr-` segment, the deployment is not a PR environment and the run +is skipped unless the deployed branch is opted in - see [Release and other +non-PR deployments](#release-and-other-non-pr-deployments). This means the PR lookup works uniformly across hosting providers - the host only needs to expose the deployed environment URL, which all of @@ -218,8 +230,8 @@ be the sole gate. PRs raised by Renovate (or any other automated dependency-update bot) typically do not carry the `VR` label - they carry their own bot label (e.g. `Dependencies`). To still run visual regression on them, the -workflow consults `VR_DIFFY_AUTO_BRANCHES`: a comma-separated glob list -of PR head branches that bypass the `VR` label gate. +workflow consults `VR_DIFFY_PR_SKIP_BRANCHES`: a comma-separated glob +list of PR head branches that skip the `VR` label gate. The default value is `deps/*`, matching **Vortex**'s Renovate `branchPrefix` configuration. Other common values: @@ -231,13 +243,12 @@ configuration. Other common values: | Dependabot | `dependabot/` | `dependabot/*` | Multiple patterns can be combined with commas: -`VR_DIFFY_AUTO_BRANCHES=deps/*,dependabot/*`. Set the variable to empty -to disable the bypass entirely (every PR, including bot PRs, then needs -the label). +`VR_DIFFY_PR_SKIP_BRANCHES=deps/*,dependabot/*`. Set the variable to +empty to require the label on every PR, including bot PRs. ### Default behavior out of the box -With `VR_DIFFY_AUTO_BRANCHES=deps/*` (default) and +With `VR_DIFFY_PR_SKIP_BRANCHES=deps/*` (default) and `VR_DIFFY_PR_LABEL=VR` (default): | PR head branch | Has `VR` label? | Runs? | @@ -248,8 +259,47 @@ With `VR_DIFFY_AUTO_BRANCHES=deps/*` (default) and | `deps/drupal-core-11.2` | yes | yes (matches `deps/*`, label irrelevant) | Consumers using Dependabot just append: -`VR_DIFFY_AUTO_BRANCHES=deps/*,dependabot/*`. Consumers who want the -label as the only gate (no auto-bypass) set the variable to empty. +`VR_DIFFY_PR_SKIP_BRANCHES=deps/*,dependabot/*`. Consumers who want the +label as the only gate set the variable to empty. + +## Release and other non-PR deployments + +A deployment that is not a pull request environment - a release branch, an +integration branch such as `develop`, or a permanent `dev`/`stage` +environment - has no pull request to carry the `VR` label, so it is skipped +by default. + +Set `VR_DIFFY_BRANCHES` to a comma-separated glob list of deployed branch +names to compare them automatically: + +```ini +VR_DIFFY_BRANCHES=release/* +``` + +| Deployment | `VR_DIFFY_BRANCHES` | Runs? | +|---|---|---| +| PR environment (`pr-123` in the URL) | any value | gated by the `VR` label and `VR_DIFFY_PR_SKIP_BRANCHES` as usual | +| Branch `release/1.2.3` | (empty) | no | +| Branch `release/1.2.3` | `release/*` | **yes** | +| Branch `develop` | `release/*` | no | +| Branch `develop` | `release/*,develop` | **yes** | +| Any branch | `*` | **yes** (every non-PR deployment) | + +The deployed branch travels in the dispatch payload, so no GitHub API call +is needed to resolve it. + +:::note + +Patterns match the **git branch name**, not the hostname. Hosting providers +sanitize branch names for URLs - Lagoon deploys `release/26.7.5` to a host +containing `release-26-7-5` - but the gate compares against the original +`release/26.7.5`. + +::: + +There is no pull request to comment on for these runs, so the result +appears in the workflow run summary and in the Diffy UI instead of as a PR +comment. ## How the PR is resolved @@ -272,7 +322,8 @@ This means: If the target URL has no `pr-` segment (for example, a deploy to a named environment like `dev`/`test`/`prod`), the workflow treats it as -"not a PR deployment" and exits without running. +"not a PR deployment" and runs only when the deployed branch matches +`VR_DIFFY_BRANCHES`. ## Missed-window behavior @@ -288,8 +339,9 @@ entry point. `vr-compare`: -1. Resolves the PR number from the target URL and gates on the label - (or auto-branch pattern for dependency PRs). +1. Resolves the PR number from the target URL and gates on the label (or + the auto-branch pattern for dependency PRs, or the deployed-branch + opt-in for non-PR deployments). 2. Installs the pinned Diffy CLI. 3. Calls `diffy project:compare` with the target URL and a label. 4. Polls the comparison status, printing progress to the job log every @@ -303,7 +355,8 @@ entry point. 1. Posts the same report as a sticky comment on the PR, with a link to the Diffy report. Re-deploys edit the same comment rather than - stacking. + stacking. The job runs only when a PR was resolved - manual runs and + opted-in branch deployments report through the run summary alone. ## Making it blocking @@ -323,10 +376,15 @@ be posted, which is not a merge-blocker. ## Costs and quotas Diffy bills per screenshot set. **Vortex**'s default gating (`VR` label on -PRs only, plus the `deps/*` auto-branch list) keeps the run rate low. -Adjust `VR_DIFFY_PR_LABEL`, `VR_DIFFY_AUTO_BRANCHES`, and +PRs only, plus the `deps/*` auto-branch list, with non-PR deployments off +entirely) keeps the run rate low. Adjust `VR_DIFFY_PR_LABEL`, +`VR_DIFFY_PR_SKIP_BRANCHES`, `VR_DIFFY_BRANCHES`, and `VORTEX_NOTIFY_DIFFY_BRANCHES` to suit the team's review rhythm. +Scope `VR_DIFFY_BRANCHES` as narrowly as the workflow allows: `*` +compares on every deployment to every permanent environment, which on a +busy integration branch consumes quota quickly. + ## Disabling Remove `diffy` from `VORTEX_NOTIFY_CHANNELS` and delete the From 6c658cef1a678b3632f431ff48436e3d3204bd7e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 30 Jul 2026 01:30:18 +0000 Subject: [PATCH 09/16] Update GitHub Actions - All - Major, minor and patch (#2866) | datasource | package | from | to | | ----------- | ------------------------------- | -------- | ------- | | github-tags | release-drafter/release-drafter | v7.6.0 | v7.7.0 | | github-tags | renovatebot/github-action | v46.1.21 | v46.2.0 | (cherry picked from commit d41596ac7a49edfc362f7449fe99772fc6741e0a) --- .github/workflows/draft-release-notes.yml | 2 +- .github/workflows/update-dependencies.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/draft-release-notes.yml b/.github/workflows/draft-release-notes.yml index d4c0666a3..f0f295837 100644 --- a/.github/workflows/draft-release-notes.yml +++ b/.github/workflows/draft-release-notes.yml @@ -41,7 +41,7 @@ jobs: echo "Version set to ${VERSION}" - name: Draft release notes - uses: release-drafter/release-drafter@eada3c96a64734dd381cfbda23511034e328ddb0 # v7.6.0 + uses: release-drafter/release-drafter@34d80673e067bdc0c24568d3af899c216adcfaa9 # v7.7.0 with: tag: ${{ steps.calver.outputs.version }} name: ${{ steps.calver.outputs.version }} diff --git a/.github/workflows/update-dependencies.yml b/.github/workflows/update-dependencies.yml index fe2b53eca..1d3db2473 100644 --- a/.github/workflows/update-dependencies.yml +++ b/.github/workflows/update-dependencies.yml @@ -67,7 +67,7 @@ jobs: - name: Run Renovate if: steps.check-requirements.outputs.should_run == 'true' - uses: renovatebot/github-action@1a96852b0384df1837619d04c60b2d10d1f9ff08 # v46.1.21 + uses: renovatebot/github-action@973d3e5a68e735a444e8c03432b66eedb343c302 # v46.2.0 with: configurationFile: renovate.json token: ${{ secrets.RENOVATE_TOKEN }} From e8c0405a944f9ee7a4f1923898f17a41ada396ab Mon Sep 17 00:00:00 2001 From: Alex Skrypnyk Date: Fri, 31 Jul 2026 15:42:46 +1000 Subject: [PATCH 10/16] Pinned Hadolint to v2.15.0 and replaced its global ignores with justified per-line ones. Forward-ported from main c98f69d7125c216f989537791be226fb23909c0d. --- .circleci/config.yml | 2 +- .claude/skills/prepare-vortex-release/SKILL.md | 11 +++++++++++ .docker/clamav.dockerfile | 5 +++-- .docker/cli.dockerfile | 12 ++++++------ .docker/database.dockerfile | 4 +++- .docker/nginx-drupal.dockerfile | 4 +--- .docker/php.dockerfile | 4 +--- .docker/solr.dockerfile | 4 ++-- .github/workflows/build-test-deploy.yml | 2 +- .../Handlers/ToolsHandlerProcessTest.php | 4 ++-- .vortex/tests/lint.dockerfiles.sh | 2 +- renovate.json | 16 +++++++++++++++- 12 files changed, 47 insertions(+), 23 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 987551e4e..6582a9e35 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -152,7 +152,7 @@ jobs: name: Lint Dockerfiles with Hadolint command: | for file in $(find .docker \( -name 'Dockerfile' -o -name '*.dockerfile' \)); do - echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] + echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint:v2.15.0 || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] done #;> TOOL_HADOLINT diff --git a/.claude/skills/prepare-vortex-release/SKILL.md b/.claude/skills/prepare-vortex-release/SKILL.md index 34e19fdb1..800f2f9ea 100644 --- a/.claude/skills/prepare-vortex-release/SKILL.md +++ b/.claude/skills/prepare-vortex-release/SKILL.md @@ -62,6 +62,17 @@ Work through each checklist item from the release process doc: 1. **Dependencies** - Skip Renovate (user must run manually). Note as unchecked. 2. **Container images** - Check current versions in CI configs, verify if latest. + - **CI tool images** - `hadolint`, `dclint`, `gitleaks` and `actionlint` are + invoked as `docker run :` inside `.github/workflows/**` and + `.circleci/config.yml`. The `customManagers` regex in `renovate.json` + tracks them, so this is a verification step, not a manual bump: confirm + no open Renovate PR is bumping them and that both CI providers carry the + same tag for the same tool. Bump by hand only when Renovate has not + picked a release up, and in that case pin the identical tag in + `.vortex/tests/lint.dockerfiles.sh` so a local run matches CI. + - An untagged image reference is a release blocker regardless of Renovate: + it resolves to `latest` and lets an upstream release break a default + branch on a commit that changed nothing. 3. **PHP version** - Run `docker compose run --rm cli php -r "echo PHP_VERSION;"` and `docker compose run --rm cli php -r "echo PHP_VERSION_ID;"` to get the container PHP version. Update `composer.json` (`config.platform.php`), `phpstan.neon` diff --git a/.docker/clamav.dockerfile b/.docker/clamav.dockerfile index e80ced3c7..6479c1081 100644 --- a/.docker/clamav.dockerfile +++ b/.docker/clamav.dockerfile @@ -5,8 +5,6 @@ # Allow running ClamAV in rootless mode. # @see https://github.com/Cisco-Talos/clamav/issues/478 # -# hadolint global ignore=DL3008,DL3018 -# # @see https://hub.docker.com/r/uselagoon/commons/tags # @see https://github.com/uselagoon/lagoon-images/tree/main/images/commons @@ -17,6 +15,7 @@ FROM clamav/clamav-debian:1.5.3 COPY --from=commons /lagoon /lagoon COPY --from=commons /bin/fix-permissions /bin/ep /bin/docker-sleep /bin/wait-for /bin/ +# hadolint ignore=DL3008 # the package set tracks the pinned base image RUN apt-get update -qq && \ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends tzdata && \ apt-get clean && rm -rf /var/lib/apt/lists/* @@ -30,10 +29,12 @@ RUN cat /tmp/clamav.conf >> /etc/clamav/clamd.conf && \ sed -i "s/^UpdateLogFile /# UpdateLogFile /g" /etc/clamav/freshclam.conf && \ sed -i "s/^#LogSyslog /LogSyslog /g" /etc/clamav/freshclam.conf +# hadolint ignore=DL3066 # named account provided by the base image USER root RUN fix-permissions /var/lib/clamav +# hadolint ignore=DL3066 # named account provided by the base image USER clamav ENTRYPOINT [ "/init-unprivileged" ] diff --git a/.docker/cli.dockerfile b/.docker/cli.dockerfile index 527c3700a..229c9da3f 100644 --- a/.docker/cli.dockerfile +++ b/.docker/cli.dockerfile @@ -3,11 +3,7 @@ # # All CLI operations performed in this container. # -# The `PACKAGE_TOKEN` argument below is flagged by name by the -# `SecretsUsedInArgOrEnv` build check, but the token is passed as a build -# secret and never written to an image layer, so that check is skipped. -# -# hadolint global ignore=DL3018,SC2174 +# The check skipped above is BuildKit's twin of DL3064, ignored inline below. # # @see https://hub.docker.com/r/uselagoon/php-8.4-cli-drupal/tags # @see https://github.com/uselagoon/lagoon-images/tree/main/images/php-cli-drupal @@ -26,12 +22,15 @@ ENV WEBROOT=${WEBROOT} # Token is used to access private repositories. Not exposed as an environment # variable within an image to avoid baking it into the image. +# hadolint ignore=DL3064 # empty here, the value comes from a build secret ARG PACKAGE_TOKEN="" ARG DRUPAL_PUBLIC_FILES="sites/default/files" ENV DRUPAL_PUBLIC_FILES=${DRUPAL_PUBLIC_FILES} +# hadolint ignore=DL3064 # a path, not a secret ARG DRUPAL_PRIVATE_FILES="sites/default/files/private" +# hadolint ignore=DL3064 # a path, not a secret ENV DRUPAL_PRIVATE_FILES=${DRUPAL_PRIVATE_FILES} ARG DRUPAL_TEMPORARY_FILES="${TMP:-/tmp}" @@ -61,6 +60,7 @@ ENV PHP_INI_SCAN_DIR="${PHP_INI_SCAN_DIR}:/app/drush/php-ini" # earlier in the build process (near the top of this file). # Add more tools. +# hadolint ignore=DL3018 # the package set tracks the pinned base image RUN apk add --no-cache ncurses pv tzdata autoconf g++ make && \ pecl install pcov && \ docker-php-ext-enable pcov && \ @@ -89,7 +89,6 @@ COPY .vortex/tooling /app/.vortex/tooling # Install PHP dependencies without development packages to avoid exposing # potential security vulnerabilities in the production environment. -# hadolint ignore=SC2155 RUN --mount=type=secret,id=package_token \ token=$(if [ -s /run/secrets/package_token ]; then cat /run/secrets/package_token; else echo "${PACKAGE_TOKEN}"; fi) && \ if [ -n "${token}" ]; then export COMPOSER_AUTH="{\"github-oauth\": {\"github.com\": \"${token}\"}}"; fi && \ @@ -100,6 +99,7 @@ RUN --mount=type=secret,id=package_token \ COPY . /app # Create file directories and set correct permissions. +# hadolint ignore=SC2174 # only the leaf directory needs the mode RUN mkdir -p -m 2775 "/app/${WEBROOT}/${DRUPAL_PUBLIC_FILES}" "/app/${WEBROOT}/${DRUPAL_PRIVATE_FILES}" "${DRUPAL_TEMPORARY_FILES}" #;< DRUPAL_THEME diff --git a/.docker/database.dockerfile b/.docker/database.dockerfile index 1a0929ff7..a59f3a6da 100644 --- a/.docker/database.dockerfile +++ b/.docker/database.dockerfile @@ -6,15 +6,17 @@ # The ARG value will be updated with a value passed from docker-compose.yml ARG IMAGE=uselagoon/mysql-8.4:26.8.0 -# hadolint ignore=DL3006 FROM ${IMAGE} +# hadolint ignore=DL3066 # named account provided by the base image USER root COPY ./.docker/config/database/my.cnf /etc/my.cnf.d/server.cnf RUN fix-permissions /etc/my.cnf.d/ +# hadolint ignore=DL3064 # local development credentials only ENV MYSQL_DATABASE=drupal \ MYSQL_USER=drupal \ MYSQL_PASSWORD=drupal +# hadolint ignore=DL3066 # named account provided by the base image USER mysql diff --git a/.docker/nginx-drupal.dockerfile b/.docker/nginx-drupal.dockerfile index 5c5fce0ed..c0f531fcc 100644 --- a/.docker/nginx-drupal.dockerfile +++ b/.docker/nginx-drupal.dockerfile @@ -2,13 +2,10 @@ # # All web requests are sent to this container. # -# hadolint global ignore=DL3018 -# # @see https://hub.docker.com/r/uselagoon/nginx-drupal/tags # @see https://github.com/uselagoon/lagoon-images/tree/main/images/nginx-drupal ARG CLI_IMAGE -# hadolint ignore=DL3006 FROM ${CLI_IMAGE:-cli} AS cli FROM uselagoon/nginx-drupal:26.7.0 @@ -17,6 +14,7 @@ FROM uselagoon/nginx-drupal:26.7.0 ARG WEBROOT=web ENV WEBROOT=${WEBROOT} +# hadolint ignore=DL3018 # the package set tracks the pinned base image RUN apk add --no-cache tzdata COPY ./.docker/config/nginx/redirects-map.conf /etc/nginx/redirects-map.conf diff --git a/.docker/php.dockerfile b/.docker/php.dockerfile index d95489543..114a34726 100644 --- a/.docker/php.dockerfile +++ b/.docker/php.dockerfile @@ -3,17 +3,15 @@ # All web requests are sent from Nginx to this container. # This container would be scaled up/down in production. # -# hadolint global ignore=DL3018 -# # @see https://hub.docker.com/r/uselagoon/php-8.4-fpm/tags # @see https://github.com/uselagoon/lagoon-images/tree/main/images/php-fpm ARG CLI_IMAGE -# hadolint ignore=DL3006 FROM ${CLI_IMAGE:-cli} AS cli FROM uselagoon/php-8.4-fpm:26.7.0 +# hadolint ignore=DL3018 # the package set tracks the pinned base image RUN apk add --no-cache tzdata COPY --from=cli /app /app diff --git a/.docker/solr.dockerfile b/.docker/solr.dockerfile index 6bc87c6a5..26051ef14 100644 --- a/.docker/solr.dockerfile +++ b/.docker/solr.dockerfile @@ -1,7 +1,5 @@ # Solr container. # -# hadolint global ignore=DL3018 -# # @see https://hub.docker.com/r/uselagoon/solr-9-drupal/tags # @see https://github.com/uselagoon/lagoon-images/blob/main/images/solr-drupal/9.Dockerfile @@ -16,6 +14,7 @@ FROM uselagoon/solr-9-drupal:26.8.0 # whenever this image tag is bumped. COPY .docker/config/solr/config-set /solr-conf/conf/ +# hadolint ignore=DL3066 # named account provided by the base image USER root # Apply custom modifications for Lagoon environment compatibility. @@ -23,6 +22,7 @@ RUN sed -i -e "s#\${solr.data.dir:}#/var/solr/\${solr.core.nam sed -i -e "s#solr.lock.type:native#solr.lock.type:none#g" /solr-conf/conf/solrconfig.xml && \ sed -i -e "s#solr.autoSoftCommit.MaxTime=5000#solr.autoSoftCommit.MaxTime=-1#g" /solr-conf/conf/solrcore.properties +# hadolint ignore=DL3066 # named account provided by the base image USER solr # solr-precreate is provided by the base Solr container image. diff --git a/.github/workflows/build-test-deploy.yml b/.github/workflows/build-test-deploy.yml index 1616ac744..bbd35040f 100644 --- a/.github/workflows/build-test-deploy.yml +++ b/.github/workflows/build-test-deploy.yml @@ -121,7 +121,7 @@ jobs: - name: Lint Dockerfiles with Hadolint run: | find .docker \( -name 'Dockerfile' -o -name '*.dockerfile' \) | while read -r file; do - echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint + echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint:v2.15.0 done continue-on-error: ${{ vars.VORTEX_CI_HADOLINT_IGNORE_FAILURE == '1' }} #;> TOOL_HADOLINT diff --git a/.vortex/cli/tests/Functional/Handlers/ToolsHandlerProcessTest.php b/.vortex/cli/tests/Functional/Handlers/ToolsHandlerProcessTest.php index bf247c9bb..97b697228 100644 --- a/.vortex/cli/tests/Functional/Handlers/ToolsHandlerProcessTest.php +++ b/.vortex/cli/tests/Functional/Handlers/ToolsHandlerProcessTest.php @@ -193,7 +193,7 @@ public static function dataProviderHandlerProcess(): \Iterator { $test->assertFileExists(static::$sut . '/.dclintrc'); // Dockerfile directives are inert comments that remain useful when // the tool is run by hand, so they survive deselection. - $test->assertFileContainsString(static::$sut . '/.docker/cli.dockerfile', '# hadolint global ignore='); + $test->assertFileContainsString(static::$sut . '/.docker/cli.dockerfile', '# hadolint ignore='); }), ]; yield 'tools_no_hadolint_circleci' => [ @@ -208,7 +208,7 @@ public static function dataProviderHandlerProcess(): \Iterator { $test->assertFileNotContainsString($ci, 'VORTEX_CI_HADOLINT_IGNORE_FAILURE'); $test->assertFileContainsString($ci, 'dclint'); $test->assertFileExists(static::$sut . '/.dclintrc'); - $test->assertFileContainsString(static::$sut . '/.docker/cli.dockerfile', '# hadolint global ignore='); + $test->assertFileContainsString(static::$sut . '/.docker/cli.dockerfile', '# hadolint ignore='); }), ]; yield 'tools_no_docker_linters' => [ diff --git a/.vortex/tests/lint.dockerfiles.sh b/.vortex/tests/lint.dockerfiles.sh index 4f8e2e6fe..cd9037c05 100755 --- a/.vortex/tests/lint.dockerfiles.sh +++ b/.vortex/tests/lint.dockerfiles.sh @@ -33,6 +33,6 @@ for file in "${targets[@]}"; do if [ -f "${file}" ]; then echo "Checking file ${file}" - docker run --rm -i hadolint/hadolint <"${file}" + docker run --rm -i hadolint/hadolint:v2.15.0 <"${file}" fi done diff --git a/renovate.json b/renovate.json index 053415769..bcaeacbe4 100644 --- a/renovate.json +++ b/renovate.json @@ -173,7 +173,9 @@ "groupName": "Container images - All - Major, minor and patch", "groupSlug": "container-images-all-major-minor-patch", "matchFileNames": [ - ".docker/**" + ".docker/**", + ".circleci/**", + ".github/workflows/**" ], "schedule": [ "before 3am" @@ -208,6 +210,18 @@ ], "datasourceTemplate": "docker", "versioningTemplate": "docker" + }, + { + "customType": "regex", + "managerFilePatterns": [ + "/^\\.circleci/config\\.yml$/", + "/^\\.github/workflows/.+\\.yml$/" + ], + "matchStrings": [ + "docker run[^\\n]*?\\s(?[a-z0-9][a-z0-9._\\-]*(?:/[a-z0-9._\\-]+)+):(?v?[0-9][\\w.\\-]*)" + ], + "datasourceTemplate": "docker", + "versioningTemplate": "docker" } ] } From a2316ca9fd605237e0a10058d4fa725a3e980ffd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 31 Jul 2026 12:30:10 +1000 Subject: [PATCH 11/16] Update rhysd/actionlint Docker tag to v1.7.12 (#2873) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> (cherry picked from commit 6816fc3b60cde264c84f51e15f93b382fbd1b0a2) --- .github/workflows/vortex-test-common.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/vortex-test-common.yml b/.github/workflows/vortex-test-common.yml index a697ca4d3..3b7e0f0d0 100644 --- a/.github/workflows/vortex-test-common.yml +++ b/.github/workflows/vortex-test-common.yml @@ -255,7 +255,7 @@ jobs: continue-on-error: ${{ vars.VORTEX_CI_YAMLLINT_IGNORE_FAILURE == '1' }} - name: Check coding standards with actionlint - run: docker run --rm -v "${GITHUB_WORKSPACE:-.}":/app --workdir /app rhysd/actionlint:1.7.10 -ignore 'SC2002:' -ignore 'SC2155:' -ignore 'SC2015:' -ignore 'SC2046:' -ignore 'SC1090:' + run: docker run --rm -v "${GITHUB_WORKSPACE:-.}":/app --workdir /app rhysd/actionlint:1.7.12 -ignore 'SC2002:' -ignore 'SC2155:' -ignore 'SC2015:' -ignore 'SC2046:' -ignore 'SC1090:' continue-on-error: ${{ vars.VORTEX_CI_ACTIONLINT_IGNORE_FAILURE == '1' }} - name: Check GitHub Actions security with Zizmor From b49f74c2378d730ffc5fb258a2441bab123a115f Mon Sep 17 00:00:00 2001 From: Alex Skrypnyk Date: Fri, 31 Jul 2026 15:44:02 +1000 Subject: [PATCH 12/16] Pinned the seed script, 'asciinema' and 'sharp-cli' versions. Forward-ported from main d4e62457cc9c5133246b7e26a2fcdd8559fa8f29. --- .github/workflows/vortex-release.yml | 8 +- .github/workflows/vortex-test-cli.yml | 8 +- .vortex/docs/.utils/VideoRecorder.php | 3 +- .../content/contributing/maintenance/cli.mdx | 11 + .../contributing/maintenance/template.mdx | 1 + .vortex/docs/package.json | 1 + .vortex/docs/yarn.lock | 330 +++++++++++++++++- .vortex/tests/update-test-assets | 24 +- 8 files changed, 374 insertions(+), 12 deletions(-) diff --git a/.github/workflows/vortex-release.yml b/.github/workflows/vortex-release.yml index 251d9a507..4409b71dd 100644 --- a/.github/workflows/vortex-release.yml +++ b/.github/workflows/vortex-release.yml @@ -235,11 +235,15 @@ jobs: VORTEX_CURRENT_MAJOR: ${{ env.CURRENT_MAJOR }} - name: Generate video for the install demo (not used in the final artifact) + env: + ASCIINEMA_VERSION: v3.2.1 + ASCIINEMA_SHA256: 1b405bbda565b33c3c4718de67fedc3535580603c0694b1ff3fb04f363430a20 run: | sudo apt-get update sudo apt install expect - sudo curl -sSL https://github.com/asciinema/asciinema/releases/latest/download/asciinema-x86_64-unknown-linux-gnu -o /usr/local/bin/asciinema && sudo chmod +x /usr/local/bin/asciinema - npm i -g sharp-cli + sudo curl -sSL "https://github.com/asciinema/asciinema/releases/download/${ASCIINEMA_VERSION}/asciinema-x86_64-unknown-linux-gnu" -o /usr/local/bin/asciinema + echo "${ASCIINEMA_SHA256} /usr/local/bin/asciinema" | sha256sum --check --strict + sudo chmod +x /usr/local/bin/asciinema yarn install --frozen-lockfile php .utils/update-videos.php cli-install working-directory: .vortex/docs diff --git a/.github/workflows/vortex-test-cli.yml b/.github/workflows/vortex-test-cli.yml index b7aa4d49a..9a3309b02 100644 --- a/.github/workflows/vortex-test-cli.yml +++ b/.github/workflows/vortex-test-cli.yml @@ -77,11 +77,15 @@ jobs: - name: Generate video for the install demo (not used in the final artifact) if: matrix.php-versions == '8.2' + env: + ASCIINEMA_VERSION: v3.2.1 + ASCIINEMA_SHA256: 1b405bbda565b33c3c4718de67fedc3535580603c0694b1ff3fb04f363430a20 run: | sudo apt-get update sudo apt install expect - sudo curl -sSL https://github.com/asciinema/asciinema/releases/latest/download/asciinema-x86_64-unknown-linux-gnu -o /usr/local/bin/asciinema && sudo chmod +x /usr/local/bin/asciinema - npm i -g sharp-cli + sudo curl -sSL "https://github.com/asciinema/asciinema/releases/download/${ASCIINEMA_VERSION}/asciinema-x86_64-unknown-linux-gnu" -o /usr/local/bin/asciinema + echo "${ASCIINEMA_SHA256} /usr/local/bin/asciinema" | sha256sum --check --strict + sudo chmod +x /usr/local/bin/asciinema yarn install --frozen-lockfile php .utils/update-videos.php cli-install working-directory: .vortex/docs diff --git a/.vortex/docs/.utils/VideoRecorder.php b/.vortex/docs/.utils/VideoRecorder.php index 224a7af70..98bd81713 100644 --- a/.vortex/docs/.utils/VideoRecorder.php +++ b/.vortex/docs/.utils/VideoRecorder.php @@ -470,7 +470,8 @@ public function renderPng(string $cast_path, string $png_path, ?int $at_ms = NUL ]); $this->run([ - 'npx', 'sharp-cli', + // 'sharp-cli' installs its executable as 'sharp'. + 'npx', 'sharp', '-i', $frame_svg, '-o', $png_path, '-f', 'png', diff --git a/.vortex/docs/content/contributing/maintenance/cli.mdx b/.vortex/docs/content/contributing/maintenance/cli.mdx index 19098fde6..4b9a5c75e 100644 --- a/.vortex/docs/content/contributing/maintenance/cli.mdx +++ b/.vortex/docs/content/contributing/maintenance/cli.mdx @@ -256,3 +256,14 @@ To update the video: cd .vortex ahoy update-videos cli-install ``` + +`sharp-cli`, which renders the PNG posters, is a declared dependency of +`.vortex/docs`, so it is pinned by `yarn.lock` and installed by the existing +`yarn install --frozen-lockfile`. + +`asciinema` is downloaded rather than installed from a lockfile, so the +workflows that regenerate the video in CI pin it explicitly: +`.github/workflows/vortex-release.yml` and +`.github/workflows/vortex-test-installer.yml` each set `ASCIINEMA_VERSION` and +`ASCIINEMA_SHA256`. Bump both workflows together, so the release and the +installer test always generate assets with identical tooling. diff --git a/.vortex/docs/content/contributing/maintenance/template.mdx b/.vortex/docs/content/contributing/maintenance/template.mdx index edb5b7bf3..514ed68ab 100644 --- a/.vortex/docs/content/contributing/maintenance/template.mdx +++ b/.vortex/docs/content/contributing/maintenance/template.mdx @@ -272,6 +272,7 @@ Without arguments, runs `all` for a full refresh. Default tag is `latest`. - `docker`, `ahoy`, `curl`, `sed`, `php` available on `PATH`. - A Docker Hub session with push permission to `drevops/` for any image mode. +- The `demo-image`, `test-image` and `all` modes download `seed.sh` from a pinned `drevops/mariadb-drupal-data` release and verify it against a recorded SHA-256 checksum. Adopting a newer release is a manual step: update `SEED_URL` and `SEED_SHA256` together. ### Caveats diff --git a/.vortex/docs/package.json b/.vortex/docs/package.json index 699693bb9..bf1117746 100644 --- a/.vortex/docs/package.json +++ b/.vortex/docs/package.json @@ -38,6 +38,7 @@ "raw-loader": "^4.0.2", "react": "^18.0.0", "react-dom": "^18.0.0", + "sharp-cli": "^5.2.0", "svg-term-cli": "^2.1.1" }, "devDependencies": { diff --git a/.vortex/docs/yarn.lock b/.vortex/docs/yarn.lock index 0e3e835d3..2379a9fe0 100644 --- a/.vortex/docs/yarn.lock +++ b/.vortex/docs/yarn.lock @@ -2674,6 +2674,134 @@ "@iconify/types" "^2.0.0" import-meta-resolve "^4.2.0" +"@img/sharp-darwin-arm64@0.34.2": + version "0.34.2" + resolved "https://registry.yarnpkg.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.2.tgz#65049ef7c6be7857da742cd028f97602ce209635" + integrity sha512-OfXHZPppddivUJnqyKoi5YVeHRkkNE2zUFT2gbpKxp/JZCFYEYubnMg+gOp6lWfasPrTS+KPosKqdI+ELYVDtg== + optionalDependencies: + "@img/sharp-libvips-darwin-arm64" "1.1.0" + +"@img/sharp-darwin-x64@0.34.2": + version "0.34.2" + resolved "https://registry.yarnpkg.com/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.2.tgz#d37ff7c75c46d5a68a3756e3f1924ef7ca7b285e" + integrity sha512-dYvWqmjU9VxqXmjEtjmvHnGqF8GrVjM2Epj9rJ6BUIXvk8slvNDJbhGFvIoXzkDhrJC2jUxNLz/GUjjvSzfw+g== + optionalDependencies: + "@img/sharp-libvips-darwin-x64" "1.1.0" + +"@img/sharp-libvips-darwin-arm64@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.1.0.tgz#843f7c09c7245dc0d3cfec2b3c83bb08799a704f" + integrity sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA== + +"@img/sharp-libvips-darwin-x64@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.1.0.tgz#1239c24426c06a8e833815562f78047a3bfbaaf8" + integrity sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ== + +"@img/sharp-libvips-linux-arm64@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.1.0.tgz#20d276cefd903ee483f0441ba35961679c286315" + integrity sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew== + +"@img/sharp-libvips-linux-arm@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.1.0.tgz#067c0b566eae8063738cf1b1db8f8a8573b5465c" + integrity sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA== + +"@img/sharp-libvips-linux-ppc64@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.1.0.tgz#682334595f2ca00e0a07a675ba170af165162802" + integrity sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ== + +"@img/sharp-libvips-linux-s390x@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.1.0.tgz#82fcd68444b3666384235279c145c2b28d8ee302" + integrity sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA== + +"@img/sharp-libvips-linux-x64@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.1.0.tgz#65b2b908bf47156b0724fde9095676c83a18cf5a" + integrity sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q== + +"@img/sharp-libvips-linuxmusl-arm64@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.1.0.tgz#72accf924e80b081c8db83b900b444a67c203f01" + integrity sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w== + +"@img/sharp-libvips-linuxmusl-x64@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.1.0.tgz#1fa052737e203f46bf44192acd01f9faf11522d7" + integrity sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A== + +"@img/sharp-linux-arm64@0.34.2": + version "0.34.2" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.2.tgz#c9690fac5f3137eaab3f7ad6065390d10f66f1fa" + integrity sha512-D8n8wgWmPDakc83LORcfJepdOSN6MvWNzzz2ux0MnIbOqdieRZwVYY32zxVx+IFUT8er5KPcyU3XXsn+GzG/0Q== + optionalDependencies: + "@img/sharp-libvips-linux-arm64" "1.1.0" + +"@img/sharp-linux-arm@0.34.2": + version "0.34.2" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.2.tgz#771dd2ec645f85f98441359bfc118afaf38cbd8b" + integrity sha512-0DZzkvuEOqQUP9mo2kjjKNok5AmnOr1jB2XYjkaoNRwpAYMDzRmAqUIa1nRi58S2WswqSfPOWLNOr0FDT3H5RQ== + optionalDependencies: + "@img/sharp-libvips-linux-arm" "1.1.0" + +"@img/sharp-linux-s390x@0.34.2": + version "0.34.2" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.2.tgz#82132d158abff57bd90b53574f2865f72f94e6c8" + integrity sha512-EGZ1xwhBI7dNISwxjChqBGELCWMGDvmxZXKjQRuqMrakhO8QoMgqCrdjnAqJq/CScxfRn+Bb7suXBElKQpPDiw== + optionalDependencies: + "@img/sharp-libvips-linux-s390x" "1.1.0" + +"@img/sharp-linux-x64@0.34.2": + version "0.34.2" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.2.tgz#d815fb87899d462b28b62a9252ad127f02fe0740" + integrity sha512-sD7J+h5nFLMMmOXYH4DD9UtSNBD05tWSSdWAcEyzqW8Cn5UxXvsHAxmxSesYUsTOBmUnjtxghKDl15EvfqLFbQ== + optionalDependencies: + "@img/sharp-libvips-linux-x64" "1.1.0" + +"@img/sharp-linuxmusl-arm64@0.34.2": + version "0.34.2" + resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.2.tgz#cfac45b2abbc04628f676e123bfe3aeb300266c7" + integrity sha512-NEE2vQ6wcxYav1/A22OOxoSOGiKnNmDzCYFOZ949xFmrWZOVII1Bp3NqVVpvj+3UeHMFyN5eP/V5hzViQ5CZNA== + optionalDependencies: + "@img/sharp-libvips-linuxmusl-arm64" "1.1.0" + +"@img/sharp-linuxmusl-x64@0.34.2": + version "0.34.2" + resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.2.tgz#b876c23ff51d0fb6d9f3b0a07e2f4d1436c203ad" + integrity sha512-DOYMrDm5E6/8bm/yQLCWyuDJwUnlevR8xtF8bs+gjZ7cyUNYXiSf/E8Kp0Ss5xasIaXSHzb888V1BE4i1hFhAA== + optionalDependencies: + "@img/sharp-libvips-linuxmusl-x64" "1.1.0" + +"@img/sharp-wasm32@0.34.2": + version "0.34.2" + resolved "https://registry.yarnpkg.com/@img/sharp-wasm32/-/sharp-wasm32-0.34.2.tgz#b1dd0bab547dccf517586eb1fa5852160bba3b82" + integrity sha512-/VI4mdlJ9zkaq53MbIG6rZY+QRN3MLbR6usYlgITEzi4Rpx5S6LFKsycOQjkOGmqTNmkIdLjEvooFKwww6OpdQ== + dependencies: + "@emnapi/runtime" "^1.4.3" + +"@img/sharp-win32-arm64@0.34.2": + version "0.34.2" + resolved "https://registry.yarnpkg.com/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.2.tgz#f37bee0f60c167f825a09d2b8de6849b823e8b30" + integrity sha512-cfP/r9FdS63VA5k0xiqaNaEoGxBg9k7uE+RQGzuK9fHt7jib4zAVVseR9LsE4gJcNWgT6APKMNnCcnyOtmSEUQ== + +"@img/sharp-win32-ia32@0.34.2": + version "0.34.2" + resolved "https://registry.yarnpkg.com/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.2.tgz#8fc30b6655bc6ff8910344a2020d334aa6361672" + integrity sha512-QLjGGvAbj0X/FXl8n1WbtQ6iVBpWU7JO94u/P2M4a8CFYsvQi4GW2mRy/JqkRx0qpBzaOdKJKw8uc930EX2AHw== + +"@img/sharp-win32-x64@0.34.2": + version "0.34.2" + resolved "https://registry.yarnpkg.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.2.tgz#ecf19250f8fe35de684aa2b0ec6f773b3447247b" + integrity sha512-aUdT6zEYtDKCaxkofmmJDJYGCf0+pJg3eU9/oBuqvEeoB9dKI6ZLc/1iLJCTuJQDO4ptntAlkUmHgGjyuobZbw== + +"@isaacs/cliui@^9.0.0": + version "9.0.0" + resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-9.0.0.tgz#4d0a3f127058043bf2e7ee169eaf30ed901302f3" + integrity sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg== + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -5247,6 +5375,13 @@ brace-expansion@^5.0.5: dependencies: balanced-match "^4.0.2" +brace-expansion@^5.0.8: + version "5.0.9" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.9.tgz#7c72438809b5fa5babf54199a1f1c281a6984fcf" + integrity sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg== + dependencies: + balanced-match "^4.0.2" + braces@^3.0.3, braces@~3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" @@ -5272,6 +5407,14 @@ bser@2.1.1: dependencies: node-int64 "^0.4.0" +bubble-stream-error@1.0.x: + version "1.0.0" + resolved "https://registry.yarnpkg.com/bubble-stream-error/-/bubble-stream-error-1.0.0.tgz#7dad97f17128da396169bf37ada4acb195361e30" + integrity sha512-Rqf0ly5H4HGt+ki/n3m7GxoR2uIGtNqezPlOLX8Vuo13j5/tfPuVvAr84eoGF7sYm6lKdbGnT/3q8qmzuT5Y9w== + dependencies: + once "^1.3.3" + sliced "^1.0.1" + buffer-from@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" @@ -5653,7 +5796,7 @@ color-name@^1.0.0, color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -color-string@^1.5.2: +color-string@^1.5.2, color-string@^1.9.0: version "1.9.1" resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== @@ -5669,6 +5812,14 @@ color@^2.0.1: color-convert "^1.9.1" color-string "^1.5.2" +color@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a" + integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A== + dependencies: + color-convert "^2.0.1" + color-string "^1.9.0" + colord@^2.9.3: version "2.9.3" resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43" @@ -5923,7 +6074,7 @@ cross-spawn@^5.0.1: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^7.0.2, cross-spawn@^7.0.3: +cross-spawn@^7.0.2, cross-spawn@^7.0.3, cross-spawn@^7.0.6: version "7.0.6" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== @@ -6813,6 +6964,11 @@ destroy@1.2.0, destroy@~1.2.0: resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== +detect-libc@^2.0.4: + version "2.1.2" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.1.2.tgz#689c5dcdc1900ef5583a4cb9f6d7b473742074ad" + integrity sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ== + detect-newline@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" @@ -7880,6 +8036,14 @@ for-each@^0.3.3, for-each@^0.3.5: dependencies: is-callable "^1.2.7" +foreground-child@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.1.tgz#32e8e9ed1b68a3497befb9ac2b6adf92a638576f" + integrity sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw== + dependencies: + cross-spawn "^7.0.6" + signal-exit "^4.0.1" + form-data-encoder@^2.1.2: version "2.1.4" resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-2.1.4.tgz#261ea35d2a70d48d30ec7a9603130fa5515e9cd5" @@ -8085,6 +8249,18 @@ glob@*: minipass "^7.1.3" path-scurry "^2.0.2" +glob@11.0.x: + version "11.0.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-11.0.3.tgz#9d8087e6d72ddb3c4707b1d2778f80ea3eaefcd6" + integrity sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA== + dependencies: + foreground-child "^3.3.1" + jackspeak "^4.1.1" + minimatch "^10.0.3" + minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^2.0.0" + glob@^7.1.3, glob@^7.1.4: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" @@ -8899,6 +9075,11 @@ is-decimal@^2.0.0: resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-2.0.1.tgz#9469d2dc190d0214fd87d78b78caecc0cc14eef7" integrity sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A== +is-directory@0.3.x: + version "0.3.1" + resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" + integrity sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw== + is-docker@^2.0.0, is-docker@^2.1.1: version "2.2.1" resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" @@ -9256,6 +9437,13 @@ iterator.prototype@^1.1.5: has-symbols "^1.1.0" set-function-name "^2.0.2" +jackspeak@^4.1.1: + version "4.2.3" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-4.2.3.tgz#27ef80f33b93412037c3bea4f8eddf80e1931483" + integrity sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg== + dependencies: + "@isaacs/cliui" "^9.0.0" + jest-changed-files@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" @@ -9975,11 +10163,60 @@ lodash-es@^4.17.21: resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.18.1.tgz#b962eeb80d9d983a900bf342961fb7418ca10b1d" integrity sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A== +lodash._baseflatten@^3.0.0: + version "3.1.4" + resolved "https://registry.yarnpkg.com/lodash._baseflatten/-/lodash._baseflatten-3.1.4.tgz#0770ff80131af6e34f3b511796a7ba5214e65ff7" + integrity sha512-fESngZd+X4k+GbTxdMutf8ohQa0s3sJEHIcwtu4/LsIQ2JTDzdRxDCMQjW+ezzwRitLmHnacVVmosCbxifefbw== + dependencies: + lodash.isarguments "^3.0.0" + lodash.isarray "^3.0.0" + +lodash._basefor@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash._basefor/-/lodash._basefor-3.0.3.tgz#7550b4e9218ef09fad24343b612021c79b4c20c2" + integrity sha512-6bc3b8grkpMgDcVJv9JYZAk/mHgcqMljzm7OsbmcE2FGUMmmLQTPHlh/dFqR8LA0GQ7z4K67JSotVKu5058v1A== + +lodash._bindcallback@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz#e531c27644cf8b57a99e17ed95b35c748789392e" + integrity sha512-2wlI0JRAGX8WEf4Gm1p/mv/SZ+jLijpj0jyaE/AXeuQphzCgD8ZQW4oSpoN8JAopujOFGU3KMuq7qfHBWlGpjQ== + +lodash._pickbyarray@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/lodash._pickbyarray/-/lodash._pickbyarray-3.0.2.tgz#1f898d9607eb560b0e167384b77c7c6d108aa4c5" + integrity sha512-tHzBIfgugzI7HV0y8MJS1z/ryWDh8NyD6AV+so9vlplRnhD4qBuwoyDt7g241ad3F43YDFghCN+R3iaFd4Azvw== + +lodash._pickbycallback@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash._pickbycallback/-/lodash._pickbycallback-3.0.0.tgz#ff61b9a017a7b3af7d30e6c53de28afa19b8750a" + integrity sha512-DVP27YmN0lB+j/Tgd/+gtxfmW/XihgWpQpHptBuwyp2fD9zEBRwwcnw6Qej16LUV8LRFuTqyoc0i6ON97d/C5w== + dependencies: + lodash._basefor "^3.0.0" + lodash.keysin "^3.0.0" + lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== +lodash.isarguments@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" + integrity sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg== + +lodash.isarray@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" + integrity sha512-JwObCrNJuT0Nnbuecmqr5DgtuBppuCvGD9lxjFpAzwnVtdGoDQ1zig+5W8k5/6Gcn0gZ3936HDAlGd28i7sOGQ== + +lodash.keysin@^3.0.0: + version "3.0.8" + resolved "https://registry.yarnpkg.com/lodash.keysin/-/lodash.keysin-3.0.8.tgz#22c4493ebbedb1427962a54b445b2c8a767fb47f" + integrity sha512-YDB/5xkL3fBKFMDaC+cfGV00pbiJ6XoJIfRmBhv7aR6wWtbCW6IzkiWnTfkiHTF6ALD7ff83dAtB3OEaSoyQPg== + dependencies: + lodash.isarguments "^3.0.0" + lodash.isarray "^3.0.0" + lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" @@ -9990,6 +10227,22 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +lodash.pick@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-3.1.0.tgz#f252a855b2046b61bcd3904b26f76bd2efc65550" + integrity sha512-Y04wnFghB7l1dkYINfjdMLpeAGM1IYEjlsGFxvjeewCbVQUlD9jw3M20ThuNrsf6yGmuPLwj60PKP+D6gZ+o2w== + dependencies: + lodash._baseflatten "^3.0.0" + lodash._bindcallback "^3.0.0" + lodash._pickbyarray "^3.0.0" + lodash._pickbycallback "^3.0.0" + lodash.restparam "^3.0.0" + +lodash.restparam@^3.0.0: + version "3.6.1" + resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" + integrity sha512-L4/arjjuq4noiUJpt3yS6KIKDtJwNe2fIYgMqyYYKoeIfV1iEqvPwhCx23o+R9dzouGihDAPN1dTIRWa7zk8tw== + lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" @@ -11027,6 +11280,13 @@ minimatch@3.1.5, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch dependencies: brace-expansion "^1.1.7" +minimatch@^10.0.3: + version "10.2.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.6.tgz#fd956bbe0b77241e9f15ac5dccb1c638060968ef" + integrity sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A== + dependencies: + brace-expansion "^5.0.8" + minimatch@^10.2.2: version "10.2.5" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.5.tgz#bd48687a0be38ed2961399105600f832095861d1" @@ -11328,7 +11588,7 @@ on-headers@~1.1.0: resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.1.0.tgz#59da4f91c45f5f989c6e4bcedc5a3b0aed70ff65" integrity sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A== -once@^1.3.0: +once@^1.3.0, once@^1.3.3: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== @@ -11476,6 +11736,11 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +package-json-from-dist@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" + integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== + package-json@^8.1.0: version "8.1.1" resolved "https://registry.yarnpkg.com/package-json/-/package-json-8.1.1.tgz#3e9948e43df40d1e8e78a85485f1070bf8f03dc8" @@ -11635,7 +11900,7 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-scurry@^2.0.2: +path-scurry@^2.0.0, path-scurry@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-2.0.2.tgz#6be0d0ee02a10d9e0de7a98bae65e182c9061f85" integrity sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg== @@ -13279,7 +13544,7 @@ semver@^6.3.0, semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.5, semver@^7.3.7, semver@^7.5.3, semver@^7.5.4, semver@^7.7.1: +semver@^7.3.5, semver@^7.3.7, semver@^7.5.3, semver@^7.5.4, semver@^7.7.1, semver@^7.7.2: version "7.8.5" resolved "https://registry.yarnpkg.com/semver/-/semver-7.8.5.tgz#39b646037dd50c14fb451e7e4cac58ed8b863f69" integrity sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA== @@ -13394,6 +13659,49 @@ shallowequal@^1.1.0: resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== +sharp-cli@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/sharp-cli/-/sharp-cli-5.2.0.tgz#94f9f6601b29fc3ae587d6a472d33e29cf8e5628" + integrity sha512-0DQyABFTWkla4FYvjguCQloSgm2htOPfGFur88HXiGu8BPfhb77frWn+E87w8Oog0eSHF4/uMt5AFhpvfY/Zrg== + dependencies: + bubble-stream-error "1.0.x" + glob "11.0.x" + is-directory "0.3.x" + lodash.pick "3.1.0" + sharp "0.34.2" + yargs "^17.6.2" + +sharp@0.34.2: + version "0.34.2" + resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.34.2.tgz#648bd639854dbe48047b0b420213c186d036b32d" + integrity sha512-lszvBmB9QURERtyKT2bNmsgxXK0ShJrL/fvqlonCo7e6xBF8nT8xU6pW+PMIbLsz0RxQk3rgH9kd8UmvOzlMJg== + dependencies: + color "^4.2.3" + detect-libc "^2.0.4" + semver "^7.7.2" + optionalDependencies: + "@img/sharp-darwin-arm64" "0.34.2" + "@img/sharp-darwin-x64" "0.34.2" + "@img/sharp-libvips-darwin-arm64" "1.1.0" + "@img/sharp-libvips-darwin-x64" "1.1.0" + "@img/sharp-libvips-linux-arm" "1.1.0" + "@img/sharp-libvips-linux-arm64" "1.1.0" + "@img/sharp-libvips-linux-ppc64" "1.1.0" + "@img/sharp-libvips-linux-s390x" "1.1.0" + "@img/sharp-libvips-linux-x64" "1.1.0" + "@img/sharp-libvips-linuxmusl-arm64" "1.1.0" + "@img/sharp-libvips-linuxmusl-x64" "1.1.0" + "@img/sharp-linux-arm" "0.34.2" + "@img/sharp-linux-arm64" "0.34.2" + "@img/sharp-linux-s390x" "0.34.2" + "@img/sharp-linux-x64" "0.34.2" + "@img/sharp-linuxmusl-arm64" "0.34.2" + "@img/sharp-linuxmusl-x64" "0.34.2" + "@img/sharp-wasm32" "0.34.2" + "@img/sharp-win32-arm64" "0.34.2" + "@img/sharp-win32-ia32" "0.34.2" + "@img/sharp-win32-x64" "0.34.2" + shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" @@ -13468,6 +13776,11 @@ signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +signal-exit@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + simple-swizzle@^0.2.2: version "0.2.4" resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.4.tgz#a8d11a45a11600d6a1ecdff6363329e3648c3667" @@ -13521,6 +13834,11 @@ slash@^5.1.0: resolved "https://registry.yarnpkg.com/slash/-/slash-5.1.0.tgz#be3adddcdf09ac38eebe8dcdc7b1a57a75b095ce" integrity sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg== +sliced@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/sliced/-/sliced-1.0.1.tgz#0b3a662b5d04c3177b1926bea82b03f837a2ef41" + integrity sha512-VZBmZP8WU3sMOZm1bdgTadsQbcscK0UM8oKxKVBs4XAhUo2Xxzm/OFMGBkPusxw9xL3Uy8LrzEqGqJhclsr0yA== + snake-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-3.0.4.tgz#4f2bbd568e9935abdfd593f34c691dadb49c452c" @@ -15007,7 +15325,7 @@ yargs-parser@^21.1.1: resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== -yargs@^17.3.1: +yargs@^17.3.1, yargs@^17.6.2: version "17.7.3" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.3.tgz#779dffe6bcafec596a7172e983289a588647faaa" integrity sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g== diff --git a/.vortex/tests/update-test-assets b/.vortex/tests/update-test-assets index eb4d61123..4cd1f73d5 100755 --- a/.vortex/tests/update-test-assets +++ b/.vortex/tests/update-test-assets @@ -40,7 +40,8 @@ declare(strict_types=1); */ const SUT_DIR = '/tmp/star-wars'; -const SEED_URL = 'https://github.com/drevops/mariadb-drupal-data/releases/latest/download/seed.sh'; +const SEED_URL = 'https://github.com/drevops/mariadb-drupal-data/releases/download/26.7.0/seed.sh'; +const SEED_SHA256 = 'd97c184b2b76111b39b3cba96a624c22ed3f3408d781345770ea2b17beaba91e'; const IMAGE_DEMO = 'drevops/vortex-dev-mariadb-drupal-data-demo-11.x'; const IMAGE_TEST = 'drevops/vortex-dev-mariadb-drupal-data-test-11.x'; const IMAGE_DEST = 'drevops/vortex-dev-mariadb-drupal-data-demo-destination-11.x'; @@ -390,6 +391,9 @@ function truncate_for_log(string $value, int $max): string { function seed_image(string $cwd, string $filename, string $image): void { step('Download seed.sh'); run(sprintf('curl -fsSL -o seed.sh %s', escapeshellarg(SEED_URL)), $cwd); + // A release asset can be replaced without moving its tag, so pinning the + // URL alone does not pin the contents of the script that gets executed. + verify_checksum($cwd . '/seed.sh', SEED_SHA256); run('chmod +x seed.sh', $cwd); step('Ensure a multi-platform capable buildx builder'); @@ -405,6 +409,24 @@ function seed_image(string $cwd, string $filename, string $image): void { run(sprintf('DESTINATION_PLATFORMS=linux/amd64,linux/arm64 BUILDX_BUILDER=vortex-seed ./seed.sh .data/%s %s', escapeshellarg($filename), escapeshellarg($image)), $cwd); } +function verify_checksum(string $path, string $expected): void { + step(sprintf('Verify checksum of %s', basename($path))); + + $actual = hash_file('sha256', $path); + + if ($actual === FALSE) { + fwrite(STDERR, sprintf("Error: cannot read %s for checksum verification.\n", $path)); + exit(1); + } + + if (!hash_equals($expected, $actual)) { + fwrite(STDERR, sprintf("Error: checksum mismatch for %s.\n Expected: %s\n Actual: %s\n", $path, $expected, $actual)); + exit(1); + } + + println(sprintf(' - Checksum verified: %s', $actual)); +} + // ============================================================================= // Helpers. // ============================================================================= From 2e51df135732c3f36e7ac278233f341cbd845f95 Mon Sep 17 00:00:00 2001 From: Alex Skrypnyk Date: Fri, 31 Jul 2026 15:45:07 +1000 Subject: [PATCH 13/16] Quoted the 'content/**/*.mdx' lint and spellcheck globs so every documentation page is checked. Forward-ported from main 8bd16d31a6e2c90ebe81b7a0a36682fa9503d210. --- .../contributing/maintenance/release.mdx | 9 +- .../contributing/maintenance/template.mdx | 21 ++++- .vortex/docs/content/support.mdx | 8 +- .vortex/docs/cspell.json | 3 + .vortex/docs/package.json | 6 +- .vortex/docs/tests/unit/lint-globs.test.js | 84 +++++++++++++++++++ 6 files changed, 118 insertions(+), 13 deletions(-) create mode 100644 .vortex/docs/tests/unit/lint-globs.test.js diff --git a/.vortex/docs/content/contributing/maintenance/release.mdx b/.vortex/docs/content/contributing/maintenance/release.mdx index 35fb4bccb..b616f22c3 100644 --- a/.vortex/docs/content/contributing/maintenance/release.mdx +++ b/.vortex/docs/content/contributing/maintenance/release.mdx @@ -50,6 +50,7 @@ When creating a new release, determine the version based on the changes: Promoting a new major version is a single action: set the `VORTEX_CURRENT_MAJOR` repository variable to its number. The documentation default and the bare `/install` URL both follow it, while the per-major docs and `/v{N}/install` pins are always published. When `main` advances to a new major version: + 1. The upcoming major's branch (for example `2.x`) is merged into `main` 2. The superseded major continues on its own branch (for example `1.x`) for maintenance @@ -80,9 +81,11 @@ The following rules apply to every release: Follow the steps below to release a new version of the Vortex: 1. Run renovate bot locally to update all dependencies outside of the schedule: -``` -renovate --schedule= --force-cli=true drevops/vortex -``` + + ```shell + renovate --schedule= --force-cli=true drevops/vortex + ``` + 2. Update container images to the latest versions and check that `@see` links are working. - After bumping the `uselagoon/solr-9-drupal` image, update `solr.luceneMatchVersion` in `.docker/config/solr/config-set/solrcore.properties` to match the Lucene version bundled in that image (shown by the `lucene-core-*.jar` filename inside the image). The value in `search_api_solr`'s jump-start config-set lags the image, so it must not be copied verbatim. 3. Update PHP version in `composer.json` for `config.platform`. diff --git a/.vortex/docs/content/contributing/maintenance/template.mdx b/.vortex/docs/content/contributing/maintenance/template.mdx index 514ed68ab..21cf5a0b5 100644 --- a/.vortex/docs/content/contributing/maintenance/template.mdx +++ b/.vortex/docs/content/contributing/maintenance/template.mdx @@ -20,13 +20,14 @@ in [this issue](https://github.com/drevops/vortex/issues/1192). :::note - Please refer to [RFC2119](https://www.ietf.org/rfc/rfc2119.txt) for meaning of words `MUST`, `SHOULD` and `MAY`. +Please refer to [RFC2119](https://www.ietf.org/rfc/rfc2119.txt) for meaning of words `MUST`, `SHOULD` and `MAY`. ::: 1. MUST adhere to [POSIX standard](https://en.wikipedia.org/wiki/POSIX). 2. MUST pass Shellcheck code analysis scan 3. MUST start with: + ```shell #!/usr/bin/env bash ## @@ -39,17 +40,23 @@ in [this issue](https://github.com/drevops/vortex/issues/1192). set -eu [ "${VORTEX_DEBUG-}" = "1" ] && set -x ``` + 4. MUST list all variables with their default values and descriptions. i.e.: + ```shell # Deployment reference, such as a git SHA. VORTEX_NOTIFY_REF="${VORTEX_NOTIFY_REF:-}" ``` + 5. MUST include a delimiter between variables and the script body preceded and followed by an empty line (3 lines in total): + ```shell # ------------------------------------------------------------------------------ ``` + 6. SHOULD include formatting helper functions: + ```shell # @formatter:off note() { printf " %s\n" "${1}"; } @@ -58,22 +65,31 @@ in [this issue](https://github.com/drevops/vortex/issues/1192). fail() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[31m[FAIL] %s\033[0m\n" "${1}" || printf "[FAIL] %s\n" "${1}"; } # @formatter:on ``` -7. SHOULD include variable values checks with errors and early exist, i.e.: + +7. SHOULD include variable values checks with errors and early exit, i.e.: + ```shell [ -z "${VORTEX_NOTIFY_REF}" ] && fail "Missing required value for VORTEX_NOTIFY_REF." && exit 1 ``` + 8. SHOULD include binaries checks if the script relies on them, i.e.: + ```shell command -v curl > /dev/null || ( fail "curl command is not available." && exit 1 ) ``` + 9. MUST contain an `info` message about the start of the script body, e.g.: + ```shell info "Started GitHub notification for operation ${VORTEX_NOTIFY_EVENT}." ``` + 10. MUST contain an `pass` message about the finish of the script body, e.g.: + ```shell pass "Finished GitHub notification for operation ${VORTEX_NOTIFY_EVENT}." ``` + 11. MUST use uppercase global variables 12. MUST use lowercase local variables. 13. MUST use long options instead of short options for readability. I.e., `drush cache:rebuild` instead of `drush cr`. @@ -90,7 +106,6 @@ in [this issue](https://github.com/drevops/vortex/issues/1192). 18. SHOULD use `note` messages for informing about the script progress. 19. MUST use variables in the form of `${VAR}`. - ### Variables Follow these guidelines when creating or updating **Vortex** variables. diff --git a/.vortex/docs/content/support.mdx b/.vortex/docs/content/support.mdx index 5f8b7034c..ffd54f094 100644 --- a/.vortex/docs/content/support.mdx +++ b/.vortex/docs/content/support.mdx @@ -1,5 +1,4 @@ --- -title: Support description: Get help with Vortex - multiple support channels and resources available --- @@ -11,7 +10,6 @@ Get help with **Vortex** through our comprehensive support channels and resource New to **Vortex**? Start with our [Installation Guide](/docs/installation) and then explore the [Features](/docs/features). ::: - ## 🛟 Getting Help ### Quick Self-Help @@ -24,13 +22,14 @@ Start with these resources to solve common issues quickly: ### Community Support -**Slack Community** +#### Slack Community - Join the [`#vortex-project-template`](https://drupal.slack.com/archives/CRE86HQTW) Slack channel - Get help from the community and core maintainers - Share ideas and collaborate with other users -**GitHub Discussions** +#### GitHub Discussions + - [GitHub Issues](https://github.com/drevops/vortex/issues) for bug reports and feature requests - [Project Board](https://github.com/orgs/drevops/projects/2/views/1) to track development progress - Check existing issues before submitting new ones @@ -60,6 +59,7 @@ For organizations requiring dedicated support, we offer professional services: ### How to Report When reporting issues, please include: + - System information (`ahoy doctor info`) - Steps to reproduce the problem - Error messages and logs diff --git a/.vortex/docs/cspell.json b/.vortex/docs/cspell.json index bb0403549..a6cda6653 100644 --- a/.vortex/docs/cspell.json +++ b/.vortex/docs/cspell.json @@ -21,6 +21,7 @@ "Upsun", "acli", "acquia", + "alexskrypnyk", "amazee", "amazeeio", "apikey", @@ -43,6 +44,8 @@ "ddev", "dealerdirect", "devel", + "didi", + "downloaders", "drevops", "drupalcode", "drush", diff --git a/.vortex/docs/package.json b/.vortex/docs/package.json index bf1117746..360d997de 100644 --- a/.vortex/docs/package.json +++ b/.vortex/docs/package.json @@ -11,7 +11,7 @@ "serve": "docusaurus serve", "write-translations": "docusaurus write-translations", "write-heading-ids": "docusaurus write-heading-ids", - "spellcheck": "cspell content/*.mdx content/**/*.mdx", + "spellcheck": "cspell \"content/**/*.mdx\"", "update-variables": "./.utils/update-docs.sh", "test": "jest", "test:watch": "jest --watch", @@ -20,8 +20,8 @@ "lint-fix": "yarn lint-js-fix && yarn lint-docs-fix", "lint-js": "eslint src/ tests/ && prettier --check src/ tests/", "lint-js-fix": "eslint src/ tests/ --fix && prettier --write src/ tests/", - "lint-docs": "markdownlint-cli2 content/**/*.mdx", - "lint-docs-fix": "markdownlint-cli2 content/**/*.mdx --fix" + "lint-docs": "markdownlint-cli2 \"content/**/*.mdx\"", + "lint-docs-fix": "markdownlint-cli2 \"content/**/*.mdx\" --fix" }, "dependencies": { "@docusaurus/core": "^3.5", diff --git a/.vortex/docs/tests/unit/lint-globs.test.js b/.vortex/docs/tests/unit/lint-globs.test.js new file mode 100644 index 000000000..f4ed016d4 --- /dev/null +++ b/.vortex/docs/tests/unit/lint-globs.test.js @@ -0,0 +1,84 @@ +import fs from 'fs'; +import path from 'path'; + +// Yarn runs package scripts through 'sh', which has no 'globstar': an unquoted +// '**' collapses to a single '*', so the linters receive a shell-expanded list +// that stops one directory below 'content/'. Both tools then exit 0 and report +// a plausible file count, leaving deeper pages unchecked with no signal. +const DOCS_ROOT = path.resolve(__dirname, '../..'); +const CONTENT_DIR = 'content'; +const SCRIPTS = ['spellcheck', 'lint-docs', 'lint-docs-fix']; + +const packageJson = JSON.parse( + fs.readFileSync(path.join(DOCS_ROOT, 'package.json'), 'utf8') +); + +const tokenize = script => (script || '').match(/"[^"]*"|\S+/g) || []; + +const isQuoted = token => token.startsWith('"') && token.endsWith('"'); + +const isGlob = token => /[*?]/.test(token); + +// '**/' spans any number of directories, '*' stops at the separator. +const globToRegExp = glob => { + const source = glob.replace(/\*\*\/|\*|[.+^${}()|[\]\\?]/g, match => { + if (match === '**/') { + return '(?:[^/]+/)*'; + } + + if (match === '*') { + return '[^/]*'; + } + + return `\\${match}`; + }); + + return new RegExp(`^${source}$`); +}; + +const collectPages = (dir, pages = []) => { + const entries = fs.readdirSync(path.join(DOCS_ROOT, dir), { + withFileTypes: true, + }); + + for (const entry of entries) { + const relative = `${dir}/${entry.name}`; + + if (entry.isDirectory()) { + collectPages(relative, pages); + continue; + } + + if (entry.name.endsWith('.mdx')) { + pages.push(relative); + } + } + + return pages; +}; + +describe('Documentation lint globs', () => { + const pages = collectPages(CONTENT_DIR); + + test('documentation pages are discoverable', () => { + expect(pages.length).toBeGreaterThan(0); + }); + + describe.each(SCRIPTS)('%s', name => { + const args = tokenize(packageJson.scripts[name]); + + test('passes globs quoted so the tool expands them, not the shell', () => { + expect(args.filter(arg => !isQuoted(arg) && isGlob(arg))).toEqual([]); + }); + + test('covers every documentation page', () => { + const globs = args + .filter(isQuoted) + .map(arg => globToRegExp(arg.slice(1, -1))); + + expect( + pages.filter(page => !globs.some(glob => glob.test(page))) + ).toEqual([]); + }); + }); +}); From f54942eabb2fd396e3467650563ef01ba1d8e712 Mon Sep 17 00:00:00 2001 From: Alex Skrypnyk Date: Fri, 31 Jul 2026 15:48:10 +1000 Subject: [PATCH 14/16] Updated snapshots. --- .../_baseline/.docker/clamav.dockerfile | 5 +- .../_baseline/.docker/cli.dockerfile | 12 +- .../_baseline/.docker/database.dockerfile | 4 +- .../_baseline/.docker/nginx-drupal.dockerfile | 4 +- .../_baseline/.docker/php.dockerfile | 4 +- .../_baseline/.docker/solr.dockerfile | 4 +- .../.github/workflows/build-test-deploy.yml | 2 +- .../handler_process/_baseline/renovate.json | 16 +- .../_baseline/scripts/vortex-tooling.sh | 48 +- .../ciprovider_circleci/.circleci/config.yml | 2 +- .../.circleci/config.yml | 2 +- .../code_provider_other/renovate.json | 2 +- .../custom_modules_none/.gitignore | 8 + .../recipes/page/-recipe.yml | 0 ....entity_form_display.node.page.default.yml | 0 ....entity_view_display.node.page.default.yml | 0 ...e.entity_view_display.node.page.teaser.yml | 0 .../config/-field.field.node.page.body.yml | 0 .../page/config/-field.storage.node.body.yml | 0 .../recipes/page/config/-node.type.page.yml | 0 .../.circleci/config.yml | 2 +- .../.circleci/config.yml | 2 +- .../.circleci/config.yml | 2 +- .../hosting_acquia/.docker/cli.dockerfile | 2 +- .../.docker/nginx-drupal.dockerfile | 4 +- .../.docker/cli.dockerfile | 2 +- .../.docker/nginx-drupal.dockerfile | 4 +- .../.circleci/config.yml | 2 +- .../.circleci/config.yml | 2 +- .../names/.docker/cli.dockerfile | 2 +- .../.circleci/config.yml | 2 +- .../theme_claro/.docker/cli.dockerfile | 4 +- .../theme_custom/.docker/cli.dockerfile | 2 +- .../theme_olivero/.docker/cli.dockerfile | 4 +- .../theme_stark/.docker/cli.dockerfile | 4 +- .../timezone_circleci/.circleci/config.yml | 2 +- .../.circleci/config.yml | 2 +- .../.circleci/config.yml | 2 +- .../.circleci/config.yml | 2 +- .../.docker/cli.dockerfile | 4 +- .../.circleci/config.yml | 2 +- .../.docker/cli.dockerfile | 4 +- .../.circleci/config.yml | 2 +- .../tools_no_dclint/-.dclintrc | 0 .../.github/workflows/build-test-deploy.yml | 11 + .../tools_no_dclint_circleci/-.dclintrc | 0 .../.circleci/config.yml | 532 ++++++++++++++++++ .../.circleci/post-coverage-comment.sh | 79 +++ .../tools_no_dclint_circleci/.dockerignore | 10 + .../.github/workflows/-build-test-deploy.yml | 0 .../tools_no_dclint_circleci/README.md | 9 + .../tools_no_dclint_circleci/docs/ci.md | 12 + .../tests/phpunit/CircleCiConfigTest.php | 283 ++++++++++ .../Drupal/EnvironmentSettingsTest.php | 12 + .../tools_no_docker_linters/-.dclintrc | 0 .../.github/workflows/build-test-deploy.yml | 18 + .../-.dclintrc | 0 .../.circleci/config.yml | 525 +++++++++++++++++ .../.circleci/post-coverage-comment.sh | 79 +++ .../.dockerignore | 10 + .../.github/workflows/-build-test-deploy.yml | 0 .../README.md | 9 + .../docs/ci.md | 12 + .../tests/phpunit/CircleCiConfigTest.php | 283 ++++++++++ .../Drupal/EnvironmentSettingsTest.php | 12 + .../.circleci/config.yml | 2 +- .../.docker/cli.dockerfile | 4 +- .../.github/workflows/build-test-deploy.yml | 14 + .../.circleci/config.yml | 529 +++++++++++++++++ .../.circleci/post-coverage-comment.sh | 79 +++ .../tools_no_hadolint_circleci/.dockerignore | 10 + .../.github/workflows/-build-test-deploy.yml | 0 .../tools_no_hadolint_circleci/README.md | 9 + .../tools_no_hadolint_circleci/docs/ci.md | 12 + .../tests/phpunit/CircleCiConfigTest.php | 283 ++++++++++ .../Drupal/EnvironmentSettingsTest.php | 12 + .../.circleci/config.yml | 2 +- .../.circleci/config.yml | 2 +- .../.circleci/config.yml | 2 +- .../.circleci/config.yml | 2 +- .../.circleci/config.yml | 2 +- .../.circleci/config.yml | 2 +- .../.docker/cli.dockerfile | 4 +- .../.circleci/config.yml | 2 +- .../handler_process/tools_none/-.dclintrc | 0 .../.github/workflows/build-test-deploy.yml | 30 +- .../.github/workflows/test-vr.yml | 143 +++-- 87 files changed, 3079 insertions(+), 135 deletions(-) create mode 100644 .vortex/cli/tests/Fixtures/handler_process/custom_modules_none/.gitignore create mode 100644 .vortex/cli/tests/Fixtures/handler_process/custom_modules_none/recipes/page/-recipe.yml create mode 100644 .vortex/cli/tests/Fixtures/handler_process/custom_modules_none/recipes/page/config/-core.entity_form_display.node.page.default.yml create mode 100644 .vortex/cli/tests/Fixtures/handler_process/custom_modules_none/recipes/page/config/-core.entity_view_display.node.page.default.yml create mode 100644 .vortex/cli/tests/Fixtures/handler_process/custom_modules_none/recipes/page/config/-core.entity_view_display.node.page.teaser.yml create mode 100644 .vortex/cli/tests/Fixtures/handler_process/custom_modules_none/recipes/page/config/-field.field.node.page.body.yml create mode 100644 .vortex/cli/tests/Fixtures/handler_process/custom_modules_none/recipes/page/config/-field.storage.node.body.yml create mode 100644 .vortex/cli/tests/Fixtures/handler_process/custom_modules_none/recipes/page/config/-node.type.page.yml create mode 100644 .vortex/cli/tests/Fixtures/handler_process/tools_no_dclint/-.dclintrc create mode 100644 .vortex/cli/tests/Fixtures/handler_process/tools_no_dclint/.github/workflows/build-test-deploy.yml create mode 100644 .vortex/cli/tests/Fixtures/handler_process/tools_no_dclint_circleci/-.dclintrc create mode 100644 .vortex/cli/tests/Fixtures/handler_process/tools_no_dclint_circleci/.circleci/config.yml create mode 100755 .vortex/cli/tests/Fixtures/handler_process/tools_no_dclint_circleci/.circleci/post-coverage-comment.sh create mode 100644 .vortex/cli/tests/Fixtures/handler_process/tools_no_dclint_circleci/.dockerignore create mode 100644 .vortex/cli/tests/Fixtures/handler_process/tools_no_dclint_circleci/.github/workflows/-build-test-deploy.yml create mode 100644 .vortex/cli/tests/Fixtures/handler_process/tools_no_dclint_circleci/README.md create mode 100644 .vortex/cli/tests/Fixtures/handler_process/tools_no_dclint_circleci/docs/ci.md create mode 100644 .vortex/cli/tests/Fixtures/handler_process/tools_no_dclint_circleci/tests/phpunit/CircleCiConfigTest.php create mode 100644 .vortex/cli/tests/Fixtures/handler_process/tools_no_dclint_circleci/tests/phpunit/Drupal/EnvironmentSettingsTest.php create mode 100644 .vortex/cli/tests/Fixtures/handler_process/tools_no_docker_linters/-.dclintrc create mode 100644 .vortex/cli/tests/Fixtures/handler_process/tools_no_docker_linters/.github/workflows/build-test-deploy.yml create mode 100644 .vortex/cli/tests/Fixtures/handler_process/tools_no_docker_linters_circleci/-.dclintrc create mode 100644 .vortex/cli/tests/Fixtures/handler_process/tools_no_docker_linters_circleci/.circleci/config.yml create mode 100755 .vortex/cli/tests/Fixtures/handler_process/tools_no_docker_linters_circleci/.circleci/post-coverage-comment.sh create mode 100644 .vortex/cli/tests/Fixtures/handler_process/tools_no_docker_linters_circleci/.dockerignore create mode 100644 .vortex/cli/tests/Fixtures/handler_process/tools_no_docker_linters_circleci/.github/workflows/-build-test-deploy.yml create mode 100644 .vortex/cli/tests/Fixtures/handler_process/tools_no_docker_linters_circleci/README.md create mode 100644 .vortex/cli/tests/Fixtures/handler_process/tools_no_docker_linters_circleci/docs/ci.md create mode 100644 .vortex/cli/tests/Fixtures/handler_process/tools_no_docker_linters_circleci/tests/phpunit/CircleCiConfigTest.php create mode 100644 .vortex/cli/tests/Fixtures/handler_process/tools_no_docker_linters_circleci/tests/phpunit/Drupal/EnvironmentSettingsTest.php create mode 100644 .vortex/cli/tests/Fixtures/handler_process/tools_no_hadolint/.github/workflows/build-test-deploy.yml create mode 100644 .vortex/cli/tests/Fixtures/handler_process/tools_no_hadolint_circleci/.circleci/config.yml create mode 100755 .vortex/cli/tests/Fixtures/handler_process/tools_no_hadolint_circleci/.circleci/post-coverage-comment.sh create mode 100644 .vortex/cli/tests/Fixtures/handler_process/tools_no_hadolint_circleci/.dockerignore create mode 100644 .vortex/cli/tests/Fixtures/handler_process/tools_no_hadolint_circleci/.github/workflows/-build-test-deploy.yml create mode 100644 .vortex/cli/tests/Fixtures/handler_process/tools_no_hadolint_circleci/README.md create mode 100644 .vortex/cli/tests/Fixtures/handler_process/tools_no_hadolint_circleci/docs/ci.md create mode 100644 .vortex/cli/tests/Fixtures/handler_process/tools_no_hadolint_circleci/tests/phpunit/CircleCiConfigTest.php create mode 100644 .vortex/cli/tests/Fixtures/handler_process/tools_no_hadolint_circleci/tests/phpunit/Drupal/EnvironmentSettingsTest.php create mode 100644 .vortex/cli/tests/Fixtures/handler_process/tools_none/-.dclintrc diff --git a/.vortex/cli/tests/Fixtures/handler_process/_baseline/.docker/clamav.dockerfile b/.vortex/cli/tests/Fixtures/handler_process/_baseline/.docker/clamav.dockerfile index 0e2627622..eaabdac03 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/_baseline/.docker/clamav.dockerfile +++ b/.vortex/cli/tests/Fixtures/handler_process/_baseline/.docker/clamav.dockerfile @@ -5,8 +5,6 @@ # Allow running ClamAV in rootless mode. # @see https://github.com/Cisco-Talos/clamav/issues/478 # -# hadolint global ignore=DL3008,DL3018 -# # @see https://hub.docker.com/r/uselagoon/commons/tags # @see https://github.com/uselagoon/lagoon-images/tree/main/images/commons @@ -17,6 +15,7 @@ FROM clamav/clamav-debian:__VERSION__ COPY --from=commons /lagoon /lagoon COPY --from=commons /bin/fix-permissions /bin/ep /bin/docker-sleep /bin/wait-for /bin/ +# hadolint ignore=DL3008 # the package set tracks the pinned base image RUN apt-get update -qq && \ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends tzdata && \ apt-get clean && rm -rf /var/lib/apt/lists/* @@ -30,10 +29,12 @@ RUN cat /tmp/clamav.conf >> /etc/clamav/clamd.conf && \ sed -i "s/^UpdateLogFile /# UpdateLogFile /g" /etc/clamav/freshclam.conf && \ sed -i "s/^#LogSyslog /LogSyslog /g" /etc/clamav/freshclam.conf +# hadolint ignore=DL3066 # named account provided by the base image USER root RUN fix-permissions /var/lib/clamav +# hadolint ignore=DL3066 # named account provided by the base image USER clamav ENTRYPOINT [ "/init-unprivileged" ] diff --git a/.vortex/cli/tests/Fixtures/handler_process/_baseline/.docker/cli.dockerfile b/.vortex/cli/tests/Fixtures/handler_process/_baseline/.docker/cli.dockerfile index 7181ff576..94495b2b4 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/_baseline/.docker/cli.dockerfile +++ b/.vortex/cli/tests/Fixtures/handler_process/_baseline/.docker/cli.dockerfile @@ -3,11 +3,7 @@ # # All CLI operations performed in this container. # -# The `PACKAGE_TOKEN` argument below is flagged by name by the -# `SecretsUsedInArgOrEnv` build check, but the token is passed as a build -# secret and never written to an image layer, so that check is skipped. -# -# hadolint global ignore=DL3018,SC2174 +# The check skipped above is BuildKit's twin of DL3064, ignored inline below. # # @see https://hub.docker.com/r/uselagoon/php-8.4-cli-drupal/tags # @see https://github.com/uselagoon/lagoon-images/tree/main/images/php-cli-drupal @@ -26,12 +22,15 @@ ENV WEBROOT=${WEBROOT} # Token is used to access private repositories. Not exposed as an environment # variable within an image to avoid baking it into the image. +# hadolint ignore=DL3064 # empty here, the value comes from a build secret ARG PACKAGE_TOKEN="" ARG DRUPAL_PUBLIC_FILES="sites/default/files" ENV DRUPAL_PUBLIC_FILES=${DRUPAL_PUBLIC_FILES} +# hadolint ignore=DL3064 # a path, not a secret ARG DRUPAL_PRIVATE_FILES="sites/default/files/private" +# hadolint ignore=DL3064 # a path, not a secret ENV DRUPAL_PRIVATE_FILES=${DRUPAL_PRIVATE_FILES} ARG DRUPAL_TEMPORARY_FILES="${TMP:-/tmp}" @@ -61,6 +60,7 @@ ENV PHP_INI_SCAN_DIR="${PHP_INI_SCAN_DIR}:/app/drush/php-ini" # earlier in the build process (near the top of this file). # Add more tools. +# hadolint ignore=DL3018 # the package set tracks the pinned base image RUN apk add --no-cache ncurses pv tzdata autoconf g++ make && \ pecl install pcov && \ docker-php-ext-enable pcov && \ @@ -82,7 +82,6 @@ COPY composer.json composer.* patches.lock.* .env* auth* /app/ # Install PHP dependencies without development packages to avoid exposing # potential security vulnerabilities in the production environment. -# hadolint ignore=SC2155 RUN --mount=type=secret,id=package_token \ token=$(if [ -s /run/secrets/package_token ]; then cat /run/secrets/package_token; else echo "${PACKAGE_TOKEN}"; fi) && \ if [ -n "${token}" ]; then export COMPOSER_AUTH="{\"github-oauth\": {\"github.com\": \"${token}\"}}"; fi && \ @@ -93,6 +92,7 @@ RUN --mount=type=secret,id=package_token \ COPY . /app # Create file directories and set correct permissions. +# hadolint ignore=SC2174 # only the leaf directory needs the mode RUN mkdir -p -m 2775 "/app/${WEBROOT}/${DRUPAL_PUBLIC_FILES}" "/app/${WEBROOT}/${DRUPAL_PRIVATE_FILES}" "${DRUPAL_TEMPORARY_FILES}" RUN if [ "${VORTEX_FRONTEND_BUILD_SKIP}" != "1" ]; then \ diff --git a/.vortex/cli/tests/Fixtures/handler_process/_baseline/.docker/database.dockerfile b/.vortex/cli/tests/Fixtures/handler_process/_baseline/.docker/database.dockerfile index b21f8f9be..74b4be063 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/_baseline/.docker/database.dockerfile +++ b/.vortex/cli/tests/Fixtures/handler_process/_baseline/.docker/database.dockerfile @@ -6,15 +6,17 @@ # The ARG value will be updated with a value passed from docker-compose.yml ARG IMAGE=uselagoon/mysql-8.4:__VERSION__ -# hadolint ignore=DL3006 FROM ${IMAGE} +# hadolint ignore=DL3066 # named account provided by the base image USER root COPY ./.docker/config/database/my.cnf /etc/my.cnf.d/server.cnf RUN fix-permissions /etc/my.cnf.d/ +# hadolint ignore=DL3064 # local development credentials only ENV MYSQL_DATABASE=drupal \ MYSQL_USER=drupal \ MYSQL_PASSWORD=drupal +# hadolint ignore=DL3066 # named account provided by the base image USER mysql diff --git a/.vortex/cli/tests/Fixtures/handler_process/_baseline/.docker/nginx-drupal.dockerfile b/.vortex/cli/tests/Fixtures/handler_process/_baseline/.docker/nginx-drupal.dockerfile index 233199327..f9baeb0d2 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/_baseline/.docker/nginx-drupal.dockerfile +++ b/.vortex/cli/tests/Fixtures/handler_process/_baseline/.docker/nginx-drupal.dockerfile @@ -2,13 +2,10 @@ # # All web requests are sent to this container. # -# hadolint global ignore=DL3018 -# # @see https://hub.docker.com/r/uselagoon/nginx-drupal/tags # @see https://github.com/uselagoon/lagoon-images/tree/main/images/nginx-drupal ARG CLI_IMAGE -# hadolint ignore=DL3006 FROM ${CLI_IMAGE:-cli} AS cli FROM uselagoon/nginx-drupal:__VERSION__ @@ -17,6 +14,7 @@ FROM uselagoon/nginx-drupal:__VERSION__ ARG WEBROOT=web ENV WEBROOT=${WEBROOT} +# hadolint ignore=DL3018 # the package set tracks the pinned base image RUN apk add --no-cache tzdata COPY ./.docker/config/nginx/redirects-map.conf /etc/nginx/redirects-map.conf diff --git a/.vortex/cli/tests/Fixtures/handler_process/_baseline/.docker/php.dockerfile b/.vortex/cli/tests/Fixtures/handler_process/_baseline/.docker/php.dockerfile index ac2e827db..5808dafd4 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/_baseline/.docker/php.dockerfile +++ b/.vortex/cli/tests/Fixtures/handler_process/_baseline/.docker/php.dockerfile @@ -3,17 +3,15 @@ # All web requests are sent from Nginx to this container. # This container would be scaled up/down in production. # -# hadolint global ignore=DL3018 -# # @see https://hub.docker.com/r/uselagoon/php-8.4-fpm/tags # @see https://github.com/uselagoon/lagoon-images/tree/main/images/php-fpm ARG CLI_IMAGE -# hadolint ignore=DL3006 FROM ${CLI_IMAGE:-cli} AS cli FROM uselagoon/php-8.4-fpm:__VERSION__ +# hadolint ignore=DL3018 # the package set tracks the pinned base image RUN apk add --no-cache tzdata COPY --from=cli /app /app diff --git a/.vortex/cli/tests/Fixtures/handler_process/_baseline/.docker/solr.dockerfile b/.vortex/cli/tests/Fixtures/handler_process/_baseline/.docker/solr.dockerfile index cadf8d4c2..0ebae6b90 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/_baseline/.docker/solr.dockerfile +++ b/.vortex/cli/tests/Fixtures/handler_process/_baseline/.docker/solr.dockerfile @@ -1,7 +1,5 @@ # Solr container. # -# hadolint global ignore=DL3018 -# # @see https://hub.docker.com/r/uselagoon/solr-9-drupal/tags # @see https://github.com/uselagoon/lagoon-images/blob/main/images/solr-drupal/9.Dockerfile @@ -16,6 +14,7 @@ FROM uselagoon/solr-9-drupal:__VERSION__ # whenever this image tag is bumped. COPY .docker/config/solr/config-set /solr-conf/conf/ +# hadolint ignore=DL3066 # named account provided by the base image USER root # Apply custom modifications for Lagoon environment compatibility. @@ -23,6 +22,7 @@ RUN sed -i -e "s#\${solr.data.dir:}#/var/solr/\${solr.core.nam sed -i -e "s#solr.lock.type:native#solr.lock.type:none#g" /solr-conf/conf/solrconfig.xml && \ sed -i -e "s#solr.autoSoftCommit.MaxTime=5000#solr.autoSoftCommit.MaxTime=-1#g" /solr-conf/conf/solrcore.properties +# hadolint ignore=DL3066 # named account provided by the base image USER solr # solr-precreate is provided by the base Solr container image. diff --git a/.vortex/cli/tests/Fixtures/handler_process/_baseline/.github/workflows/build-test-deploy.yml b/.vortex/cli/tests/Fixtures/handler_process/_baseline/.github/workflows/build-test-deploy.yml index 4b7f9db05..6afb7f2ff 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/_baseline/.github/workflows/build-test-deploy.yml +++ b/.vortex/cli/tests/Fixtures/handler_process/_baseline/.github/workflows/build-test-deploy.yml @@ -109,7 +109,7 @@ jobs: - name: Lint Dockerfiles with Hadolint run: | find .docker \( -name 'Dockerfile' -o -name '*.dockerfile' \) | while read -r file; do - echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint + echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint:__VERSION__ done continue-on-error: ${{ vars.VORTEX_CI_HADOLINT_IGNORE_FAILURE == '1' }} diff --git a/.vortex/cli/tests/Fixtures/handler_process/_baseline/renovate.json b/.vortex/cli/tests/Fixtures/handler_process/_baseline/renovate.json index b1a7b1b34..b68037407 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/_baseline/renovate.json +++ b/.vortex/cli/tests/Fixtures/handler_process/_baseline/renovate.json @@ -170,7 +170,9 @@ "groupName": "Container images - All - Major, minor and patch", "groupSlug": "container-images-all-major-minor-patch", "matchFileNames": [ - ".docker/**" + ".docker/**", + ".circleci/**", + ".github/workflows/**" ], "schedule": [ "before 3am" @@ -205,6 +207,18 @@ ], "datasourceTemplate": "docker", "versioningTemplate": "docker" + }, + { + "customType": "regex", + "managerFilePatterns": [ + "/^\\.circleci/config\\.yml$/", + "/^\\.github/workflows/.+\\.yml$/" + ], + "matchStrings": [ + "docker run[^\\n]*?\\s(?[a-z0-9][a-z0-9._\\-]*(?:/[a-z0-9._\\-]+)+):(?v?[0-9][\\w.\\-]*)" + ], + "datasourceTemplate": "docker", + "versioningTemplate": "docker" } ] } diff --git a/.vortex/cli/tests/Fixtures/handler_process/_baseline/scripts/vortex-tooling.sh b/.vortex/cli/tests/Fixtures/handler_process/_baseline/scripts/vortex-tooling.sh index 63359f7fc..9784720f5 100755 --- a/.vortex/cli/tests/Fixtures/handler_process/_baseline/scripts/vortex-tooling.sh +++ b/.vortex/cli/tests/Fixtures/handler_process/_baseline/scripts/vortex-tooling.sh @@ -20,6 +20,40 @@ set -eu [ "${VORTEX_DEBUG-}" = "1" ] && set -x +# ------------------------------------------------------------------------------ + +# @formatter:off +info() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[36m[INFO] %s\033[0m\n" "${1}" || printf "[INFO] %s\n" "${1}"; } +note() { printf " %s\n" "${1}"; } +task() { _TASK_START=$(date +%s); [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[34m[TASK] %s\033[0m\n" "${1}" || printf "[TASK] %s\n" "${1}"; } +pass() { _d=""; [ -n "${_TASK_START:-}" ] && _d=" ($(($(date +%s) - _TASK_START))s)" && unset _TASK_START; [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[32m[ OK ] %s%s\033[0m\n" "${1}" "${_d}" || printf "[ OK ] %s%s\n" "${1}" "${_d}"; } +fail() { [ "${TERM:-}" != "dumb" ] && tput colors >/dev/null 2>&1 && printf "\033[31m[FAIL] %s\033[0m\n" "${1}" || printf "[FAIL] %s\n" "${1}"; } +# @formatter:on + +# Run Composer without exposing its progress output, which is an internal detail +# of this bootstrap rather than something that was asked for. The captured +# output is replayed on stderr when the command fails, so failures remain +# diagnosable. Debug mode streams the output as it happens. +composer_run() { + if [ "${VORTEX_DEBUG-}" = "1" ]; then + composer "$@" + return + fi + + local output status=0 + output=$(composer "$@" 2>&1) || status=$? + + if [ "${status}" -ne 0 ]; then + fail "Composer command failed." + + if [ -n "${output}" ]; then + printf "%s\n" "${output}" >&2 + fi + + return "${status}" + fi +} + # Already installed - nothing to do. If the package is present but its # 'vendor/bin/' proxies are missing (a workspace bootstrapped before the # binaries were surfaced), remove it so the install below re-creates both. @@ -30,6 +64,8 @@ if [ -d ./vendor/drevops/vortex-tooling ]; then rm -rf ./vendor/drevops/vortex-tooling fi +info "Started Vortex tooling installation." + mkdir -p vendor-temp vendor/drevops # Always remove the throwaway project on exit - including when an intermediate @@ -54,21 +90,21 @@ echo "{\"require\":{\"drevops/vortex-tooling\":\"${version}\"}}" >vendor-temp/co # Carry over inline patches declared for our package, if any. patches=$(composer config extra.patches.drevops/vortex-tooling --json 2>/dev/null) || patches= if [ -n "${patches}" ] && [ "${patches}" != "[]" ] && [ "${patches}" != "{}" ]; then - composer --working-dir=vendor-temp config extra.patches.drevops/vortex-tooling --json "${patches}" + composer_run --working-dir=vendor-temp config extra.patches.drevops/vortex-tooling --json "${patches}" fi # Carry over the patches-file pointer, if defined. Prefix with '..' so the # path resolves from inside 'vendor-temp/' back to the project root. patches_file=$(composer config extra.patches-file 2>/dev/null) || patches_file= if [ -n "${patches_file}" ]; then - composer --working-dir=vendor-temp config extra.patches-file "../${patches_file}" + composer_run --working-dir=vendor-temp config extra.patches-file "../${patches_file}" fi # When any patches were registered, pull in the composer-patches plugin and # allow it to run during install. if [ -n "${patches}" ] || [ -n "${patches_file}" ]; then - composer --working-dir=vendor-temp require --no-update cweagans/composer-patches:^2 - composer --working-dir=vendor-temp config allow-plugins.cweagans/composer-patches true + composer_run --working-dir=vendor-temp require --no-update cweagans/composer-patches:^2 + composer_run --working-dir=vendor-temp config allow-plugins.cweagans/composer-patches true # Inline 'extra.patches' paths (and paths inside a 'patches-file') are # relative to the project root. Copy the project 'patches/' directory into # the throwaway project so those paths resolve from inside 'vendor-temp/'. @@ -77,7 +113,7 @@ if [ -n "${patches}" ] || [ -n "${patches_file}" ]; then fi fi -composer --working-dir=vendor-temp install --no-dev --no-interaction +composer_run --working-dir=vendor-temp install --no-dev --no-interaction mv vendor-temp/vendor/drevops/vortex-tooling vendor/drevops/ @@ -92,3 +128,5 @@ if [ -d vendor-temp/vendor/bin ]; then [ -e "${bin}" ] && mv "${bin}" vendor/bin/ done fi + +pass "Finished Vortex tooling installation." diff --git a/.vortex/cli/tests/Fixtures/handler_process/ciprovider_circleci/.circleci/config.yml b/.vortex/cli/tests/Fixtures/handler_process/ciprovider_circleci/.circleci/config.yml index 52a73ee14..1b128e98e 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/ciprovider_circleci/.circleci/config.yml +++ b/.vortex/cli/tests/Fixtures/handler_process/ciprovider_circleci/.circleci/config.yml @@ -122,7 +122,7 @@ jobs: name: Lint Dockerfiles with Hadolint command: | for file in $(find .docker \( -name 'Dockerfile' -o -name '*.dockerfile' \)); do - echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] + echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint:__VERSION__ || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] done - run: diff --git a/.vortex/cli/tests/Fixtures/handler_process/code_coverage_provider_codecov_circleci/.circleci/config.yml b/.vortex/cli/tests/Fixtures/handler_process/code_coverage_provider_codecov_circleci/.circleci/config.yml index 4f6809320..5baffe58a 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/code_coverage_provider_codecov_circleci/.circleci/config.yml +++ b/.vortex/cli/tests/Fixtures/handler_process/code_coverage_provider_codecov_circleci/.circleci/config.yml @@ -122,7 +122,7 @@ jobs: name: Lint Dockerfiles with Hadolint command: | for file in $(find .docker \( -name 'Dockerfile' -o -name '*.dockerfile' \)); do - echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] + echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint:__VERSION__ || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] done - run: diff --git a/.vortex/cli/tests/Fixtures/handler_process/code_provider_other/renovate.json b/.vortex/cli/tests/Fixtures/handler_process/code_provider_other/renovate.json index 49a7bdcf3..185c0b8b9 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/code_provider_other/renovate.json +++ b/.vortex/cli/tests/Fixtures/handler_process/code_provider_other/renovate.json @@ -6,7 +6,7 @@ "custom.regex" ], "packageRules": [ -@@ -180,18 +179,6 @@ +@@ -182,18 +181,6 @@ "docker-compose", "custom.regex" ] diff --git a/.vortex/cli/tests/Fixtures/handler_process/custom_modules_none/.gitignore b/.vortex/cli/tests/Fixtures/handler_process/custom_modules_none/.gitignore new file mode 100644 index 000000000..4c478b1b2 --- /dev/null +++ b/.vortex/cli/tests/Fixtures/handler_process/custom_modules_none/.gitignore @@ -0,0 +1,8 @@ +@@ -32,7 +32,6 @@ + # Composer installs contrib recipes into 'recipes/', next to custom recipes, + # so recipes stay an allow list: un-ignore custom recipes explicitly. + recipes/* +-!recipes/page + + # Ignore all Claude files by default. Custom files should be added explicitly. + .claude/* diff --git a/.vortex/cli/tests/Fixtures/handler_process/custom_modules_none/recipes/page/-recipe.yml b/.vortex/cli/tests/Fixtures/handler_process/custom_modules_none/recipes/page/-recipe.yml new file mode 100644 index 000000000..e69de29bb diff --git a/.vortex/cli/tests/Fixtures/handler_process/custom_modules_none/recipes/page/config/-core.entity_form_display.node.page.default.yml b/.vortex/cli/tests/Fixtures/handler_process/custom_modules_none/recipes/page/config/-core.entity_form_display.node.page.default.yml new file mode 100644 index 000000000..e69de29bb diff --git a/.vortex/cli/tests/Fixtures/handler_process/custom_modules_none/recipes/page/config/-core.entity_view_display.node.page.default.yml b/.vortex/cli/tests/Fixtures/handler_process/custom_modules_none/recipes/page/config/-core.entity_view_display.node.page.default.yml new file mode 100644 index 000000000..e69de29bb diff --git a/.vortex/cli/tests/Fixtures/handler_process/custom_modules_none/recipes/page/config/-core.entity_view_display.node.page.teaser.yml b/.vortex/cli/tests/Fixtures/handler_process/custom_modules_none/recipes/page/config/-core.entity_view_display.node.page.teaser.yml new file mode 100644 index 000000000..e69de29bb diff --git a/.vortex/cli/tests/Fixtures/handler_process/custom_modules_none/recipes/page/config/-field.field.node.page.body.yml b/.vortex/cli/tests/Fixtures/handler_process/custom_modules_none/recipes/page/config/-field.field.node.page.body.yml new file mode 100644 index 000000000..e69de29bb diff --git a/.vortex/cli/tests/Fixtures/handler_process/custom_modules_none/recipes/page/config/-field.storage.node.body.yml b/.vortex/cli/tests/Fixtures/handler_process/custom_modules_none/recipes/page/config/-field.storage.node.body.yml new file mode 100644 index 000000000..e69de29bb diff --git a/.vortex/cli/tests/Fixtures/handler_process/custom_modules_none/recipes/page/config/-node.type.page.yml b/.vortex/cli/tests/Fixtures/handler_process/custom_modules_none/recipes/page/config/-node.type.page.yml new file mode 100644 index 000000000..e69de29bb diff --git a/.vortex/cli/tests/Fixtures/handler_process/deploy_types_all_circleci/.circleci/config.yml b/.vortex/cli/tests/Fixtures/handler_process/deploy_types_all_circleci/.circleci/config.yml index 254550eb6..4b304215e 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/deploy_types_all_circleci/.circleci/config.yml +++ b/.vortex/cli/tests/Fixtures/handler_process/deploy_types_all_circleci/.circleci/config.yml @@ -128,7 +128,7 @@ jobs: name: Lint Dockerfiles with Hadolint command: | for file in $(find .docker \( -name 'Dockerfile' -o -name '*.dockerfile' \)); do - echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] + echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint:__VERSION__ || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] done - run: diff --git a/.vortex/cli/tests/Fixtures/handler_process/deploy_types_none_circleci/.circleci/config.yml b/.vortex/cli/tests/Fixtures/handler_process/deploy_types_none_circleci/.circleci/config.yml index d634f83ac..cfb784610 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/deploy_types_none_circleci/.circleci/config.yml +++ b/.vortex/cli/tests/Fixtures/handler_process/deploy_types_none_circleci/.circleci/config.yml @@ -117,7 +117,7 @@ jobs: name: Lint Dockerfiles with Hadolint command: | for file in $(find .docker \( -name 'Dockerfile' -o -name '*.dockerfile' \)); do - echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] + echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint:__VERSION__ || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] done - run: diff --git a/.vortex/cli/tests/Fixtures/handler_process/deps_updates_provider_ci_circleci/.circleci/config.yml b/.vortex/cli/tests/Fixtures/handler_process/deps_updates_provider_ci_circleci/.circleci/config.yml index 52a73ee14..1b128e98e 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/deps_updates_provider_ci_circleci/.circleci/config.yml +++ b/.vortex/cli/tests/Fixtures/handler_process/deps_updates_provider_ci_circleci/.circleci/config.yml @@ -122,7 +122,7 @@ jobs: name: Lint Dockerfiles with Hadolint command: | for file in $(find .docker \( -name 'Dockerfile' -o -name '*.dockerfile' \)); do - echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] + echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint:__VERSION__ || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] done - run: diff --git a/.vortex/cli/tests/Fixtures/handler_process/hosting_acquia/.docker/cli.dockerfile b/.vortex/cli/tests/Fixtures/handler_process/hosting_acquia/.docker/cli.dockerfile index a39f0f434..7d1d14733 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/hosting_acquia/.docker/cli.dockerfile +++ b/.vortex/cli/tests/Fixtures/handler_process/hosting_acquia/.docker/cli.dockerfile @@ -1,4 +1,4 @@ -@@ -21,7 +21,7 @@ +@@ -17,7 +17,7 @@ ARG LAGOON_PR_HEAD_SHA="" ENV LAGOON_PR_HEAD_SHA=${LAGOON_PR_HEAD_SHA} diff --git a/.vortex/cli/tests/Fixtures/handler_process/hosting_acquia/.docker/nginx-drupal.dockerfile b/.vortex/cli/tests/Fixtures/handler_process/hosting_acquia/.docker/nginx-drupal.dockerfile index 7d6c276fd..5bba52b44 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/hosting_acquia/.docker/nginx-drupal.dockerfile +++ b/.vortex/cli/tests/Fixtures/handler_process/hosting_acquia/.docker/nginx-drupal.dockerfile @@ -1,4 +1,4 @@ -@@ -14,7 +14,7 @@ +@@ -11,7 +11,7 @@ FROM uselagoon/nginx-drupal:__VERSION__ # Webroot is used for Nginx web root configuration. @@ -6,4 +6,4 @@ +ARG WEBROOT=docroot ENV WEBROOT=${WEBROOT} - RUN apk add --no-cache tzdata + # hadolint ignore=DL3018 # the package set tracks the pinned base image diff --git a/.vortex/cli/tests/Fixtures/handler_process/hosting_project_name___acquia/.docker/cli.dockerfile b/.vortex/cli/tests/Fixtures/handler_process/hosting_project_name___acquia/.docker/cli.dockerfile index a39f0f434..7d1d14733 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/hosting_project_name___acquia/.docker/cli.dockerfile +++ b/.vortex/cli/tests/Fixtures/handler_process/hosting_project_name___acquia/.docker/cli.dockerfile @@ -1,4 +1,4 @@ -@@ -21,7 +21,7 @@ +@@ -17,7 +17,7 @@ ARG LAGOON_PR_HEAD_SHA="" ENV LAGOON_PR_HEAD_SHA=${LAGOON_PR_HEAD_SHA} diff --git a/.vortex/cli/tests/Fixtures/handler_process/hosting_project_name___acquia/.docker/nginx-drupal.dockerfile b/.vortex/cli/tests/Fixtures/handler_process/hosting_project_name___acquia/.docker/nginx-drupal.dockerfile index 7d6c276fd..5bba52b44 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/hosting_project_name___acquia/.docker/nginx-drupal.dockerfile +++ b/.vortex/cli/tests/Fixtures/handler_process/hosting_project_name___acquia/.docker/nginx-drupal.dockerfile @@ -1,4 +1,4 @@ -@@ -14,7 +14,7 @@ +@@ -11,7 +11,7 @@ FROM uselagoon/nginx-drupal:__VERSION__ # Webroot is used for Nginx web root configuration. @@ -6,4 +6,4 @@ +ARG WEBROOT=docroot ENV WEBROOT=${WEBROOT} - RUN apk add --no-cache tzdata + # hadolint ignore=DL3018 # the package set tracks the pinned base image diff --git a/.vortex/cli/tests/Fixtures/handler_process/migration_disabled_circleci/.circleci/config.yml b/.vortex/cli/tests/Fixtures/handler_process/migration_disabled_circleci/.circleci/config.yml index 52a73ee14..1b128e98e 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/migration_disabled_circleci/.circleci/config.yml +++ b/.vortex/cli/tests/Fixtures/handler_process/migration_disabled_circleci/.circleci/config.yml @@ -122,7 +122,7 @@ jobs: name: Lint Dockerfiles with Hadolint command: | for file in $(find .docker \( -name 'Dockerfile' -o -name '*.dockerfile' \)); do - echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] + echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint:__VERSION__ || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] done - run: diff --git a/.vortex/cli/tests/Fixtures/handler_process/migration_enabled_circleci/.circleci/config.yml b/.vortex/cli/tests/Fixtures/handler_process/migration_enabled_circleci/.circleci/config.yml index 784715221..456bded7f 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/migration_enabled_circleci/.circleci/config.yml +++ b/.vortex/cli/tests/Fixtures/handler_process/migration_enabled_circleci/.circleci/config.yml @@ -122,7 +122,7 @@ jobs: name: Lint Dockerfiles with Hadolint command: | for file in $(find .docker \( -name 'Dockerfile' -o -name '*.dockerfile' \)); do - echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] + echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint:__VERSION__ || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] done - run: diff --git a/.vortex/cli/tests/Fixtures/handler_process/names/.docker/cli.dockerfile b/.vortex/cli/tests/Fixtures/handler_process/names/.docker/cli.dockerfile index 20c5a59d5..e94e3fe1c 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/names/.docker/cli.dockerfile +++ b/.vortex/cli/tests/Fixtures/handler_process/names/.docker/cli.dockerfile @@ -1,4 +1,4 @@ -@@ -37,7 +37,7 @@ +@@ -36,7 +36,7 @@ ARG DRUPAL_TEMPORARY_FILES="${TMP:-/tmp}" ENV DRUPAL_TEMPORARY_FILES=${DRUPAL_TEMPORARY_FILES} diff --git a/.vortex/cli/tests/Fixtures/handler_process/provision_profile_circleci/.circleci/config.yml b/.vortex/cli/tests/Fixtures/handler_process/provision_profile_circleci/.circleci/config.yml index 2be693cbb..3900a7e59 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/provision_profile_circleci/.circleci/config.yml +++ b/.vortex/cli/tests/Fixtures/handler_process/provision_profile_circleci/.circleci/config.yml @@ -114,7 +114,7 @@ jobs: name: Lint Dockerfiles with Hadolint command: | for file in $(find .docker \( -name 'Dockerfile' -o -name '*.dockerfile' \)); do - echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] + echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint:__VERSION__ || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] done - run: diff --git a/.vortex/cli/tests/Fixtures/handler_process/theme_claro/.docker/cli.dockerfile b/.vortex/cli/tests/Fixtures/handler_process/theme_claro/.docker/cli.dockerfile index c6e5e90e0..4abfebbec 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/theme_claro/.docker/cli.dockerfile +++ b/.vortex/cli/tests/Fixtures/handler_process/theme_claro/.docker/cli.dockerfile @@ -1,4 +1,4 @@ -@@ -37,7 +37,7 @@ +@@ -36,7 +36,7 @@ ARG DRUPAL_TEMPORARY_FILES="${TMP:-/tmp}" ENV DRUPAL_TEMPORARY_FILES=${DRUPAL_TEMPORARY_FILES} @@ -8,8 +8,8 @@ ARG VORTEX_FRONTEND_BUILD_SKIP="0" @@ -94,12 +94,5 @@ - # Create file directories and set correct permissions. + # hadolint ignore=SC2174 # only the leaf directory needs the mode RUN mkdir -p -m 2775 "/app/${WEBROOT}/${DRUPAL_PUBLIC_FILES}" "/app/${WEBROOT}/${DRUPAL_PRIVATE_FILES}" "${DRUPAL_TEMPORARY_FILES}" - -RUN if [ "${VORTEX_FRONTEND_BUILD_SKIP}" != "1" ]; then \ diff --git a/.vortex/cli/tests/Fixtures/handler_process/theme_custom/.docker/cli.dockerfile b/.vortex/cli/tests/Fixtures/handler_process/theme_custom/.docker/cli.dockerfile index cf9161fb9..2611497a4 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/theme_custom/.docker/cli.dockerfile +++ b/.vortex/cli/tests/Fixtures/handler_process/theme_custom/.docker/cli.dockerfile @@ -1,4 +1,4 @@ -@@ -37,7 +37,7 @@ +@@ -36,7 +36,7 @@ ARG DRUPAL_TEMPORARY_FILES="${TMP:-/tmp}" ENV DRUPAL_TEMPORARY_FILES=${DRUPAL_TEMPORARY_FILES} diff --git a/.vortex/cli/tests/Fixtures/handler_process/theme_olivero/.docker/cli.dockerfile b/.vortex/cli/tests/Fixtures/handler_process/theme_olivero/.docker/cli.dockerfile index c6e5e90e0..4abfebbec 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/theme_olivero/.docker/cli.dockerfile +++ b/.vortex/cli/tests/Fixtures/handler_process/theme_olivero/.docker/cli.dockerfile @@ -1,4 +1,4 @@ -@@ -37,7 +37,7 @@ +@@ -36,7 +36,7 @@ ARG DRUPAL_TEMPORARY_FILES="${TMP:-/tmp}" ENV DRUPAL_TEMPORARY_FILES=${DRUPAL_TEMPORARY_FILES} @@ -8,8 +8,8 @@ ARG VORTEX_FRONTEND_BUILD_SKIP="0" @@ -94,12 +94,5 @@ - # Create file directories and set correct permissions. + # hadolint ignore=SC2174 # only the leaf directory needs the mode RUN mkdir -p -m 2775 "/app/${WEBROOT}/${DRUPAL_PUBLIC_FILES}" "/app/${WEBROOT}/${DRUPAL_PRIVATE_FILES}" "${DRUPAL_TEMPORARY_FILES}" - -RUN if [ "${VORTEX_FRONTEND_BUILD_SKIP}" != "1" ]; then \ diff --git a/.vortex/cli/tests/Fixtures/handler_process/theme_stark/.docker/cli.dockerfile b/.vortex/cli/tests/Fixtures/handler_process/theme_stark/.docker/cli.dockerfile index c6e5e90e0..4abfebbec 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/theme_stark/.docker/cli.dockerfile +++ b/.vortex/cli/tests/Fixtures/handler_process/theme_stark/.docker/cli.dockerfile @@ -1,4 +1,4 @@ -@@ -37,7 +37,7 @@ +@@ -36,7 +36,7 @@ ARG DRUPAL_TEMPORARY_FILES="${TMP:-/tmp}" ENV DRUPAL_TEMPORARY_FILES=${DRUPAL_TEMPORARY_FILES} @@ -8,8 +8,8 @@ ARG VORTEX_FRONTEND_BUILD_SKIP="0" @@ -94,12 +94,5 @@ - # Create file directories and set correct permissions. + # hadolint ignore=SC2174 # only the leaf directory needs the mode RUN mkdir -p -m 2775 "/app/${WEBROOT}/${DRUPAL_PUBLIC_FILES}" "/app/${WEBROOT}/${DRUPAL_PRIVATE_FILES}" "${DRUPAL_TEMPORARY_FILES}" - -RUN if [ "${VORTEX_FRONTEND_BUILD_SKIP}" != "1" ]; then \ diff --git a/.vortex/cli/tests/Fixtures/handler_process/timezone_circleci/.circleci/config.yml b/.vortex/cli/tests/Fixtures/handler_process/timezone_circleci/.circleci/config.yml index 52a73ee14..1b128e98e 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/timezone_circleci/.circleci/config.yml +++ b/.vortex/cli/tests/Fixtures/handler_process/timezone_circleci/.circleci/config.yml @@ -122,7 +122,7 @@ jobs: name: Lint Dockerfiles with Hadolint command: | for file in $(find .docker \( -name 'Dockerfile' -o -name '*.dockerfile' \)); do - echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] + echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint:__VERSION__ || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] done - run: diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_groups_no_be_lint_circleci/.circleci/config.yml b/.vortex/cli/tests/Fixtures/handler_process/tools_groups_no_be_lint_circleci/.circleci/config.yml index d9b59211a..4bb378a18 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/tools_groups_no_be_lint_circleci/.circleci/config.yml +++ b/.vortex/cli/tests/Fixtures/handler_process/tools_groups_no_be_lint_circleci/.circleci/config.yml @@ -122,7 +122,7 @@ jobs: name: Lint Dockerfiles with Hadolint command: | for file in $(find .docker \( -name 'Dockerfile' -o -name '*.dockerfile' \)); do - echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] + echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint:__VERSION__ || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] done - run: diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_groups_no_be_tests_circleci/.circleci/config.yml b/.vortex/cli/tests/Fixtures/handler_process/tools_groups_no_be_tests_circleci/.circleci/config.yml index 607078f5f..746a9e3de 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/tools_groups_no_be_tests_circleci/.circleci/config.yml +++ b/.vortex/cli/tests/Fixtures/handler_process/tools_groups_no_be_tests_circleci/.circleci/config.yml @@ -122,7 +122,7 @@ jobs: name: Lint Dockerfiles with Hadolint command: | for file in $(find .docker \( -name 'Dockerfile' -o -name '*.dockerfile' \)); do - echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] + echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint:__VERSION__ || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] done - run: diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_groups_no_fe_lint_circleci/.circleci/config.yml b/.vortex/cli/tests/Fixtures/handler_process/tools_groups_no_fe_lint_circleci/.circleci/config.yml index 13a3bc735..88d2cf996 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/tools_groups_no_fe_lint_circleci/.circleci/config.yml +++ b/.vortex/cli/tests/Fixtures/handler_process/tools_groups_no_fe_lint_circleci/.circleci/config.yml @@ -122,7 +122,7 @@ jobs: name: Lint Dockerfiles with Hadolint command: | for file in $(find .docker \( -name 'Dockerfile' -o -name '*.dockerfile' \)); do - echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] + echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint:__VERSION__ || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] done - run: diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme/.docker/cli.dockerfile b/.vortex/cli/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme/.docker/cli.dockerfile index c6e5e90e0..4abfebbec 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme/.docker/cli.dockerfile +++ b/.vortex/cli/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme/.docker/cli.dockerfile @@ -1,4 +1,4 @@ -@@ -37,7 +37,7 @@ +@@ -36,7 +36,7 @@ ARG DRUPAL_TEMPORARY_FILES="${TMP:-/tmp}" ENV DRUPAL_TEMPORARY_FILES=${DRUPAL_TEMPORARY_FILES} @@ -8,8 +8,8 @@ ARG VORTEX_FRONTEND_BUILD_SKIP="0" @@ -94,12 +94,5 @@ - # Create file directories and set correct permissions. + # hadolint ignore=SC2174 # only the leaf directory needs the mode RUN mkdir -p -m 2775 "/app/${WEBROOT}/${DRUPAL_PUBLIC_FILES}" "/app/${WEBROOT}/${DRUPAL_PRIVATE_FILES}" "${DRUPAL_TEMPORARY_FILES}" - -RUN if [ "${VORTEX_FRONTEND_BUILD_SKIP}" != "1" ]; then \ diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme_circleci/.circleci/config.yml b/.vortex/cli/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme_circleci/.circleci/config.yml index c3a1b5309..f774e2c15 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme_circleci/.circleci/config.yml +++ b/.vortex/cli/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme_circleci/.circleci/config.yml @@ -122,7 +122,7 @@ jobs: name: Lint Dockerfiles with Hadolint command: | for file in $(find .docker \( -name 'Dockerfile' -o -name '*.dockerfile' \)); do - echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] + echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint:__VERSION__ || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] done - run: diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme_circleci/.docker/cli.dockerfile b/.vortex/cli/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme_circleci/.docker/cli.dockerfile index c6e5e90e0..4abfebbec 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme_circleci/.docker/cli.dockerfile +++ b/.vortex/cli/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme_circleci/.docker/cli.dockerfile @@ -1,4 +1,4 @@ -@@ -37,7 +37,7 @@ +@@ -36,7 +36,7 @@ ARG DRUPAL_TEMPORARY_FILES="${TMP:-/tmp}" ENV DRUPAL_TEMPORARY_FILES=${DRUPAL_TEMPORARY_FILES} @@ -8,8 +8,8 @@ ARG VORTEX_FRONTEND_BUILD_SKIP="0" @@ -94,12 +94,5 @@ - # Create file directories and set correct permissions. + # hadolint ignore=SC2174 # only the leaf directory needs the mode RUN mkdir -p -m 2775 "/app/${WEBROOT}/${DRUPAL_PUBLIC_FILES}" "/app/${WEBROOT}/${DRUPAL_PRIVATE_FILES}" "${DRUPAL_TEMPORARY_FILES}" - -RUN if [ "${VORTEX_FRONTEND_BUILD_SKIP}" != "1" ]; then \ diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_no_behat_circleci/.circleci/config.yml b/.vortex/cli/tests/Fixtures/handler_process/tools_no_behat_circleci/.circleci/config.yml index 6588edf20..2ef98b1c8 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/tools_no_behat_circleci/.circleci/config.yml +++ b/.vortex/cli/tests/Fixtures/handler_process/tools_no_behat_circleci/.circleci/config.yml @@ -122,7 +122,7 @@ jobs: name: Lint Dockerfiles with Hadolint command: | for file in $(find .docker \( -name 'Dockerfile' -o -name '*.dockerfile' \)); do - echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] + echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint:__VERSION__ || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] done - run: diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_no_dclint/-.dclintrc b/.vortex/cli/tests/Fixtures/handler_process/tools_no_dclint/-.dclintrc new file mode 100644 index 000000000..e69de29bb diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_no_dclint/.github/workflows/build-test-deploy.yml b/.vortex/cli/tests/Fixtures/handler_process/tools_no_dclint/.github/workflows/build-test-deploy.yml new file mode 100644 index 000000000..9001767a0 --- /dev/null +++ b/.vortex/cli/tests/Fixtures/handler_process/tools_no_dclint/.github/workflows/build-test-deploy.yml @@ -0,0 +1,11 @@ +@@ -113,10 +113,6 @@ + done + continue-on-error: ${{ vars.VORTEX_CI_HADOLINT_IGNORE_FAILURE == '1' }} + +- - name: Lint Docker Compose files with DCLint +- run: docker run --rm -v "${PWD}":/app zavoloklom/dclint:__VERSION__ . +- continue-on-error: ${{ vars.VORTEX_CI_DCLINT_IGNORE_FAILURE == '1' }} +- + - name: Scan for committed secrets with Gitleaks + run: docker run --rm -v "${PWD}":/repo -w /repo ghcr.io/gitleaks/gitleaks:__VERSION__ dir . --no-banner + continue-on-error: ${{ vars.VORTEX_CI_GITLEAKS_IGNORE_FAILURE == '1' }} diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_no_dclint_circleci/-.dclintrc b/.vortex/cli/tests/Fixtures/handler_process/tools_no_dclint_circleci/-.dclintrc new file mode 100644 index 000000000..e69de29bb diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_no_dclint_circleci/.circleci/config.yml b/.vortex/cli/tests/Fixtures/handler_process/tools_no_dclint_circleci/.circleci/config.yml new file mode 100644 index 000000000..6ec9836a0 --- /dev/null +++ b/.vortex/cli/tests/Fixtures/handler_process/tools_no_dclint_circleci/.circleci/config.yml @@ -0,0 +1,532 @@ +# CircleCI configuration file for building, testing, and deploying. +# +# This configuration file uses the "docker" executor to run the Docker stack. +# +# A "runner" container, created from a specified container image, is used to +# checkout source code and run commands defined in this file. Application Docker +# containers defined in `docker-compose.yml` run on a *remote* Docker server +# controlled by CircleCI. +# The "runner" container uses Docker client to control the remote Docker server. +version: '2.1' + +aliases: + + # SSH key fingerprint to deploy code. + # Replace this key fingerprint with your own and remove this comment. + - &deploy_ssh_fingerprint "SHA256:6d+U5QubT0eAWz+4N2wt+WM2qx6o4cvyvQ6xILETJ84" + + # Shared runner container configuration applied to each job. + - &runner_config + working_directory: &working_directory ~/project + environment: + VORTEX_DEPLOY_SSH_FINGERPRINT: *deploy_ssh_fingerprint + docker: + # Run each job on CircleCI's public PHP convenience image. It ships PHP, + # Composer and the Docker CLI (with the Compose plugin), which drive the + # dockerized stack against the remote Docker engine (see setup_remote_docker). + # https://circleci.com/developer/images/image/cimg/php + - image: cimg/php:__VERSION__ + environment: + # Set runner timezone via UI to ensure that executed operations use correct timestamps. + # https://en.wikipedia.org/wiki/List_of_tz_database_time_zones + TZ: UTC + # Set runner terminal capabilities. + TERM: xterm-256color + # Disable strict host key checking for SSH connections. + VORTEX_SSH_DISABLE_STRICT_HOST_KEY_CHECKING: "1" + # Remove all SSH keys from the runner container. + VORTEX_SSH_REMOVE_ALL_KEYS: "1" + # How often to refresh the cache of the DB dump. Refer to `date` command. + VORTEX_CI_DB_CACHE_TIMESTAMP: +%Y%m%d + # Use previous database caches on this branch as a fallback if the above cache + # does not match (for example, the cache is available only from the previous + # day). If "no" is set, the cache will be rebuilt from scratch. + VORTEX_CI_DB_CACHE_FALLBACK: "yes" + # Which branch to use as a source of DB caches. + VORTEX_CI_DB_CACHE_BRANCH: "develop" + # Directory to store test results. + VORTEX_CI_TEST_RESULTS: &test_results /tmp/tests + # Directory to store test artifacts. + VORTEX_CI_ARTIFACTS: &artifacts /tmp/artifacts + # Check only minimal stack requirements. + VORTEX_DOCTOR_CHECK_MINIMAL: 1 + # CI runner resource class. + # https://circleci.com/docs/2.0/configuration-reference/#resource_class + # Change to 'large' for faster builds. + resource_class: medium + + - &step_setup_remote_docker + setup_remote_docker: + # Docker Layer Caching allows to significantly speed up builds by caching + # images built during previous runs. + # https://circleci.com/docs/2.0/docker-layer-caching/ + docker_layer_caching: false + version: default + + - &step_install_docker_tools + run: + name: Install pinned Docker Compose and Buildx CLI plugins + # The convenience image ships older Docker CLI plugins than the stack + # relies on; install the versions that resolve 'service:' build contexts. + command: | + mkdir -p "${HOME}/.docker/cli-plugins" + curl -fsSL -o "${HOME}/.docker/cli-plugins/docker-compose" https://github.com/docker/compose/releases/download/__VERSION__/docker-compose-linux-x86_64 + curl -fsSL -o "${HOME}/.docker/cli-plugins/docker-buildx" https://github.com/docker/buildx/releases/download/__VERSION__/buildx-__VERSION__.linux-amd64 + chmod +x "${HOME}/.docker/cli-plugins/docker-compose" "${HOME}/.docker/cli-plugins/docker-buildx" + docker compose version + docker buildx version + + - &step_process_codebase_for_ci + run: + name: Process codebase to run in CI + command: | + find . -name "docker-compose.yml" -print0 | xargs -0 -I {} sh -c "sed -i -e ''/###/d'' {} && sed -i -e ''s/##//'' {}" + mkdir -p /tmp/workspace/code + + - &load_variables_from_dotenv + run: + name: Load environment variables from .env file + # Load variables from .env file, respecting existing values, and make them available for the next steps. + command: t=$(mktemp) && export -p >"${t}" && set -a && . ./.env && set +a && . "${t}" && export -p >> "$BASH_ENV" + +################################################################################ +# JOBS +################################################################################ + +jobs: + + # Lint job runs in parallel with database and build jobs. + lint: + <<: *runner_config + steps: + - checkout + - *step_process_codebase_for_ci + - *load_variables_from_dotenv + + - run: + name: Validate Composer configuration + command: composer validate --strict || [ "${VORTEX_CI_COMPOSER_VALIDATE_IGNORE_FAILURE:-0}" -eq 1 ] + + - run: + name: Install Vortex tooling + command: ./scripts/vortex-tooling.sh + + - *step_setup_remote_docker + - *step_install_docker_tools + + - run: + name: Login to container registry + command: ./vendor/bin/vortex-login-container-registry + + - run: + name: Lint Dockerfiles with Hadolint + command: | + for file in $(find .docker \( -name 'Dockerfile' -o -name '*.dockerfile' \)); do + echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint:__VERSION__ || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] + done + + - run: + name: Scan for committed secrets with Gitleaks + command: docker run --rm -v "${PWD}":/repo -w /repo ghcr.io/gitleaks/gitleaks:__VERSION__ dir . --no-banner || [ "${VORTEX_CI_GITLEAKS_IGNORE_FAILURE:-0}" -eq 1 ] + + - run: + name: Build stack + command: docker compose up --no-deps --detach cli + + - run: + name: Install development dependencies + command: | + docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c " \ + if [ -n \"\${PACKAGE_TOKEN:-}\" ]; then composer config --global --auth github-oauth.github.com \"\${PACKAGE_TOKEN}\"; fi && \ + COMPOSER_MEMORY_LIMIT=-1 composer --ansi install --prefer-dist" + docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c "yarn install --frozen-lockfile" + + - run: + name: Audit Composer packages + command: docker compose exec -T cli composer audit || [ "${VORTEX_CI_COMPOSER_AUDIT_IGNORE_FAILURE:-0}" -eq 1 ] + + - run: + name: Validate Composer configuration is normalized + command: docker compose exec -T cli composer normalize --dry-run || [ "${VORTEX_CI_COMPOSER_NORMALIZE_IGNORE_FAILURE:-0}" -eq 1 ] + + - run: + name: Lint code with PHPCS + command: docker compose exec -T cli vendor/bin/phpcs || [ "${VORTEX_CI_PHPCS_IGNORE_FAILURE:-0}" -eq 1 ] + + - run: + name: Lint code with PHPStan + command: docker compose exec -T cli vendor/bin/phpstan || [ "${VORTEX_CI_PHPSTAN_IGNORE_FAILURE:-0}" -eq 1 ] + + - run: + name: Lint code with Rector + command: docker compose exec -T cli vendor/bin/rector --dry-run || [ "${VORTEX_CI_RECTOR_IGNORE_FAILURE:-0}" -eq 1 ] + + - run: + name: Lint code with Twig CS Fixer + command: docker compose exec -T cli vendor/bin/twig-cs-fixer || [ "${VORTEX_CI_TWIG_CS_FIXER_IGNORE_FAILURE:-0}" -eq 1 ] + + - run: + name: Lint code with Gherkin Lint + command: docker compose exec -T cli vendor/bin/gherkinlint lint tests/behat/features || [ "${VORTEX_CI_GHERKIN_LINT_IGNORE_FAILURE:-0}" -eq 1 ] + + - run: + name: Lint module code with NodeJS linters + command: docker compose exec -T cli bash -c "yarn run lint" || [ "${VORTEX_CI_NODEJS_LINT_IGNORE_FAILURE:-0}" -eq 1 ] + + - run: + name: Lint theme code with NodeJS linters + command: | + [ "${VORTEX_FRONTEND_BUILD_SKIP:-0}" -eq 1 ] && exit 0 + docker compose exec -T cli bash -c "yarn --cwd=\${WEBROOT}/themes/custom/\${DRUPAL_THEME} run lint" || [ "${VORTEX_CI_NODEJS_LINT_IGNORE_FAILURE:-0}" -eq 1 ] + + # Test the provisioned site. Runs in parallel with the lint and build jobs. + # Provisioning and testing happen within the same job to save time on + # re-provisioning. + test: &job_test + <<: *runner_config + parallelism: 2 + steps: + - attach_workspace: + at: /tmp/workspace + + - checkout + - *step_process_codebase_for_ci + - *load_variables_from_dotenv + + - run: + name: Validate Composer configuration + command: composer validate --strict || [ "${VORTEX_CI_COMPOSER_VALIDATE_IGNORE_FAILURE:-0}" -eq 1 ] + + - run: + name: Install Vortex tooling + command: ./scripts/vortex-tooling.sh + + - run: + name: Set cache keys for database caching + command: | + echo "${VORTEX_CI_DB_CACHE_BRANCH}" | tee /tmp/db_cache_branch + echo "yes" | tee /tmp/db_cache_fallback_yes + echo "$(date ${VORTEX_CI_DB_CACHE_TIMESTAMP})" | tee /tmp/db_cache_timestamp + + - restore_cache: + keys: + # Use cached artifacts from previous builds of this branch. + # https://circleci.com/docs/2.0/caching/#restoring-cache + - __VERSION__-{{ checksum "/tmp/db_cache_branch" }}-{{ checksum "/tmp/db_cache_fallback_yes" }}-{{ checksum "/tmp/db_cache_timestamp" }} + - __VERSION__-{{ checksum "/tmp/db_cache_branch" }}-{{ checksum "/tmp/db_cache_fallback_yes" }}- + + - *step_setup_remote_docker + - *step_install_docker_tools + + - run: + name: Login to container registry + command: ./vendor/bin/vortex-login-container-registry + + # On the first build of the day (cache miss), fetch a fresh DB dump. + # On subsequent builds, the restored cache already contains the dump and + # the fetch script skips itself, leaving no semaphore file behind. + - run: + name: Fetch DB + command: VORTEX_FETCH_DB_SEMAPHORE=/tmp/fetch-db-success ./vendor/bin/vortex-fetch-db + no_output_timeout: 30m + + - run: + name: Build stack + command: docker compose up --detach && docker builder prune --all --force && docker image prune --force + + # Execute commands after the database fetch finished: if a fresh DB + # dump was fetched (cache miss), import it and export it back to + # produce a clean dump cached for the rest of the day's builds. This also + # validates the dump and supports "file-to-image" / "image-to-file" + # conversions. Configuration changes and DB updates are not applied, so + # the cached database stays in the same state as fetched. Only the + # primary parallel runner pushes a refreshed database container image. + - run: + name: Export DB after fetch + command: | + [ ! -f /tmp/fetch-db-success ] && echo "==> Database fetch semaphore file is missing. DB export will not proceed." && exit 0 + docker compose exec cli mkdir -p .data && docker compose cp -L .data/db.sql cli:/app/.data/db.sql || true + docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c "VORTEX_PROVISION_POST_OPERATIONS_SKIP=1 ./vendor/bin/vortex-provision" + ./vendor/bin/vortex-export-db db.sql + docker compose exec -T cli test -f /app/.data/db.sql && docker compose cp -L cli:/app/.data/db.sql .data/db.sql || true + no_output_timeout: 30m + + # Save the clean DB dump to the cache before the slow dev-dependency + # install and provisioning, so it lands sooner and survives a later + # failure. save_cache has no per-node runtime condition, and two parallel + # runners creating the same key concurrently fail the build, so only the + # primary runner may store it: stash .data aside on the non-primary + # runners to make their save a no-op, then restore it for provisioning. + - run: + name: Stash DB on non-primary runners so only the primary saves the cache + command: | + if [ "${CIRCLE_NODE_TOTAL:-1}" -gt 1 ] && [ "${CIRCLE_NODE_INDEX:-0}" -ne 0 ] && [ -e .data ]; then + echo "Stashing .data on non-primary node ${CIRCLE_NODE_INDEX} so only the primary saves the DB cache." + rm -rf .data.nosave + mv .data .data.nosave + fi + + - save_cache: + # Save cache per default branch and the timestamp. + # The cache will not be saved if it already exists. + # Note that the cache fallback flag is enabled for this case in order + # to save cache even if the fallback is not used when restoring it. + key: __VERSION__-{{ checksum "/tmp/db_cache_branch" }}-{{ checksum "/tmp/db_cache_fallback_yes" }}-{{ checksum "/tmp/db_cache_timestamp" }} + paths: + - ~/project/.data + + - run: + name: Restore stashed DB on non-primary runners for provisioning + command: | + if [ -e .data.nosave ]; then + echo "Restoring stashed .data on node ${CIRCLE_NODE_INDEX} for provisioning." + rm -rf .data + mv .data.nosave .data + fi + + - run: + name: Install development dependencies + command: | + docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c " \ + if [ -n \"\${PACKAGE_TOKEN:-}\" ]; then composer config --global --auth github-oauth.github.com \"\${PACKAGE_TOKEN}\"; fi && \ + COMPOSER_MEMORY_LIMIT=-1 composer --ansi install --prefer-dist" + docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c "yarn install --frozen-lockfile" + + - run: + name: Provision site + command: | + # Database dumps are removed from the runner once they are inside the + # container to avoid holding two copies for the rest of the job. + if [ -f .data/db.sql ]; then + docker compose exec cli mkdir -p .data + docker compose cp -L .data/db.sql cli:/app/.data/db.sql + rm -f .data/db.sql + fi + docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli ./vendor/bin/vortex-provision + no_output_timeout: 30m + + - run: + name: Test with Jest + command: docker compose exec -T cli bash -c "yarn test" || [ "${VORTEX_CI_JEST_IGNORE_FAILURE:-0}" -eq 1 ] + + - run: + name: Test with PHPUnit + command: | + [ "${CIRCLE_NODE_TOTAL:-1}" -gt 1 ] && [ "${CIRCLE_NODE_INDEX:-0}" -ne 0 ] && exit 0 + docker compose exec -T cli vendor/bin/phpunit || [ "${VORTEX_CI_PHPUNIT_IGNORE_FAILURE:-0}" -eq 1 ] + + - run: + name: Process PHPUnit logs and coverage + command: | + [ "${CIRCLE_NODE_TOTAL:-1}" -gt 1 ] && [ "${CIRCLE_NODE_INDEX:-0}" -ne 0 ] && exit 0 + mkdir -p "${VORTEX_CI_ARTIFACTS}" + if docker compose ps --services --filter "status=running" | grep -q cli && docker compose exec cli test -d /app/.logs; then + docker compose cp cli:/app/.logs/. "${VORTEX_CI_ARTIFACTS}/" + fi + + - run: + name: Extract code coverage + command: | + [ "${CIRCLE_NODE_TOTAL:-1}" -gt 1 ] && [ "${CIRCLE_NODE_INDEX:-0}" -ne 0 ] && exit 0 + RATE=$(grep -om1 'line-rate="[0-9.]*"' /tmp/artifacts/coverage/phpunit/cobertura.xml | tr -cd '0-9.') + PERCENT=$(awk "BEGIN {printf \"%.2f\", $RATE*100}") + echo "Coverage: $PERCENT% (threshold: ${VORTEX_CI_CODE_COVERAGE_THRESHOLD:-90}%)" + echo "export COVERAGE_PERCENT=${PERCENT}" >> "${BASH_ENV}" + + - run: + name: Post coverage summary as PR comment + command: | + [ "${CIRCLE_NODE_TOTAL:-1}" -gt 1 ] && [ "${CIRCLE_NODE_INDEX:-0}" -ne 0 ] && exit 0 + [ "${VORTEX_CI_CODE_COVERAGE_PR_COMMENT_SKIP:-0}" = "1" ] && exit 0 + .circleci/post-coverage-comment.sh /tmp/artifacts/coverage/phpunit/coverage.txt + + - run: + name: Check code coverage threshold + command: | + [ "${CIRCLE_NODE_TOTAL:-1}" -gt 1 ] && [ "${CIRCLE_NODE_INDEX:-0}" -ne 0 ] && exit 0 + if [ "${COVERAGE_PERCENT//.}" -lt "$((${VORTEX_CI_CODE_COVERAGE_THRESHOLD:-90}*100))" ]; then + echo "FAIL: coverage ${COVERAGE_PERCENT}% is below threshold ${VORTEX_CI_CODE_COVERAGE_THRESHOLD:-90}%" + exit 1 + fi + + - run: + name: Validate Single Directory Components + command: | + [ "${CIRCLE_NODE_TOTAL:-1}" -gt 1 ] && [ "${CIRCLE_NODE_INDEX:-0}" -ne 0 ] && exit 0 + [ "${VORTEX_FRONTEND_BUILD_SKIP:-0}" -eq 1 ] && exit 0 + output=$(docker compose exec -T cli vendor/bin/drush sdc-devel:validate "${DRUPAL_THEME}" 2>&1) + echo "${output}" + if echo "${output}" | grep -qE 'Critical|Errors?|Warnings?'; then + echo "SDC validation reported problems." + [ "${VORTEX_CI_SDC_DEVEL_IGNORE_FAILURE:-0}" -eq 1 ] || exit 1 + fi + + - run: + name: Test with Behat + command: | + if [ "${CIRCLE_NODE_TOTAL:-1}" -gt 1 ]; then export VORTEX_CI_BEHAT_PROFILE="${VORTEX_CI_BEHAT_PROFILE:-p${CIRCLE_NODE_INDEX}}"; fi + echo "Running with ${VORTEX_CI_BEHAT_PROFILE:-default} profile" + docker compose exec -T cli php -d memory_limit=-1 vendor/bin/behat --colors --strict --profile="${VORTEX_CI_BEHAT_PROFILE:-default}" || \ + docker compose exec -T cli php -d memory_limit=-1 vendor/bin/behat --colors --strict --rerun --profile="${VORTEX_CI_BEHAT_PROFILE:-default}" || \ + [ "${VORTEX_CI_BEHAT_IGNORE_FAILURE:-0}" -eq 1 ] + no_output_timeout: 30m + + - run: + name: Process test logs and artifacts + command: | + mkdir -p "${VORTEX_CI_TEST_RESULTS}" "${VORTEX_CI_ARTIFACTS}" + if docker compose ps --services --filter "status=running" | grep -q cli && docker compose exec cli test -d /app/.logs; then + docker compose cp cli:/app/.logs/. "${VORTEX_CI_ARTIFACTS}/" + if docker compose exec -T cli sh -c '[ -d /app/.logs/test_results/ ]'; then + docker compose cp cli:/app/.logs/test_results/. "${VORTEX_CI_TEST_RESULTS}/" + fi + fi + when: always + + - store_test_results: + path: *test_results + + - store_artifacts: + path: *artifacts + + # Build the deployable in parallel with the lint and test jobs. For artifact + # deployments, it exports the built codebase for the deploy job; for + # image-based hosting, a successful build validates that the production images + # are deployable. + build: + <<: *runner_config + steps: + - checkout + - *step_process_codebase_for_ci + - *load_variables_from_dotenv + + - run: + name: Install Vortex tooling + command: ./scripts/vortex-tooling.sh + + - *step_setup_remote_docker + - *step_install_docker_tools + + - run: + name: Login to container registry + command: ./vendor/bin/vortex-login-container-registry + + - run: + name: Build stack + command: docker compose up --detach && docker builder prune --all --force && docker image prune --force + + # Deploy primary branches. + deploy: &job_deploy + <<: *runner_config + steps: + + - add_ssh_keys: + fingerprints: + - *deploy_ssh_fingerprint + + - checkout + - *step_process_codebase_for_ci + - *load_variables_from_dotenv + + - run: + name: Check if deployment should be skipped + command: | + if [ "${CIRCLE_PULL_REQUEST}" != "" ] && echo "${CIRCLE_BRANCH}" | grep -q "^project/"; then + echo "Skipping deployment - PR from project/* branch" + circleci-agent step halt + fi + + - run: + name: Install Vortex tooling + command: ./scripts/vortex-tooling.sh + + - run: + name: Deploy + command: | + VORTEX_DEPLOY_BRANCH="${CIRCLE_BRANCH}" \ + VORTEX_DEPLOY_PR="$(echo ${CIRCLE_PULL_REQUEST} | cut -d'/' -f 7)" \ + VORTEX_DEPLOY_PR_HEAD=${CIRCLE_SHA1} \ + ./vendor/bin/vortex-deploy + no_output_timeout: 30m + + - store_artifacts: + path: *artifacts + + # Deploy tags. + deploy-tags: &job-deploy-tags + <<: *runner_config + steps: + + - add_ssh_keys: + fingerprints: + - *deploy_ssh_fingerprint + + - checkout + - *step_process_codebase_for_ci + - *load_variables_from_dotenv + + - run: + name: Install Vortex tooling + command: ./scripts/vortex-tooling.sh + + - run: + name: Deploy + command: VORTEX_DEPLOY_MODE="tag" ./vendor/bin/vortex-deploy + no_output_timeout: 30m + + - store_artifacts: + path: *artifacts + +################################################################################ +# WORKFLOWS +################################################################################ + +workflows: + # Commit workflow. Runs for every commit push to the remote repository. + commit: + jobs: + - lint: + filters: + tags: + only: /.*/ + - test: + filters: + tags: + only: /.*/ + - build: + filters: + tags: + only: /.*/ + - deploy: + requires: + - test + - lint + - build + filters: + branches: + # Allowed branches: + # - production, main, master, develop, ci, cisomething + # - project/description + # - deps/* + # - feature/description, feature/123-description + # - bugfix/description, bugfix/123-description + # - release/__VERSION__, release/__VERSION__ (per https://semver.org/) + # - release/2023-04-17, release/2023-04-17.123 (date-based) + # - hotfix/__VERSION__, hotfix/__VERSION__ (per https://semver.org/) + # - hotfix/2023-04-17, hotfix/2023-04-17.123 (date-based) + only: /^(production|main|master|develop)$|^project\/[a-zA-Z0-9\-\.]+|^(feature|bugfix)\/[a-zA-Z0-9\-\.\,_]+$|^ci.*|^(release|hotfix)\/[0-9]+(\.[0-9]+){2}(-rc\.[0-9]+)?$|^(release|hotfix)\/[0-9]{4}-[0-9]{2}-[0-9]{2}(\.[0-9]+)?$/ + tags: + ignore: /.*/ + - deploy-tags: + requires: + - test + - lint + - build + filters: + branches: + ignore: /.*/ + tags: + # Allowed tags: + # - __VERSION__, __VERSION__ (per https://semver.org/) + # - 2023-04-17, 2023-04-17.123 (date-based) + only: /^[0-9]+(\.[0-9]+){2}(-rc\.[0-9]+)?$|^[0-9]{4}-[0-9]{2}-[0-9]{2}(\.[0-9]+)?$/ diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_no_dclint_circleci/.circleci/post-coverage-comment.sh b/.vortex/cli/tests/Fixtures/handler_process/tools_no_dclint_circleci/.circleci/post-coverage-comment.sh new file mode 100755 index 000000000..d9e737409 --- /dev/null +++ b/.vortex/cli/tests/Fixtures/handler_process/tools_no_dclint_circleci/.circleci/post-coverage-comment.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env bash +## +## Post code coverage summary as a PR comment on GitHub. +## +## Minimizes previous coverage comments before posting a new one. +## +## Environment variables: +## CIRCLE_PULL_REQUEST - CircleCI PR URL. +## GITHUB_TOKEN - GitHub token for API access. +## CIRCLE_PROJECT_USERNAME - GitHub org/user. +## CIRCLE_PROJECT_REPONAME - GitHub repo name. +## VORTEX_CI_CODE_COVERAGE_THRESHOLD - Coverage threshold percentage (default: 90). +## +## Usage: +## .circleci/post-coverage-comment.sh /path/to/coverage.txt + +set -euo pipefail + +COVERAGE_FILE="${1:-}" + +if [ -z "${COVERAGE_FILE}" ] || [ ! -f "${COVERAGE_FILE}" ]; then + echo "ERROR: Coverage file not found: ${COVERAGE_FILE}" >&2 + exit 1 +fi + +if [ -z "${CIRCLE_PULL_REQUEST:-}" ]; then + echo "Not a pull request. Skipping." + exit 0 +fi + +if [ -z "${GITHUB_TOKEN:-}" ]; then + echo "GITHUB_TOKEN is not set. Skipping." + exit 0 +fi + +COVERAGE_SUMMARY=$(awk '/^ *Summary:/{f=1;next} f && /^$/{exit} f' "${COVERAGE_FILE}") +COVERAGE_DETAILS=$(awk 'BEGIN{s=0} /^ *Summary:/{s=1} s==1 && /^$/{s=2;next} s==2' "${COVERAGE_FILE}") +PR_NUMBER=$(echo "${CIRCLE_PULL_REQUEST}" | cut -d'/' -f 7) +REPO_SLUG="${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}" +THRESHOLD="${VORTEX_CI_CODE_COVERAGE_THRESHOLD:-90}" + +MARKER="" + +BODY=$(jq -n --arg body "**Code coverage** (threshold: ${THRESHOLD}%) +\`\`\` +${COVERAGE_SUMMARY} +\`\`\` +
+Per-class coverage + +\`\`\` +${COVERAGE_DETAILS} +\`\`\` +
+${MARKER}" '{body: $body}') + +# Minimize previous coverage comments. +COMMENTS_JSON=$(curl -s \ + -H "Authorization: token ${GITHUB_TOKEN}" \ + -H "Accept: application/vnd.github.v3+json" \ + "https://api.github.com/repos/${REPO_SLUG}/issues/${PR_NUMBER}/comments?per_page=100") + +EXISTING_IDS=$(echo "${COMMENTS_JSON}" | jq -r '.[] | select(.body | contains("")) | .node_id') + +for NODE_ID in ${EXISTING_IDS}; do + GRAPHQL_BODY=$(jq -n --arg id "${NODE_ID}" '{query: "mutation($id:ID!){minimizeComment(input:{subjectId:$id,classifier:OUTDATED}){minimizedComment{isMinimized}}}", variables: {id: $id}}') + curl -s -X POST \ + -H "Authorization: bearer ${GITHUB_TOKEN}" \ + -H "Content-Type: application/json" \ + "https://api.github.com/graphql" \ + -d "${GRAPHQL_BODY}" +done + +# Post new coverage comment. +curl -s -X POST \ + -H "Authorization: token ${GITHUB_TOKEN}" \ + -H "Accept: application/vnd.github.v3+json" \ + "https://api.github.com/repos/${REPO_SLUG}/issues/${PR_NUMBER}/comments" \ + -d "${BODY}" diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_no_dclint_circleci/.dockerignore b/.vortex/cli/tests/Fixtures/handler_process/tools_no_dclint_circleci/.dockerignore new file mode 100644 index 000000000..e0fa230b8 --- /dev/null +++ b/.vortex/cli/tests/Fixtures/handler_process/tools_no_dclint_circleci/.dockerignore @@ -0,0 +1,10 @@ +@@ -42,9 +42,6 @@ + **/.phpunit.cache + **/.twig-cs-fixer.cache + +-# Database cache key files written into the workspace by the CI workflow. +-db_cache_* +- + # Local override files. + docker-compose.override.yml + .env.local diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_no_dclint_circleci/.github/workflows/-build-test-deploy.yml b/.vortex/cli/tests/Fixtures/handler_process/tools_no_dclint_circleci/.github/workflows/-build-test-deploy.yml new file mode 100644 index 000000000..e69de29bb diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_no_dclint_circleci/README.md b/.vortex/cli/tests/Fixtures/handler_process/tools_no_dclint_circleci/README.md new file mode 100644 index 000000000..c803a0514 --- /dev/null +++ b/.vortex/cli/tests/Fixtures/handler_process/tools_no_dclint_circleci/README.md @@ -0,0 +1,9 @@ +@@ -9,7 +9,7 @@ + + Drupal 11 implementation of star wars for star wars Org + +-[![Build, Test and Deploy](https://github.com/star_wars_org/star_wars/actions/workflows/build-test-deploy.yml/badge.svg)](https://github.com/star_wars_org/star_wars/actions/workflows/build-test-deploy.yml) ++[![CircleCI](https://circleci.com/gh/star_wars_org/star_wars.svg?style=shield)](https://circleci.com/gh/star_wars_org/star_wars) + + ![Drupal 11](https://img.shields.io/badge/Drupal-11-0678BE?logo=drupal&logoColor=white) + diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_no_dclint_circleci/docs/ci.md b/.vortex/cli/tests/Fixtures/handler_process/tools_no_dclint_circleci/docs/ci.md new file mode 100644 index 000000000..542f58060 --- /dev/null +++ b/.vortex/cli/tests/Fixtures/handler_process/tools_no_dclint_circleci/docs/ci.md @@ -0,0 +1,12 @@ +@@ -5,9 +5,9 @@ + + ## CI provider + +-This project uses [GitHub Actions](https://github.com/features/actions). ++This project uses [CircleCI](https://circleci.com/). + +-See [GitHub Actions documentation](https://www.vortextemplate.com/docs/continuous-integration/github-actions) ++See [CircleCI documentation](https://www.vortextemplate.com/docs/continuous-integration/circleci) + for setup and configuration details. + + ## Project-specific configuration diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_no_dclint_circleci/tests/phpunit/CircleCiConfigTest.php b/.vortex/cli/tests/Fixtures/handler_process/tools_no_dclint_circleci/tests/phpunit/CircleCiConfigTest.php new file mode 100644 index 000000000..e1ca36aa7 --- /dev/null +++ b/.vortex/cli/tests/Fixtures/handler_process/tools_no_dclint_circleci/tests/phpunit/CircleCiConfigTest.php @@ -0,0 +1,283 @@ +config = Yaml::decode($file); + } + + /** + * Tests for deploy branch regex. + * + * @see https://semver.org/ + */ + #[DataProvider('dataProviderDeployBranchRegex')] + public function testDeployBranchRegex(string $branch, bool $expected = TRUE): void { + $pattern = $this->getCommitWorkflowJob('deploy')['filters']['branches']['only']; + $result = preg_match($pattern, $branch); + $this->assertEquals($expected, $result); + } + + /** + * Data provider for testDeployBranchRegex(). + */ + public static function dataProviderDeployBranchRegex(): \Iterator { + // Positive branches. + yield ['production']; + yield ['main']; + yield ['master']; + yield ['develop']; + + yield ['ci']; + yield ['cisomething']; + + yield ['release/__VERSION__']; + yield ['release/__VERSION__']; + yield ['hotfix/__VERSION__']; + yield ['hotfix/__VERSION__']; + + yield ['release/2023-04-17']; + yield ['release/2023-04-17.1']; + yield ['hotfix/2023-04-17']; + yield ['hotfix/2023-04-17.1']; + + yield ['feature/description']; + yield ['feature/Description']; + yield ['feature/Description-With-Hyphens']; + yield ['feature/Description-With_Underscores']; + yield ['feature/123-description']; + yield ['feature/123-Description']; + yield ['feature/UNDERSCORES_UNDERSCORES']; + yield ['feature/123-Description-With_UNDERSCORES']; + yield ['feature/1.x']; + yield ['feature/0.x']; + yield ['feature/0.1.x']; + yield ['feature/__VERSION__.x']; + yield ['feature/1.x-description']; + yield ['feature/0.x-description']; + yield ['feature/0.1.x-description']; + yield ['feature/__VERSION__.x-description']; + + yield ['bugfix/description']; + yield ['bugfix/Description']; + yield ['bugfix/Description-With-Hyphens']; + yield ['bugfix/Description-With_Underscores']; + yield ['bugfix/123-description']; + yield ['bugfix/123-Description']; + yield ['bugfix/UNDERSCORES_UNDERSCORES']; + yield ['bugfix/123-Description-With_UNDERSCORES']; + yield ['bugfix/1.x']; + yield ['bugfix/0.x']; + yield ['bugfix/0.1.x']; + yield ['bugfix/__VERSION__.x']; + yield ['bugfix/1.x-description']; + yield ['bugfix/0.x-description']; + yield ['bugfix/0.1.x-description']; + yield ['bugfix/__VERSION__.x-description']; + + yield ['project/description']; + yield ['project/Description']; + yield ['project/Description-With-Hyphens']; + yield ['project/123-description']; + yield ['project/123-Description']; + yield ['project/1.x']; + yield ['project/0.x']; + yield ['project/0.1.x']; + yield ['project/__VERSION__.x']; + yield ['project/1.x-description']; + yield ['project/0.x-description']; + yield ['project/0.1.x-description']; + yield ['project/__VERSION__.x-description']; + + // Negative branches. + yield ['something', FALSE]; + yield ['premain', FALSE]; + yield ['premaster', FALSE]; + yield ['predevelop', FALSE]; + yield ['mainpost', FALSE]; + yield ['masterpost', FALSE]; + yield ['developpost', FALSE]; + yield ['premainpost', FALSE]; + yield ['premasterpost', FALSE]; + yield ['predeveloppost', FALSE]; + + yield ['preci', FALSE]; + yield ['precipost', FALSE]; + + yield ['deps/something', FALSE]; + yield ['deps', FALSE]; + yield ['predeps', FALSE]; + yield ['depspost', FALSE]; + yield ['predepspost', FALSE]; + + yield ['feature', FALSE]; + yield ['release', FALSE]; + yield ['hotfix', FALSE]; + yield ['prefeature', FALSE]; + yield ['prerelease', FALSE]; + yield ['prehotfix', FALSE]; + yield ['featurepost', FALSE]; + yield ['releasepost', FALSE]; + yield ['hotfixpost', FALSE]; + yield ['prefeaturepost', FALSE]; + yield ['prereleasepost', FALSE]; + yield ['prehotfixpost', FALSE]; + + yield ['release/123', FALSE]; + yield ['release/123.456', FALSE]; + yield ['hotfix/123', FALSE]; + yield ['hotfix/123.456', FALSE]; + + yield ['release/202-04-17', FALSE]; + yield ['release/2023-4-17', FALSE]; + yield ['release/2023-04-1', FALSE]; + yield ['release/pre2023-04-17', FALSE]; + yield ['release/2023-04-17post', FALSE]; + yield ['release/pre2023-04-17post', FALSE]; + + yield ['hotfix/202-04-17', FALSE]; + yield ['hotfix/2023-4-17', FALSE]; + yield ['hotfix/2023-04-1', FALSE]; + yield ['hotfix/pre2023-04-17', FALSE]; + yield ['hotfix/2023-04-17post', FALSE]; + yield ['hotfix/pre2023-04-17post', FALSE]; + + yield ['release/__VERSION__', FALSE]; + yield ['release/__VERSION__', FALSE]; + yield ['release/__VERSION__', FALSE]; + yield ['release/__VERSION__', FALSE]; + yield ['release/__VERSION__', FALSE]; + yield ['release/__VERSION__', FALSE]; + yield ['release/__VERSION__', FALSE]; + yield ['release/__VERSION__', FALSE]; + yield ['release/__VERSION__', FALSE]; + yield ['release/__VERSION__', FALSE]; + + yield ['hotfix/__VERSION__', FALSE]; + yield ['hotfix/__VERSION__', FALSE]; + yield ['hotfix/__VERSION__', FALSE]; + yield ['hotfix/__VERSION__', FALSE]; + yield ['hotfix/__VERSION__', FALSE]; + yield ['hotfix/__VERSION__', FALSE]; + yield ['hotfix/__VERSION__', FALSE]; + yield ['hotfix/__VERSION__', FALSE]; + yield ['hotfix/__VERSION__', FALSE]; + yield ['hotfix/__VERSION__', FALSE]; + + yield ['prefeature/something', FALSE]; + yield ['prefbugfix/something', FALSE]; + yield ['prerelease/something', FALSE]; + yield ['prehotfix/something', FALSE]; + yield ['featurepost/something', FALSE]; + yield ['bugfixpost/something', FALSE]; + yield ['releasepost/something', FALSE]; + yield ['hotfixpost/something', FALSE]; + yield ['prefeaturepost/something', FALSE]; + yield ['prebugfixpost/something', FALSE]; + yield ['prereleasepost/something', FALSE]; + yield ['prehotfixpost/something', FALSE]; + yield ['preproject/something', FALSE]; + yield ['projectpost/something', FALSE]; + } + + /** + * Tests for deploy tag regex. + * + * @see https://semver.org/ + */ + #[DataProvider('dataProviderDeployTagRegex')] + public function testDeployTagRegex(string $branch, bool $expected = TRUE): void { + $pattern = $this->getCommitWorkflowJob('deploy-tags')['filters']['tags']['only']; + $result = preg_match($pattern, $branch); + $this->assertEquals($expected, $result); + } + + /** + * Data provider for testDeployTagRegex(). + */ + public static function dataProviderDeployTagRegex(): \Iterator { + // Positive tags. + yield ['__VERSION__']; + yield ['__VERSION__']; + yield ['2023-04-17']; + yield ['2023-04-17.123']; + + // Negative tags. + yield ['123', FALSE]; + yield ['123.456', FALSE]; + yield ['__VERSION__', FALSE]; + yield ['__VERSION__', FALSE]; + yield ['__VERSION__', FALSE]; + yield ['__VERSION__', FALSE]; + yield ['__VERSION__', FALSE]; + + yield ['202-04-17', FALSE]; + yield ['2023-0-17', FALSE]; + yield ['2023-04-1', FALSE]; + yield ['pre2023-04-17', FALSE]; + yield ['2023-04-17post', FALSE]; + yield ['pre2023-04-17post', FALSE]; + yield ['2023-04-17.123.', FALSE]; + yield ['2023-04-17.pre123', FALSE]; + yield ['2023-04-17.pre123post', FALSE]; + yield ['2023-04-17.123post', FALSE]; + } + + /** + * Get a commit workflow job configuration by its job name. + * + * Looks the job up by name rather than by position, so the assertions stay + * correct regardless of how many other jobs surround it in the workflow. + * + * @param string $name + * The job name (the single key of a workflow job entry). + * + * @return mixed + * The job configuration. + */ + protected function getCommitWorkflowJob(string $name): mixed { + $jobs = $this->config['workflows']['commit']['jobs']; + + foreach ($jobs as $job) { + if (is_array($job) && array_key_exists($name, $job)) { + return $job[$name]; + } + } + + throw new \RuntimeException(sprintf('Job "%s" not found in the commit workflow.', $name)); + } + +} diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_no_dclint_circleci/tests/phpunit/Drupal/EnvironmentSettingsTest.php b/.vortex/cli/tests/Fixtures/handler_process/tools_no_dclint_circleci/tests/phpunit/Drupal/EnvironmentSettingsTest.php new file mode 100644 index 000000000..aa0127049 --- /dev/null +++ b/.vortex/cli/tests/Fixtures/handler_process/tools_no_dclint_circleci/tests/phpunit/Drupal/EnvironmentSettingsTest.php @@ -0,0 +1,12 @@ +@@ -332,9 +332,9 @@ + } + + /** +- * Test per-environment settings for GitHub Actions. ++ * Test per-environment settings for CircleCI. + */ +- public function testEnvironmentGha(): void { ++ public function testEnvironmentCircleCi(): void { + $this->setEnvVars([ + 'CI' => TRUE, + ]); diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_no_docker_linters/-.dclintrc b/.vortex/cli/tests/Fixtures/handler_process/tools_no_docker_linters/-.dclintrc new file mode 100644 index 000000000..e69de29bb diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_no_docker_linters/.github/workflows/build-test-deploy.yml b/.vortex/cli/tests/Fixtures/handler_process/tools_no_docker_linters/.github/workflows/build-test-deploy.yml new file mode 100644 index 000000000..72560abab --- /dev/null +++ b/.vortex/cli/tests/Fixtures/handler_process/tools_no_docker_linters/.github/workflows/build-test-deploy.yml @@ -0,0 +1,18 @@ +@@ -106,17 +106,6 @@ + - name: Login to container registry + run: ./vendor/bin/vortex-login-container-registry + +- - name: Lint Dockerfiles with Hadolint +- run: | +- find .docker \( -name 'Dockerfile' -o -name '*.dockerfile' \) | while read -r file; do +- echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint:__VERSION__ +- done +- continue-on-error: ${{ vars.VORTEX_CI_HADOLINT_IGNORE_FAILURE == '1' }} +- +- - name: Lint Docker Compose files with DCLint +- run: docker run --rm -v "${PWD}":/app zavoloklom/dclint:__VERSION__ . +- continue-on-error: ${{ vars.VORTEX_CI_DCLINT_IGNORE_FAILURE == '1' }} +- + - name: Scan for committed secrets with Gitleaks + run: docker run --rm -v "${PWD}":/repo -w /repo ghcr.io/gitleaks/gitleaks:__VERSION__ dir . --no-banner + continue-on-error: ${{ vars.VORTEX_CI_GITLEAKS_IGNORE_FAILURE == '1' }} diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_no_docker_linters_circleci/-.dclintrc b/.vortex/cli/tests/Fixtures/handler_process/tools_no_docker_linters_circleci/-.dclintrc new file mode 100644 index 000000000..e69de29bb diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_no_docker_linters_circleci/.circleci/config.yml b/.vortex/cli/tests/Fixtures/handler_process/tools_no_docker_linters_circleci/.circleci/config.yml new file mode 100644 index 000000000..e59840540 --- /dev/null +++ b/.vortex/cli/tests/Fixtures/handler_process/tools_no_docker_linters_circleci/.circleci/config.yml @@ -0,0 +1,525 @@ +# CircleCI configuration file for building, testing, and deploying. +# +# This configuration file uses the "docker" executor to run the Docker stack. +# +# A "runner" container, created from a specified container image, is used to +# checkout source code and run commands defined in this file. Application Docker +# containers defined in `docker-compose.yml` run on a *remote* Docker server +# controlled by CircleCI. +# The "runner" container uses Docker client to control the remote Docker server. +version: '2.1' + +aliases: + + # SSH key fingerprint to deploy code. + # Replace this key fingerprint with your own and remove this comment. + - &deploy_ssh_fingerprint "SHA256:6d+U5QubT0eAWz+4N2wt+WM2qx6o4cvyvQ6xILETJ84" + + # Shared runner container configuration applied to each job. + - &runner_config + working_directory: &working_directory ~/project + environment: + VORTEX_DEPLOY_SSH_FINGERPRINT: *deploy_ssh_fingerprint + docker: + # Run each job on CircleCI's public PHP convenience image. It ships PHP, + # Composer and the Docker CLI (with the Compose plugin), which drive the + # dockerized stack against the remote Docker engine (see setup_remote_docker). + # https://circleci.com/developer/images/image/cimg/php + - image: cimg/php:__VERSION__ + environment: + # Set runner timezone via UI to ensure that executed operations use correct timestamps. + # https://en.wikipedia.org/wiki/List_of_tz_database_time_zones + TZ: UTC + # Set runner terminal capabilities. + TERM: xterm-256color + # Disable strict host key checking for SSH connections. + VORTEX_SSH_DISABLE_STRICT_HOST_KEY_CHECKING: "1" + # Remove all SSH keys from the runner container. + VORTEX_SSH_REMOVE_ALL_KEYS: "1" + # How often to refresh the cache of the DB dump. Refer to `date` command. + VORTEX_CI_DB_CACHE_TIMESTAMP: +%Y%m%d + # Use previous database caches on this branch as a fallback if the above cache + # does not match (for example, the cache is available only from the previous + # day). If "no" is set, the cache will be rebuilt from scratch. + VORTEX_CI_DB_CACHE_FALLBACK: "yes" + # Which branch to use as a source of DB caches. + VORTEX_CI_DB_CACHE_BRANCH: "develop" + # Directory to store test results. + VORTEX_CI_TEST_RESULTS: &test_results /tmp/tests + # Directory to store test artifacts. + VORTEX_CI_ARTIFACTS: &artifacts /tmp/artifacts + # Check only minimal stack requirements. + VORTEX_DOCTOR_CHECK_MINIMAL: 1 + # CI runner resource class. + # https://circleci.com/docs/2.0/configuration-reference/#resource_class + # Change to 'large' for faster builds. + resource_class: medium + + - &step_setup_remote_docker + setup_remote_docker: + # Docker Layer Caching allows to significantly speed up builds by caching + # images built during previous runs. + # https://circleci.com/docs/2.0/docker-layer-caching/ + docker_layer_caching: false + version: default + + - &step_install_docker_tools + run: + name: Install pinned Docker Compose and Buildx CLI plugins + # The convenience image ships older Docker CLI plugins than the stack + # relies on; install the versions that resolve 'service:' build contexts. + command: | + mkdir -p "${HOME}/.docker/cli-plugins" + curl -fsSL -o "${HOME}/.docker/cli-plugins/docker-compose" https://github.com/docker/compose/releases/download/__VERSION__/docker-compose-linux-x86_64 + curl -fsSL -o "${HOME}/.docker/cli-plugins/docker-buildx" https://github.com/docker/buildx/releases/download/__VERSION__/buildx-__VERSION__.linux-amd64 + chmod +x "${HOME}/.docker/cli-plugins/docker-compose" "${HOME}/.docker/cli-plugins/docker-buildx" + docker compose version + docker buildx version + + - &step_process_codebase_for_ci + run: + name: Process codebase to run in CI + command: | + find . -name "docker-compose.yml" -print0 | xargs -0 -I {} sh -c "sed -i -e ''/###/d'' {} && sed -i -e ''s/##//'' {}" + mkdir -p /tmp/workspace/code + + - &load_variables_from_dotenv + run: + name: Load environment variables from .env file + # Load variables from .env file, respecting existing values, and make them available for the next steps. + command: t=$(mktemp) && export -p >"${t}" && set -a && . ./.env && set +a && . "${t}" && export -p >> "$BASH_ENV" + +################################################################################ +# JOBS +################################################################################ + +jobs: + + # Lint job runs in parallel with database and build jobs. + lint: + <<: *runner_config + steps: + - checkout + - *step_process_codebase_for_ci + - *load_variables_from_dotenv + + - run: + name: Validate Composer configuration + command: composer validate --strict || [ "${VORTEX_CI_COMPOSER_VALIDATE_IGNORE_FAILURE:-0}" -eq 1 ] + + - run: + name: Install Vortex tooling + command: ./scripts/vortex-tooling.sh + + - *step_setup_remote_docker + - *step_install_docker_tools + + - run: + name: Login to container registry + command: ./vendor/bin/vortex-login-container-registry + + - run: + name: Scan for committed secrets with Gitleaks + command: docker run --rm -v "${PWD}":/repo -w /repo ghcr.io/gitleaks/gitleaks:__VERSION__ dir . --no-banner || [ "${VORTEX_CI_GITLEAKS_IGNORE_FAILURE:-0}" -eq 1 ] + + - run: + name: Build stack + command: docker compose up --no-deps --detach cli + + - run: + name: Install development dependencies + command: | + docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c " \ + if [ -n \"\${PACKAGE_TOKEN:-}\" ]; then composer config --global --auth github-oauth.github.com \"\${PACKAGE_TOKEN}\"; fi && \ + COMPOSER_MEMORY_LIMIT=-1 composer --ansi install --prefer-dist" + docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c "yarn install --frozen-lockfile" + + - run: + name: Audit Composer packages + command: docker compose exec -T cli composer audit || [ "${VORTEX_CI_COMPOSER_AUDIT_IGNORE_FAILURE:-0}" -eq 1 ] + + - run: + name: Validate Composer configuration is normalized + command: docker compose exec -T cli composer normalize --dry-run || [ "${VORTEX_CI_COMPOSER_NORMALIZE_IGNORE_FAILURE:-0}" -eq 1 ] + + - run: + name: Lint code with PHPCS + command: docker compose exec -T cli vendor/bin/phpcs || [ "${VORTEX_CI_PHPCS_IGNORE_FAILURE:-0}" -eq 1 ] + + - run: + name: Lint code with PHPStan + command: docker compose exec -T cli vendor/bin/phpstan || [ "${VORTEX_CI_PHPSTAN_IGNORE_FAILURE:-0}" -eq 1 ] + + - run: + name: Lint code with Rector + command: docker compose exec -T cli vendor/bin/rector --dry-run || [ "${VORTEX_CI_RECTOR_IGNORE_FAILURE:-0}" -eq 1 ] + + - run: + name: Lint code with Twig CS Fixer + command: docker compose exec -T cli vendor/bin/twig-cs-fixer || [ "${VORTEX_CI_TWIG_CS_FIXER_IGNORE_FAILURE:-0}" -eq 1 ] + + - run: + name: Lint code with Gherkin Lint + command: docker compose exec -T cli vendor/bin/gherkinlint lint tests/behat/features || [ "${VORTEX_CI_GHERKIN_LINT_IGNORE_FAILURE:-0}" -eq 1 ] + + - run: + name: Lint module code with NodeJS linters + command: docker compose exec -T cli bash -c "yarn run lint" || [ "${VORTEX_CI_NODEJS_LINT_IGNORE_FAILURE:-0}" -eq 1 ] + + - run: + name: Lint theme code with NodeJS linters + command: | + [ "${VORTEX_FRONTEND_BUILD_SKIP:-0}" -eq 1 ] && exit 0 + docker compose exec -T cli bash -c "yarn --cwd=\${WEBROOT}/themes/custom/\${DRUPAL_THEME} run lint" || [ "${VORTEX_CI_NODEJS_LINT_IGNORE_FAILURE:-0}" -eq 1 ] + + # Test the provisioned site. Runs in parallel with the lint and build jobs. + # Provisioning and testing happen within the same job to save time on + # re-provisioning. + test: &job_test + <<: *runner_config + parallelism: 2 + steps: + - attach_workspace: + at: /tmp/workspace + + - checkout + - *step_process_codebase_for_ci + - *load_variables_from_dotenv + + - run: + name: Validate Composer configuration + command: composer validate --strict || [ "${VORTEX_CI_COMPOSER_VALIDATE_IGNORE_FAILURE:-0}" -eq 1 ] + + - run: + name: Install Vortex tooling + command: ./scripts/vortex-tooling.sh + + - run: + name: Set cache keys for database caching + command: | + echo "${VORTEX_CI_DB_CACHE_BRANCH}" | tee /tmp/db_cache_branch + echo "yes" | tee /tmp/db_cache_fallback_yes + echo "$(date ${VORTEX_CI_DB_CACHE_TIMESTAMP})" | tee /tmp/db_cache_timestamp + + - restore_cache: + keys: + # Use cached artifacts from previous builds of this branch. + # https://circleci.com/docs/2.0/caching/#restoring-cache + - __VERSION__-{{ checksum "/tmp/db_cache_branch" }}-{{ checksum "/tmp/db_cache_fallback_yes" }}-{{ checksum "/tmp/db_cache_timestamp" }} + - __VERSION__-{{ checksum "/tmp/db_cache_branch" }}-{{ checksum "/tmp/db_cache_fallback_yes" }}- + + - *step_setup_remote_docker + - *step_install_docker_tools + + - run: + name: Login to container registry + command: ./vendor/bin/vortex-login-container-registry + + # On the first build of the day (cache miss), fetch a fresh DB dump. + # On subsequent builds, the restored cache already contains the dump and + # the fetch script skips itself, leaving no semaphore file behind. + - run: + name: Fetch DB + command: VORTEX_FETCH_DB_SEMAPHORE=/tmp/fetch-db-success ./vendor/bin/vortex-fetch-db + no_output_timeout: 30m + + - run: + name: Build stack + command: docker compose up --detach && docker builder prune --all --force && docker image prune --force + + # Execute commands after the database fetch finished: if a fresh DB + # dump was fetched (cache miss), import it and export it back to + # produce a clean dump cached for the rest of the day's builds. This also + # validates the dump and supports "file-to-image" / "image-to-file" + # conversions. Configuration changes and DB updates are not applied, so + # the cached database stays in the same state as fetched. Only the + # primary parallel runner pushes a refreshed database container image. + - run: + name: Export DB after fetch + command: | + [ ! -f /tmp/fetch-db-success ] && echo "==> Database fetch semaphore file is missing. DB export will not proceed." && exit 0 + docker compose exec cli mkdir -p .data && docker compose cp -L .data/db.sql cli:/app/.data/db.sql || true + docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c "VORTEX_PROVISION_POST_OPERATIONS_SKIP=1 ./vendor/bin/vortex-provision" + ./vendor/bin/vortex-export-db db.sql + docker compose exec -T cli test -f /app/.data/db.sql && docker compose cp -L cli:/app/.data/db.sql .data/db.sql || true + no_output_timeout: 30m + + # Save the clean DB dump to the cache before the slow dev-dependency + # install and provisioning, so it lands sooner and survives a later + # failure. save_cache has no per-node runtime condition, and two parallel + # runners creating the same key concurrently fail the build, so only the + # primary runner may store it: stash .data aside on the non-primary + # runners to make their save a no-op, then restore it for provisioning. + - run: + name: Stash DB on non-primary runners so only the primary saves the cache + command: | + if [ "${CIRCLE_NODE_TOTAL:-1}" -gt 1 ] && [ "${CIRCLE_NODE_INDEX:-0}" -ne 0 ] && [ -e .data ]; then + echo "Stashing .data on non-primary node ${CIRCLE_NODE_INDEX} so only the primary saves the DB cache." + rm -rf .data.nosave + mv .data .data.nosave + fi + + - save_cache: + # Save cache per default branch and the timestamp. + # The cache will not be saved if it already exists. + # Note that the cache fallback flag is enabled for this case in order + # to save cache even if the fallback is not used when restoring it. + key: __VERSION__-{{ checksum "/tmp/db_cache_branch" }}-{{ checksum "/tmp/db_cache_fallback_yes" }}-{{ checksum "/tmp/db_cache_timestamp" }} + paths: + - ~/project/.data + + - run: + name: Restore stashed DB on non-primary runners for provisioning + command: | + if [ -e .data.nosave ]; then + echo "Restoring stashed .data on node ${CIRCLE_NODE_INDEX} for provisioning." + rm -rf .data + mv .data.nosave .data + fi + + - run: + name: Install development dependencies + command: | + docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c " \ + if [ -n \"\${PACKAGE_TOKEN:-}\" ]; then composer config --global --auth github-oauth.github.com \"\${PACKAGE_TOKEN}\"; fi && \ + COMPOSER_MEMORY_LIMIT=-1 composer --ansi install --prefer-dist" + docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c "yarn install --frozen-lockfile" + + - run: + name: Provision site + command: | + # Database dumps are removed from the runner once they are inside the + # container to avoid holding two copies for the rest of the job. + if [ -f .data/db.sql ]; then + docker compose exec cli mkdir -p .data + docker compose cp -L .data/db.sql cli:/app/.data/db.sql + rm -f .data/db.sql + fi + docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli ./vendor/bin/vortex-provision + no_output_timeout: 30m + + - run: + name: Test with Jest + command: docker compose exec -T cli bash -c "yarn test" || [ "${VORTEX_CI_JEST_IGNORE_FAILURE:-0}" -eq 1 ] + + - run: + name: Test with PHPUnit + command: | + [ "${CIRCLE_NODE_TOTAL:-1}" -gt 1 ] && [ "${CIRCLE_NODE_INDEX:-0}" -ne 0 ] && exit 0 + docker compose exec -T cli vendor/bin/phpunit || [ "${VORTEX_CI_PHPUNIT_IGNORE_FAILURE:-0}" -eq 1 ] + + - run: + name: Process PHPUnit logs and coverage + command: | + [ "${CIRCLE_NODE_TOTAL:-1}" -gt 1 ] && [ "${CIRCLE_NODE_INDEX:-0}" -ne 0 ] && exit 0 + mkdir -p "${VORTEX_CI_ARTIFACTS}" + if docker compose ps --services --filter "status=running" | grep -q cli && docker compose exec cli test -d /app/.logs; then + docker compose cp cli:/app/.logs/. "${VORTEX_CI_ARTIFACTS}/" + fi + + - run: + name: Extract code coverage + command: | + [ "${CIRCLE_NODE_TOTAL:-1}" -gt 1 ] && [ "${CIRCLE_NODE_INDEX:-0}" -ne 0 ] && exit 0 + RATE=$(grep -om1 'line-rate="[0-9.]*"' /tmp/artifacts/coverage/phpunit/cobertura.xml | tr -cd '0-9.') + PERCENT=$(awk "BEGIN {printf \"%.2f\", $RATE*100}") + echo "Coverage: $PERCENT% (threshold: ${VORTEX_CI_CODE_COVERAGE_THRESHOLD:-90}%)" + echo "export COVERAGE_PERCENT=${PERCENT}" >> "${BASH_ENV}" + + - run: + name: Post coverage summary as PR comment + command: | + [ "${CIRCLE_NODE_TOTAL:-1}" -gt 1 ] && [ "${CIRCLE_NODE_INDEX:-0}" -ne 0 ] && exit 0 + [ "${VORTEX_CI_CODE_COVERAGE_PR_COMMENT_SKIP:-0}" = "1" ] && exit 0 + .circleci/post-coverage-comment.sh /tmp/artifacts/coverage/phpunit/coverage.txt + + - run: + name: Check code coverage threshold + command: | + [ "${CIRCLE_NODE_TOTAL:-1}" -gt 1 ] && [ "${CIRCLE_NODE_INDEX:-0}" -ne 0 ] && exit 0 + if [ "${COVERAGE_PERCENT//.}" -lt "$((${VORTEX_CI_CODE_COVERAGE_THRESHOLD:-90}*100))" ]; then + echo "FAIL: coverage ${COVERAGE_PERCENT}% is below threshold ${VORTEX_CI_CODE_COVERAGE_THRESHOLD:-90}%" + exit 1 + fi + + - run: + name: Validate Single Directory Components + command: | + [ "${CIRCLE_NODE_TOTAL:-1}" -gt 1 ] && [ "${CIRCLE_NODE_INDEX:-0}" -ne 0 ] && exit 0 + [ "${VORTEX_FRONTEND_BUILD_SKIP:-0}" -eq 1 ] && exit 0 + output=$(docker compose exec -T cli vendor/bin/drush sdc-devel:validate "${DRUPAL_THEME}" 2>&1) + echo "${output}" + if echo "${output}" | grep -qE 'Critical|Errors?|Warnings?'; then + echo "SDC validation reported problems." + [ "${VORTEX_CI_SDC_DEVEL_IGNORE_FAILURE:-0}" -eq 1 ] || exit 1 + fi + + - run: + name: Test with Behat + command: | + if [ "${CIRCLE_NODE_TOTAL:-1}" -gt 1 ]; then export VORTEX_CI_BEHAT_PROFILE="${VORTEX_CI_BEHAT_PROFILE:-p${CIRCLE_NODE_INDEX}}"; fi + echo "Running with ${VORTEX_CI_BEHAT_PROFILE:-default} profile" + docker compose exec -T cli php -d memory_limit=-1 vendor/bin/behat --colors --strict --profile="${VORTEX_CI_BEHAT_PROFILE:-default}" || \ + docker compose exec -T cli php -d memory_limit=-1 vendor/bin/behat --colors --strict --rerun --profile="${VORTEX_CI_BEHAT_PROFILE:-default}" || \ + [ "${VORTEX_CI_BEHAT_IGNORE_FAILURE:-0}" -eq 1 ] + no_output_timeout: 30m + + - run: + name: Process test logs and artifacts + command: | + mkdir -p "${VORTEX_CI_TEST_RESULTS}" "${VORTEX_CI_ARTIFACTS}" + if docker compose ps --services --filter "status=running" | grep -q cli && docker compose exec cli test -d /app/.logs; then + docker compose cp cli:/app/.logs/. "${VORTEX_CI_ARTIFACTS}/" + if docker compose exec -T cli sh -c '[ -d /app/.logs/test_results/ ]'; then + docker compose cp cli:/app/.logs/test_results/. "${VORTEX_CI_TEST_RESULTS}/" + fi + fi + when: always + + - store_test_results: + path: *test_results + + - store_artifacts: + path: *artifacts + + # Build the deployable in parallel with the lint and test jobs. For artifact + # deployments, it exports the built codebase for the deploy job; for + # image-based hosting, a successful build validates that the production images + # are deployable. + build: + <<: *runner_config + steps: + - checkout + - *step_process_codebase_for_ci + - *load_variables_from_dotenv + + - run: + name: Install Vortex tooling + command: ./scripts/vortex-tooling.sh + + - *step_setup_remote_docker + - *step_install_docker_tools + + - run: + name: Login to container registry + command: ./vendor/bin/vortex-login-container-registry + + - run: + name: Build stack + command: docker compose up --detach && docker builder prune --all --force && docker image prune --force + + # Deploy primary branches. + deploy: &job_deploy + <<: *runner_config + steps: + + - add_ssh_keys: + fingerprints: + - *deploy_ssh_fingerprint + + - checkout + - *step_process_codebase_for_ci + - *load_variables_from_dotenv + + - run: + name: Check if deployment should be skipped + command: | + if [ "${CIRCLE_PULL_REQUEST}" != "" ] && echo "${CIRCLE_BRANCH}" | grep -q "^project/"; then + echo "Skipping deployment - PR from project/* branch" + circleci-agent step halt + fi + + - run: + name: Install Vortex tooling + command: ./scripts/vortex-tooling.sh + + - run: + name: Deploy + command: | + VORTEX_DEPLOY_BRANCH="${CIRCLE_BRANCH}" \ + VORTEX_DEPLOY_PR="$(echo ${CIRCLE_PULL_REQUEST} | cut -d'/' -f 7)" \ + VORTEX_DEPLOY_PR_HEAD=${CIRCLE_SHA1} \ + ./vendor/bin/vortex-deploy + no_output_timeout: 30m + + - store_artifacts: + path: *artifacts + + # Deploy tags. + deploy-tags: &job-deploy-tags + <<: *runner_config + steps: + + - add_ssh_keys: + fingerprints: + - *deploy_ssh_fingerprint + + - checkout + - *step_process_codebase_for_ci + - *load_variables_from_dotenv + + - run: + name: Install Vortex tooling + command: ./scripts/vortex-tooling.sh + + - run: + name: Deploy + command: VORTEX_DEPLOY_MODE="tag" ./vendor/bin/vortex-deploy + no_output_timeout: 30m + + - store_artifacts: + path: *artifacts + +################################################################################ +# WORKFLOWS +################################################################################ + +workflows: + # Commit workflow. Runs for every commit push to the remote repository. + commit: + jobs: + - lint: + filters: + tags: + only: /.*/ + - test: + filters: + tags: + only: /.*/ + - build: + filters: + tags: + only: /.*/ + - deploy: + requires: + - test + - lint + - build + filters: + branches: + # Allowed branches: + # - production, main, master, develop, ci, cisomething + # - project/description + # - deps/* + # - feature/description, feature/123-description + # - bugfix/description, bugfix/123-description + # - release/__VERSION__, release/__VERSION__ (per https://semver.org/) + # - release/2023-04-17, release/2023-04-17.123 (date-based) + # - hotfix/__VERSION__, hotfix/__VERSION__ (per https://semver.org/) + # - hotfix/2023-04-17, hotfix/2023-04-17.123 (date-based) + only: /^(production|main|master|develop)$|^project\/[a-zA-Z0-9\-\.]+|^(feature|bugfix)\/[a-zA-Z0-9\-\.\,_]+$|^ci.*|^(release|hotfix)\/[0-9]+(\.[0-9]+){2}(-rc\.[0-9]+)?$|^(release|hotfix)\/[0-9]{4}-[0-9]{2}-[0-9]{2}(\.[0-9]+)?$/ + tags: + ignore: /.*/ + - deploy-tags: + requires: + - test + - lint + - build + filters: + branches: + ignore: /.*/ + tags: + # Allowed tags: + # - __VERSION__, __VERSION__ (per https://semver.org/) + # - 2023-04-17, 2023-04-17.123 (date-based) + only: /^[0-9]+(\.[0-9]+){2}(-rc\.[0-9]+)?$|^[0-9]{4}-[0-9]{2}-[0-9]{2}(\.[0-9]+)?$/ diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_no_docker_linters_circleci/.circleci/post-coverage-comment.sh b/.vortex/cli/tests/Fixtures/handler_process/tools_no_docker_linters_circleci/.circleci/post-coverage-comment.sh new file mode 100755 index 000000000..d9e737409 --- /dev/null +++ b/.vortex/cli/tests/Fixtures/handler_process/tools_no_docker_linters_circleci/.circleci/post-coverage-comment.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env bash +## +## Post code coverage summary as a PR comment on GitHub. +## +## Minimizes previous coverage comments before posting a new one. +## +## Environment variables: +## CIRCLE_PULL_REQUEST - CircleCI PR URL. +## GITHUB_TOKEN - GitHub token for API access. +## CIRCLE_PROJECT_USERNAME - GitHub org/user. +## CIRCLE_PROJECT_REPONAME - GitHub repo name. +## VORTEX_CI_CODE_COVERAGE_THRESHOLD - Coverage threshold percentage (default: 90). +## +## Usage: +## .circleci/post-coverage-comment.sh /path/to/coverage.txt + +set -euo pipefail + +COVERAGE_FILE="${1:-}" + +if [ -z "${COVERAGE_FILE}" ] || [ ! -f "${COVERAGE_FILE}" ]; then + echo "ERROR: Coverage file not found: ${COVERAGE_FILE}" >&2 + exit 1 +fi + +if [ -z "${CIRCLE_PULL_REQUEST:-}" ]; then + echo "Not a pull request. Skipping." + exit 0 +fi + +if [ -z "${GITHUB_TOKEN:-}" ]; then + echo "GITHUB_TOKEN is not set. Skipping." + exit 0 +fi + +COVERAGE_SUMMARY=$(awk '/^ *Summary:/{f=1;next} f && /^$/{exit} f' "${COVERAGE_FILE}") +COVERAGE_DETAILS=$(awk 'BEGIN{s=0} /^ *Summary:/{s=1} s==1 && /^$/{s=2;next} s==2' "${COVERAGE_FILE}") +PR_NUMBER=$(echo "${CIRCLE_PULL_REQUEST}" | cut -d'/' -f 7) +REPO_SLUG="${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}" +THRESHOLD="${VORTEX_CI_CODE_COVERAGE_THRESHOLD:-90}" + +MARKER="" + +BODY=$(jq -n --arg body "**Code coverage** (threshold: ${THRESHOLD}%) +\`\`\` +${COVERAGE_SUMMARY} +\`\`\` +
+Per-class coverage + +\`\`\` +${COVERAGE_DETAILS} +\`\`\` +
+${MARKER}" '{body: $body}') + +# Minimize previous coverage comments. +COMMENTS_JSON=$(curl -s \ + -H "Authorization: token ${GITHUB_TOKEN}" \ + -H "Accept: application/vnd.github.v3+json" \ + "https://api.github.com/repos/${REPO_SLUG}/issues/${PR_NUMBER}/comments?per_page=100") + +EXISTING_IDS=$(echo "${COMMENTS_JSON}" | jq -r '.[] | select(.body | contains("")) | .node_id') + +for NODE_ID in ${EXISTING_IDS}; do + GRAPHQL_BODY=$(jq -n --arg id "${NODE_ID}" '{query: "mutation($id:ID!){minimizeComment(input:{subjectId:$id,classifier:OUTDATED}){minimizedComment{isMinimized}}}", variables: {id: $id}}') + curl -s -X POST \ + -H "Authorization: bearer ${GITHUB_TOKEN}" \ + -H "Content-Type: application/json" \ + "https://api.github.com/graphql" \ + -d "${GRAPHQL_BODY}" +done + +# Post new coverage comment. +curl -s -X POST \ + -H "Authorization: token ${GITHUB_TOKEN}" \ + -H "Accept: application/vnd.github.v3+json" \ + "https://api.github.com/repos/${REPO_SLUG}/issues/${PR_NUMBER}/comments" \ + -d "${BODY}" diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_no_docker_linters_circleci/.dockerignore b/.vortex/cli/tests/Fixtures/handler_process/tools_no_docker_linters_circleci/.dockerignore new file mode 100644 index 000000000..e0fa230b8 --- /dev/null +++ b/.vortex/cli/tests/Fixtures/handler_process/tools_no_docker_linters_circleci/.dockerignore @@ -0,0 +1,10 @@ +@@ -42,9 +42,6 @@ + **/.phpunit.cache + **/.twig-cs-fixer.cache + +-# Database cache key files written into the workspace by the CI workflow. +-db_cache_* +- + # Local override files. + docker-compose.override.yml + .env.local diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_no_docker_linters_circleci/.github/workflows/-build-test-deploy.yml b/.vortex/cli/tests/Fixtures/handler_process/tools_no_docker_linters_circleci/.github/workflows/-build-test-deploy.yml new file mode 100644 index 000000000..e69de29bb diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_no_docker_linters_circleci/README.md b/.vortex/cli/tests/Fixtures/handler_process/tools_no_docker_linters_circleci/README.md new file mode 100644 index 000000000..c803a0514 --- /dev/null +++ b/.vortex/cli/tests/Fixtures/handler_process/tools_no_docker_linters_circleci/README.md @@ -0,0 +1,9 @@ +@@ -9,7 +9,7 @@ + + Drupal 11 implementation of star wars for star wars Org + +-[![Build, Test and Deploy](https://github.com/star_wars_org/star_wars/actions/workflows/build-test-deploy.yml/badge.svg)](https://github.com/star_wars_org/star_wars/actions/workflows/build-test-deploy.yml) ++[![CircleCI](https://circleci.com/gh/star_wars_org/star_wars.svg?style=shield)](https://circleci.com/gh/star_wars_org/star_wars) + + ![Drupal 11](https://img.shields.io/badge/Drupal-11-0678BE?logo=drupal&logoColor=white) + diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_no_docker_linters_circleci/docs/ci.md b/.vortex/cli/tests/Fixtures/handler_process/tools_no_docker_linters_circleci/docs/ci.md new file mode 100644 index 000000000..542f58060 --- /dev/null +++ b/.vortex/cli/tests/Fixtures/handler_process/tools_no_docker_linters_circleci/docs/ci.md @@ -0,0 +1,12 @@ +@@ -5,9 +5,9 @@ + + ## CI provider + +-This project uses [GitHub Actions](https://github.com/features/actions). ++This project uses [CircleCI](https://circleci.com/). + +-See [GitHub Actions documentation](https://www.vortextemplate.com/docs/continuous-integration/github-actions) ++See [CircleCI documentation](https://www.vortextemplate.com/docs/continuous-integration/circleci) + for setup and configuration details. + + ## Project-specific configuration diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_no_docker_linters_circleci/tests/phpunit/CircleCiConfigTest.php b/.vortex/cli/tests/Fixtures/handler_process/tools_no_docker_linters_circleci/tests/phpunit/CircleCiConfigTest.php new file mode 100644 index 000000000..e1ca36aa7 --- /dev/null +++ b/.vortex/cli/tests/Fixtures/handler_process/tools_no_docker_linters_circleci/tests/phpunit/CircleCiConfigTest.php @@ -0,0 +1,283 @@ +config = Yaml::decode($file); + } + + /** + * Tests for deploy branch regex. + * + * @see https://semver.org/ + */ + #[DataProvider('dataProviderDeployBranchRegex')] + public function testDeployBranchRegex(string $branch, bool $expected = TRUE): void { + $pattern = $this->getCommitWorkflowJob('deploy')['filters']['branches']['only']; + $result = preg_match($pattern, $branch); + $this->assertEquals($expected, $result); + } + + /** + * Data provider for testDeployBranchRegex(). + */ + public static function dataProviderDeployBranchRegex(): \Iterator { + // Positive branches. + yield ['production']; + yield ['main']; + yield ['master']; + yield ['develop']; + + yield ['ci']; + yield ['cisomething']; + + yield ['release/__VERSION__']; + yield ['release/__VERSION__']; + yield ['hotfix/__VERSION__']; + yield ['hotfix/__VERSION__']; + + yield ['release/2023-04-17']; + yield ['release/2023-04-17.1']; + yield ['hotfix/2023-04-17']; + yield ['hotfix/2023-04-17.1']; + + yield ['feature/description']; + yield ['feature/Description']; + yield ['feature/Description-With-Hyphens']; + yield ['feature/Description-With_Underscores']; + yield ['feature/123-description']; + yield ['feature/123-Description']; + yield ['feature/UNDERSCORES_UNDERSCORES']; + yield ['feature/123-Description-With_UNDERSCORES']; + yield ['feature/1.x']; + yield ['feature/0.x']; + yield ['feature/0.1.x']; + yield ['feature/__VERSION__.x']; + yield ['feature/1.x-description']; + yield ['feature/0.x-description']; + yield ['feature/0.1.x-description']; + yield ['feature/__VERSION__.x-description']; + + yield ['bugfix/description']; + yield ['bugfix/Description']; + yield ['bugfix/Description-With-Hyphens']; + yield ['bugfix/Description-With_Underscores']; + yield ['bugfix/123-description']; + yield ['bugfix/123-Description']; + yield ['bugfix/UNDERSCORES_UNDERSCORES']; + yield ['bugfix/123-Description-With_UNDERSCORES']; + yield ['bugfix/1.x']; + yield ['bugfix/0.x']; + yield ['bugfix/0.1.x']; + yield ['bugfix/__VERSION__.x']; + yield ['bugfix/1.x-description']; + yield ['bugfix/0.x-description']; + yield ['bugfix/0.1.x-description']; + yield ['bugfix/__VERSION__.x-description']; + + yield ['project/description']; + yield ['project/Description']; + yield ['project/Description-With-Hyphens']; + yield ['project/123-description']; + yield ['project/123-Description']; + yield ['project/1.x']; + yield ['project/0.x']; + yield ['project/0.1.x']; + yield ['project/__VERSION__.x']; + yield ['project/1.x-description']; + yield ['project/0.x-description']; + yield ['project/0.1.x-description']; + yield ['project/__VERSION__.x-description']; + + // Negative branches. + yield ['something', FALSE]; + yield ['premain', FALSE]; + yield ['premaster', FALSE]; + yield ['predevelop', FALSE]; + yield ['mainpost', FALSE]; + yield ['masterpost', FALSE]; + yield ['developpost', FALSE]; + yield ['premainpost', FALSE]; + yield ['premasterpost', FALSE]; + yield ['predeveloppost', FALSE]; + + yield ['preci', FALSE]; + yield ['precipost', FALSE]; + + yield ['deps/something', FALSE]; + yield ['deps', FALSE]; + yield ['predeps', FALSE]; + yield ['depspost', FALSE]; + yield ['predepspost', FALSE]; + + yield ['feature', FALSE]; + yield ['release', FALSE]; + yield ['hotfix', FALSE]; + yield ['prefeature', FALSE]; + yield ['prerelease', FALSE]; + yield ['prehotfix', FALSE]; + yield ['featurepost', FALSE]; + yield ['releasepost', FALSE]; + yield ['hotfixpost', FALSE]; + yield ['prefeaturepost', FALSE]; + yield ['prereleasepost', FALSE]; + yield ['prehotfixpost', FALSE]; + + yield ['release/123', FALSE]; + yield ['release/123.456', FALSE]; + yield ['hotfix/123', FALSE]; + yield ['hotfix/123.456', FALSE]; + + yield ['release/202-04-17', FALSE]; + yield ['release/2023-4-17', FALSE]; + yield ['release/2023-04-1', FALSE]; + yield ['release/pre2023-04-17', FALSE]; + yield ['release/2023-04-17post', FALSE]; + yield ['release/pre2023-04-17post', FALSE]; + + yield ['hotfix/202-04-17', FALSE]; + yield ['hotfix/2023-4-17', FALSE]; + yield ['hotfix/2023-04-1', FALSE]; + yield ['hotfix/pre2023-04-17', FALSE]; + yield ['hotfix/2023-04-17post', FALSE]; + yield ['hotfix/pre2023-04-17post', FALSE]; + + yield ['release/__VERSION__', FALSE]; + yield ['release/__VERSION__', FALSE]; + yield ['release/__VERSION__', FALSE]; + yield ['release/__VERSION__', FALSE]; + yield ['release/__VERSION__', FALSE]; + yield ['release/__VERSION__', FALSE]; + yield ['release/__VERSION__', FALSE]; + yield ['release/__VERSION__', FALSE]; + yield ['release/__VERSION__', FALSE]; + yield ['release/__VERSION__', FALSE]; + + yield ['hotfix/__VERSION__', FALSE]; + yield ['hotfix/__VERSION__', FALSE]; + yield ['hotfix/__VERSION__', FALSE]; + yield ['hotfix/__VERSION__', FALSE]; + yield ['hotfix/__VERSION__', FALSE]; + yield ['hotfix/__VERSION__', FALSE]; + yield ['hotfix/__VERSION__', FALSE]; + yield ['hotfix/__VERSION__', FALSE]; + yield ['hotfix/__VERSION__', FALSE]; + yield ['hotfix/__VERSION__', FALSE]; + + yield ['prefeature/something', FALSE]; + yield ['prefbugfix/something', FALSE]; + yield ['prerelease/something', FALSE]; + yield ['prehotfix/something', FALSE]; + yield ['featurepost/something', FALSE]; + yield ['bugfixpost/something', FALSE]; + yield ['releasepost/something', FALSE]; + yield ['hotfixpost/something', FALSE]; + yield ['prefeaturepost/something', FALSE]; + yield ['prebugfixpost/something', FALSE]; + yield ['prereleasepost/something', FALSE]; + yield ['prehotfixpost/something', FALSE]; + yield ['preproject/something', FALSE]; + yield ['projectpost/something', FALSE]; + } + + /** + * Tests for deploy tag regex. + * + * @see https://semver.org/ + */ + #[DataProvider('dataProviderDeployTagRegex')] + public function testDeployTagRegex(string $branch, bool $expected = TRUE): void { + $pattern = $this->getCommitWorkflowJob('deploy-tags')['filters']['tags']['only']; + $result = preg_match($pattern, $branch); + $this->assertEquals($expected, $result); + } + + /** + * Data provider for testDeployTagRegex(). + */ + public static function dataProviderDeployTagRegex(): \Iterator { + // Positive tags. + yield ['__VERSION__']; + yield ['__VERSION__']; + yield ['2023-04-17']; + yield ['2023-04-17.123']; + + // Negative tags. + yield ['123', FALSE]; + yield ['123.456', FALSE]; + yield ['__VERSION__', FALSE]; + yield ['__VERSION__', FALSE]; + yield ['__VERSION__', FALSE]; + yield ['__VERSION__', FALSE]; + yield ['__VERSION__', FALSE]; + + yield ['202-04-17', FALSE]; + yield ['2023-0-17', FALSE]; + yield ['2023-04-1', FALSE]; + yield ['pre2023-04-17', FALSE]; + yield ['2023-04-17post', FALSE]; + yield ['pre2023-04-17post', FALSE]; + yield ['2023-04-17.123.', FALSE]; + yield ['2023-04-17.pre123', FALSE]; + yield ['2023-04-17.pre123post', FALSE]; + yield ['2023-04-17.123post', FALSE]; + } + + /** + * Get a commit workflow job configuration by its job name. + * + * Looks the job up by name rather than by position, so the assertions stay + * correct regardless of how many other jobs surround it in the workflow. + * + * @param string $name + * The job name (the single key of a workflow job entry). + * + * @return mixed + * The job configuration. + */ + protected function getCommitWorkflowJob(string $name): mixed { + $jobs = $this->config['workflows']['commit']['jobs']; + + foreach ($jobs as $job) { + if (is_array($job) && array_key_exists($name, $job)) { + return $job[$name]; + } + } + + throw new \RuntimeException(sprintf('Job "%s" not found in the commit workflow.', $name)); + } + +} diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_no_docker_linters_circleci/tests/phpunit/Drupal/EnvironmentSettingsTest.php b/.vortex/cli/tests/Fixtures/handler_process/tools_no_docker_linters_circleci/tests/phpunit/Drupal/EnvironmentSettingsTest.php new file mode 100644 index 000000000..aa0127049 --- /dev/null +++ b/.vortex/cli/tests/Fixtures/handler_process/tools_no_docker_linters_circleci/tests/phpunit/Drupal/EnvironmentSettingsTest.php @@ -0,0 +1,12 @@ +@@ -332,9 +332,9 @@ + } + + /** +- * Test per-environment settings for GitHub Actions. ++ * Test per-environment settings for CircleCI. + */ +- public function testEnvironmentGha(): void { ++ public function testEnvironmentCircleCi(): void { + $this->setEnvVars([ + 'CI' => TRUE, + ]); diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_no_eslint_circleci/.circleci/config.yml b/.vortex/cli/tests/Fixtures/handler_process/tools_no_eslint_circleci/.circleci/config.yml index dd4a38784..200ab7eff 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/tools_no_eslint_circleci/.circleci/config.yml +++ b/.vortex/cli/tests/Fixtures/handler_process/tools_no_eslint_circleci/.circleci/config.yml @@ -122,7 +122,7 @@ jobs: name: Lint Dockerfiles with Hadolint command: | for file in $(find .docker \( -name 'Dockerfile' -o -name '*.dockerfile' \)); do - echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] + echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint:__VERSION__ || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] done - run: diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_no_eslint_no_theme/.docker/cli.dockerfile b/.vortex/cli/tests/Fixtures/handler_process/tools_no_eslint_no_theme/.docker/cli.dockerfile index c6e5e90e0..4abfebbec 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/tools_no_eslint_no_theme/.docker/cli.dockerfile +++ b/.vortex/cli/tests/Fixtures/handler_process/tools_no_eslint_no_theme/.docker/cli.dockerfile @@ -1,4 +1,4 @@ -@@ -37,7 +37,7 @@ +@@ -36,7 +36,7 @@ ARG DRUPAL_TEMPORARY_FILES="${TMP:-/tmp}" ENV DRUPAL_TEMPORARY_FILES=${DRUPAL_TEMPORARY_FILES} @@ -8,8 +8,8 @@ ARG VORTEX_FRONTEND_BUILD_SKIP="0" @@ -94,12 +94,5 @@ - # Create file directories and set correct permissions. + # hadolint ignore=SC2174 # only the leaf directory needs the mode RUN mkdir -p -m 2775 "/app/${WEBROOT}/${DRUPAL_PUBLIC_FILES}" "/app/${WEBROOT}/${DRUPAL_PRIVATE_FILES}" "${DRUPAL_TEMPORARY_FILES}" - -RUN if [ "${VORTEX_FRONTEND_BUILD_SKIP}" != "1" ]; then \ diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_no_hadolint/.github/workflows/build-test-deploy.yml b/.vortex/cli/tests/Fixtures/handler_process/tools_no_hadolint/.github/workflows/build-test-deploy.yml new file mode 100644 index 000000000..3ba1c4d7d --- /dev/null +++ b/.vortex/cli/tests/Fixtures/handler_process/tools_no_hadolint/.github/workflows/build-test-deploy.yml @@ -0,0 +1,14 @@ +@@ -106,13 +106,6 @@ + - name: Login to container registry + run: ./vendor/bin/vortex-login-container-registry + +- - name: Lint Dockerfiles with Hadolint +- run: | +- find .docker \( -name 'Dockerfile' -o -name '*.dockerfile' \) | while read -r file; do +- echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint:__VERSION__ +- done +- continue-on-error: ${{ vars.VORTEX_CI_HADOLINT_IGNORE_FAILURE == '1' }} +- + - name: Lint Docker Compose files with DCLint + run: docker run --rm -v "${PWD}":/app zavoloklom/dclint:__VERSION__ . + continue-on-error: ${{ vars.VORTEX_CI_DCLINT_IGNORE_FAILURE == '1' }} diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_no_hadolint_circleci/.circleci/config.yml b/.vortex/cli/tests/Fixtures/handler_process/tools_no_hadolint_circleci/.circleci/config.yml new file mode 100644 index 000000000..dc57cd0a4 --- /dev/null +++ b/.vortex/cli/tests/Fixtures/handler_process/tools_no_hadolint_circleci/.circleci/config.yml @@ -0,0 +1,529 @@ +# CircleCI configuration file for building, testing, and deploying. +# +# This configuration file uses the "docker" executor to run the Docker stack. +# +# A "runner" container, created from a specified container image, is used to +# checkout source code and run commands defined in this file. Application Docker +# containers defined in `docker-compose.yml` run on a *remote* Docker server +# controlled by CircleCI. +# The "runner" container uses Docker client to control the remote Docker server. +version: '2.1' + +aliases: + + # SSH key fingerprint to deploy code. + # Replace this key fingerprint with your own and remove this comment. + - &deploy_ssh_fingerprint "SHA256:6d+U5QubT0eAWz+4N2wt+WM2qx6o4cvyvQ6xILETJ84" + + # Shared runner container configuration applied to each job. + - &runner_config + working_directory: &working_directory ~/project + environment: + VORTEX_DEPLOY_SSH_FINGERPRINT: *deploy_ssh_fingerprint + docker: + # Run each job on CircleCI's public PHP convenience image. It ships PHP, + # Composer and the Docker CLI (with the Compose plugin), which drive the + # dockerized stack against the remote Docker engine (see setup_remote_docker). + # https://circleci.com/developer/images/image/cimg/php + - image: cimg/php:__VERSION__ + environment: + # Set runner timezone via UI to ensure that executed operations use correct timestamps. + # https://en.wikipedia.org/wiki/List_of_tz_database_time_zones + TZ: UTC + # Set runner terminal capabilities. + TERM: xterm-256color + # Disable strict host key checking for SSH connections. + VORTEX_SSH_DISABLE_STRICT_HOST_KEY_CHECKING: "1" + # Remove all SSH keys from the runner container. + VORTEX_SSH_REMOVE_ALL_KEYS: "1" + # How often to refresh the cache of the DB dump. Refer to `date` command. + VORTEX_CI_DB_CACHE_TIMESTAMP: +%Y%m%d + # Use previous database caches on this branch as a fallback if the above cache + # does not match (for example, the cache is available only from the previous + # day). If "no" is set, the cache will be rebuilt from scratch. + VORTEX_CI_DB_CACHE_FALLBACK: "yes" + # Which branch to use as a source of DB caches. + VORTEX_CI_DB_CACHE_BRANCH: "develop" + # Directory to store test results. + VORTEX_CI_TEST_RESULTS: &test_results /tmp/tests + # Directory to store test artifacts. + VORTEX_CI_ARTIFACTS: &artifacts /tmp/artifacts + # Check only minimal stack requirements. + VORTEX_DOCTOR_CHECK_MINIMAL: 1 + # CI runner resource class. + # https://circleci.com/docs/2.0/configuration-reference/#resource_class + # Change to 'large' for faster builds. + resource_class: medium + + - &step_setup_remote_docker + setup_remote_docker: + # Docker Layer Caching allows to significantly speed up builds by caching + # images built during previous runs. + # https://circleci.com/docs/2.0/docker-layer-caching/ + docker_layer_caching: false + version: default + + - &step_install_docker_tools + run: + name: Install pinned Docker Compose and Buildx CLI plugins + # The convenience image ships older Docker CLI plugins than the stack + # relies on; install the versions that resolve 'service:' build contexts. + command: | + mkdir -p "${HOME}/.docker/cli-plugins" + curl -fsSL -o "${HOME}/.docker/cli-plugins/docker-compose" https://github.com/docker/compose/releases/download/__VERSION__/docker-compose-linux-x86_64 + curl -fsSL -o "${HOME}/.docker/cli-plugins/docker-buildx" https://github.com/docker/buildx/releases/download/__VERSION__/buildx-__VERSION__.linux-amd64 + chmod +x "${HOME}/.docker/cli-plugins/docker-compose" "${HOME}/.docker/cli-plugins/docker-buildx" + docker compose version + docker buildx version + + - &step_process_codebase_for_ci + run: + name: Process codebase to run in CI + command: | + find . -name "docker-compose.yml" -print0 | xargs -0 -I {} sh -c "sed -i -e ''/###/d'' {} && sed -i -e ''s/##//'' {}" + mkdir -p /tmp/workspace/code + + - &load_variables_from_dotenv + run: + name: Load environment variables from .env file + # Load variables from .env file, respecting existing values, and make them available for the next steps. + command: t=$(mktemp) && export -p >"${t}" && set -a && . ./.env && set +a && . "${t}" && export -p >> "$BASH_ENV" + +################################################################################ +# JOBS +################################################################################ + +jobs: + + # Lint job runs in parallel with database and build jobs. + lint: + <<: *runner_config + steps: + - checkout + - *step_process_codebase_for_ci + - *load_variables_from_dotenv + + - run: + name: Validate Composer configuration + command: composer validate --strict || [ "${VORTEX_CI_COMPOSER_VALIDATE_IGNORE_FAILURE:-0}" -eq 1 ] + + - run: + name: Install Vortex tooling + command: ./scripts/vortex-tooling.sh + + - *step_setup_remote_docker + - *step_install_docker_tools + + - run: + name: Login to container registry + command: ./vendor/bin/vortex-login-container-registry + + - run: + name: Lint Docker Compose files with DCLint + command: docker run --rm -v "${PWD}":/app zavoloklom/dclint:__VERSION__ . || [ "${VORTEX_CI_DCLINT_IGNORE_FAILURE:-0}" -eq 1 ] + + - run: + name: Scan for committed secrets with Gitleaks + command: docker run --rm -v "${PWD}":/repo -w /repo ghcr.io/gitleaks/gitleaks:__VERSION__ dir . --no-banner || [ "${VORTEX_CI_GITLEAKS_IGNORE_FAILURE:-0}" -eq 1 ] + + - run: + name: Build stack + command: docker compose up --no-deps --detach cli + + - run: + name: Install development dependencies + command: | + docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c " \ + if [ -n \"\${PACKAGE_TOKEN:-}\" ]; then composer config --global --auth github-oauth.github.com \"\${PACKAGE_TOKEN}\"; fi && \ + COMPOSER_MEMORY_LIMIT=-1 composer --ansi install --prefer-dist" + docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c "yarn install --frozen-lockfile" + + - run: + name: Audit Composer packages + command: docker compose exec -T cli composer audit || [ "${VORTEX_CI_COMPOSER_AUDIT_IGNORE_FAILURE:-0}" -eq 1 ] + + - run: + name: Validate Composer configuration is normalized + command: docker compose exec -T cli composer normalize --dry-run || [ "${VORTEX_CI_COMPOSER_NORMALIZE_IGNORE_FAILURE:-0}" -eq 1 ] + + - run: + name: Lint code with PHPCS + command: docker compose exec -T cli vendor/bin/phpcs || [ "${VORTEX_CI_PHPCS_IGNORE_FAILURE:-0}" -eq 1 ] + + - run: + name: Lint code with PHPStan + command: docker compose exec -T cli vendor/bin/phpstan || [ "${VORTEX_CI_PHPSTAN_IGNORE_FAILURE:-0}" -eq 1 ] + + - run: + name: Lint code with Rector + command: docker compose exec -T cli vendor/bin/rector --dry-run || [ "${VORTEX_CI_RECTOR_IGNORE_FAILURE:-0}" -eq 1 ] + + - run: + name: Lint code with Twig CS Fixer + command: docker compose exec -T cli vendor/bin/twig-cs-fixer || [ "${VORTEX_CI_TWIG_CS_FIXER_IGNORE_FAILURE:-0}" -eq 1 ] + + - run: + name: Lint code with Gherkin Lint + command: docker compose exec -T cli vendor/bin/gherkinlint lint tests/behat/features || [ "${VORTEX_CI_GHERKIN_LINT_IGNORE_FAILURE:-0}" -eq 1 ] + + - run: + name: Lint module code with NodeJS linters + command: docker compose exec -T cli bash -c "yarn run lint" || [ "${VORTEX_CI_NODEJS_LINT_IGNORE_FAILURE:-0}" -eq 1 ] + + - run: + name: Lint theme code with NodeJS linters + command: | + [ "${VORTEX_FRONTEND_BUILD_SKIP:-0}" -eq 1 ] && exit 0 + docker compose exec -T cli bash -c "yarn --cwd=\${WEBROOT}/themes/custom/\${DRUPAL_THEME} run lint" || [ "${VORTEX_CI_NODEJS_LINT_IGNORE_FAILURE:-0}" -eq 1 ] + + # Test the provisioned site. Runs in parallel with the lint and build jobs. + # Provisioning and testing happen within the same job to save time on + # re-provisioning. + test: &job_test + <<: *runner_config + parallelism: 2 + steps: + - attach_workspace: + at: /tmp/workspace + + - checkout + - *step_process_codebase_for_ci + - *load_variables_from_dotenv + + - run: + name: Validate Composer configuration + command: composer validate --strict || [ "${VORTEX_CI_COMPOSER_VALIDATE_IGNORE_FAILURE:-0}" -eq 1 ] + + - run: + name: Install Vortex tooling + command: ./scripts/vortex-tooling.sh + + - run: + name: Set cache keys for database caching + command: | + echo "${VORTEX_CI_DB_CACHE_BRANCH}" | tee /tmp/db_cache_branch + echo "yes" | tee /tmp/db_cache_fallback_yes + echo "$(date ${VORTEX_CI_DB_CACHE_TIMESTAMP})" | tee /tmp/db_cache_timestamp + + - restore_cache: + keys: + # Use cached artifacts from previous builds of this branch. + # https://circleci.com/docs/2.0/caching/#restoring-cache + - __VERSION__-{{ checksum "/tmp/db_cache_branch" }}-{{ checksum "/tmp/db_cache_fallback_yes" }}-{{ checksum "/tmp/db_cache_timestamp" }} + - __VERSION__-{{ checksum "/tmp/db_cache_branch" }}-{{ checksum "/tmp/db_cache_fallback_yes" }}- + + - *step_setup_remote_docker + - *step_install_docker_tools + + - run: + name: Login to container registry + command: ./vendor/bin/vortex-login-container-registry + + # On the first build of the day (cache miss), fetch a fresh DB dump. + # On subsequent builds, the restored cache already contains the dump and + # the fetch script skips itself, leaving no semaphore file behind. + - run: + name: Fetch DB + command: VORTEX_FETCH_DB_SEMAPHORE=/tmp/fetch-db-success ./vendor/bin/vortex-fetch-db + no_output_timeout: 30m + + - run: + name: Build stack + command: docker compose up --detach && docker builder prune --all --force && docker image prune --force + + # Execute commands after the database fetch finished: if a fresh DB + # dump was fetched (cache miss), import it and export it back to + # produce a clean dump cached for the rest of the day's builds. This also + # validates the dump and supports "file-to-image" / "image-to-file" + # conversions. Configuration changes and DB updates are not applied, so + # the cached database stays in the same state as fetched. Only the + # primary parallel runner pushes a refreshed database container image. + - run: + name: Export DB after fetch + command: | + [ ! -f /tmp/fetch-db-success ] && echo "==> Database fetch semaphore file is missing. DB export will not proceed." && exit 0 + docker compose exec cli mkdir -p .data && docker compose cp -L .data/db.sql cli:/app/.data/db.sql || true + docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c "VORTEX_PROVISION_POST_OPERATIONS_SKIP=1 ./vendor/bin/vortex-provision" + ./vendor/bin/vortex-export-db db.sql + docker compose exec -T cli test -f /app/.data/db.sql && docker compose cp -L cli:/app/.data/db.sql .data/db.sql || true + no_output_timeout: 30m + + # Save the clean DB dump to the cache before the slow dev-dependency + # install and provisioning, so it lands sooner and survives a later + # failure. save_cache has no per-node runtime condition, and two parallel + # runners creating the same key concurrently fail the build, so only the + # primary runner may store it: stash .data aside on the non-primary + # runners to make their save a no-op, then restore it for provisioning. + - run: + name: Stash DB on non-primary runners so only the primary saves the cache + command: | + if [ "${CIRCLE_NODE_TOTAL:-1}" -gt 1 ] && [ "${CIRCLE_NODE_INDEX:-0}" -ne 0 ] && [ -e .data ]; then + echo "Stashing .data on non-primary node ${CIRCLE_NODE_INDEX} so only the primary saves the DB cache." + rm -rf .data.nosave + mv .data .data.nosave + fi + + - save_cache: + # Save cache per default branch and the timestamp. + # The cache will not be saved if it already exists. + # Note that the cache fallback flag is enabled for this case in order + # to save cache even if the fallback is not used when restoring it. + key: __VERSION__-{{ checksum "/tmp/db_cache_branch" }}-{{ checksum "/tmp/db_cache_fallback_yes" }}-{{ checksum "/tmp/db_cache_timestamp" }} + paths: + - ~/project/.data + + - run: + name: Restore stashed DB on non-primary runners for provisioning + command: | + if [ -e .data.nosave ]; then + echo "Restoring stashed .data on node ${CIRCLE_NODE_INDEX} for provisioning." + rm -rf .data + mv .data.nosave .data + fi + + - run: + name: Install development dependencies + command: | + docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c " \ + if [ -n \"\${PACKAGE_TOKEN:-}\" ]; then composer config --global --auth github-oauth.github.com \"\${PACKAGE_TOKEN}\"; fi && \ + COMPOSER_MEMORY_LIMIT=-1 composer --ansi install --prefer-dist" + docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c "yarn install --frozen-lockfile" + + - run: + name: Provision site + command: | + # Database dumps are removed from the runner once they are inside the + # container to avoid holding two copies for the rest of the job. + if [ -f .data/db.sql ]; then + docker compose exec cli mkdir -p .data + docker compose cp -L .data/db.sql cli:/app/.data/db.sql + rm -f .data/db.sql + fi + docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli ./vendor/bin/vortex-provision + no_output_timeout: 30m + + - run: + name: Test with Jest + command: docker compose exec -T cli bash -c "yarn test" || [ "${VORTEX_CI_JEST_IGNORE_FAILURE:-0}" -eq 1 ] + + - run: + name: Test with PHPUnit + command: | + [ "${CIRCLE_NODE_TOTAL:-1}" -gt 1 ] && [ "${CIRCLE_NODE_INDEX:-0}" -ne 0 ] && exit 0 + docker compose exec -T cli vendor/bin/phpunit || [ "${VORTEX_CI_PHPUNIT_IGNORE_FAILURE:-0}" -eq 1 ] + + - run: + name: Process PHPUnit logs and coverage + command: | + [ "${CIRCLE_NODE_TOTAL:-1}" -gt 1 ] && [ "${CIRCLE_NODE_INDEX:-0}" -ne 0 ] && exit 0 + mkdir -p "${VORTEX_CI_ARTIFACTS}" + if docker compose ps --services --filter "status=running" | grep -q cli && docker compose exec cli test -d /app/.logs; then + docker compose cp cli:/app/.logs/. "${VORTEX_CI_ARTIFACTS}/" + fi + + - run: + name: Extract code coverage + command: | + [ "${CIRCLE_NODE_TOTAL:-1}" -gt 1 ] && [ "${CIRCLE_NODE_INDEX:-0}" -ne 0 ] && exit 0 + RATE=$(grep -om1 'line-rate="[0-9.]*"' /tmp/artifacts/coverage/phpunit/cobertura.xml | tr -cd '0-9.') + PERCENT=$(awk "BEGIN {printf \"%.2f\", $RATE*100}") + echo "Coverage: $PERCENT% (threshold: ${VORTEX_CI_CODE_COVERAGE_THRESHOLD:-90}%)" + echo "export COVERAGE_PERCENT=${PERCENT}" >> "${BASH_ENV}" + + - run: + name: Post coverage summary as PR comment + command: | + [ "${CIRCLE_NODE_TOTAL:-1}" -gt 1 ] && [ "${CIRCLE_NODE_INDEX:-0}" -ne 0 ] && exit 0 + [ "${VORTEX_CI_CODE_COVERAGE_PR_COMMENT_SKIP:-0}" = "1" ] && exit 0 + .circleci/post-coverage-comment.sh /tmp/artifacts/coverage/phpunit/coverage.txt + + - run: + name: Check code coverage threshold + command: | + [ "${CIRCLE_NODE_TOTAL:-1}" -gt 1 ] && [ "${CIRCLE_NODE_INDEX:-0}" -ne 0 ] && exit 0 + if [ "${COVERAGE_PERCENT//.}" -lt "$((${VORTEX_CI_CODE_COVERAGE_THRESHOLD:-90}*100))" ]; then + echo "FAIL: coverage ${COVERAGE_PERCENT}% is below threshold ${VORTEX_CI_CODE_COVERAGE_THRESHOLD:-90}%" + exit 1 + fi + + - run: + name: Validate Single Directory Components + command: | + [ "${CIRCLE_NODE_TOTAL:-1}" -gt 1 ] && [ "${CIRCLE_NODE_INDEX:-0}" -ne 0 ] && exit 0 + [ "${VORTEX_FRONTEND_BUILD_SKIP:-0}" -eq 1 ] && exit 0 + output=$(docker compose exec -T cli vendor/bin/drush sdc-devel:validate "${DRUPAL_THEME}" 2>&1) + echo "${output}" + if echo "${output}" | grep -qE 'Critical|Errors?|Warnings?'; then + echo "SDC validation reported problems." + [ "${VORTEX_CI_SDC_DEVEL_IGNORE_FAILURE:-0}" -eq 1 ] || exit 1 + fi + + - run: + name: Test with Behat + command: | + if [ "${CIRCLE_NODE_TOTAL:-1}" -gt 1 ]; then export VORTEX_CI_BEHAT_PROFILE="${VORTEX_CI_BEHAT_PROFILE:-p${CIRCLE_NODE_INDEX}}"; fi + echo "Running with ${VORTEX_CI_BEHAT_PROFILE:-default} profile" + docker compose exec -T cli php -d memory_limit=-1 vendor/bin/behat --colors --strict --profile="${VORTEX_CI_BEHAT_PROFILE:-default}" || \ + docker compose exec -T cli php -d memory_limit=-1 vendor/bin/behat --colors --strict --rerun --profile="${VORTEX_CI_BEHAT_PROFILE:-default}" || \ + [ "${VORTEX_CI_BEHAT_IGNORE_FAILURE:-0}" -eq 1 ] + no_output_timeout: 30m + + - run: + name: Process test logs and artifacts + command: | + mkdir -p "${VORTEX_CI_TEST_RESULTS}" "${VORTEX_CI_ARTIFACTS}" + if docker compose ps --services --filter "status=running" | grep -q cli && docker compose exec cli test -d /app/.logs; then + docker compose cp cli:/app/.logs/. "${VORTEX_CI_ARTIFACTS}/" + if docker compose exec -T cli sh -c '[ -d /app/.logs/test_results/ ]'; then + docker compose cp cli:/app/.logs/test_results/. "${VORTEX_CI_TEST_RESULTS}/" + fi + fi + when: always + + - store_test_results: + path: *test_results + + - store_artifacts: + path: *artifacts + + # Build the deployable in parallel with the lint and test jobs. For artifact + # deployments, it exports the built codebase for the deploy job; for + # image-based hosting, a successful build validates that the production images + # are deployable. + build: + <<: *runner_config + steps: + - checkout + - *step_process_codebase_for_ci + - *load_variables_from_dotenv + + - run: + name: Install Vortex tooling + command: ./scripts/vortex-tooling.sh + + - *step_setup_remote_docker + - *step_install_docker_tools + + - run: + name: Login to container registry + command: ./vendor/bin/vortex-login-container-registry + + - run: + name: Build stack + command: docker compose up --detach && docker builder prune --all --force && docker image prune --force + + # Deploy primary branches. + deploy: &job_deploy + <<: *runner_config + steps: + + - add_ssh_keys: + fingerprints: + - *deploy_ssh_fingerprint + + - checkout + - *step_process_codebase_for_ci + - *load_variables_from_dotenv + + - run: + name: Check if deployment should be skipped + command: | + if [ "${CIRCLE_PULL_REQUEST}" != "" ] && echo "${CIRCLE_BRANCH}" | grep -q "^project/"; then + echo "Skipping deployment - PR from project/* branch" + circleci-agent step halt + fi + + - run: + name: Install Vortex tooling + command: ./scripts/vortex-tooling.sh + + - run: + name: Deploy + command: | + VORTEX_DEPLOY_BRANCH="${CIRCLE_BRANCH}" \ + VORTEX_DEPLOY_PR="$(echo ${CIRCLE_PULL_REQUEST} | cut -d'/' -f 7)" \ + VORTEX_DEPLOY_PR_HEAD=${CIRCLE_SHA1} \ + ./vendor/bin/vortex-deploy + no_output_timeout: 30m + + - store_artifacts: + path: *artifacts + + # Deploy tags. + deploy-tags: &job-deploy-tags + <<: *runner_config + steps: + + - add_ssh_keys: + fingerprints: + - *deploy_ssh_fingerprint + + - checkout + - *step_process_codebase_for_ci + - *load_variables_from_dotenv + + - run: + name: Install Vortex tooling + command: ./scripts/vortex-tooling.sh + + - run: + name: Deploy + command: VORTEX_DEPLOY_MODE="tag" ./vendor/bin/vortex-deploy + no_output_timeout: 30m + + - store_artifacts: + path: *artifacts + +################################################################################ +# WORKFLOWS +################################################################################ + +workflows: + # Commit workflow. Runs for every commit push to the remote repository. + commit: + jobs: + - lint: + filters: + tags: + only: /.*/ + - test: + filters: + tags: + only: /.*/ + - build: + filters: + tags: + only: /.*/ + - deploy: + requires: + - test + - lint + - build + filters: + branches: + # Allowed branches: + # - production, main, master, develop, ci, cisomething + # - project/description + # - deps/* + # - feature/description, feature/123-description + # - bugfix/description, bugfix/123-description + # - release/__VERSION__, release/__VERSION__ (per https://semver.org/) + # - release/2023-04-17, release/2023-04-17.123 (date-based) + # - hotfix/__VERSION__, hotfix/__VERSION__ (per https://semver.org/) + # - hotfix/2023-04-17, hotfix/2023-04-17.123 (date-based) + only: /^(production|main|master|develop)$|^project\/[a-zA-Z0-9\-\.]+|^(feature|bugfix)\/[a-zA-Z0-9\-\.\,_]+$|^ci.*|^(release|hotfix)\/[0-9]+(\.[0-9]+){2}(-rc\.[0-9]+)?$|^(release|hotfix)\/[0-9]{4}-[0-9]{2}-[0-9]{2}(\.[0-9]+)?$/ + tags: + ignore: /.*/ + - deploy-tags: + requires: + - test + - lint + - build + filters: + branches: + ignore: /.*/ + tags: + # Allowed tags: + # - __VERSION__, __VERSION__ (per https://semver.org/) + # - 2023-04-17, 2023-04-17.123 (date-based) + only: /^[0-9]+(\.[0-9]+){2}(-rc\.[0-9]+)?$|^[0-9]{4}-[0-9]{2}-[0-9]{2}(\.[0-9]+)?$/ diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_no_hadolint_circleci/.circleci/post-coverage-comment.sh b/.vortex/cli/tests/Fixtures/handler_process/tools_no_hadolint_circleci/.circleci/post-coverage-comment.sh new file mode 100755 index 000000000..d9e737409 --- /dev/null +++ b/.vortex/cli/tests/Fixtures/handler_process/tools_no_hadolint_circleci/.circleci/post-coverage-comment.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env bash +## +## Post code coverage summary as a PR comment on GitHub. +## +## Minimizes previous coverage comments before posting a new one. +## +## Environment variables: +## CIRCLE_PULL_REQUEST - CircleCI PR URL. +## GITHUB_TOKEN - GitHub token for API access. +## CIRCLE_PROJECT_USERNAME - GitHub org/user. +## CIRCLE_PROJECT_REPONAME - GitHub repo name. +## VORTEX_CI_CODE_COVERAGE_THRESHOLD - Coverage threshold percentage (default: 90). +## +## Usage: +## .circleci/post-coverage-comment.sh /path/to/coverage.txt + +set -euo pipefail + +COVERAGE_FILE="${1:-}" + +if [ -z "${COVERAGE_FILE}" ] || [ ! -f "${COVERAGE_FILE}" ]; then + echo "ERROR: Coverage file not found: ${COVERAGE_FILE}" >&2 + exit 1 +fi + +if [ -z "${CIRCLE_PULL_REQUEST:-}" ]; then + echo "Not a pull request. Skipping." + exit 0 +fi + +if [ -z "${GITHUB_TOKEN:-}" ]; then + echo "GITHUB_TOKEN is not set. Skipping." + exit 0 +fi + +COVERAGE_SUMMARY=$(awk '/^ *Summary:/{f=1;next} f && /^$/{exit} f' "${COVERAGE_FILE}") +COVERAGE_DETAILS=$(awk 'BEGIN{s=0} /^ *Summary:/{s=1} s==1 && /^$/{s=2;next} s==2' "${COVERAGE_FILE}") +PR_NUMBER=$(echo "${CIRCLE_PULL_REQUEST}" | cut -d'/' -f 7) +REPO_SLUG="${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}" +THRESHOLD="${VORTEX_CI_CODE_COVERAGE_THRESHOLD:-90}" + +MARKER="" + +BODY=$(jq -n --arg body "**Code coverage** (threshold: ${THRESHOLD}%) +\`\`\` +${COVERAGE_SUMMARY} +\`\`\` +
+Per-class coverage + +\`\`\` +${COVERAGE_DETAILS} +\`\`\` +
+${MARKER}" '{body: $body}') + +# Minimize previous coverage comments. +COMMENTS_JSON=$(curl -s \ + -H "Authorization: token ${GITHUB_TOKEN}" \ + -H "Accept: application/vnd.github.v3+json" \ + "https://api.github.com/repos/${REPO_SLUG}/issues/${PR_NUMBER}/comments?per_page=100") + +EXISTING_IDS=$(echo "${COMMENTS_JSON}" | jq -r '.[] | select(.body | contains("")) | .node_id') + +for NODE_ID in ${EXISTING_IDS}; do + GRAPHQL_BODY=$(jq -n --arg id "${NODE_ID}" '{query: "mutation($id:ID!){minimizeComment(input:{subjectId:$id,classifier:OUTDATED}){minimizedComment{isMinimized}}}", variables: {id: $id}}') + curl -s -X POST \ + -H "Authorization: bearer ${GITHUB_TOKEN}" \ + -H "Content-Type: application/json" \ + "https://api.github.com/graphql" \ + -d "${GRAPHQL_BODY}" +done + +# Post new coverage comment. +curl -s -X POST \ + -H "Authorization: token ${GITHUB_TOKEN}" \ + -H "Accept: application/vnd.github.v3+json" \ + "https://api.github.com/repos/${REPO_SLUG}/issues/${PR_NUMBER}/comments" \ + -d "${BODY}" diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_no_hadolint_circleci/.dockerignore b/.vortex/cli/tests/Fixtures/handler_process/tools_no_hadolint_circleci/.dockerignore new file mode 100644 index 000000000..e0fa230b8 --- /dev/null +++ b/.vortex/cli/tests/Fixtures/handler_process/tools_no_hadolint_circleci/.dockerignore @@ -0,0 +1,10 @@ +@@ -42,9 +42,6 @@ + **/.phpunit.cache + **/.twig-cs-fixer.cache + +-# Database cache key files written into the workspace by the CI workflow. +-db_cache_* +- + # Local override files. + docker-compose.override.yml + .env.local diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_no_hadolint_circleci/.github/workflows/-build-test-deploy.yml b/.vortex/cli/tests/Fixtures/handler_process/tools_no_hadolint_circleci/.github/workflows/-build-test-deploy.yml new file mode 100644 index 000000000..e69de29bb diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_no_hadolint_circleci/README.md b/.vortex/cli/tests/Fixtures/handler_process/tools_no_hadolint_circleci/README.md new file mode 100644 index 000000000..c803a0514 --- /dev/null +++ b/.vortex/cli/tests/Fixtures/handler_process/tools_no_hadolint_circleci/README.md @@ -0,0 +1,9 @@ +@@ -9,7 +9,7 @@ + + Drupal 11 implementation of star wars for star wars Org + +-[![Build, Test and Deploy](https://github.com/star_wars_org/star_wars/actions/workflows/build-test-deploy.yml/badge.svg)](https://github.com/star_wars_org/star_wars/actions/workflows/build-test-deploy.yml) ++[![CircleCI](https://circleci.com/gh/star_wars_org/star_wars.svg?style=shield)](https://circleci.com/gh/star_wars_org/star_wars) + + ![Drupal 11](https://img.shields.io/badge/Drupal-11-0678BE?logo=drupal&logoColor=white) + diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_no_hadolint_circleci/docs/ci.md b/.vortex/cli/tests/Fixtures/handler_process/tools_no_hadolint_circleci/docs/ci.md new file mode 100644 index 000000000..542f58060 --- /dev/null +++ b/.vortex/cli/tests/Fixtures/handler_process/tools_no_hadolint_circleci/docs/ci.md @@ -0,0 +1,12 @@ +@@ -5,9 +5,9 @@ + + ## CI provider + +-This project uses [GitHub Actions](https://github.com/features/actions). ++This project uses [CircleCI](https://circleci.com/). + +-See [GitHub Actions documentation](https://www.vortextemplate.com/docs/continuous-integration/github-actions) ++See [CircleCI documentation](https://www.vortextemplate.com/docs/continuous-integration/circleci) + for setup and configuration details. + + ## Project-specific configuration diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_no_hadolint_circleci/tests/phpunit/CircleCiConfigTest.php b/.vortex/cli/tests/Fixtures/handler_process/tools_no_hadolint_circleci/tests/phpunit/CircleCiConfigTest.php new file mode 100644 index 000000000..e1ca36aa7 --- /dev/null +++ b/.vortex/cli/tests/Fixtures/handler_process/tools_no_hadolint_circleci/tests/phpunit/CircleCiConfigTest.php @@ -0,0 +1,283 @@ +config = Yaml::decode($file); + } + + /** + * Tests for deploy branch regex. + * + * @see https://semver.org/ + */ + #[DataProvider('dataProviderDeployBranchRegex')] + public function testDeployBranchRegex(string $branch, bool $expected = TRUE): void { + $pattern = $this->getCommitWorkflowJob('deploy')['filters']['branches']['only']; + $result = preg_match($pattern, $branch); + $this->assertEquals($expected, $result); + } + + /** + * Data provider for testDeployBranchRegex(). + */ + public static function dataProviderDeployBranchRegex(): \Iterator { + // Positive branches. + yield ['production']; + yield ['main']; + yield ['master']; + yield ['develop']; + + yield ['ci']; + yield ['cisomething']; + + yield ['release/__VERSION__']; + yield ['release/__VERSION__']; + yield ['hotfix/__VERSION__']; + yield ['hotfix/__VERSION__']; + + yield ['release/2023-04-17']; + yield ['release/2023-04-17.1']; + yield ['hotfix/2023-04-17']; + yield ['hotfix/2023-04-17.1']; + + yield ['feature/description']; + yield ['feature/Description']; + yield ['feature/Description-With-Hyphens']; + yield ['feature/Description-With_Underscores']; + yield ['feature/123-description']; + yield ['feature/123-Description']; + yield ['feature/UNDERSCORES_UNDERSCORES']; + yield ['feature/123-Description-With_UNDERSCORES']; + yield ['feature/1.x']; + yield ['feature/0.x']; + yield ['feature/0.1.x']; + yield ['feature/__VERSION__.x']; + yield ['feature/1.x-description']; + yield ['feature/0.x-description']; + yield ['feature/0.1.x-description']; + yield ['feature/__VERSION__.x-description']; + + yield ['bugfix/description']; + yield ['bugfix/Description']; + yield ['bugfix/Description-With-Hyphens']; + yield ['bugfix/Description-With_Underscores']; + yield ['bugfix/123-description']; + yield ['bugfix/123-Description']; + yield ['bugfix/UNDERSCORES_UNDERSCORES']; + yield ['bugfix/123-Description-With_UNDERSCORES']; + yield ['bugfix/1.x']; + yield ['bugfix/0.x']; + yield ['bugfix/0.1.x']; + yield ['bugfix/__VERSION__.x']; + yield ['bugfix/1.x-description']; + yield ['bugfix/0.x-description']; + yield ['bugfix/0.1.x-description']; + yield ['bugfix/__VERSION__.x-description']; + + yield ['project/description']; + yield ['project/Description']; + yield ['project/Description-With-Hyphens']; + yield ['project/123-description']; + yield ['project/123-Description']; + yield ['project/1.x']; + yield ['project/0.x']; + yield ['project/0.1.x']; + yield ['project/__VERSION__.x']; + yield ['project/1.x-description']; + yield ['project/0.x-description']; + yield ['project/0.1.x-description']; + yield ['project/__VERSION__.x-description']; + + // Negative branches. + yield ['something', FALSE]; + yield ['premain', FALSE]; + yield ['premaster', FALSE]; + yield ['predevelop', FALSE]; + yield ['mainpost', FALSE]; + yield ['masterpost', FALSE]; + yield ['developpost', FALSE]; + yield ['premainpost', FALSE]; + yield ['premasterpost', FALSE]; + yield ['predeveloppost', FALSE]; + + yield ['preci', FALSE]; + yield ['precipost', FALSE]; + + yield ['deps/something', FALSE]; + yield ['deps', FALSE]; + yield ['predeps', FALSE]; + yield ['depspost', FALSE]; + yield ['predepspost', FALSE]; + + yield ['feature', FALSE]; + yield ['release', FALSE]; + yield ['hotfix', FALSE]; + yield ['prefeature', FALSE]; + yield ['prerelease', FALSE]; + yield ['prehotfix', FALSE]; + yield ['featurepost', FALSE]; + yield ['releasepost', FALSE]; + yield ['hotfixpost', FALSE]; + yield ['prefeaturepost', FALSE]; + yield ['prereleasepost', FALSE]; + yield ['prehotfixpost', FALSE]; + + yield ['release/123', FALSE]; + yield ['release/123.456', FALSE]; + yield ['hotfix/123', FALSE]; + yield ['hotfix/123.456', FALSE]; + + yield ['release/202-04-17', FALSE]; + yield ['release/2023-4-17', FALSE]; + yield ['release/2023-04-1', FALSE]; + yield ['release/pre2023-04-17', FALSE]; + yield ['release/2023-04-17post', FALSE]; + yield ['release/pre2023-04-17post', FALSE]; + + yield ['hotfix/202-04-17', FALSE]; + yield ['hotfix/2023-4-17', FALSE]; + yield ['hotfix/2023-04-1', FALSE]; + yield ['hotfix/pre2023-04-17', FALSE]; + yield ['hotfix/2023-04-17post', FALSE]; + yield ['hotfix/pre2023-04-17post', FALSE]; + + yield ['release/__VERSION__', FALSE]; + yield ['release/__VERSION__', FALSE]; + yield ['release/__VERSION__', FALSE]; + yield ['release/__VERSION__', FALSE]; + yield ['release/__VERSION__', FALSE]; + yield ['release/__VERSION__', FALSE]; + yield ['release/__VERSION__', FALSE]; + yield ['release/__VERSION__', FALSE]; + yield ['release/__VERSION__', FALSE]; + yield ['release/__VERSION__', FALSE]; + + yield ['hotfix/__VERSION__', FALSE]; + yield ['hotfix/__VERSION__', FALSE]; + yield ['hotfix/__VERSION__', FALSE]; + yield ['hotfix/__VERSION__', FALSE]; + yield ['hotfix/__VERSION__', FALSE]; + yield ['hotfix/__VERSION__', FALSE]; + yield ['hotfix/__VERSION__', FALSE]; + yield ['hotfix/__VERSION__', FALSE]; + yield ['hotfix/__VERSION__', FALSE]; + yield ['hotfix/__VERSION__', FALSE]; + + yield ['prefeature/something', FALSE]; + yield ['prefbugfix/something', FALSE]; + yield ['prerelease/something', FALSE]; + yield ['prehotfix/something', FALSE]; + yield ['featurepost/something', FALSE]; + yield ['bugfixpost/something', FALSE]; + yield ['releasepost/something', FALSE]; + yield ['hotfixpost/something', FALSE]; + yield ['prefeaturepost/something', FALSE]; + yield ['prebugfixpost/something', FALSE]; + yield ['prereleasepost/something', FALSE]; + yield ['prehotfixpost/something', FALSE]; + yield ['preproject/something', FALSE]; + yield ['projectpost/something', FALSE]; + } + + /** + * Tests for deploy tag regex. + * + * @see https://semver.org/ + */ + #[DataProvider('dataProviderDeployTagRegex')] + public function testDeployTagRegex(string $branch, bool $expected = TRUE): void { + $pattern = $this->getCommitWorkflowJob('deploy-tags')['filters']['tags']['only']; + $result = preg_match($pattern, $branch); + $this->assertEquals($expected, $result); + } + + /** + * Data provider for testDeployTagRegex(). + */ + public static function dataProviderDeployTagRegex(): \Iterator { + // Positive tags. + yield ['__VERSION__']; + yield ['__VERSION__']; + yield ['2023-04-17']; + yield ['2023-04-17.123']; + + // Negative tags. + yield ['123', FALSE]; + yield ['123.456', FALSE]; + yield ['__VERSION__', FALSE]; + yield ['__VERSION__', FALSE]; + yield ['__VERSION__', FALSE]; + yield ['__VERSION__', FALSE]; + yield ['__VERSION__', FALSE]; + + yield ['202-04-17', FALSE]; + yield ['2023-0-17', FALSE]; + yield ['2023-04-1', FALSE]; + yield ['pre2023-04-17', FALSE]; + yield ['2023-04-17post', FALSE]; + yield ['pre2023-04-17post', FALSE]; + yield ['2023-04-17.123.', FALSE]; + yield ['2023-04-17.pre123', FALSE]; + yield ['2023-04-17.pre123post', FALSE]; + yield ['2023-04-17.123post', FALSE]; + } + + /** + * Get a commit workflow job configuration by its job name. + * + * Looks the job up by name rather than by position, so the assertions stay + * correct regardless of how many other jobs surround it in the workflow. + * + * @param string $name + * The job name (the single key of a workflow job entry). + * + * @return mixed + * The job configuration. + */ + protected function getCommitWorkflowJob(string $name): mixed { + $jobs = $this->config['workflows']['commit']['jobs']; + + foreach ($jobs as $job) { + if (is_array($job) && array_key_exists($name, $job)) { + return $job[$name]; + } + } + + throw new \RuntimeException(sprintf('Job "%s" not found in the commit workflow.', $name)); + } + +} diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_no_hadolint_circleci/tests/phpunit/Drupal/EnvironmentSettingsTest.php b/.vortex/cli/tests/Fixtures/handler_process/tools_no_hadolint_circleci/tests/phpunit/Drupal/EnvironmentSettingsTest.php new file mode 100644 index 000000000..aa0127049 --- /dev/null +++ b/.vortex/cli/tests/Fixtures/handler_process/tools_no_hadolint_circleci/tests/phpunit/Drupal/EnvironmentSettingsTest.php @@ -0,0 +1,12 @@ +@@ -332,9 +332,9 @@ + } + + /** +- * Test per-environment settings for GitHub Actions. ++ * Test per-environment settings for CircleCI. + */ +- public function testEnvironmentGha(): void { ++ public function testEnvironmentCircleCi(): void { + $this->setEnvVars([ + 'CI' => TRUE, + ]); diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_no_jest_circleci/.circleci/config.yml b/.vortex/cli/tests/Fixtures/handler_process/tools_no_jest_circleci/.circleci/config.yml index 27e096e42..844260e1c 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/tools_no_jest_circleci/.circleci/config.yml +++ b/.vortex/cli/tests/Fixtures/handler_process/tools_no_jest_circleci/.circleci/config.yml @@ -122,7 +122,7 @@ jobs: name: Lint Dockerfiles with Hadolint command: | for file in $(find .docker \( -name 'Dockerfile' -o -name '*.dockerfile' \)); do - echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] + echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint:__VERSION__ || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] done - run: diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_no_phpcs_circleci/.circleci/config.yml b/.vortex/cli/tests/Fixtures/handler_process/tools_no_phpcs_circleci/.circleci/config.yml index e14fb79eb..aac4bea62 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/tools_no_phpcs_circleci/.circleci/config.yml +++ b/.vortex/cli/tests/Fixtures/handler_process/tools_no_phpcs_circleci/.circleci/config.yml @@ -122,7 +122,7 @@ jobs: name: Lint Dockerfiles with Hadolint command: | for file in $(find .docker \( -name 'Dockerfile' -o -name '*.dockerfile' \)); do - echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] + echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint:__VERSION__ || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] done - run: diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_no_phpstan_circleci/.circleci/config.yml b/.vortex/cli/tests/Fixtures/handler_process/tools_no_phpstan_circleci/.circleci/config.yml index 44e434d32..b7c3c1814 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/tools_no_phpstan_circleci/.circleci/config.yml +++ b/.vortex/cli/tests/Fixtures/handler_process/tools_no_phpstan_circleci/.circleci/config.yml @@ -122,7 +122,7 @@ jobs: name: Lint Dockerfiles with Hadolint command: | for file in $(find .docker \( -name 'Dockerfile' -o -name '*.dockerfile' \)); do - echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] + echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint:__VERSION__ || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] done - run: diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_no_phpunit_circleci/.circleci/config.yml b/.vortex/cli/tests/Fixtures/handler_process/tools_no_phpunit_circleci/.circleci/config.yml index 848a26f09..da17c0db1 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/tools_no_phpunit_circleci/.circleci/config.yml +++ b/.vortex/cli/tests/Fixtures/handler_process/tools_no_phpunit_circleci/.circleci/config.yml @@ -122,7 +122,7 @@ jobs: name: Lint Dockerfiles with Hadolint command: | for file in $(find .docker \( -name 'Dockerfile' -o -name '*.dockerfile' \)); do - echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] + echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint:__VERSION__ || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] done - run: diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_no_rector_circleci/.circleci/config.yml b/.vortex/cli/tests/Fixtures/handler_process/tools_no_rector_circleci/.circleci/config.yml index d9f4dc4a5..55a5c8304 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/tools_no_rector_circleci/.circleci/config.yml +++ b/.vortex/cli/tests/Fixtures/handler_process/tools_no_rector_circleci/.circleci/config.yml @@ -122,7 +122,7 @@ jobs: name: Lint Dockerfiles with Hadolint command: | for file in $(find .docker \( -name 'Dockerfile' -o -name '*.dockerfile' \)); do - echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] + echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint:__VERSION__ || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] done - run: diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_no_stylelint_circleci/.circleci/config.yml b/.vortex/cli/tests/Fixtures/handler_process/tools_no_stylelint_circleci/.circleci/config.yml index 52a73ee14..1b128e98e 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/tools_no_stylelint_circleci/.circleci/config.yml +++ b/.vortex/cli/tests/Fixtures/handler_process/tools_no_stylelint_circleci/.circleci/config.yml @@ -122,7 +122,7 @@ jobs: name: Lint Dockerfiles with Hadolint command: | for file in $(find .docker \( -name 'Dockerfile' -o -name '*.dockerfile' \)); do - echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] + echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint:__VERSION__ || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] done - run: diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_no_stylelint_no_theme/.docker/cli.dockerfile b/.vortex/cli/tests/Fixtures/handler_process/tools_no_stylelint_no_theme/.docker/cli.dockerfile index c6e5e90e0..4abfebbec 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/tools_no_stylelint_no_theme/.docker/cli.dockerfile +++ b/.vortex/cli/tests/Fixtures/handler_process/tools_no_stylelint_no_theme/.docker/cli.dockerfile @@ -1,4 +1,4 @@ -@@ -37,7 +37,7 @@ +@@ -36,7 +36,7 @@ ARG DRUPAL_TEMPORARY_FILES="${TMP:-/tmp}" ENV DRUPAL_TEMPORARY_FILES=${DRUPAL_TEMPORARY_FILES} @@ -8,8 +8,8 @@ ARG VORTEX_FRONTEND_BUILD_SKIP="0" @@ -94,12 +94,5 @@ - # Create file directories and set correct permissions. + # hadolint ignore=SC2174 # only the leaf directory needs the mode RUN mkdir -p -m 2775 "/app/${WEBROOT}/${DRUPAL_PUBLIC_FILES}" "/app/${WEBROOT}/${DRUPAL_PRIVATE_FILES}" "${DRUPAL_TEMPORARY_FILES}" - -RUN if [ "${VORTEX_FRONTEND_BUILD_SKIP}" != "1" ]; then \ diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_no_twig_circleci/.circleci/config.yml b/.vortex/cli/tests/Fixtures/handler_process/tools_no_twig_circleci/.circleci/config.yml index dbbb019c3..1c73d5306 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/tools_no_twig_circleci/.circleci/config.yml +++ b/.vortex/cli/tests/Fixtures/handler_process/tools_no_twig_circleci/.circleci/config.yml @@ -122,7 +122,7 @@ jobs: name: Lint Dockerfiles with Hadolint command: | for file in $(find .docker \( -name 'Dockerfile' -o -name '*.dockerfile' \)); do - echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] + echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint:__VERSION__ || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ] done - run: diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_none/-.dclintrc b/.vortex/cli/tests/Fixtures/handler_process/tools_none/-.dclintrc new file mode 100644 index 000000000..e69de29bb diff --git a/.vortex/cli/tests/Fixtures/handler_process/tools_none/.github/workflows/build-test-deploy.yml b/.vortex/cli/tests/Fixtures/handler_process/tools_none/.github/workflows/build-test-deploy.yml index e84ec83ea..bc4b3786a 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/tools_none/.github/workflows/build-test-deploy.yml +++ b/.vortex/cli/tests/Fixtures/handler_process/tools_none/.github/workflows/build-test-deploy.yml @@ -1,4 +1,22 @@ -@@ -129,7 +129,6 @@ +@@ -106,17 +106,6 @@ + - name: Login to container registry + run: ./vendor/bin/vortex-login-container-registry + +- - name: Lint Dockerfiles with Hadolint +- run: | +- find .docker \( -name 'Dockerfile' -o -name '*.dockerfile' \) | while read -r file; do +- echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint:__VERSION__ +- done +- continue-on-error: ${{ vars.VORTEX_CI_HADOLINT_IGNORE_FAILURE == '1' }} +- +- - name: Lint Docker Compose files with DCLint +- run: docker run --rm -v "${PWD}":/app zavoloklom/dclint:__VERSION__ . +- continue-on-error: ${{ vars.VORTEX_CI_DCLINT_IGNORE_FAILURE == '1' }} +- + - name: Scan for committed secrets with Gitleaks + run: docker run --rm -v "${PWD}":/repo -w /repo ghcr.io/gitleaks/gitleaks:__VERSION__ dir . --no-banner + continue-on-error: ${{ vars.VORTEX_CI_GITLEAKS_IGNORE_FAILURE == '1' }} +@@ -129,7 +118,6 @@ docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c " \ if [ -n \"\${PACKAGE_TOKEN:-}\" ]; then composer config --global --auth github-oauth.github.com \"\${PACKAGE_TOKEN}\"; fi && \ COMPOSER_MEMORY_LIMIT=-1 composer --ansi install --prefer-dist" @@ -6,7 +24,7 @@ - name: Audit Composer packages run: docker compose exec -T cli composer audit -@@ -139,30 +138,6 @@ +@@ -139,30 +127,6 @@ run: docker compose exec -T cli composer normalize --dry-run continue-on-error: ${{ vars.VORTEX_CI_COMPOSER_NORMALIZE_IGNORE_FAILURE == '1' }} @@ -37,7 +55,7 @@ - name: Lint theme code with NodeJS linters if: ${{ vars.VORTEX_FRONTEND_BUILD_SKIP != '1' }} run: docker compose exec -T cli bash -c "yarn --cwd=\${WEBROOT}/themes/custom/\${DRUPAL_THEME} run lint" -@@ -298,7 +273,6 @@ +@@ -298,7 +262,6 @@ docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c " \ if [ -n \"\${PACKAGE_TOKEN:-}\" ]; then composer config --global --auth github-oauth.github.com \"\${PACKAGE_TOKEN}\"; fi && \ COMPOSER_MEMORY_LIMIT=-1 composer --ansi install --prefer-dist" @@ -45,7 +63,7 @@ - name: Provision site run: | -@@ -312,71 +286,6 @@ +@@ -312,71 +275,6 @@ docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli ./vendor/bin/vortex-provision timeout-minutes: 30 @@ -117,7 +135,7 @@ - name: Validate Single Directory Components if: ${{ matrix.instance == 0 || strategy.job-total == 1 }} run: | -@@ -389,20 +298,6 @@ +@@ -389,20 +287,6 @@ fi continue-on-error: ${{ vars.VORTEX_CI_SDC_DEVEL_IGNORE_FAILURE == '1' }} @@ -138,7 +156,7 @@ - name: Process test logs and artifacts if: always() run: | -@@ -419,16 +314,6 @@ +@@ -419,16 +303,6 @@ path: .logs include-hidden-files: true if-no-files-found: error diff --git a/.vortex/cli/tests/Fixtures/handler_process/visual_regression_enabled/.github/workflows/test-vr.yml b/.vortex/cli/tests/Fixtures/handler_process/visual_regression_enabled/.github/workflows/test-vr.yml index a9429f9b6..a48ce4a88 100644 --- a/.vortex/cli/tests/Fixtures/handler_process/visual_regression_enabled/.github/workflows/test-vr.yml +++ b/.vortex/cli/tests/Fixtures/handler_process/visual_regression_enabled/.github/workflows/test-vr.yml @@ -1,9 +1,9 @@ # GitHub Actions visual regression testing workflow. # # Runs a Diffy visual regression comparison after a deployment lands. -# Triggered automatically by 'notify-diffy' (via repository_dispatch) -# when the deployed PR has the configured label, or manually via -# 'workflow_dispatch' against any URL. +# Triggered automatically by 'notify-diffy' (via repository_dispatch) when +# the deployed PR has the configured label or the deployed branch is opted +# in, or manually via 'workflow_dispatch' against any URL. name: Test - Visual regression on: @@ -43,10 +43,12 @@ env: DIFFY_CLI_VERSION: ${{ vars.VR_DIFFY_CLI_VERSION || '__VERSION__' }} DIFFY_MAX_WAIT: ${{ vars.VR_DIFFY_MAX_WAIT || '2700' }} DIFFY_PR_LABEL: ${{ vars.VR_DIFFY_PR_LABEL || 'VR' }} - DIFFY_AUTO_BRANCHES: ${{ vars.VR_DIFFY_AUTO_BRANCHES || 'deps/*' }} + DIFFY_PR_SKIP_BRANCHES: ${{ vars.VR_DIFFY_PR_SKIP_BRANCHES || 'deps/*' }} + DIFFY_BRANCHES: ${{ vars.VR_DIFFY_BRANCHES }} DIFFY_POLL_INTERVAL: ${{ vars.VR_DIFFY_POLL_INTERVAL || '30' }} SOURCE_ENV: ${{ github.event.client_payload.source_env || inputs.source_env || 'production' }} TARGET_URL: ${{ github.event.client_payload.target_url || inputs.target_url }} + BRANCH: ${{ github.event.client_payload.branch }} LABEL: ${{ github.event.client_payload.label || inputs.label || 'manual' }} jobs: @@ -64,6 +66,7 @@ jobs: shared_url: ${{ steps.result.outputs.shared_url }} pages_changed: ${{ steps.result.outputs.pages_changed }} pages_total: ${{ steps.result.outputs.pages_total }} + summary: ${{ steps.summary.outputs.summary }} steps: - name: Resolve PR number from target URL @@ -73,18 +76,18 @@ jobs: # Hosting providers expose PR environments via URLs that contain # a 'pr-' segment (e.g. 'app.pr-123.example.lagoon.cloud'). - # Extract the number from the URL; if no match, this is not a PR - # deployment and visual regression should not run. + # Extract the number from the URL; a URL without one is not a PR + # deployment and is gated on the deployed branch instead. pr_number="$(printf '%s' "${TARGET_URL}" | sed -n 's|.*pr-\([0-9]\{1,\}\).*|\1|p')" if [ -n "${pr_number}" ]; then echo "PR #${pr_number} resolved from target URL ${TARGET_URL}." else - echo "::notice::No PR pattern found in target URL ${TARGET_URL}. Visual regression will not run." + echo "No PR pattern found in target URL ${TARGET_URL}." fi echo "pr_number=${pr_number}" >> "$GITHUB_OUTPUT" - - name: Gate run on PR label (or skip for non-PR contexts on dispatch) + - name: Gate run on PR label or opted-in branch id: gate env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -94,55 +97,50 @@ jobs: run: | set -euo pipefail - # Manual workflow_dispatch always proceeds - the operator - # explicitly asked for a comparison. - if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ]; then - echo "Manual dispatch, proceeding." - echo "skipped=false" >> "$GITHUB_OUTPUT" - exit 0 - fi + # Matches a git ref against a comma-separated glob list. Splitting + # on whitespace as well as commas absorbs padding around each + # pattern. + ref_matches() { + local ref="${1}" pattern patterns + [ -n "${ref}" ] || return 1 + [ -n "${2}" ] || return 1 + IFS=$', \t' read -ra patterns <<<"${2}" + for pattern in "${patterns[@]}"; do + # shellcheck disable=SC2254 + case "${ref}" in ${pattern}) return 0 ;; esac + done + return 1 + } - # repository_dispatch with no PR resolved - nothing to gate on. + # Record the gate decision and end the step. + proceed() { echo "${1}"; echo "skipped=false" >> "$GITHUB_OUTPUT"; exit 0; } + skip() { echo "::notice::${1}"; echo "skipped=true" >> "$GITHUB_OUTPUT"; exit 0; } + + # The operator explicitly asked for a comparison. + [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ] && proceed "Manual dispatch." + + # No PR resolved - the deployment targets a permanent environment + # such as a release branch, 'develop', 'dev' or 'stage'. Those run + # only when the deployed branch is explicitly opted in. if [ -z "${PR_NUMBER}" ]; then - echo "::notice::No PR associated with this deployment, skipping visual regression." - echo "skipped=true" >> "$GITHUB_OUTPUT" - exit 0 + ref_matches "${BRANCH}" "${DIFFY_BRANCHES}" && proceed "Deployed branch '${BRANCH}' matches VR_DIFFY_BRANCHES." + skip "No PR associated with this deployment and branch '${BRANCH}' does not match VR_DIFFY_BRANCHES, skipping visual regression." fi - # PR resolved - fetch labels and head branch in one call. - pr_data="$(gh pr view "${PR_NUMBER}" --repo "${GITHUB_REPOSITORY}" --json labels,headRefName)" + # A failed lookup means the 'pr-' segment in the URL did + # not come from a pull request in this repository. + pr_data="$(gh pr view "${PR_NUMBER}" --repo "${GITHUB_REPOSITORY}" --json labels,headRefName)" || skip "PR #${PR_NUMBER} could not be read, skipping visual regression." + head_ref="$(printf '%s' "${pr_data}" | jq -r '.headRefName')" - labels_lower="$(printf '%s' "${pr_data}" | jq -r '.labels[].name' | tr '[:upper:]' '[:lower:]')" + labels="$(printf '%s' "${pr_data}" | jq -r '.labels[].name')" - # Auto-run bypass: PRs from configured branches (e.g. Renovate's - # `deps/*`) skip the label check. - if [ -n "${DIFFY_AUTO_BRANCHES}" ]; then - IFS=',' read -ra patterns <<<"${DIFFY_AUTO_BRANCHES}" - for pattern in "${patterns[@]}"; do - # Trim surrounding whitespace. - pattern="${pattern# }" - pattern="${pattern% }" - [ -z "${pattern}" ] && continue - # shellcheck disable=SC2254 - case "${head_ref}" in - ${pattern}) - echo "PR #${PR_NUMBER} head branch '${head_ref}' matches auto-run pattern '${pattern}', proceeding." - echo "skipped=false" >> "$GITHUB_OUTPUT" - exit 0 - ;; - esac - done - fi + # PRs from configured branches (e.g. Renovate's `deps/*`) skip the + # label check. + ref_matches "${head_ref}" "${DIFFY_PR_SKIP_BRANCHES}" && proceed "PR #${PR_NUMBER} head branch '${head_ref}' matches VR_DIFFY_PR_SKIP_BRANCHES." - # Label check (case-insensitive). - needle="$(printf '%s' "${DIFFY_PR_LABEL}" | tr '[:upper:]' '[:lower:]')" - if echo "${labels_lower}" | grep -qx "${needle}"; then - echo "PR #${PR_NUMBER} has the '${DIFFY_PR_LABEL}' label, proceeding." - echo "skipped=false" >> "$GITHUB_OUTPUT" - else - echo "::notice::PR #${PR_NUMBER} does not have the '${DIFFY_PR_LABEL}' label and head branch '${head_ref}' does not match VR_DIFFY_AUTO_BRANCHES, skipping visual regression." - echo "skipped=true" >> "$GITHUB_OUTPUT" - fi + echo "${labels}" | grep -qixF "${DIFFY_PR_LABEL}" && proceed "PR #${PR_NUMBER} has the '${DIFFY_PR_LABEL}' label." + + skip "PR #${PR_NUMBER} does not have the '${DIFFY_PR_LABEL}' label and head branch '${head_ref}' does not match VR_DIFFY_PR_SKIP_BRANCHES, skipping visual regression." - name: Validate target URL if: steps.gate.outputs.skipped != 'true' @@ -253,7 +251,7 @@ jobs: result_json="$(/tmp/diffy.phar diff:get-result "${diff_id}" --format=json)" shared_url="$(printf '%s' "${result_json}" | jq -r '.diffSharedUrl // empty')" - pages_changed="$(printf '%s' "${result_json}" | jq -r '[.diffs | to_entries[] | select(.value | to_entries | map(.value.percentageChanges // 0) | add > 0)] | length')" + pages_changed="$(printf '%s' "${result_json}" | jq -r '[.diffs[] | select([.[].percentageChanges | tonumber? // 0] | add > 0)] | length')" pages_total="$(printf '%s' "${result_json}" | jq -r '.diffs | length')" { @@ -268,6 +266,39 @@ jobs: env: STEPS_COMPARE_OUTPUTS_DIFF_ID: ${{ steps.compare.outputs.diff_id }} + - name: Write run summary + id: summary + if: steps.gate.outputs.skipped != 'true' + run: | + set -euo pipefail + + summary="$(printf '%s\n' \ + "### Visual regression report" \ + "" \ + "**${PAGES_CHANGED} of ${PAGES_TOTAL}** pages changed, **${CHANGES_PERCENT}%** overall difference." \ + "" \ + "| Setting | Value |" \ + "| --- | --- |" \ + "| Source environment | \`${SOURCE_ENV}\` |" \ + "| Target environment | \`${TARGET_URL}\` |" \ + "| Diff ID | \`${DIFF_ID}\` |" \ + "" \ + "[View full Diffy report](${SHARED_URL})")" + + printf '%s\n' "${summary}" >> "$GITHUB_STEP_SUMMARY" + + { + echo "summary<> "$GITHUB_OUTPUT" + env: + CHANGES_PERCENT: ${{ steps.result.outputs.changes_percent }} + SHARED_URL: ${{ steps.result.outputs.shared_url }} + PAGES_CHANGED: ${{ steps.result.outputs.pages_changed }} + PAGES_TOTAL: ${{ steps.result.outputs.pages_total }} + DIFF_ID: ${{ steps.compare.outputs.diff_id }} + vr-report: runs-on: ubuntu-latest needs: vr-compare @@ -282,13 +313,5 @@ jobs: with: number: ${{ needs.vr-compare.outputs.pr_number }} header: vr-diffy - message: | - ### Visual regression report - - - **Pages changed**: ${{ needs.vr-compare.outputs.pages_changed }} of ${{ needs.vr-compare.outputs.pages_total }} - - **Overall difference**: ${{ needs.vr-compare.outputs.changes_percent }}% - - **Target environment**: ${{ env.TARGET_URL }} - - **Source environment**: ${{ env.SOURCE_ENV }} - - [View full Diffy report](${{ needs.vr-compare.outputs.shared_url }}) + message: ${{ needs.vr-compare.outputs.summary }} hide_and_recreate: true From b7b0084f05ec597d12ae3b8c2435d66a4b3c8029 Mon Sep 17 00:00:00 2001 From: Alex Skrypnyk Date: Fri, 31 Jul 2026 19:25:40 +1000 Subject: [PATCH 15/16] Update PHP - All packages except core - Minor and patch (#2879) Co-authored-by: Renovate Self Hosted (cherry picked from commit 7840b3c4e20652af2f9c02517f27709ffe607f1a) --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 4acec27d1..d666d6adc 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,7 @@ "drupal/robotstxt": "^1.6", "drupal/sdc_devel": "^1.0.2", "drupal/search_api": "^1.41.0", - "drupal/search_api_solr": "^4.3.13", + "drupal/search_api_solr": "^4.4.0", "drupal/seckit": "^2.0.3", "drupal/shield": "^1.8", "drupal/stage_file_proxy": "^4.0.0", @@ -58,10 +58,10 @@ "phpcompatibility/php-compatibility": "^10.0@alpha", "phpspec/prophecy-phpunit": "^2.5", "phpstan/extension-installer": "^1.4.3", - "phpstan/phpstan": "^2.2.6", + "phpstan/phpstan": "^2.2.7", "phpunit/phpunit": "^11.5.56", "pyrech/composer-changelogs": "^2.2", - "rector/rector": "^2.5.8", + "rector/rector": "^2.5.9", "vincentlanglet/twig-cs-fixer": "^4.0.2" }, "conflict": { From d9449a848fb9544f80df4acd9e6acc6f793f5a8b Mon Sep 17 00:00:00 2001 From: Alex Skrypnyk Date: Fri, 31 Jul 2026 19:52:22 +1000 Subject: [PATCH 16/16] Triggered 'Vortex - Test docs' from both per-major test workflows. Forward-ported from main 9a8883100e949f3c194882105d91f7d20f594ccd. --- .github/workflows/vortex-test-docs.yml | 24 ++++++++++++++++-------- .vortex/tests/zizmor.yml | 6 +++--- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/.github/workflows/vortex-test-docs.yml b/.github/workflows/vortex-test-docs.yml index 50e559cf2..af9ab7cb5 100644 --- a/.github/workflows/vortex-test-docs.yml +++ b/.github/workflows/vortex-test-docs.yml @@ -1,27 +1,32 @@ # This action is used for Vortex maintenance. It will not be used in the scaffolded project. name: Vortex - Test docs +# Each major ships its own binary from its own workflow, so listen to both: +# 'workflow_run' is registered from the default branch only, making this the +# single definition that has to serve branches of either major. on: workflow_run: - workflows: ['Vortex - Test CLI'] + workflows: ['Vortex - Test installer', 'Vortex - Test CLI'] types: - completed jobs: vortex-test-docs: runs-on: ubuntu-latest - # Only run if the CLI workflow succeeded + # Only run if the triggering workflow succeeded if: github.event.workflow_run.conclusion == 'success' permissions: contents: read # Checkout the repository at the triggering commit. - actions: read # Download the CLI artifact from the test-cli workflow run. + actions: read # Download the binary artifact from the triggering workflow run. statuses: write # Post pending/final commit statuses via 'gh api repos/.../statuses/...'. pull-requests: write # Post the Netlify preview link comment on the originating PR. env: CURRENT_MAJOR: ${{ vars.VORTEX_CURRENT_MAJOR || '1' }} OTHER_MAJOR: ${{ (vars.VORTEX_CURRENT_MAJOR || '1') == '1' && '2' || '1' }} + BINARY_ARTIFACT: ${{ github.event.workflow_run.name == 'Vortex - Test CLI' && 'vortex-cli' || 'vortex-installer' }} + BINARY_FILE: ${{ github.event.workflow_run.name == 'Vortex - Test CLI' && 'vortex.phar' || 'installer.phar' }} steps: # Post pending status to the PR commit. @@ -55,18 +60,21 @@ jobs: with: php-version: 8.3 - - name: Download CLI artifact + # Pin the download to the triggering run rather than a workflow name: + # searching by name returns the most recent successful run across all + # branches, which would pull a binary built from a different commit. + - name: Download the binary artifact uses: dawidd6/action-download-artifact@b6e2e70617bc3265edd6dab6c906732b2f1ae151 # v21 with: - workflow: vortex-test-cli.yml - name: vortex-cli + run_id: ${{ github.event.workflow_run.id }} + name: ${{ env.BINARY_ARTIFACT }} path: .vortex/docs/static if_no_artifact_found: fail allow_forks: true - - name: Copy the CLI to docs + - name: Copy the binary to docs run: | - mv .vortex/docs/static/vortex.phar .vortex/docs/static/install + mv ".vortex/docs/static/${BINARY_FILE}" .vortex/docs/static/install php .vortex/docs/static/install --version - name: Check docs up-to-date diff --git a/.vortex/tests/zizmor.yml b/.vortex/tests/zizmor.yml index 4f4871974..76eddce23 100644 --- a/.vortex/tests/zizmor.yml +++ b/.vortex/tests/zizmor.yml @@ -14,9 +14,9 @@ rules: # needs a write token on fork PRs and never checks out PR code. # - label-merge-conflict: labels PRs with merge-conflict status via # 'eps1lon/actions-label-merge-conflict'; never checks out PR code. - # - vortex-test-docs: chained run after 'Vortex - Test CLI'; the - # 'head_sha' interpolation has been moved into 'env:' to remove the - # injection surface. + # - vortex-test-docs: chained run after a per-major test workflow; the + # 'head_sha' interpolation lives in 'env:' to remove the injection + # surface. ignore: - assign-author.yml - label-merge-conflict.yml