Skip to content

Add plugin infrastructure: PHPUnit, PHPCS, ESLint, Jest, Playwright#1

Merged
kraftbj merged 7 commits intotrunkfrom
infra-setup
Apr 17, 2026
Merged

Add plugin infrastructure: PHPUnit, PHPCS, ESLint, Jest, Playwright#1
kraftbj merged 7 commits intotrunkfrom
infra-setup

Conversation

@kraftbj
Copy link
Copy Markdown
Contributor

@kraftbj kraftbj commented Apr 16, 2026

Summary

Wires up the standard build-less dev ergonomics so the first feature PR lands against a CI-checked baseline.

  • PHP: composer.json with wordbless + PHPUnit for the test harness. Isolated tools/composer.json holds automattic/jetpack-codesniffer so the root still installs on PHP 7.4 (PHPCS itself needs PHP 8.2+). .phpcs.xml.dist extends the Jetpack ruleset with text domain fosse.
  • JS: package.json pins pnpm via Corepack, uses wp-prettier for WordPress-style paren spacing, and a flat ESLint config consuming eslint-plugin from WordPress. Jest (jsdom) + Playwright smoke tests included.
  • E2E: Playwright against WordPress Playground (wp-playground-cli), no Docker or MySQL. Blueprint mounts the repo as the fosse plugin and auto-logs in.
  • CI (.github/workflows/):
    • tests.yml: PHPUnit matrix of PHP 7.4 → 8.5 x WP 6.9 / trunk (trunk rows continue-on-error). WP 7.0 will be added as its own column once it releases; until then the trunk row exercises it.
    • linting.yml: PHPCS (PHP 8.4) + ESLint/Prettier, with dorny/paths-filter skipping unaffected jobs.
    • e2e.yml: Playwright against Playground, report uploaded on failure.
    • dependabot.yml: weekly composer + pnpm, monthly gh-actions.
  • Docs: AGENTS.md modeled after wordbless's doc; CLAUDE.md points at it.

Worth noting

  • wordbless pinned to dev-trunk — the tagged 0.6.0 caps roots/wordpress at ^6.6, which is fine for the 6.9 column today, but trunk will soon need the unreleased wordbless fix. minimum-stability: dev + prefer-stable: true keeps everything else on releases.
  • composer.lock is intentionally untracked. phpunit/phpunit: ^9.6 || ^11.0 spans PHP versions that can't share a single resolution (PHPUnit 11 needs PHP 8.2+; PHPUnit 9 covers 7.4–8.1), so each install resolves against composer.json. tools/composer.lock stays tracked — that isolated project is PHP-8.4-only and benefits from pinning.
  • phpunit.xml.dist avoids the PHPUnit-10+ cacheDirectory attribute so PHPUnit 9.x doesn't emit config-validation warnings on the PHP 7.4–8.1 rows.
  • prettier is installed as the wp-prettier npm alias; the WordPress prettier config only applies the Jetpack-style ( foo ) paren spacing when the prettier binary identifies as wp-prettier.
  • Playwright's webServer.url points at /readme.html; / and /wp-admin/ both 302-loop during the Playground auto-login and stall the health probe.
  • PHP compatibility checks come for free via the Jetpack ruleset (which pulls in PHPCompatibilityWP) so no separate workflow was added.
  • Plugin header still declares Tested up to: 7.0 since that release is imminent; the matrix will add its column when 7.0 ships.

