Shared tooling and configuration
What is centralized at the repository root
These files intentionally live once at the monorepo root and apply to all (or most) packages:
| Area | Examples |
|---|---|
| JavaScript / Node | Root package.json scripts, pnpm-workspace.yaml, shared typescript / @wordpress/scripts versions |
| Linting | eslint.config.js, .stylelintrc, .prettierrc.js, markdown lint config, phpcs.xml via Composer at root |
| Testing | Shared Playwright setup under packages/e2e/, PHPUnit XML in packages/phpunit-config/ |
| Git | Root .gitignore; release export rules often use .gitattributes per package |
Run monorepo-wide checks from the root, for example:
pnpm lint # JS, CSS, PHP, MD, package.json (see root package.json)
pnpm build # all projects with a build targetRelease packaging with .gitattributes
Release zips for plugins and themes are built with git archive. Any path marked export-ignore in a package's .gitattributes is left out of the zip, so these lines decide what ships to production.
Every plugin and theme must contain this standard list (it currently only excludes paths; no line-ending or language rules):
.gitattributes export-ignore
.gitignore export-ignore
.gitkeep export-ignore
.wp-env.json export-ignore
.eslintignore export-ignore
.eslintrc.js export-ignore
eslint.config.js export-ignore
.markdownlint.json export-ignore
.markdownlintignore export-ignore
.npmrc export-ignore
CHANGELOG.md export-ignore
CODEOWNERS export-ignore
checklist.md export-ignore
jest.config.js export-ignore
phpunit.xml export-ignore
playwright.config.js export-ignore
playwright.regression.config.js export-ignore
package-lock.json export-ignore
package.json export-ignore
README.md export-ignore
docs export-ignore
documentation export-ignore
scripts export-ignore
tests export-ignore
.editorconfig export-ignore
.github export-ignore
.distignore export-ignore
.husky export-ignore
.vscode export-ignore
phpcs.xml export-ignore
phpcs.xml.dist export-ignore
phpstan.neon export-ignore
phpstan.neon.dist export-ignore
tsconfig.json export-ignore
project.json export-ignore
webpack.config.js export-ignore
eslint.json export-ignoreThe release zip (.github/scripts/prepare-release-zip.sh) is git archive HEAD of the product folder plus the built dist/. vendor/ is not shipped. src/ is not excluded because it can hold runtime PHP.
Rules for files a product does not have are harmless, so the list is shared as-is.
Kept in the zip:
composer.jsonandcomposer.lock. Composer sites install frompackages.json, which is built from the repositorycomposer.jsonat release time. Keeping both files lets a site that deploys the zip without Composer runcomposer install --no-devinside the product folder and get the locked dependencies.Excluded:
phpcs.xmlandproject.json.phpcs.xmlis lint configuration, andproject.jsonis Nx configuration that the release workflow reads from the repository, not from the zip. Neither is used at runtime.Source of truth:
tools/monorepo/src/generators/plugin/files/.gitattributes. The theme generator copy is identical, so new products inherit the standard.Enforcement:
pnpm lint-gitattributes(tools/check-gitattributes.mjs) runs in the PR workflow and fails if a plugin, theme, or generator copy is missing a standard rule.Extra rules: products may add lines for product-specific files, but should not remove standard ones. If many products need the same extra line, propose adding it to the standard.
What stays package-specific
Keep configuration in the package when it encodes product behavior:
block.json, themetheme.json, WordPress templates, and PHP bootstrap files.wp-env.json— port mappings, plugin/theme mappings, and env-specific optionscomposer.json— package name, autoload namespaces,require/require-devfor that productproject.json— which Nx targets the package implements andimplicitDependencies- Playwright specs under
tests/e2e/,tests/accessibility/, andtests/screenshot/(generator conventions)
Examples
Linting
- JavaScript:
pnpm lint-jsuses the ESLint version installed for@wordpress/scriptswith rooteslint.config.js; fix withpnpm fix-js. - CSS/SCSS:
pnpm lint-css→wp-scripts lint-style; fix withpnpm fix-css. - PHP:
pnpm lint-php→ root Composerlint-php(PHPCS); fix withpnpm fix-php. - Markdown:
pnpm lint-mdfor docs;pnpm fix-mdfor autofix.
Testing
- Playwright configuration:
packages/e2e/playwright.config.jsdefines thee2e,accessibility, andscreenshotprojects. Each plugin/themeplaywright.config.jsreuses this shared configuration; put specs in the matchingtests/subdirectory. - E2E:
npx nx run <project>:test-e2eruns thee2eproject;npx nx run <project>:test-e2e-dockerruns it in the Playwright container. Both depend onwp-env-startand upstreambuildpernx.json. - Accessibility:
npx nx run <project>:test-accessibilityruns theaccessibilityproject. - Screenshot regression:
npx nx run <project>:test-screenshotcompares against committed screenshots;npx nx run <project>:test-screenshot-generateregenerates them. - PHP integration:
npx nx run <project>:test-integrationruns PHPUnit insidewp-envusing shared config frompackages/phpunit-config.
Build
- Production assets:
npx nx run <project>:build→wp-scripts build, output typicallydist/.
The root flat config preserves the WordPress recommended rules and BCEW's custom overrides. @wordpress/scripts 31.8's lint-js wrapper adds legacy CLI flags that ESLint 8 rejects in flat-config mode, so tools/lint-js.js invokes the same ESLint 8.57 binary directly and enables flat config. The flat config also retains the build, node_modules, and vendor ignores formerly supplied by @wordpress/scripts. FlatCompat adapts the WordPress shareable config, which is still in the legacy format; this compatibility layer can be removed when the WordPress tooling provides a native flat config. Under this compatibility setup, jsdoc/no-undefined-types reports imported TypeScript types as undefined even though the legacy config accepts them, so the rule remains enabled for JavaScript and is disabled for TypeScript to preserve existing lint results.
Migration expectation: remove redundant configuration
When importing a standalone repository:
- Delete duplicate lint configs, per-repo GitHub Actions, and redundant Prettier/Stylelint files unless the monorepo root cannot express a needed exception.
- If a one-off rule is required, prefer the smallest local override documented in a comment or package readme rather than forking entire configs.
Documentation site tooling
The docs portal uses VitePress and a sync script (tools/sync-docs.mjs). Package docs remain in plugins/<slug>/docs/ and themes/<slug>/docs/. See Documentation site.