Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .distignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
.distignore
AGENTS.md
CLAUDE.md
CONTRIBUTING.md
SECURITY.md
/changelog
node_modules
tests
Expand All @@ -31,6 +33,7 @@ eslint.config.*
stylelint.config.*
tsconfig.json
playwright.config.*
postcss.config.*
scoper.inc.php
.wp-env*
.wordpress-org
Expand Down
6 changes: 2 additions & 4 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,11 @@ end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = tab
indent_size = 4

[*.{yml,yaml,json}]
[*.{yml,yaml}]
indent_style = space
indent_size = 2

[*.md]
trim_trailing_whitespace = false

[*.txt]
end_of_line = crlf
File renamed without changes.
8 changes: 8 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ updates:
open-pull-requests-limit: 5
cooldown:
default-days: 7
# The ESLint base loads typescript-eslint, which refuses TypeScript 7.
ignore:
- dependency-name: typescript
update-types: ['version-update:semver-major']
groups:
npm-deps:
patterns:
Expand All @@ -55,6 +59,10 @@ updates:
open-pull-requests-limit: 5
cooldown:
default-days: 7
# The ESLint base loads typescript-eslint, which refuses TypeScript 7.
ignore:
- dependency-name: typescript
update-types: ['version-update:semver-major']
groups:
block-json-tools:
patterns:
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
uses: ./.github/workflows/reusable-php-syntax-check.yml
with:
php-versions: '["7.4"]'
paths: '["tests/fixtures/build-fixture"]'
paths: '["tests/Fixtures/build-fixture"]'

phpcs-smoke:
name: PHPCS dist-file smoke
Expand Down Expand Up @@ -74,7 +74,7 @@ jobs:
shell: bash
run: |
# The production profile skips */tests/*, so a temp copy is scanned outside that path.
cp -R tests/fixtures/plugin-stub "$RUNNER_TEMP/plugin-stub"
cp -R tests/Fixtures/plugin-stub "$RUNNER_TEMP/plugin-stub"
vendor/bin/phpcs -v --standard=php/quality-assurance/phpcs.dist.xml "$RUNNER_TEMP/plugin-stub" | tee phpcs-scan.log
# The ruleset's own exclude-patterns can silently empty the file list, and an empty scan proves nothing.
! grep -qF '(0 files in queue)' phpcs-scan.log
Expand Down Expand Up @@ -118,7 +118,7 @@ jobs:
contents: read
uses: ./.github/workflows/reusable-block-json-check.yml
with:
project-path: tests/fixtures/block-json
project-path: tests/Fixtures/block-json