Test plan

  • CI: Tests workflow passes across the 7.4-8.5 x 6.9/trunk matrix (trunk rows may print warnings; they're non-blocking).
  • CI: Linting workflow passes (PHPCS + ESLint + Prettier).
  • CI: E2E workflow passes (Playwright finds FOSSE active on the Plugins screen).
  • Local: composer install && composer install --working-dir=tools && pnpm install completes cleanly.
  • Local: composer run-script test-php, composer run-script lint-php, pnpm test, pnpm run lint, pnpm run format:check, pnpm run test:e2e all green.
  • AGENTS.md commands in the "First-time setup" section reflect reality.

kraftbj added 4 commits April 16, 2026 16:59
Wires up the standard build-less dev ergonomics so the first feature PR
lands against a CI-checked baseline.

PHP
- composer.json with wordbless (dev-trunk, for WP 7.0 support) + PHPUnit
- Isolated tools/composer.json holding automattic/jetpack-codesniffer so
  the root still installs on PHP 7.4
- .phpcs.xml.dist extends the Jetpack ruleset, text-domain: fosse
- phpunit.xml.dist + tests/php/bootstrap.php + PluginLoadsTest smoke test

JS
- package.json pinning pnpm via Corepack, using wp-prettier for
  WordPress-style paren spacing
- WordPress eslint-plugin flat config with jest globals
- Jest + Playwright harnesses with smoke tests

E2E
- Playwright against WordPress Playground via the wp-playground CLI; no
  Docker or MySQL required. Blueprint mounts the repo as the fosse plugin
  and auto-logs in.

CI
- .github/workflows/tests.yml: PHP 7.4-8.5 x WP 6.9/7.0/trunk matrix,
  trunk rows continue-on-error
- .github/workflows/linting.yml: PHPCS (PHP 8.4) + ESLint/Prettier with
  dorny/paths-filter skipping unaffected jobs
- .github/workflows/e2e.yml: Playwright against Playground
- .github/dependabot.yml: weekly composer + pnpm, monthly gh-actions

Docs
- AGENTS.md modeled after wordbless's; CLAUDE.md points to it
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR sets up baseline developer tooling and CI for the FOSSE WordPress plugin: PHP unit testing via WorDBless/PHPUnit, PHP linting via PHPCS (Jetpack ruleset), JS lint/format/test via ESLint/Prettier/Jest, and E2E via Playwright against WordPress Playground.

Changes:

  • Add Composer configuration for plugin dev deps (WorDBless + PHPUnit) and isolated tools/ Composer project for PHPCS/Jetpack codesniffer.
  • Add JS tooling (pnpm, ESLint flat config, Prettier config aliasing wp-prettier, Jest) plus Playwright + Playground blueprint for E2E.
  • Add GitHub Actions workflows for tests, linting, E2E, and Dependabot configuration.

Reviewed changes

Copilot reviewed 20 out of 26 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tools/composer.lock Locks PHPCS/Jetpack codesniffer toolchain for the isolated tools/ install.
tools/composer.json Defines isolated lint-only Composer project for PHPCS (PHP 8.4 platform).
tests/php/bootstrap.php Boots WordPress via WorDBless and loads the plugin under PHPUnit.
tests/php/PluginLoadsTest.php Adds a PHP smoke test ensuring plugin header parses.
tests/js/fosse.test.js Adds a minimal Jest smoke test.
tests/e2e/plugin-activates.spec.ts Adds Playwright smoke test verifying plugin active in wp-admin.
tests/e2e/blueprint.json Defines Playground blueprint to auto-login and activate the plugin.
src/.gitkeep Keeps src/ tracked as the autoloaded PHP source directory.
playwright.config.ts Configures Playwright to run against Playground webServer.
phpunit.xml.dist Adds PHPUnit configuration for the PHP test suite.
package.json Defines pnpm tooling + scripts and devDependencies (ESLint/Jest/Playwright).
jest.config.js Configures Jest to run tests under tests/js/.
fosse.php Adds optional Composer autoloader loading when vendor/ is present.
eslint.config.mjs Adds ESLint flat config using WordPress plugin presets and test overrides.
composer.lock Locks root dev dependencies (WorDBless/PHPUnit/etc.).
composer.json Adds root Composer setup for plugin dev deps and scripts (test/lint).
CLAUDE.md Points agents to AGENTS.md.
AGENTS.md Documents project conventions and setup commands for contributors/agents.
.prettierignore Prevents formatting generated/vendored directories and lockfiles.
.phpcs.xml.dist Defines PHPCS ruleset (Jetpack) with FOSSE text domain and paths.
.gitignore Ignores vendor/node/build/test artifact directories.
.github/workflows/tests.yml Adds PHPUnit CI matrix across PHP and WP versions.
.github/workflows/linting.yml Adds PHPCS + ESLint/Prettier workflows with path filtering.
.github/workflows/e2e.yml Adds Playwright E2E workflow against Playground.
.github/dependabot.yml Configures Dependabot for Composer, npm, and GitHub Actions.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread phpunit.xml.dist
Comment thread AGENTS.md
kraftbj added 2 commits April 17, 2026 11:08
The plugin's Composer constraints (phpunit/phpunit ^9.6 || ^11.0) intentionally
span PHP versions that can't share a single resolution: PHPUnit 11 needs PHP
8.2+, PHPUnit 9 covers 7.4-8.1. A committed lockfile pins one of those and
breaks composer install on the other side of the split, which is what the
CI matrix sidesteps with composer update.

Dropping the lock lets each install resolve against composer.json, so PHP 7.4
gets PHPUnit 9.x and 8.2+ gets 11.x without a per-environment workaround.
tools/composer.lock stays tracked because that isolated project is a pinned,
PHP-8.4-only toolchain where reproducibility matters more than flexibility.
cacheDirectory was introduced in PHPUnit 10 and is rejected as an invalid
attribute by PHPUnit 9.x, which the PHP 7.4-8.1 CI rows run. Tests still
passed but each run emitted a config-validation warning. PHPUnit's default
cache location is fine for this project's needs, so the attribute isn't
worth keeping just to trigger warnings on older PHP.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 25 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread .github/workflows/linting.yml
Comment thread phpunit.xml.dist
@kraftbj kraftbj removed the request for review from RCowles April 17, 2026 16:25
The Tests workflow only ran the PHPUnit matrix, so Jest failures in the
tests/js/ suite would ship silently even though package.json already wires
`pnpm test` to Jest and a smoke test exists. Adding a standalone jest job
(Node 20, pnpm via Corepack) keeps JS and PHP unit tests in the same
workflow without conflating them with the Linting workflow.
@kraftbj kraftbj merged commit 798dba3 into trunk Apr 17, 2026
19 checks passed
@kraftbj kraftbj deleted the infra-setup branch April 17, 2026 19:50
kraftbj added a commit that referenced this pull request Apr 19, 2026
Aligns with the new 8.2 floor. Also refreshes AGENTS.md pitfall #1
which referenced the old 7.4 constraint.
kraftbj added a commit that referenced this pull request Apr 21, 2026
* Add SDD docs for bundled-backends

Requirements, spec (Approach A — minimal shim), and implementation plan
for vendoring wordpress-activitypub and wordpress-atmosphere release
builds into FOSSE. Short-term bootstrap that unblocks Week 1 federation
work; crisp FOSSE UI replacement is a later SDD item.

* Raise FOSSE PHP floor to 8.2

Atmosphere (wordpress-atmosphere) requires PHP 8.2; bundling it into
FOSSE forces the same floor.

* ci: drop PHP <8.2 from test matrix

Aligns with the new 8.2 floor. Also refreshes AGENTS.md pitfall #1
which referenced the old 7.4 constraint.

* Exclude bundled/ from FOSSE tooling

Pre-declared for the vendored backend plugins:
- composer autoload.exclude-from-classmap
- PHPCS exclude-pattern (also raises testVersion to 8.2-)
- PHPUnit testsuite exclude
- ESLint ignores
- Prettier ignore
- Jest testPathIgnorePatterns

Also ignores sdd/ and local .claude/ artifacts in Prettier to silence
CI warnings on in-flight discussion docs and contributor-local files.

* Add rsync exclude list for bundled plugin sync

Mirrors the export-ignore patterns from each upstream plugin's
.gitattributes (what wordpress.org distribution drops on release).
vendor/ is globally excluded; sync-bundled.sh handles Atmosphere's
production vendor/ as a separate step.

* Add tools/sync-bundled.sh

Bash+rsync sync script for vendoring wordpress-activitypub and
wordpress-atmosphere into bundled/. Reads source paths from
FOSSE_AP_SOURCE / FOSSE_ATMO_SOURCE env vars. Runs composer install
--no-dev --optimize-autoloader in Atmosphere's source so its
production vendor/ is present, then rsyncs vendor/ as a separate step
(vendor/ is excluded from the main rsync).

* Vendor bundled activitypub + atmosphere release builds

First sync via tools/sync-bundled.sh. Vendored source for both plugins
(mirroring each upstream .gitattributes export-ignore), plus
Atmosphere's production composer vendor/ (web-token/jwt-library and
its transitive deps).

