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
7 changes: 6 additions & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,16 @@ jobs:

- name: Setup PHP
uses: shivammathur/setup-php@v2
env:
update: "true"
with:
php-version: "7.4"
php-version: "8.4.27"
tools: composer:v2
coverage: none

- name: Verify host PHP security version
run: node scripts/check-php-version.js

- name: Install npm dependencies
run: npm ci

Expand Down
22 changes: 17 additions & 5 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,20 @@ jobs:
grep -q "'BEEHIIV_REGISTRATION_TOKEN_PLACEHOLDER' !== \$token" includes/OAuth/Config.php

lint:
name: Lint
name: Lint (${{ matrix.compatibility }})
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
php: ["7.4", "8.0", "8.1", "8.2", "8.3", "8.4", "8.5"]
include:
- { compatibility: "7.4", php: "8.4.27" }
- { compatibility: "8.0", php: "8.4.27" }
- { compatibility: "8.1", php: "8.4.27" }
- { compatibility: "8.2", php: "8.2.35" }
- { compatibility: "8.3", php: "8.3.36" }
- { compatibility: "8.4", php: "8.4.27" }
- { compatibility: "8.5", php: "8.5.12" }

steps:
- name: Checkout
Expand All @@ -48,13 +55,18 @@ jobs:
node-version-file: .nvmrc
cache: npm

- name: Setup PHP
- name: Setup patched PHP ${{ matrix.php }}
uses: shivammathur/setup-php@v2
env:
update: "true"
with:
php-version: ${{ matrix.php }}
tools: composer:v2
coverage: none

- name: Verify host PHP security version
run: node scripts/check-php-version.js

- name: Install Composer dependencies
run: composer install --prefer-dist --no-progress

Expand All @@ -67,5 +79,5 @@ jobs:
- name: Lint CSS
run: npm run lint:css

- name: Lint PHP
run: npm run lint:php
- name: Check PHP ${{ matrix.compatibility }}+ static compatibility
run: npm run lint:php -- --runtime-set testVersion "${{ matrix.compatibility }}-"
53 changes: 31 additions & 22 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,18 @@ concurrency:

jobs:
unit-tests:
name: "PHPUnit (PHP ${{ matrix.php-version }}, WP ${{ matrix.wp-version }})"
name: "PHPUnit (PHP ${{ matrix.php.patch }}, WP ${{ matrix.wp-version }})"
runs-on: ubuntu-latest
timeout-minutes: 15

strategy:
fail-fast: false
matrix:
# Keep in sync with "Requires PHP" / "Requires at least" in readme.txt.
php-version: ["7.4", "8.0", "8.1", "8.2", "8.3"]
php:
- { series: "8.2", patch: "8.2.35" }
- { series: "8.3", patch: "8.3.36" }
- { series: "8.4", patch: "8.4.27" }
- { series: "8.5", patch: "8.5.12" }
wp-version: ["6.8", "latest"]

steps:
Expand All @@ -36,45 +39,46 @@ jobs:

- name: Setup PHP
uses: shivammathur/setup-php@v2
env:
update: "true"
with:
php-version: ${{ matrix.php-version }}
php-version: ${{ matrix.php.patch }}
tools: composer:v2
coverage: none

- name: Verify host PHP security version
run: node scripts/check-php-version.js

- name: Install Composer dependencies
run: composer install --prefer-dist --no-progress

- name: Install npm dependencies
run: npm ci

- name: Pin wp-env PHP/WP core version
run: npm run env:set-version -- --php=${{ matrix.php-version }} --wp=${{ matrix.wp-version }}

- name: Patch archived Debian base image (PHP 7.4 / 8.0)
if: matrix.php-version == '7.4' || matrix.php-version == '8.0'
run: scripts/ci-patch-debian-base.sh ${{ matrix.php-version }}
run: npm run env:set-version -- --php=${{ matrix.php.series }} --wp=${{ matrix.wp-version }}

- name: Start wp-env
run: npx wp-env start
run: npm run env:start

- name: Run PHPUnit
run: npx wp-env run tests-cli -- bash -c "cd wp-content/plugins/$(basename "$GITHUB_WORKSPACE") && composer test"

- name: Generate PHPUnit dashboard
if: always()
run: node tests/phpunit/report/generate-dashboard.js --label="PHP ${{ matrix.php-version }} / WP ${{ matrix.wp-version }}"
if: always() && hashFiles('tests/phpunit/test-results/junit.xml') != ''
run: node tests/phpunit/report/generate-dashboard.js --label="PHP ${{ matrix.php.series }} / WP ${{ matrix.wp-version }}"

