Skip to content
Open
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
4 changes: 1 addition & 3 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,7 @@ jobs:
- name: Install dependencies
run: pip install tox
- name: Build docs
env:
TOXENV: docs
run: tox
run: tox -c ci/tox.ini --workdir . -e docs
- name: Archive generated docs
uses: actions/upload-artifact@v4.6.0
with:
Expand Down
145 changes: 88 additions & 57 deletions .github/workflows/lint-and-analyse-php.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ on:
types: [opened, synchronize, reopened]
branches:
- develop
workflow_dispatch:

permissions:
contents: read

jobs:
lint:
basic-checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4.2.2
Expand All @@ -28,9 +29,11 @@ jobs:
python-version: "3.13"
- run: pip install --upgrade tox
- name: Run commitizen (https://commitizen-tools.github.io/commitizen/)
run: tox -e cz
run: tox -c ci/tox.ini --workdir . -e cz
- name: Run config-check
run: tox -e config-check
run: tox -c ci/tox.ini --workdir . -e config-check
- name: Run configKeys-check
run: tox -c ci/tox.ini --workdir . -e configKeys-check
- name: Ensure no merge-commits in the Pull Request (PR)
if: github.event_name == 'pull_request'
run: |
Expand All @@ -44,7 +47,7 @@ jobs:
exit 1
fi

markdownlint:
lint-markdown:
runs-on: ubuntu-latest
steps:
- name: Checkout code
Expand All @@ -57,7 +60,7 @@ jobs:
globs: |
*.md

phpunit:
lint-php-files:
runs-on: ubuntu-latest
strategy:
matrix:
Expand All @@ -70,63 +73,20 @@ jobs:
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
tools: cs2pr

- name: Set up PHP ${{ matrix.php-version }}
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}

- name: Install Composer dependencies
# Allow the previous check to fail but not abort
if: always()
uses: ramsey/composer-install@v2
with:
# Ignore zip for php-webdriver/webdriver
composer-options: "--ignore-platform-req=ext-zip"

- name: Create main config.php for unit tests
run: cp config/config.dist.php config/config.php

- name: Create plugin config.php for unit tests
run: |
for source in $(find plugins -type f -name "*dist*"); do
target=$(echo "${source}" | sed -e "s/.dist//")
if ! [ -f "config/$(basename ${target})" ]; then
cp --no-clobber "${source}" "config/$(basename ${target})"
sudo chown www-data:www-data "config/$(basename ${target})"
fi
if ! [ -f ${target} ]; then
ln -s "config/$(basename ${target})" "${target}"
fi
done

- name: Unit Tests
run: |
composer phpunit | tee phpunit.log
if ! grep -qE "Tests:|OK \(" phpunit.log; then
echo "❌ PHPUnit exited early (no summary line found)"
exit 1
fi

lint-php-files:
runs-on: ubuntu-latest
strategy:
matrix:
php-version: ["8.2", "8.3", "8.4"]
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up PHP ${{ matrix.php-version }}
uses: shivammathur/setup-php@v2
- name: Cache Composer packages
uses: actions/cache@v4
with:
php-version: ${{ matrix.php-version }}
path: ~/.composer/cache
key: composer-${{ hashFiles('**/composer.lock') }}
restore-keys: composer-

- name: Install Composer dependencies
uses: ramsey/composer-install@v2
with:
# Ignore zip for php-webdriver/webdriver
composer-options: "--ignore-platform-reqs"
composer-options: "--ignore-platform-reqs --no-progress --no-scripts"

- name: Update the composer.lock file
run: composer update
Expand All @@ -144,7 +104,10 @@ jobs:
# key: phpcs-cache

- name: Lint PHP files
run: ./ci/ci-phplint
run: vendor/bin/parallel-lint . --exclude vendor --exclude tmp --exclude node_modules --checkstyle | cs2pr

# - name: PHP-CS-Fixer (dry-run for CI)
# run: composer php-cs-fixer:lint --format=checkstyle | cs2pr

# TODO: Enable this after resolving issues
# - name: Check coding-standard
Expand All @@ -153,6 +116,7 @@ jobs:
# run: composer phpcs

analyse-php:
needs: [basic-checks, lint-php-files]
runs-on: ubuntu-latest
strategy:
matrix:
Expand All @@ -167,13 +131,80 @@ jobs:
php-version: ${{ matrix.php-version }}
# extensions: mbstring, iconv, mysqli, zip, gd, bz2

- name: Cache Composer packages
uses: actions/cache@v4
with:
path: ~/.composer/cache
key: composer-${{ hashFiles('**/composer.lock') }}
restore-keys: composer-

- name: Install Composer dependencies
uses: ramsey/composer-install@v2

# native github actions support no c2pr required
- name: Analyse files with PHPStan
run: composer phpstan -- --memory-limit 2G
run: ./vendor/bin/phpstan analyse --memory-limit=2G

# - name: Analyse files with Psalm
# # Allow the previous check to fail but not abort
# if: always()
# run: composer psalm -- --shepherd

phpunit:
needs: [analyse-php]
runs-on: ubuntu-latest
strategy:
matrix:
php-version: ["8.2", "8.3", "8.4"]
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up PHP ${{ matrix.php-version }}
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}