Upstream SHAs:
- wordpress-activitypub: c7d64fb2
- wordpress-atmosphere:  d4bc2b7

These are release-build equivalents — the files that ship in the
wordpress.org distribution.

* Load bundled backends when standalone plugin not active

fosse.php now requires bundled/activitypub/activitypub.php and
bundled/atmosphere/atmosphere.php on boot, guarded by sentinel
constants (ACTIVITYPUB_PLUGIN_VERSION, ATMOSPHERE_VERSION). When the
user has the real plugin active, WP's alphabetical plugin load order
ensures its constants are defined before fosse.php runs, so the
bundled copy is skipped.

* Add idempotent first-load bootstrap for bundled backends

Automattic\Fosse\Bundled\Bootstrap::maybe_run closes the activation-
hook gap for plugins FOSSE loads programmatically (bundled AP +
Atmosphere). It runs the provided activate callable the first time it
sees a given upstream version for a given option key, then no-ops on
subsequent calls with the same version. If the upstream version
changes on a future sync, activate runs again so new side-effects take
hold.

* Run bundled-plugin activation on first load

Hooks a plugins_loaded@20 callback that invokes
Bundled\Bootstrap::maybe_run() for each bundled backend we actually
loaded (tracked via local flags set during the require_once block).
Uses ACTIVITYPUB_PLUGIN_VERSION / ATMOSPHERE_VERSION as the activation
key so a bundled-version bump re-runs the side-effects, but steady
state is a no-op.