- name: Upload PHPUnit dashboard
if: always()
if: always() && hashFiles('tests/phpunit/test-results/**') != ''
uses: actions/upload-artifact@v4
with:
name: phpunit-report-php${{ matrix.php-version }}-wp${{ matrix.wp-version }}
name: phpunit-report-php${{ matrix.php.series }}-wp${{ matrix.wp-version }}
path: tests/phpunit/test-results
retention-days: 7

- name: Stop wp-env
if: always()
run: npx wp-env stop
run: npm run env:stop

e2e:
name: E2E tests
Expand All @@ -93,17 +97,25 @@ jobs:

- name: Setup PHP
uses: shivammathur/setup-php@v2
env:
update: "true"
with:
php-version: "7.4"
php-version: "8.4.27"
tools: composer:v2
coverage: none

- name: Verify host PHP security version
run: node scripts/check-php-version.js

- name: Install Composer dependencies
run: composer install --prefer-dist --no-progress

- name: Install npm dependencies
run: npm ci

- name: Test PHP runtime safeguards
run: npm run test:php-runtime

- name: Build plugin assets
run: npm run build

Expand All @@ -116,17 +128,14 @@ jobs:
- name: Install Playwright browsers
run: npx playwright install --with-deps chromium

- name: Patch archived Debian base image (PHP 7.4)
run: scripts/ci-patch-debian-base.sh 7.4

- name: Start wp-env
run: npx wp-env start
run: npm run env:start

- name: Run Playwright E2E tests
run: npx playwright test

- name: Upload Playwright report on failure
if: failure()
if: failure() && hashFiles('tests/e2e/test-results/**') != ''
uses: actions/upload-artifact@v4
with:
name: playwright-report
Expand All @@ -135,4 +144,4 @@ jobs:

