npm install # install npm workspace deps for node-packages/*
composer install # install shared dev tooling (phpcs, phpstan, phpunit)
(cd composer-packages/phpcs && composer install) # phpcs package needs its own install too
(cd composer-packages/phpstan && composer install) # phpstan package needs its own install toonode-packages/wp-tooling/—@rtcamp/wp-tooling, the CLI consumed by every rtCamp plugin/theme skeleton; has its ownAGENTS.mdnode-packages/eslint-config/—@rtcamp/eslint-config, shareable ESLint flat config extending@wordpress/eslint-pluginnode-packages/stylelint-config/—@rtcamp/stylelint-config, shareable Stylelint config extending@wordpress/stylelint-confignode-packages/tailwind-config/—@rtcamp/tailwind-config, Tailwind v4 PostCSS config +theme.jsonwebpack plugincomposer-packages/phpcs/—rtcamp/wp-phpcs, PHP_CodeSniffer standards (rtCampWP,rtCampWP-Basic)composer-packages/phpstan/—rtcamp/wp-phpstan, shared PHPStan baseline for WordPress projects.github/workflows/holdsci.yml(lint + tests on every PR and push to main) and the two subtree-split release workflows
Read only what your task needs, when it needs it:
- Contributor docs: see
CONTRIBUTING.mdfor setup, the PR checklist, and the exact test commands for both ecosystems. - Directory guides: some directories carry their own
AGENTS.mdandREADME.mdwith rules for working there (e.g.node-packages/wp-tooling/AGENTS.md) — read it before changing files in that directory.
npm run lint # ESLint across every npm workspace with a lint script
npm run lint:fix # same, with --fix
npm test # Jest across every npm workspace with a test script
(cd composer-packages/phpcs && composer test) # phpcs package's own PHPUnit suite
(cd composer-packages/phpstan && composer test) # phpstan package's own PHPUnit suite- Package layering:
node-packages/*(npm workspaces,@rtcampscope) andcomposer-packages/*(Composer,rtcampvendor) are independent ecosystems sharing one repo for coordinated development only — nothing internal is shared or imported across that boundary. - Composer packages release by subtree split: a
v*tag push runsrelease-php.yml, which splits eachcomposer-packages/*directory withgit subtree split --prefix=<dir>to its read-only mirror repo (rtCamp/wp-phpcs,rtCamp/wp-phpstan) and tags it there for Packagist. - npm packages publish to the npm registry: each
node-packages/*workspace is published to npmjs.com as@rtcamp/<dirname>(public access, set in itspublishConfig), with its own version. Every push tomainalso splits each workspace to annpm/<dirname>branch of this repo, which keeps serving git-URL installs for projects that have not moved to registry versions. - Dependency discipline differs by package type:
wp-toolingships zero runtime dependencies (full banned-package list in its ownAGENTS.md); the three config packages instead rely onpeerDependencies— consumers bring their owneslint/stylelint/tailwindcss. - Prefer official WordPress tooling: build custom only when no official
@wordpress/*(or upstream PHPCS/PHPStan) option covers the need, or the official option blocks a hard constraint.eslint-configextends@wordpress/eslint-plugin,stylelint-configextends@wordpress/stylelint-config,wp-phpcslayers on WPCS/VIPCS/PHPCompatibilityWP/Slevomat,wp-phpstanwrapsszepeviktor/phpstan-wordpress. - PHP version skew is intentional: the packages target PHP
>=8.2(the rtCamp plugin/theme floor) while the monorepo root requires>=8.4.1forsymplify/monorepo-builder.monorepo-builder validatewill flag this — expected, not a bug.
For full release-mechanism details, see .github/workflows/release-php.yml and .github/workflows/split-npm-packages.yml.
- Composer packages have no
versionfield incomposer.json— they version via git tag at the subtree-split step, not a manifest bump. Only theirCHANGELOG.mdgets a heading cut. - There is no root
composer test/composer check— rootcomposer.jsonhas noscriptskey. Run tests from inside eachcomposer-packages/*directory. wp-tooling release:bump/release:changelogare for consumer WordPress plugins/themes, not this monorepo. They require a.phpfile with aPlugin Name:header at cwd root and throw otherwise.- Subtree-split artifacts — the
npm/<dirname>branches and thewp-phpcs/wp-phpstanmirror repos — are generated. Never hand-edit them; a diverged target fails the next split run instead of being silently rewritten. - Never use a
v*tag for an npm release:release-php.ymlfires on everyv*tag and tags share one namespace, so it would cut a Composer release too. Tag npm releases<name>@<version>(for example@rtcamp/eslint-config@1.1.0). - Every one of the six packages carries its own
LICENSEfile —git subtree splitonly carries history of files inside the split directory, so the rootLICENSEnever reaches a mirror repo or split branch. ci.ymlruns every workspace lint, every Jest suite with its coverage threshold (Node 22.19 and 24), and eachcomposer-packages/*suite installed standalone (PHP 8.2, 8.3 and 8.4) on every PR and push to main. A red CI blocks the merge that would otherwise ship straight onto thenpm/*branches..vscode/extensions.jsononly recommends extensions from verified publishers (Microsoft, GitHub, Red Hat, EditorConfig Foundation) — don't add others, regardless of popularity.
- Ensure
npm run check(and thecomposer testcommands for whichevercomposer-packages/*you touched) pass. - Fix all linting/formatting issues — see
CONTRIBUTING.mdfor the full PR checklist.