Files
wp-plugin-starter-template-…/DEVELOPMENT.md
T
marcus 2f91c3e6af WP Plugin Starter 1.0.0: a fresh start
The parts every wpallstars plugin needs: settings screen with search and an empty General tab, Read Me tab, features as classes, replaced plugins, the shared GitHub updater, release and check scripts, CI and the shared rules. scripts/rename-plugin.sh starts a new plugin; scripts/sync-core.sh keeps plugins' core files in step with this starter. Built with aidevops (https://aidevops.sh).
2026-10-03 03:30:42 +01:00

10 KiB

Development

How changes are made and checked in every plugin made from the wpallstars starter plugin. This file is the same in each of them (names as placeholders: STANDARDS.md lists them, and the plugin's AGENTS.md gives its values). Rules for features, code, styling and testing: STANDARDS.md; the plugin's own rules: AGENTS.md. Releases: RELEASING.md.

Workflow

  1. Open or pick an issue that says what should change and how to check it.
  2. Work on a branch in its own worktree, never on main. Branch names: feature/…, bugfix/…, chore/….
  3. Commit small, working steps. Run scripts/lint.sh before pushing.
  4. Push and open a pull request (Resolves #N). Open it as a draft while the work is in progress: CI lints every push, and the longer release and smoke-test jobs start when the pull request is marked ready for review.
  5. Check the change on the shared preview site (scripts/preview-site.sh, STANDARDS.md → Testing), in light and dark mode for front-end styles.
  6. Merge once CI passes. Releases are separate: RELEASING.md.

Set up

Needs PHP 7.4 or later, Composer 2, Node.js (syntax checks only), ShellCheck and Docker (release checks and smoke test). actionlint is optional locally; CI runs it.

composer install

composer.json lists development tools only. The plugin has no Composer dependencies, and vendor/, composer.*, the tool configuration and .github/ are left out of release zips (.distignore; the preflight fails if one gets in).

Checks

Every pull request and every push to main runs these in GitHub Actions (.github/workflows/ci.yml). Each one runs the same way locally.

Check Command What it finds
PHP syntax scripts/lint.sh php Syntax errors; CI uses PHP 7.4, the minimum.
JavaScript syntax scripts/lint.sh js Syntax errors (node --check).
Shell scripts scripts/lint.sh shell ShellCheck findings in scripts/.
Workflows scripts/lint.sh workflows actionlint findings in .github/workflows/.
Coding standards scripts/lint.sh phpcs WordPress Coding Standards: escaping, sanitising, nonces, prepared SQL, i18n, PHP 7.4 and WordPress 6.2 compatibility (phpcs.xml.dist).
Static analysis scripts/lint.sh phpstan Unknown functions, classes and methods, wrong argument counts and types, dead code (PHPStan level 5, phpstan.neon.dist).
Release build scripts/preflight-release.sh --offline Versions, headers, readme.txt, presets (where the plugin has them) and the contents of both zips.
Plugin Check scripts/plugin-check.sh The WordPress.org review tool, on both zips.
Smoke test scripts/smoke-test.sh --wp 6.2 --php 7.4 and scripts/smoke-test.sh Installs the GitHub zip, loads the site and admin screens with default settings and with every feature on, runs cron, uninstalls. Fails on any PHP message, a failed page or leftover options.

scripts/lint.sh with no arguments runs the first six.

The scripts work out which plugin they are in from its main file (scripts/lib/plugin.sh): the PHP file at the top of the repository with a Plugin Name: header gives the slug (its file name), the name, the class prefix (@package) and the constant prefix (define('<PREFIX>_VERSION', …)). Environment overrides use that prefix, for example {PREFIX}_PREVIEW_SITE or {PREFIX}_DB_IMAGE. Checks for parts only some plugins have (presets and starter data, the replaced plugins count) run when their files exist, so the same scripts serve every plugin made from the starter.

Coding standards

phpcs.xml.dist uses the WordPress ruleset. The plugin's own style differs from it in a few places (spacing, array(), file names, Yoda conditions), and those sniffs are off; the comments in the file say why. Security, database and compatibility sniffs stay on. Fix findings in the code. Where a finding is intended, add an inline // phpcs:ignore Sniff.Name -- reason on that line only.

vendor/bin/phpcbf fixes what it can automatically.

Static analysis

PHPStan reads the code with WordPress's stubs and PHP 7.4's functions. scripts/phpstan-bootstrap.php defines the constants WordPress and the plugin set while loading. Classes and functions of other plugins the code uses (WP-CLI, WooCommerce and the like) are ignored in phpstan-plugin.neon, because the code uses them only after checking they are loaded.

phpstan-baseline.neon is empty: every finding fails the check. Fix the code. Where PHPStan or the stubs are wrong (a custom wp_hash() scheme, a check for a method newer WordPress versions have, variables a closure changes by reference), add an entry under ignoreErrors with the identifier, the file and the reason: in phpstan.neon.dist for the files every plugin made from the starter shares, in phpstan-plugin.neon for the plugin's own files and extra paths. Never put findings in the baseline to get a change through.

Secrets in history

Scan the whole Git history before the repository goes public, and after importing code from elsewhere:

docker run --rm -v "$PWD:/repo:ro" ghcr.io/gitleaks/gitleaks:latest git /repo --redact --no-banner

In a linked worktree, also mount the main repository's .git folder at the same path. .gitleaks.toml lists the false positives with reasons. A real secret is rotated first, then removed from history.

Smoke test

scripts/smoke-test.sh starts a throwaway WordPress in Docker (MariaDB, Apache and WP-CLI images for the chosen PHP version), so nothing touches the shared preview site. CI runs it on WordPress 6.2 with PHP 7.4 and on the latest WordPress with PHP 8.3. Every feature with an on/off setting is switched on at once, except one with the key maintenance (a maintenance mode would answer every visitor page with its notice). --keep-log FILE saves debug.log; CI keeps it as an artifact when the test fails.

Test site resources

Test sites run the plugin alongside many others (a plugin that recommends plugins is tested with all of them active). PHP's defaults are too small for that: OPcache fills and restarts, admin requests queue behind two workers, and the slowdowns look like bugs. Size every test site, shared or throwaway, with room to spare:

Setting Where (LocalWP, nginx) Value PHP or Local default
opcache.memory_consumption conf/php/php.ini.hbs 1024 128
opcache.interned_strings_buffer conf/php/php.ini.hbs 64 8
opcache.max_accelerated_files conf/php/php.ini.hbs 50000 10000
memory_limit conf/php/php.ini.hbs 768M 256M
pm.max_children conf/php/php-fpm.d/www.conf.hbs 5 2

Measured on a shared test site with 72 plugins installed: OPcache used 509 MB of 512 MB and restarted, then 315 MB of 1 GB with 31,723 PHP files cached; pages peaked at 352 MB of memory; the busiest hour needed 5 workers.

  • LocalWP keeps these per site in <site>/conf/. Edit the .hbs templates, not the generated files under Local's run/ folder, then stop and start the site in Local: it rebuilds the configuration only on start. Add a comment with the date and reason next to each change.
  • The OPcache lines sit inside {{#unless apache}}: on an Apache site, move them out or set them in Apache's own configuration.
  • Check after the restart: Site Health → Info → Server shows the new values.
  • Raise them again when a site needs more, and update this table in the starter.
  • To test low-resource warnings, lower a value on a throwaway site, never the shared one.
  • Docker sites (scripts/smoke-test.sh, step 4 in STANDARDS.md → Testing) take the same values in a .ini file mounted into /usr/local/etc/php/conf.d/.

Dependencies

Dependabot (.github/dependabot.yml) opens one pull request a week for the GitHub Actions and one for the Composer tools. Actions are pinned to commit SHAs with the version in a comment; keep it that way when editing workflows. Do not add third-party actions that only save a few lines of shell.

While private: checks are advisory

The repository is private until it is released, so Actions minutes cost money and most code-review services are paid. Until then, work moves fast:

  • No branch protection or required checks on main. A failing check does not block a merge, so a merge never waits on an unrelated failure.
  • Fix any failure your change causes before merging. For a failure your change did not cause, open an issue with the run link and the first error, and merge anyway.
  • Draft pull requests run only the lint job. A new push cancels the run for the previous one.
  • The build zips and Plugin Check reports are kept for seven days on each run (artifact <repository>-build-…) for testing a branch on a site.
  • Review apps that are already installed (CodeRabbit, qlty, Socket) give advice only. A rate-limited or missing review never holds up a merge.

At public launch: full sweep

Making the repository public needs the owner's say. Do this sweep in the same step, while the code-review services are free for public repositories. It goes through the whole codebase once, then keeps it at that standard:

  1. Turn on the free reviewers for the whole codebase, not just new changes: CodeRabbit full review, Codacy, SonarCloud (SonarQube Cloud) and qlty, plus GitHub's CodeQL (PHP and JavaScript), Dependabot security alerts, secret scanning with push protection, and OpenSSF Scorecard. Socket keeps checking dependencies.
  2. Fix what they find in the code, in small pull requests by area (security first). Each finding is either fixed, explained in an inline comment, or marked as a false positive in that service with the reason.
  3. Raise the PHPStan level one step at a time (6, then higher if the findings are real bugs and not noise). The baseline is already empty.
  4. Require the CI checks on main (Lint, Release build, both Smoke tests) with a branch ruleset, without "branch must be up to date": the checks are fast, and changelog lines conflict on every merge.
  5. Turn on private vulnerability reporting (Settings → Security), which SECURITY.md asks reporters to use, and add the CI badge to README.md. SECURITY.md, CONTRIBUTING.md and the issue and pull request templates are already in place.
  6. Run workflows/public-launch-checklist.md from the AI DevOps framework for anything public: no private paths, site names or secrets in the code, history, issues or docs. Run the history scan above again for commits made since the last one.

What a plugin has already done ahead of launch is in its LAUNCH.md.