xwp/wp-dev-lib
XWP WP Dev Lib bundles development tools and configuration for WordPress plugins and themes, including coding standards, static analysis, testing, and CI helpers to streamline building and maintaining high-quality WordPress projects.
wp-dev-lib is not a Composer library you require — it is a set of bash runners and config templates that live inside your plugin/theme repo. The supported install is to vendor a pinned copy:
git submodule add https://github.com/xwp/wp-dev-lib.git .dev-lib
git commit -m "Add wp-dev-lib for lint/test tooling"
(Many teams just clone it into dev-lib/ and .gitignore it — either works, but pin a commit, see Gotchas.)
Three things to orient yourself before writing any code:
scripts/ — the actual entry points (check-diff.sh, run-phpcs.sh, run-phpunit.sh/run-wpunit.sh, run-jshint.sh, install-wp-tests.sh, deploy.sh). Read check-diff.sh first; it is the dispatcher that everything else hangs off..travis.yml — the canonical wiring example. Copy its env and script block into your project, change the plugin slug, done.phpcs.xml.dist, phpunit.xml.dist, .jshintrc, .editorconfig) — these are starting configs to copy into your project, not configs to edit in place.Your first useful run, from the plugin root:
DEV_LIB_PATH=.dev-lib bash .dev-lib/check-diff.sh
That lints only files touched by your working tree (phpcs for PHP, jshint/jscs for JS, plus optional unit tests), which is the day-to-day workflow: edit → run → commit. Make it automatic by copying the bundled pre-commit hook to .git/hooks/pre-commit and chmod +x it (check your pinned version for the exact path — some releases ship an installer script alongside it).
Pre-commit as the primary gate. The hook calls check-diff.sh against staged changes, so it stays fast no matter how large the codebase gets. Keep the heavy checks (full phpcs over the repo, multi-version WP unit tests) in CI and reserve the hook for diff-scoped work.
CI parity via the same script. Because check-diff.sh is the single entry point in both places, use it in .travis.yml/GitHub Actions too, with DEV_LIB_PATH exported in env. That guarantees a green local hook means a green CI lint job — the most valuable property of the package.
Config in your project, logic in the library. Put .phpcs.xml.dist and phpunit.xml.dist at your repo root; the runners look there first. Extend the lib's ruleset rather than replacing it:
<rule ref="./.dev-lib/phpcs.xml.dist"/>
<exclude-pattern>tests/*</exclude-pattern>
Unit tests via install-wp-tests.sh. Point WP_TESTS_DIR at a per-version directory, run install-wp-tests.sh once (it pulls WordPress + the core test suite), then run-wpunit.sh reuses it. In CI, loop WP_VERSION over a matrix to get 4.x/5.x coverage from the same checkout.
Release pipeline. deploy.sh handles the git-tag → WordPress.org SVN push (build, tag, rsync to trunk, copy to tags/x.y.z). Trigger it only from a tagged CI build so you never deploy a dirty tree.
Escape hatch for project-specific steps. Don't fork the runners — wrap them:
DEV_LIB_PATH=.dev-lib bash .dev-lib/check-diff.sh && composer run-script build-assets
Same trick for custom WPCS sniffs, PHPStan, or ESLint configs the library predates.
wp-coding-standards/wpcs via Composer yourself and let the lib script invoke phpcs from your vendor/bin — expect to override more than you'd like.mapfile, associative arrays). brew install bash and ensure /usr/local/bin/bash is first in PATH, or pre-commit will fail with cryptic syntax errors.DEV_LIB_PATH, configs, and paths relative to CWD. Invoking them from a subdirectory silently skips checks.phpcs . is still worth running before release.DEV_LIB_SKIP, colon-separated tool names) to bypass e.g. jshint while you fix unrelated PHP. Confirm the accepted values against your pinned commit — they changed across releases.check-diff.sh, so you can't see PHP failures behind a JS warning. Run individual run-*.sh scripts when you need to isolate.echo — the scripts source an includes.sh shim and the flow is otherwise opaque: bash -x .dev-lib/check-diff.sh 2>&1 | less.How can I help you explore Laravel packages today?