- name: Set up PHP ${{ matrix.php-version }}
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}

- name: Cache Composer packages
uses: actions/cache@v4
with:
path: ~/.composer/cache
key: composer-${{ hashFiles('**/composer.lock') }}
restore-keys: composer-

- name: Install Composer dependencies
# Allow the previous check to fail but not abort
if: always()
uses: ramsey/composer-install@v2
with:
# Ignore zip for php-webdriver/webdriver
composer-options: "--ignore-platform-req=ext-zip --no-progress --no-scripts"

- name: Create main config.php for unit tests
run: cp config/config.dist.php config/config.php

- name: Create plugin config.php for unit tests
run: |
for source in $(find plugins -type f -name "*dist*"); do
target=$(echo "${source}" | sed -e "s/.dist//")
if ! [ -f "config/$(basename ${target})" ]; then
cp --no-clobber "${source}" "config/$(basename ${target})"
sudo chown www-data:www-data "config/$(basename ${target})"
fi
if ! [ -f ${target} ]; then
ln -s "config/$(basename ${target})" "${target}"
fi
done

- name: Unit Tests
run: |
composer phpunit | tee phpunit.log
if ! grep -qE "Tests:|OK \(" phpunit.log; then
echo "❌ PHPUnit exited early (no summary line found)"
exit 1
fi
16 changes: 0 additions & 16 deletions ci/ci-phplint

This file was deleted.

117 changes: 76 additions & 41 deletions ci/config-check.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,63 +4,98 @@
import argparse
import dataclasses
import pathlib
import pprint
from typing import Set
import re
import sys


def parse_php_config_array(content: str) -> Set[str]:
keys = set()
stack = []
lines = content.splitlines()

skipping_first_level = True
for line in lines:
line = line.strip()

if not line or line.startswith("//") or line.startswith("#"):
continue

# Match a key like 'key' =>
match = re.match(r"'([^']+)'\s*=>", line)
if match:
key = match.group(1)

if "=> [" in line or line.endswith("["):
# Nested array starts
stack.append(key)
# Skip top-level 'settings' entry
if skipping_first_level and key == "settings":
stack = []
continue
skipping_first_level = False
else:
full_key = ".".join(stack + [key])
keys.add(full_key)

# Handle array closing
if line in ("]", "],"):
if stack:
stack.pop()

return keys


def to_env_key(config_key: str) -> str:
return "LB_" + config_key.upper().replace(".", "_").replace("-", "_")

def extract_keys_from_env(env_path: pathlib.Path) -> Set[str]:
keys = set()
for line in env_path.read_text().splitlines():
line = line.strip()
if not line or line.startswith("#"):
continue
match = re.match(r"([A-Z0-9_]+)\s*=", line)
if match:
keys.add(match.group(1))
return keys

def main() -> int:

args = parse_args()
config_text= args.config_path.read_text()
config_keys = parse_php_config_array(config_text)
env_keys = extract_keys_from_env(args.env_path)

dist_path = args.config_dir / "config.dist.php"
devel_path = args.config_dir / "config.devel.php"
dist_configs = read_config_values(filepath=dist_path)
devel_configs = read_config_values(filepath=devel_path)

exit_value = 0
missing_devel = dist_configs - devel_configs
if missing_devel:
print(f"Config values set in {dist_path} but not in {devel_path}")
pprint.pprint(sorted(missing_devel))
print()
exit_value = 1

missing_dist = devel_configs - dist_configs
if missing_dist:
print(f"Config values set in {devel_path} but not in {dist_path}")
pprint.pprint(sorted(missing_dist))
exit_value = 1
return exit_value


def read_config_values(*, filepath: pathlib.Path) -> set[str]:
configs = set()
with open(filepath) as in_file:
for line in in_file.readlines():
line = line.strip()
if not line.startswith("$conf"):
continue
result = re.search(r"^\$conf\S+", line)
if not result:
raise ValueError(f"Invalid configuration line: {line}")
conf_value = result.group()[5:]
configs.add(conf_value)
return configs
missing_keys = []
for key in sorted(config_keys):
env_key = to_env_key(key)
if env_key not in env_keys:
missing_keys.append((key, env_key))

if missing_keys:
print("❌ Missing environment variables:")
for key, env_key in missing_keys:
print(f" - {env_key} (from '{key}')")
return 1

print("✅ All config keys are covered by environment variables.")
return 0

@dataclasses.dataclass(kw_only=True)
class ProgramArgs:
config_dir: pathlib.Path
config_path: pathlib.Path
env_path: pathlib.Path



def parse_args() -> ProgramArgs:
parser = argparse.ArgumentParser()

parser.add_argument("config_dir")

parser.add_argument("config_path", type=pathlib.Path)
parser.add_argument("env_path", type=pathlib.Path)
args = parser.parse_args()
args.config_dir = pathlib.Path(args.config_dir).expanduser().resolve()
return ProgramArgs(**vars(args))
return ProgramArgs(config_path=args.config_path, env_path=args.env_path)



if "__main__" == __name__:
Expand Down
Loading
Loading