* Add e2e smoke for bundled backends + move bootstrap to init

Spec originally called for the first-load bootstrap to fire on
plugins_loaded @ 20. That doesn't work: AP's activate() calls
flush_rewrite_rules(), which requires $wp_rewrite — only set up on
'init'. Playground boot fataled with 'Call to a member function
add_rule() on null'. Moved the hook to init @ 20.

E2E spec confirms admin boots and the bundled AP submenu is present
under Settings → ActivityPub.

* docs: document bundled backends + sync script in AGENTS.md

Adds bundled/ to the directory tree, documents tools/sync-bundled.sh
and its env vars, and adds two pitfalls: bundled/ is excluded from all
FOSSE tooling and must not be edited by hand; first-load bootstrap
fires on init (not plugins_loaded) because AP's activate() needs
$wp_rewrite. Also notes that bundling is a short-term bootstrap we
expect to replace.

* Add implementation notes for bundled-backends

Records deviations from spec.md:
- Bootstrap hook moved plugins_loaded@20 → init@20 (AP's activate()
  needs $wp_rewrite).
- Activitypub::activate requires $network_wide; wrapped in a closure.
- PHPCS testVersion bumped to 8.2- in the tooling-excludes commit.
- Prettier gained sdd/ and .claude/ ignores to unbreak CI.
- Bundled\Bootstrap class extraction (already flagged and approved in
  plan.md; noted here for the record).

