Add plugin infrastructure: PHPUnit, PHPCS, ESLint, Jest, Playwright#1
Merged
Add plugin infrastructure: PHPUnit, PHPCS, ESLint, Jest, Playwright#1
Conversation
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
Contributor
There was a problem hiding this comment.
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.
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.
Contributor
There was a problem hiding this comment.
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.
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
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.
8 tasks
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>
8 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Wires up the standard build-less dev ergonomics so the first feature PR lands against a CI-checked baseline.
composer.jsonwith wordbless + PHPUnit for the test harness. Isolatedtools/composer.jsonholdsautomattic/jetpack-codesnifferso the root still installs on PHP 7.4 (PHPCS itself needs PHP 8.2+)..phpcs.xml.distextends theJetpackruleset with text domainfosse.package.jsonpins pnpm via Corepack, useswp-prettierfor WordPress-style paren spacing, and a flat ESLint config consumingeslint-pluginfrom WordPress. Jest (jsdom) + Playwright smoke tests included.wp-playground-cli), no Docker or MySQL. Blueprint mounts the repo as thefosseplugin and auto-logs in..github/workflows/):tests.yml: PHPUnit matrix of PHP 7.4 → 8.5 x WP 6.9 / trunk (trunk rowscontinue-on-error). WP 7.0 will be added as its own column once it releases; until then thetrunkrow exercises it.linting.yml: PHPCS (PHP 8.4) + ESLint/Prettier, withdorny/paths-filterskipping unaffected jobs.e2e.yml: Playwright against Playground, report uploaded on failure.dependabot.yml: weekly composer + pnpm, monthly gh-actions.AGENTS.mdmodeled after wordbless's doc;CLAUDE.mdpoints at it.Worth noting
wordblesspinned todev-trunk— the tagged0.6.0capsroots/wordpressat^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: truekeeps everything else on releases.composer.lockis intentionally untracked.phpunit/phpunit: ^9.6 || ^11.0spans 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 againstcomposer.json.tools/composer.lockstays tracked — that isolated project is PHP-8.4-only and benefits from pinning.phpunit.xml.distavoids the PHPUnit-10+cacheDirectoryattribute so PHPUnit 9.x doesn't emit config-validation warnings on the PHP 7.4–8.1 rows.prettieris installed as thewp-prettiernpm alias; the WordPress prettier config only applies the Jetpack-style( foo )paren spacing when the prettier binary identifies as wp-prettier.webServer.urlpoints at/readme.html;/and/wp-admin/both 302-loop during the Playground auto-login and stall the health probe.Tested up to: 7.0since that release is imminent; the matrix will add its column when 7.0 ships.Test plan
Testsworkflow passes across the 7.4-8.5 x 6.9/trunk matrix (trunk rows may print warnings; they're non-blocking).Lintingworkflow passes (PHPCS + ESLint + Prettier).E2Eworkflow passes (Playwright finds FOSSE active on the Plugins screen).composer install && composer install --working-dir=tools && pnpm installcompletes cleanly.composer run-script test-php,composer run-script lint-php,pnpm test,pnpm run lint,pnpm run format:check,pnpm run test:e2eall green.