Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions .docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
ARG PHP_VERSION=8.3
FROM php:${PHP_VERSION}-cli

# git + unzip: the official php:*-cli image ships neither. Composer needs git to fall
# back to a source clone when a dist (zipball) download fails transiently — without it a
# single failed download aborts the whole install ("Source fallback is disabled. Not
# trying alternative sources."). unzip gives faster, more reliable archive extraction
# than composer's PHP-zip fallback.
RUN apt-get update \
&& apt-get install -y --no-install-recommends git unzip \
&& rm -rf /var/lib/apt/lists/*

# mlocati's installer builds the needed extensions across all target PHP versions.
COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/local/bin/
RUN install-php-extensions intl gd mysqli pdo_mysql zip bcmath exif pcov

# Measure coverage against the mounted module source only.
RUN echo 'pcov.directory=/module/src' > /usr/local/etc/php/conf.d/99-pcov.ini

# The TinyMCE build task loads a translation catalogue per i18n locale (hundreds),
# so PHP's 128M default is insufficient; raise it container-wide so every invocation
# (task, CI running vendor/bin/phpunit directly, and manual runs) is covered.
RUN echo 'memory_limit=512M' > /usr/local/etc/php/conf.d/99-memory.ini

COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

WORKDIR /app

COPY app/composer.json /app/composer.json

# SilverStripe requires Page and PageController classes in the project. These are
# generated here (not committed) to avoid duplicate class detection when the module
# is symlinked into vendor/. SilverStripe discovers modules by looking for _config/;
# without a project-root _config/, /app/src/ is not scanned and Page/PageController
# stay invisible to the class manifest (which breaks temp-database creation in tests).
RUN mkdir -p /app/src /app/_config \
&& printf '<?php\n\nuse SilverStripe\\CMS\\Model\\SiteTree;\n\nclass Page extends SiteTree\n{\n}\n' > /app/src/Page.php \
&& printf '<?php\n\nuse SilverStripe\\CMS\\Controllers\\ContentController;\n\nclass PageController extends ContentController\n{\n}\n' > /app/src/PageController.php

COPY app/_config/ /app/_config/
COPY app/phpunit.xml.dist /app/phpunit.xml.dist
COPY app/phpstan.neon.dist /app/phpstan.neon.dist
COPY app/phpstan-php85.neon.dist /app/phpstan-php85.neon.dist
COPY app/rector.php /app/rector.php

COPY entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh

ENTRYPOINT ["entrypoint.sh"]
3 changes: 3 additions & 0 deletions .docker/app/_config/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
Name: app-e2e
---
60 changes: 60 additions & 0 deletions .docker/app/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{
"minimum-stability": "dev",
"prefer-stable": true,
"require": {
"silverstripe/recipe-cms": "^6.0",
"wedevelopnl/silverstripe-e2e": "*"
},
"repositories": [
{
"type": "path",
"url": "/module",
"options": {
"symlink": true
}
}
],
"require-dev": {
"cambis/silverstan": "2.1.9",
"phpstan/extension-installer": "1.4.3",
"phpstan/phpstan": "2.2.1",
"phpstan/phpstan-deprecation-rules": "2.0.4",
"phpunit/phpunit": "11.5.55",
"rregeer/phpunit-coverage-check": "0.3.1",
"tomasvotruba/type-coverage": "2.2.1",
"wernerkrauss/silverstripe-rector": "1.3.0"
},
"autoload": {
"classmap": [
"src/"
]
},
"autoload-dev": {
"psr-4": {
"WeDevelop\\E2e\\Tests\\": "vendor/wedevelopnl/silverstripe-e2e/tests/"
}
},
"extra": {
"project-files-installed": [
".htaccess",
"app/.htaccess",
"app/_config/mimevalidator.yml",
"app/_config/mysite.yml",
"app/src/Page.php",
"app/src/PageController.php"
],
"public-files-installed": [
".htaccess",
"index.php",
"web.config"
]
},
"config": {
"allow-plugins": {
"composer/installers": true,
"phpstan/extension-installer": true,
"silverstripe/recipe-plugin": true,
"silverstripe/vendor-plugin": true
}
}
}
9 changes: 9 additions & 0 deletions .docker/app/phpstan-php85.neon.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Secondary PHPStan pass with the analysis target pinned to PHP 8.5, so
# target-gated checks (e.g. #[\NoDiscard]) run while the primary pass keeps
# verifying the 8.3 floor.
includes:
- phpstan.neon.dist
parameters:
phpVersion:
min: 80500
max: 80599
13 changes: 13 additions & 0 deletions .docker/app/phpstan.neon.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
parameters:
level: max
phpVersion:
min: 80300
max: 80599
paths:
- /module/src
type_coverage:
return_type: 100
param_type: 100
property_type: 100
constant_type: 100
declare: 100
21 changes: 21 additions & 0 deletions .docker/app/phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/silverstripe/framework/tests/bootstrap.php"
cacheDirectory=".phpunit.cache"
colors="true"
beStrictAboutTestsThatDoNotTestAnything="true">
<testsuites>
<testsuite name="unit">
<directory>vendor/wedevelopnl/silverstripe-e2e/tests/Unit</directory>
</testsuite>
<testsuite name="integration">
<directory>vendor/wedevelopnl/silverstripe-e2e/tests/Integration</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory suffix=".php">vendor/wedevelopnl/silverstripe-e2e/src</directory>
</include>
</source>
</phpunit>
34 changes: 34 additions & 0 deletions .docker/app/rector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

use Netwerkstatt\SilverstripeRector\Set\SilverstripeLevelSetList;
use Netwerkstatt\SilverstripeRector\Set\SilverstripeSetList;
use Rector\CodeQuality\Rector\Identical\FlipTypeControlToUseExclusiveTypeRector;
use Rector\CodingStyle\Rector\PostInc\PostIncDecToPreIncDecRector;
use Rector\Config\RectorConfig;
use Rector\EarlyReturn\Rector\If_\ChangeOrIfContinueToMultiContinueRector;

return RectorConfig::configure()
->withPaths([
'/module/src',
])
->withPreparedSets(
deadCode: true,
codeQuality: true,
typeDeclarations: true,
instanceOf: true,
earlyReturn: true,
rectorPreset: true,
)
->withPhpSets(php83: true)
->withSets([
SilverstripeSetList::CODE_STYLE,
SilverstripeLevelSetList::UP_TO_SS_6_0,
])
->withSkip([
// Subjective style choices we deliberately keep.
ChangeOrIfContinueToMultiContinueRector::class,
FlipTypeControlToUseExclusiveTypeRector::class,
PostIncDecToPreIncDecRector::class,
]);
16 changes: 16 additions & 0 deletions .docker/compose.ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# CI-only overlay adding GitHub Actions layer caching + a mirrored db image.
# Layered after compose.yml by .github/workflows/ci.yml:
# COMPOSE_BAKE=true docker compose -f .docker/compose.yml -f .docker/compose.ci.yml build app
# CACHE_SCOPE namespaces the cache per PHP version so matrix jobs don't clobber
# each other. cache_to ignore-error=true lets fork/Dependabot PRs (no cache write
# access) degrade to an uncached build instead of failing.
services:
db:
image: mirror.gcr.io/library/mysql:8

app:
build:
cache_from:
- type=gha,scope=${CACHE_SCOPE:-app}
cache_to:
- type=gha,mode=max,scope=${CACHE_SCOPE:-app},ignore-error=true
49 changes: 49 additions & 0 deletions .docker/compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
services:
app:
build:
context: .
dockerfile: Dockerfile
args:
PHP_VERSION: ${PHP_VERSION:-8.3}
volumes:
- ../composer.json:/module/composer.json:ro
- ../src:/module/src
- ../tests:/module/tests:ro
- ../_config:/module/_config:ro
- ../coverage:/app/coverage
- vendor:/app/vendor
healthcheck:
test: test -f /tmp/.app-ready
interval: 3s
start_period: 60s
retries: 20
depends_on:
db:
condition: service_healthy
environment:
SS_DATABASE_SERVER: db
SS_DATABASE_NAME: silverstripe
SS_DATABASE_USERNAME: silverstripe
SS_DATABASE_PASSWORD: silverstripe
SS_DEFAULT_ADMIN_USERNAME: admin
SS_DEFAULT_ADMIN_PASSWORD: admin
SS_ENVIRONMENT_TYPE: dev
SS_PHPUNIT_FLUSH: 1

db:
image: mysql:8
volumes:
- db-data:/var/lib/mysql
environment:
MYSQL_DATABASE: silverstripe
MYSQL_USER: silverstripe
MYSQL_PASSWORD: silverstripe
MYSQL_ROOT_PASSWORD: root
healthcheck:
test: mysqladmin ping -h localhost
interval: 5s
retries: 10

volumes:
vendor:
db-data:
13 changes: 13 additions & 0 deletions .docker/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/sh
set -e

composer install --no-interaction

# Build the schema + class/config manifest so silverstan and sapphire tests have
# a ready environment. DB is guaranteed up (compose depends_on: db healthy).
vendor/bin/sake dev/build flush=1

touch /tmp/.app-ready

# No webserver: block so `docker compose exec` can run tests/analysis in this container.
exec tail -f /dev/null
26 changes: 26 additions & 0 deletions .github/actions/setup-docker-mirror/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Setup buildx with Docker Hub mirror
description: >-
Configure buildx so build-time docker.io pulls go through mirror.gcr.io
(Google's public pull-through cache for Docker Hub), avoiding Docker Hub's
anonymous rate limit. The buildx docker-container driver pulls base images
(php-cli, composer, the extension installer) and its own buildkit image
independently of the daemon's authenticated session; across the CI matrix
that trips HTTP 429.

Daemon-side pulls (the db/mysql service at `compose up`) are pointed at the
mirror via an explicit image ref in compose.ci.yml instead.

runs:
using: composite
steps:
# Export ACTIONS_CACHE_URL / ACTIONS_RUNTIME_TOKEN into the job env. GitHub
# only exposes these to JavaScript actions, not to `run:` steps — so without
# this, the `type=gha` cache used by `docker compose build` (a run step)
# silently no-ops and every build is cold.
- uses: crazy-max/ghaction-github-runtime@v3
- uses: docker/setup-buildx-action@v3
with:
driver-opts: image=mirror.gcr.io/moby/buildkit:buildx-stable-1
buildkitd-config-inline: |
[registry."docker.io"]
mirrors = ["mirror.gcr.io"]
17 changes: 17 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
version: 2

updates:
- package-ecosystem: composer
directory: /
schedule:
interval: daily

- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly

- package-ecosystem: docker
directory: /.docker
schedule:
interval: weekly
Loading
Loading