- name: Stop wp-env
if: always()
run: npx wp-env stop
run: npm run env:stop
2 changes: 1 addition & 1 deletion .wp-env.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"phpVersion": "7.4",
"phpVersion": "8.4",
"core": null,
"plugins": [ "." ],
"themes": [ "https://downloads.wordpress.org/theme/twentytwentyfive.zip" ],
Expand Down
64 changes: 44 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,42 @@ Your beehiiv newsletters, launched straight from the WordPress editor.
- [Node.js](https://nodejs.org/) (version from `.nvmrc`, currently 20) and npm
- [Composer](https://getcomposer.org/)
- [Docker](https://www.docker.com/) — only if you use wp-env
- **WordPress 6.5+** and **PHP 7.4+** (see `beehiiv.php`)
- **PHP 8.4.27** for the default development environment (`mise install` uses `mise.toml`)
- The plugin declares compatibility with **WordPress 6.5+** and **PHP 7.4+** (see `beehiiv.php`); use a supported, patched PHP runtime for development and hosting

## Development environments

Use **wp-env** if you're running Docker — it spins up WordPress, creates the database, mounts this repo as the plugin, and activates it for you. No separate WordPress install required.

Or, if you prefer **Local**, **MAMP**, **Valet**, or similar, clone or symlink this repo into `wp-content/plugins/beehiiv`, run the [setup commands](#setup) below, and activate the plugin manually under **Plugins** in wp-admin.

### PHP security requirements

Development and CI execute PHP only on these supported branches, at or above the listed security release:

| PHP branch | Minimum patch |
| ---------- | ------------- |
| 8.2 | 8.2.35 |
| 8.3 | 8.3.36 |
| 8.4 | 8.4.27 |
| 8.5 | 8.5.12 |

These releases fix [CVE-2026-109618](https://github.com/php/php-src/security/advisories/GHSA-w8hw-w9fx-j79w) and [CVE-2026-109619](https://github.com/php/php-src/security/advisories/GHSA-6wmf-4rv5-p3rp). See the [PHP security advisory](https://www.php.net/archive/2026.php#2026-10-21-5). PHP 8.1 and older do not receive upstream fixes and are excluded from executable development/CI environments.

CI requests the listed host patches with `setup-php` updates enabled and checks the actual interpreter before Composer runs. `setup-php` resolves the requested version to its minor branch, so the patch selector expresses the required minimum; only the runtime check proves that minimum was installed. wp-env uses minor Docker tags such as `wordpress:php8.4` and `wordpress:cli-php8.4`; those tags also do not prove the PHP patch version. `npm run env:start` stops this project's existing containers, pulls both base images, and checks their actual PHP versions with networking disabled. After those checks pass, it removes this project's derived images and recreates the environment, then checks each enabled web/CLI service. A failed check leaves the project stopped. The wrapper does not remove Docker volumes; back up local content before changing WordPress/PHP versions, since wp-env may change core files or require database updates.

Direct `npx wp-env start` bypasses this gate and must not be used for development or CI. Keep phpMyAdmin disabled in wp-env configuration because its separate PHP image is not covered by these checks.

The plugin's PHP 7.4 compatibility declaration, Composer platform, and PHPCompatibility static checks remain in place. Removing PHP 7.4–8.1 from the execution matrix means those versions no longer receive dynamic PHPUnit coverage; static checks do not replace that coverage.

## Setup

```bash
git clone <repository-url> beehiiv
cd beehiiv
nvm install && nvm use # optional; use Node version from .nvmrc
npm install
node scripts/check-php-version.js
composer install
npm run build
```
Expand Down Expand Up @@ -62,7 +83,7 @@ That:
1. Mounts an ephemeral `.wp-env.override.json` from `.wp-env.override.tmpl` for `wp-env start`
2. If `docker-compose.extra-hosts.yml` exists, maps those hostnames to the Docker host (`host-gateway`) so WordPress inside the container can reach your machine

Re-run after changing Doppler secrets. If containers were recreated without the overlay, run `npm run env:hosts`.
Re-run after changing Doppler secrets. `npm run env:hosts` also performs the guarded PHP checks and project restart before applying the overlay; it is an alias for `npm run env:start`.

Without Doppler, define the same constants in `wp-config.php`, or put them in a gitignored `.wp-env.override.json` (`config` map) and use `npm run env:start`.

Expand All @@ -73,32 +94,33 @@ If your local app uses HTTPS with a private CA, set `BEEHIIV_SSLVERIFY` to `fals
Point wp-env at a specific PHP / WordPress core version to check compatibility with older or newer combinations than your default local setup, and get an HTML dashboard for that combination:

```bash
npm run test:compat -- --php=8.1 --wp=6.8
npm run test:compat -- --php=8.3 --wp=6.8
```

That pins wp-env to PHP 8.1 / WP 6.8 (leaving it set for subsequent `npm run env:start` runs — clear with `npm run env:set-version -- --clear`), **destroys and recreates the wp-env environment** so the combination starts from a genuinely clean install, runs PHPUnit, and writes `tests/phpunit/test-results/php8.1-wp6.8/dashboard.html` — pass/fail counts, a per-class breakdown, and failure details. Run it again with different `--php`/`--wp` values to get a separate dashboard per combination; nothing gets overwritten. `--wp` accepts a WordPress.org release number (e.g. `6.8`), `latest` (wp-env's own default), or `nightly`.
That selects PHP 8.3 / WP 6.8 (leaving it set for subsequent `npm run env:start` runs; clear with `npm run env:set-version -- --clear`), **destroys and recreates the wp-env environment**, checks the actual PHP versions against the security requirements above, runs PHPUnit, and writes `tests/phpunit/test-results/php8.3-wp6.8/dashboard.html`. The dashboard contains pass/fail counts, a per-class breakdown, and failure details. Different `--php`/`--wp` combinations have separate report directories. `--php` accepts supported branches 8.2–8.5; `--wp` accepts a WordPress.org release number (e.g. `6.8`), `latest` (wp-env's own default), or `nightly`.

⚠️ `test:compat` deletes your current wp-env database and content for this project (`wp-env destroy`) every time it runs. Only use it against a wp-env instance you're fine wiping — not one with dev content you want to keep.

To only change the pinned version without wiping anything, use `npm run env:set-version -- --php=8.1 --wp=6.8` directly — it writes to the gitignored `.wp-env.override.json`, so it never touches tracked config, and wp-env rebuilds the WordPress core files (not the database) the next time it starts. Because the database is left as-is, wp-admin will show the "database needs to be updated" screen after switching to a different WP version; run `npm run env:destroy` first if you want a clean database for manual browsing too. `npm run test:php:report` regenerates a dashboard from an existing JUnit report (e.g. after `composer test`) without re-running anything.
To update only the configuration, use `npm run env:set-version -- --php=8.3 --wp=6.8`. It writes to the gitignored `.wp-env.override.json`; apply the change with `npm run env:start`. Back up local content before switching versions. For an intentionally clean environment, `npm run env:destroy` deletes the project's database and content. `npm run test:php:report` regenerates a dashboard from an existing JUnit report (e.g. after `composer test`) without re-running anything.

CI runs the full PHP × WP matrix on every PR (see `.github/workflows/test.yml`) and uploads one `phpunit-report-php{X}-wp{Y}` dashboard artifact per combination.
CI runs PHP 8.2–8.5 against WP 6.8 and `latest` on every PR (see `.github/workflows/test.yml`) and uploads one `phpunit-report-php{X}-wp{Y}` dashboard artifact per combination. E2E tests and release builds require host PHP 8.4.27 or later in that branch; E2E containers use the guarded PHP 8.4 images.

## Development Commands

| Command | Purpose |
| --------------------------- | ---------------------------------------------------- |
| `npm run start` | Webpack watcher — rebuilds `build/` on save |
| `npm run build` | Production asset build |
| `npm run env:start` | Start wp-env (+ optional extra_hosts overlay) |
| `npm run env:start:doppler` | Start with Doppler-mounted OAuth/API overrides |
| `npm run env:hosts` | Re-apply `docker-compose.extra-hosts.yml` if present |
| `npm run env:set-version` | Pin wp-env's PHP/WP core version (`--php` / `--wp`) |
| `npm run env:stop` | Stop wp-env |
| `npm run env:destroy` | Tear down wp-env |
| `npm run test:compat` | Run PHPUnit for one PHP/WP combo + its own dashboard |
| `npm run test:php:report` | Regenerate a PHPUnit dashboard from an existing run |
| `npm run lint` | Lint JS, CSS, and PHP (or individually) |
| Command | Purpose |
| --------------------------- | ---------------------------------------------------------------------- |
| `npm run start` | Webpack watcher — rebuilds `build/` on save |
| `npm run build` | Production asset build |
| `npm run env:start` | Verify PHP runtimes, recreate wp-env, and apply optional hosts overlay |
| `npm run env:start:doppler` | Start with Doppler-mounted OAuth/API overrides |
| `npm run env:hosts` | Alias for guarded startup and optional hosts overlay |
| `npm run env:set-version` | Pin wp-env's PHP/WP core version (`--php` / `--wp`) |
| `npm run env:stop` | Stop wp-env |
| `npm run env:destroy` | Tear down wp-env |
| `npm run test:compat` | Run PHPUnit for one PHP/WP combo + its own dashboard |
| `npm run test:php:report` | Regenerate a PHPUnit dashboard from an existing run |
| `npm run test:php-runtime` | Test PHP version policy and guarded startup without Docker |
| `npm run lint` | Lint JS, CSS, and PHP (or individually) |

## Project layout

Expand Down Expand Up @@ -183,7 +205,9 @@ npm run lint

Or individually: `lint:js`, `lint:css`, `lint:php`. Autofix variants: `lint:js:fix`, `lint:css:fix`, `lint:php:fix`, plus `npm run format` for Prettier.

On every pull request (and pushes to `main` / `master`), GitHub Actions runs the same checks via [`.github/workflows/lint.yml`](.github/workflows/lint.yml) on PHP **7.4** through **8.5**.
On every pull request (and pushes to `main` / `master`), [`.github/workflows/lint.yml`](.github/workflows/lint.yml) runs JS, CSS, and PHP lint under the required check names `Lint (7.4)` through `Lint (8.5)`. These labels identify static compatibility targets. Targets **7.4–8.1** run on patched PHP **8.4.27**; targets **8.2–8.5** run on their patched branches listed above. Each job passes `--runtime-set testVersion <target>-` to PHPCS, keeping end-of-life PHP versions out of CI execution.

The pinned PHPCompatibility **9.3.5** rules provide partial static checks and predate PHP 8. They do not cover all PHP 8 changes or replace runtime and PHPUnit coverage on the target versions.

## Releasing (maintainers)

Expand Down
2 changes: 1 addition & 1 deletion mise.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ php = "https://github.com/verzly/mise-php#latest"

[tools]
node = "20.20.2"
php = "8.4"
php = "8.4.27"

[env]
# Prebuilt static binaries (Composer is bundled per PHP version by verzly/mise-php).
Expand Down
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,18 @@
"lint:css:fix": "wp-scripts lint-style --fix",
"lint:php": "vendor/bin/phpcs",
"lint:php:fix": "vendor/bin/phpcbf",
"env:start": "wp-env start && npm run env:hosts",
"env:start": "node scripts/wp-env-start.js && sh scripts/wp-env-extra-hosts.sh",
"env:start:doppler": "doppler run --mount .wp-env.override.json --mount-template .wp-env.override.tmpl -- npm run env:start",
"env:hosts": "sh scripts/wp-env-extra-hosts.sh",
"env:hosts": "npm run env:start",
"env:set-version": "node scripts/wp-env-set-version.js",
"env:stop": "wp-env stop",
"env:stop": "node scripts/wp-env-start.js --stop",
"env:destroy": "wp-env destroy",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui",
"test:e2e:report": "node tests/e2e/report/generate-dashboard.js",
"test:php:report": "node tests/phpunit/report/generate-dashboard.js",
"test:compat": "node scripts/test-compat.js"
"test:compat": "node scripts/test-compat.js",
"test:php-runtime": "node --test tests/php-runtime/*.test.js"
},
"devDependencies": {
"@playwright/test": "^1.60.0",
Expand Down
Loading
Loading