* ci: composer install on e2e so FOSSE autoload exists

Playwright CI mounts the repo as a WP plugin and boots Playground.
Without composer install running first, vendor/autoload.php is absent
and Automattic\Fosse\Bundled\Bootstrap (from src/) can't be
autoloaded — fatal at init@20 when the first-load bootstrap fires.

Use --no-dev since e2e doesn't need phpunit/wordbless.

* docs: record e2e composer-install deviation in notes

* fix: degrade cleanly when FOSSE autoload is missing

Addresses review finding: the init@20 bootstrap hook referenced
Automattic\Fosse\Bundled\Bootstrap unconditionally. If vendor/ was
missing (bare clone, unpackaged release), init would fatal with
'Class not found'. Now guarded with class_exists() — bundled plugins
still load, the first-run activation shim is skipped.

Also documents Atmosphere's 'unreleased' version string limitation in
implementation-notes.md: once the bootstrap option is seeded, it
won't re-fire for that backend even after sync, until Atmosphere
starts versioning or we swap bundling for a cleaner distribution.

* fix: address codex adversarial review findings

P1 — Guard bundled load against standalone-activation collision.
When FOSSE is active and the user activates standalone AP/Atmosphere,
WP's plugin_sandbox_scrape loads the standalone plugin into the same
request where FOSSE already required the bundled copy — causing a
class-redeclare fatal mid-activation. Now the bundled-load gate also
checks WP_PLUGIN_DIR for the standalone plugin's presence on disk,
not just its sentinel constants. If the standalone plugin exists at
all, we yield to it.

P2 — Bound Bootstrap::maybe_run's retry blast radius with a static
per-request flag. If update_option fails (read-only DB, write error),
the activate callable would otherwise re-fire on every later hook
within the same request — expensive for AP since activate() flushes
rewrites. Static flag ensures at-most-once-per-request semantics.

Tests updated to use unique option keys per method so the static
state doesn't leak across PHPUnit methods in the same process. Added
a test for the persist-failure case.

Multisite (forced $network_wide = false) documented as known
limitation in implementation-notes.md; FOSSE Week-1 is single-site
personal-hub scope.

* Build: require bundled/ plugins in the zip sanity check

The trunk merge brings the git-archive-based build, which picks up
bundled/ automatically. Extend the post-zip sanity check to require
bundled/activitypub/activitypub.php and bundled/atmosphere/atmosphere.php
so a stray `bundled export-ignore` or an incomplete tools/sync-bundled.sh
run fails the build loudly instead of silently shipping a zip without
federation backends.

* Repo: mark bundled/ vendored so Linguist and Copilot skip it

Add linguist-vendored for all of bundled/, plus linguist-generated for
bundled/*/build/ (compiled JS/CSS) and bundled/*/vendor/ (upstream
Atmosphere composer output). Effect: language stats stop reporting
FOSSE as ~80% third-party code, PR diffs for those files auto-collapse,
and Copilot code review skips them — which matters because fixes there
belong upstream (refresh via tools/sync-bundled.sh), not in this repo.

Pair it with a short .github/copilot-instructions.md so any Copilot
surface that doesn't respect the Linguist attrs still gets the
bundled/-is-vendored rule spelled out, and reviewers are pointed back
to the upstream repos for real issues.

* Docs: fix wordpress-activitypub spelling in AGENTS.md

Same typo still lives in tools/sync-bundled.sh and the sdd/ docs; left
alone here because the script's default path is load-bearing for any
local checkout that matched the original misspelling.

* Fix typo: wordpress-activitiypub → wordpress-activitypub in sync-bundled.sh

Agent-Logs-Url: https://github.com/Automattic/fosse/sessions/f7143d3d-8bb3-41aa-8545-f8cf13be835a

Co-authored-by: kraftbj <88897+kraftbj@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: kraftbj <88897+kraftbj@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants