diff --git a/.gitea/workflows/anti-eav-lint.yml b/.gitea/workflows/anti-eav-lint.yml new file mode 100644 index 0000000..da30dd9 --- /dev/null +++ b/.gitea/workflows/anti-eav-lint.yml @@ -0,0 +1,66 @@ +name: Anti-EAV Lint + Quality Gate + +on: + push: + branches: [main, master, develop] + pull_request: + branches: [main, master] + +concurrency: + group: ${{ gitea.workflow }}-${{ gitea.ref }} + cancel-in-progress: true + +jobs: + anti-eav-lint: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Composer install (no-dev) + run: | + if [ -f composer.json ]; then + composer install --no-dev --optimize-autoloader --no-interaction + fi + + - name: PHP syntax lint + run: | + set -e + FAILED=$(find . -name '*.php' \ + -not -path './vendor/*' \ + -not -path './node_modules/*' \ + -not -path './e2e/*' \ + -not -path './tests/*' \ + -exec php -l {} \; 2>&1 | grep -v 'No syntax errors' || true) + if [ -n "$FAILED" ]; then + echo "PHP syntax errors detected:" + echo "$FAILED" + exit 1 + fi + + - name: Anti-EAV strict lint + run: | + if ! command -v wp >/dev/null 2>&1; then + echo "wp-cli not available; skipping anti-eav lint" + exit 0 + fi + # dev30 hosts the plugin family this repo belongs to. + TMDO_HOST="/var/www/Studio/wp-local-dev30" + if [ -d "$TMDO_HOST" ]; then + wp --path="$TMDO_HOST" tmdo lint --plugin="${GITHUB_WORKSPACE}" --strict + else + echo "TMDO_HOST not found; skipping anti-eav lint" + fi + + - name: Packaging audit (10-check) + run: | + # package-plugin.sh lives in the wp-local-dev tree and is shared by all + # custom plugins; its output goes to wp-local-dev/dist/. + PACKAGING_HOST="/var/www/Studio/wp-local-dev" + if [ -d "$PACKAGING_HOST" ]; then + SLUG="2meet-data-optimizer" + bash "$PACKAGING_HOST/scripts/package-plugin.sh" "$SLUG" + else + echo "PACKAGING_HOST not found; skipping packaging audit" + fi diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml new file mode 100644 index 0000000..049b4ad --- /dev/null +++ b/.gitea/workflows/release.yml @@ -0,0 +1,87 @@ +name: Release + +on: + push: + tags: + - 'v*' + +jobs: + test-gate: + name: Test Gate (lint + phpcs + phpstan + unit + integration) + runs-on: ubuntu-latest + # Host-mode runner: no service containers. Uses the host MariaDB, same as + # the Tests workflow. + + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: composer install --no-interaction --prefer-dist + + - name: PHP Lint + run: | + find . -name "*.php" \ + ! -path "./vendor/*" \ + ! -path "./tests/*" \ + -print0 | xargs -0 -n1 php -l + echo "PHP syntax OK" + + - name: PHPCS + run: vendor/bin/phpcs --standard=phpcs.xml --report=checkstyle -q . + + - name: PHPStan + run: vendor/bin/phpstan analyse --no-progress --error-format=github + + - name: Unit Tests + run: php vendor/bin/phpunit --configuration phpunit.xml --testdox + + - name: Integration Tests + env: + TMDO_TEST_DB_HOST: 127.0.0.1 + TMDO_TEST_DB_USER: dbo + TMDO_TEST_DB_PASS: ${{ secrets.TMDO_TEST_DB_PASS }} + TMDO_TEST_DB_NAME: wp_wpdo_test + run: php vendor/bin/phpunit --configuration phpunit-integration.xml --testdox + + release: + needs: [test-gate] + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Build ZIP + run: bash scripts/ci-package.sh + + - name: Create Gitea Release + env: + GITEA_TOKEN: ${{ secrets.RELEASE_TOKEN }} + GITEA_URL: ${{ vars.RELEASE_GITEA_URL }} + run: | + set -euo pipefail + VERSION="${{ gitea.ref_name }}" + ZIP=$(ls dist/*.zip | head -1) + ZIP_NAME=$(basename "$ZIP") + MD5=$(md5sum "$ZIP" | awk '{print $1}') + + # Create the release + RELEASE_ID=$(curl -s -X POST \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Content-Type: application/json" \ + "${GITEA_URL}/api/v1/repos/${{ gitea.repository }}/releases" \ + -d "{ + \"tag_name\": \"${VERSION}\", + \"name\": \"${VERSION}\", + \"body\": \"MD5: \`${MD5}\`\", + \"draft\": false, + \"prerelease\": false + }" | grep -o '"id":[0-9]*' | head -1 | grep -o '[0-9]*') + + # Upload ZIP asset + curl -s -X POST \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Content-Type: application/zip" \ + "${GITEA_URL}/api/v1/repos/${{ gitea.repository }}/releases/${RELEASE_ID}/assets?name=${ZIP_NAME}" \ + --data-binary "@${ZIP}" + + echo "Released ${VERSION} — asset: ${ZIP_NAME} (MD5: ${MD5})" diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml new file mode 100644 index 0000000..eaed1bc --- /dev/null +++ b/.gitea/workflows/test.yml @@ -0,0 +1,111 @@ +name: Tests + +on: + push: + branches: [ main, master, develop ] + pull_request: + branches: [ main, master, develop ] + +jobs: + unit: + name: Unit Tests + runs-on: ubuntu-latest + # Host-mode gitea runner (label `ubuntu-latest:host`) uses the host PHP + # toolchain (currently 8.3). `shivammathur/setup-php` cannot provision + # alternate PHP versions in host mode, so there is no 8.1/8.2/8.3 matrix. + # (Restore one if the runner ever moves to container mode.) + + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: composer install --no-interaction --prefer-dist + + - name: Run unit tests + run: php vendor/bin/phpunit --configuration phpunit.xml --testdox + + integration: + name: Integration Tests + runs-on: ubuntu-latest + # Host-mode runner: service containers are NOT started, so connect to the + # host MariaDB at 127.0.0.1:3306 directly. The password comes from the repo + # secret TMDO_TEST_DB_PASS (test DB `wp_wpdo_test` and user `dbo` already + # exist on the host). The bootstrap also accepts the legacy WPDO_TEST_DB_* + # names, so an existing secret of either name works. + + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: composer install --no-interaction --prefer-dist + + # Preflight: a DB outage or a rotated secret would otherwise surface as an + # opaque PHPUnit bootstrap error. Fail fast with an INFRA-vs-test + # distinction so a red integration job is diagnosable. + - name: DB preflight (host MariaDB reachable?) + env: + TMDO_TEST_DB_HOST: 127.0.0.1 + TMDO_TEST_DB_USER: dbo + TMDO_TEST_DB_PASS: ${{ secrets.TMDO_TEST_DB_PASS }} + TMDO_TEST_DB_NAME: wp_wpdo_test + run: | + php -r ' + mysqli_report(MYSQLI_REPORT_OFF); + $c = @mysqli_connect(getenv("TMDO_TEST_DB_HOST"), getenv("TMDO_TEST_DB_USER"), getenv("TMDO_TEST_DB_PASS"), getenv("TMDO_TEST_DB_NAME"), 3306); + if (!$c) { + fwrite(STDERR, "::error::Integration DB unreachable at " . getenv("TMDO_TEST_DB_HOST") . ":3306 db=" . getenv("TMDO_TEST_DB_NAME") . " user=" . getenv("TMDO_TEST_DB_USER") . " — host MariaDB down or TMDO_TEST_DB_PASS secret stale/rotated. This is an INFRA failure, not a test failure: " . mysqli_connect_error() . "\n"); + exit(1); + } + echo "DB preflight OK: connected to " . getenv("TMDO_TEST_DB_NAME") . " on " . getenv("TMDO_TEST_DB_HOST") . "\n"; + ' + + - name: Run integration tests + env: + TMDO_TEST_DB_HOST: 127.0.0.1 + TMDO_TEST_DB_USER: dbo + TMDO_TEST_DB_PASS: ${{ secrets.TMDO_TEST_DB_PASS }} + TMDO_TEST_DB_NAME: wp_wpdo_test + run: php vendor/bin/phpunit --configuration phpunit-integration.xml --testdox + + lint: + name: PHP Lint + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Check PHP syntax + run: | + find . -name "*.php" \ + ! -path "./vendor/*" \ + ! -path "./tests/*" \ + -print0 | xargs -0 -n1 php -l + echo "PHP syntax OK" + + phpcs: + name: PHPCS + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: composer install --no-interaction --prefer-dist + + - name: Run PHPCS + run: vendor/bin/phpcs --standard=phpcs.xml --report=checkstyle -q . + + phpstan: + name: PHPStan + runs-on: ubuntu-latest + # A never ran static analysis in CI; the baseline is committed, so new code + # is checked at level 6 while existing debt stays silent. + + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: composer install --no-interaction --prefer-dist + + - name: Run PHPStan (level 6, baselined) + run: vendor/bin/phpstan analyse --no-progress --error-format=github diff --git a/scripts/ci-package.sh b/scripts/ci-package.sh new file mode 100644 index 0000000..281a14b --- /dev/null +++ b/scripts/ci-package.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +# CI packaging entry point for 2meet-data-optimizer. +# +# The project mandates that every custom plugin ZIP is produced by the shared +# scripts/package-plugin.sh (10 mandatory release checks, unified exclude list, +# output under wp-local-dev/dist/). This wrapper therefore delegates instead of +# maintaining a second copy of that logic, and only falls back to a minimal +# self-contained build when the shared script is unreachable (e.g. a runner +# without the wp-local-dev tree mounted). +# +# Usage: bash scripts/ci-package.sh +set -euo pipefail + +SLUG="2meet-data-optimizer" +VERSION=$(grep -m1 "Version:" "${SLUG}.php" | sed "s/.*Version:[[:space:]]*//" | tr -d '[:space:]') +SHARED="/var/www/Studio/wp-local-dev/scripts/package-plugin.sh" +SHARED_DIST="/var/www/Studio/wp-local-dev/dist" + +echo "=== Packaging ${SLUG} v${VERSION} ===" + +if [ -f "${SHARED}" ]; then + bash "${SHARED}" "${SLUG}" + # Surface the artefact under ./dist too so the release job's `ls dist/*.zip` + # works regardless of which path produced it. + mkdir -p dist + cp "${SHARED_DIST}/${SLUG}-v${VERSION}.zip" "dist/${SLUG}-v${VERSION}.zip" + echo "=== Done: dist/${SLUG}-v${VERSION}.zip (via shared package-plugin.sh) ===" + exit 0 +fi + +echo "!! Shared package-plugin.sh not found — falling back to a minimal build." +echo "!! This path SKIPS the 10 release checks; do not use it for real releases." + +if [ -f composer.json ]; then + composer install --no-dev --optimize-autoloader --quiet +fi + +mkdir -p dist +TMPDIR=$(mktemp -d) +trap 'rm -rf "${TMPDIR}"' EXIT + +rsync -a \ + --exclude='.git' \ + --exclude='.gitea' \ + --exclude='.github' \ + --exclude='.vscode' \ + --exclude='.idea' \ + --exclude='.claude' \ + --exclude='.phpstan' \ + --exclude='tests' \ + --exclude='docs' \ + --exclude='e2e' \ + --exclude='node_modules' \ + --exclude='dist' \ + --exclude='scripts' \ + --exclude='phpunit*.xml' \ + --exclude='phpcs.xml' \ + --exclude='phpstan*.neon' \ + --exclude='composer.lock' \ + --exclude='.phpunit.result.cache' \ + --exclude='PLAN.md' \ + --exclude='CLAUDE.md' \ + --exclude='DEPLOY.md' \ + ./ "${TMPDIR}/${SLUG}/" + +( cd "${TMPDIR}" && zip -qr "${SLUG}-v${VERSION}.zip" "${SLUG}" ) +mv "${TMPDIR}/${SLUG}-v${VERSION}.zip" "dist/" + +echo "=== Done: dist/${SLUG}-v${VERSION}.zip (fallback build) ===" +md5sum "dist/${SLUG}-v${VERSION}.zip"