plugin-check-build:
name: Plugin Check fixture build
Expand All @@ -135,7 +135,7 @@ jobs:
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: build-fixture
path: tests/fixtures/build-fixture
path: tests/Fixtures/build-fixture
include-hidden-files: true
retention-days: 1

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/reusable-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -457,11 +457,11 @@ jobs:
case "$entry" in
"$PLUGIN_SLUG/node_modules/"*|"$PLUGIN_SLUG/"*/node_modules/*|"$PLUGIN_SLUG/tests/"*|"$PLUGIN_SLUG/"*/tests/*|"$PLUGIN_SLUG/vendor/bin/"*|"$PLUGIN_SLUG/"*/vendor/bin/* \
|"$PLUGIN_SLUG/.git/"*|"$PLUGIN_SLUG/.github/"*|"$PLUGIN_SLUG/.idea/"*|"$PLUGIN_SLUG/.vscode/"*|"$PLUGIN_SLUG/.wordpress-org/"* \
|"$PLUGIN_SLUG/.editorconfig"|"$PLUGIN_SLUG/.gitattributes"|"$PLUGIN_SLUG/.gitignore"|"$PLUGIN_SLUG/.distignore"|"$PLUGIN_SLUG/AGENTS.md"|"$PLUGIN_SLUG/CLAUDE.md"|"$PLUGIN_SLUG/changelog/"* \
|"$PLUGIN_SLUG/.editorconfig"|"$PLUGIN_SLUG/.gitattributes"|"$PLUGIN_SLUG/.gitignore"|"$PLUGIN_SLUG/.distignore"|"$PLUGIN_SLUG/AGENTS.md"|"$PLUGIN_SLUG/CLAUDE.md"|"$PLUGIN_SLUG/CONTRIBUTING.md"|"$PLUGIN_SLUG/SECURITY.md"|"$PLUGIN_SLUG/changelog/"* \
|"$PLUGIN_SLUG/composer.json"|"$PLUGIN_SLUG/composer.lock"|"$PLUGIN_SLUG/package.json"|"$PLUGIN_SLUG/package-lock.json" \
|"$PLUGIN_SLUG/phpcs"*.xml*|"$PLUGIN_SLUG/.phpcs"*.xml*|"$PLUGIN_SLUG/phpstan"*.neon*|"$PLUGIN_SLUG/.phpstan"*.neon* \
|"$PLUGIN_SLUG/phpunit"*.xml*|"$PLUGIN_SLUG/.phpunit.result.cache"|"$PLUGIN_SLUG/.phpunit.cache/"*|"$PLUGIN_SLUG/infection.json"|"$PLUGIN_SLUG/infection.json."* \
|"$PLUGIN_SLUG/eslint.config."*|"$PLUGIN_SLUG/stylelint.config."*|"$PLUGIN_SLUG/tsconfig.json"|"$PLUGIN_SLUG/playwright.config."* \
|"$PLUGIN_SLUG/eslint.config."*|"$PLUGIN_SLUG/stylelint.config."*|"$PLUGIN_SLUG/tsconfig.json"|"$PLUGIN_SLUG/playwright.config."*|"$PLUGIN_SLUG/postcss.config."* \
|"$PLUGIN_SLUG/scoper.inc.php"|"$PLUGIN_SLUG/.wp-env"*)
echo "::error::Archive must not contain $entry."
status=1
Expand Down
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
tests/.cache
/php/release/vendor/

# Secret material is never tracked, so these match at any depth as a backstop against an accidental git add.
.env*
*.pem
*.key
id_rsa*
.npmrc
auth.json

# Created by https://www.toptal.com/developers/gitignore/api/node,yarn,linux,macos,windows,composer,sublimetext,phpstorm+all,visualstudiocode,phpunit
# Edit at https://www.toptal.com/developers/gitignore?templates=node,yarn,linux,macos,windows,composer,sublimetext,phpstorm+all,visualstudiocode,phpunit

Expand Down
10 changes: 5 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,26 @@ wordpress-configs/
│ ├── composer/ # ScopePhpDependencies, ScopedPackages, GlobalSymbols, Manifest and their AST visitors
│ ├── phpstan/ # GlobalTagVisitor, which the PHPStan profile registers
│ └── release/ # the manifest and lock of the WP-CLI commands reusable-release.yml installs, which Dependabot keeps current
├── node/ # tsconfig, ESLint, Stylelint, PostCSS and Playwright baselines
├── node/ # tsconfig, ESLint, Stylelint and Playwright baselines
│ └── block-json/ # the manifest and lock of the ajv-cli reusable-block-json-check.yml installs, which the npm package leaves out
├── docs/ # workflows.md (input reference) and scripts-contract.md (scripts, files, floors)
├── tests/
│ ├── Unit/ # PHPUnit tests that run the real tools
│ ├── Support/ # RunsWorkflowStepsTrait, which runs a workflow step's script, and RecordsInstalledPackagesTrait
│ └── fixtures/ # projects and captured tool output the tests use
│ └── Fixtures/ # projects and captured tool output the tests use
├── phpcs.dist.xml # self-lint: the production profile minus WordPress-runtime sniffs
├── phpcs.tests.dist.xml # self-lint for tests/
├── phpstan.dist.neon # self-lint: the shared profile over php/ and tests/
├── phpstan.dist.neon # self-lint: the shared profile over php/
└── .github/workflows/ # 11 reusable workflows and 4 self-CI workflows (codeql, quality, tests, workflow-checks)
```

## Commands

```sh
composer install
composer quality-check # PHPCS (both profiles), PHPStan, unit tests
composer quality-check # composer validate --strict, PHPCS (both profiles), PHPStan, unit tests
composer test:unit:mutation # opt-in Infection run, no floor
npm run lint:scripts # ESLint over node/ and the smoke harness
npm run lint:scripts # ESLint over node/, the smoke harness and eslint.config.mjs
npm run lint:config # load-smokes every Node baseline
actionlint
zizmor --config .github/zizmor.yml .github/
Expand Down
3 changes: 1 addition & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ Install the dependencies on the PHP, Composer, Node and npm versions the README
```sh
composer install
npm install
composer validate --strict
composer quality-check
composer audit --abandoned=report
npm run lint:scripts
Expand All @@ -19,7 +18,7 @@ actionlint
zizmor --config .github/zizmor.yml .github/
```

`composer quality-check` runs both PHPCS profiles, PHPStan and the unit suite. CI also runs smokes with no local command, such as Plugin Check, the older-PHP syntax lane and CodeQL; [tests/README.md](tests/README.md) describes the test tiers.
`composer quality-check` validates the manifest strictly, then runs both PHPCS profiles, PHPStan and the unit suite. CI also runs smokes with no local command, such as Plugin Check, the older-PHP syntax lane and CodeQL; [tests/README.md](tests/README.md) describes the test tiers.

## Reusable workflows are public API

Expand Down
19 changes: 6 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,14 +222,13 @@ A recipe returns php-scoper settings for one package, with paths relative to it:

## Node baselines

`node/` holds one baseline per tool, which a project extends and overrides. The package declares the tools behind them as `>=` peer dependencies, floored at the versions it is tested against: `@wordpress/scripts`, `@wordpress/eslint-plugin`, `@wordpress/stylelint-config`, `@wordpress/postcss-plugins-preset`, `@playwright/test` and `typescript`, whose defaults `node/tsconfig.base.json` builds on.
`node/` holds one baseline per tool, which a project extends and overrides. The package declares the tools behind them as `>=` peer dependencies, floored at the versions it is tested against: `@wordpress/scripts`, `@wordpress/eslint-plugin`, `@wordpress/stylelint-config`, `@playwright/test` and `typescript`, whose defaults `node/tsconfig.base.json` builds on. `typescript` stays below 7, because typescript-eslint, which the ESLint base loads, refuses TypeScript 7.

| Baseline | Holds |
| --- | --- |
| `node/tsconfig.base.json` | The deltas from TypeScript's defaults: `react-jsx`, `noImplicitReturns`, `noFallthroughCasesInSwitch`, `isolatedModules`, `resolveJsonModule`, `skipLibCheck` and `noEmit` |
| `node/eslint.config.base.mjs` | `@wordpress/eslint-plugin`'s recommended flat config, with its unit-test and Playwright presets scoped to test globs |
| `node/stylelint.config.base.js` | `@wordpress/stylelint-config/scss` with generated trees ignored, `selector-class-pattern` off, and reports on needless, descriptionless and invalid-scope disable comments |
| `node/postcss.config.base.js` | `@wordpress/postcss-plugins-preset` plus the production minifier `@wordpress/scripts` uses when a project has no PostCSS config |
| `node/stylelint.config.base.js` | `@wordpress/stylelint-config/scss-stylistic`, WordPress's SCSS and formatting rules, with generated trees ignored, `selector-class-pattern` off, and reports on needless, descriptionless and invalid-scope disable comments |
| `node/playwright.config.base.js` | A factory over the `@wordpress/scripts` Playwright config, taking the project's wp-env `port` |

TypeScript extends the base by path:
Expand Down Expand Up @@ -257,12 +256,6 @@ const baseConfig = require( '@ahegyes/wordpress-configs/node/stylelint.config.ba
module.exports = { ...baseConfig, rules: { ...baseConfig.rules } };
```

PostCSS re-exports the base; a project that needs another plugin appends it to the chain and keeps the minifier:

```js
module.exports = require( '@ahegyes/wordpress-configs/node/postcss.config.base.js' );
```

Playwright calls the factory with the port the wp-env config serves on (8888 when it sets none) and spreads nested keys individually, or the top-level spread drops the WordPress defaults:

```js
Expand All @@ -272,7 +265,7 @@ const baseConfig = require( '@ahegyes/wordpress-configs/node/playwright.config.b
module.exports = defineConfig( { ...baseConfig, use: { ...baseConfig.use } } );
```

The Playwright factory sets `testDir` to `tests/e2e/` and artifacts under `tests/.cache/artifacts/`, and returns the `@wordpress/scripts` `webServer` on the given port, which runs `npm run wp-env start`. It sets `WP_BASE_URL` and `WP_ARTIFACTS_PATH` only when they are unset, before loading the `@wordpress/scripts` config, which reads them once, so an exported value wins over `port` and a second call in the same process reuses the first configuration; a project that starts wp-env through its own script spreads `webServer` and sets `command`. For WordPress fixtures, import from `@wordpress/e2e-test-utils-playwright`; for accessibility scans, add `@axe-core/playwright` to the project's own devDependencies.
The Playwright factory sets `testDir` to `tests/EndToEnd/` and artifacts under `tests/.cache/artifacts/`, and returns the `@wordpress/scripts` `webServer` on the given port, which runs `npm run wp-env start`. It sets `WP_BASE_URL` and `WP_ARTIFACTS_PATH` only when they are unset, before loading the `@wordpress/scripts` config, which reads them once, so an exported value wins over `port` and a second call in the same process reuses the first configuration; a project that starts wp-env through its own script spreads `webServer` and sets `command`. For WordPress fixtures, import from `@wordpress/e2e-test-utils-playwright`; for accessibility scans, add `@axe-core/playwright` to the project's own devDependencies.

## Reusable workflows

Expand All @@ -296,9 +289,9 @@ The Playwright factory sets `testDir` to `tests/e2e/` and artifacts under `tests

## Distribution and editor files

`.distignore` is the baseline of `wp dist-archive` exclusions: copy it to the project root and extend it with project-specific source paths. `reusable-release.yml` requires the file. It excludes tests, package-manager manifests, CI and IDE files, development configs such as every `phpcs*.xml*` ruleset, and secret material (`.env*`, root-anchored `*.pem`, `*.key`, `id_rsa*`, `.npmrc`, `auth.json`).
`.distignore` is the baseline of `wp dist-archive` exclusions: copy it to the project root and extend it with project-specific source paths. `reusable-release.yml` requires the file. It excludes tests, package-manager manifests, CI and IDE files, the agent, contributing and security documents, development configs such as every `phpcs*.xml*` ruleset, and secret material (`.env*`, root-anchored `*.pem`, `*.key`, `id_rsa*`, `.npmrc`, `auth.json`).

`.editorconfig` follows WordPress core: tabs by default, two spaces for YAML and JSON, trailing whitespace kept in Markdown, and CRLF line endings for `*.txt`.
`.editorconfig` is WordPress core's with three changes: a tab is four columns wide, as PHPCS and Prettier measure it, every YAML extension indents with two spaces, and `*.txt` files keep LF line endings. Every other file, JSON and NEON included, indents with tabs, and Markdown keeps its trailing whitespace.

## Development

Expand All @@ -314,7 +307,7 @@ Run the unit suite:
composer test
```

Run the PHP lint and the unit suite together:
Validate the manifest and run the PHP lint and the unit suite together:

```sh
composer quality-check
Expand Down
Loading
Loading