Skip to content

feat: add plugin scaffold generator with flavian-starter reference plugin#70

Merged
PAMulligan merged 2 commits into
mainfrom
19-add-plugin-scaffold-generator
May 20, 2026
Merged

feat: add plugin scaffold generator with flavian-starter reference plugin#70
PAMulligan merged 2 commits into
mainfrom
19-add-plugin-scaffold-generator

Conversation

@PAMulligan
Copy link
Copy Markdown
Collaborator

Summary

Adds a token-substitution plugin generator that produces a PSR-4 autoloaded WordPress plugin in one command. Ships plugins/flavian-starter/ as the canonical scaffold output and parallel to themes/flavian-shop/. Closes #19.

bash scripts/scaffold-plugin.sh my-plugin \
  --name "My Plugin" \
  --description "What it does" \
  --author "Your Name"

Produces a runnable plugin with proper headers, activation hooks, a CPT, a taxonomy, a Settings API page, a server-rendered block, PHPUnit tests (Brain Monkey), and a WordPress-Extra PHPCS ruleset. Feature flags: --no-cpt, --no-taxonomy, --no-settings, --no-block, --minimal, --force, --dry-run.

What the scaffold produces

plugins/<slug>/
├── <slug>.php                  ← main file, headers, autoload
├── uninstall.php
├── composer.json               ← PSR-4 + dev deps
├── phpunit.xml.dist
├── .phpcs.xml.dist
├── README.md
├── src/
│   ├── Plugin.php              ← singleton; class_exists() guards per feature
│   ├── Activator.php           ← flush_rewrite_rules on activation
│   ├── Deactivator.php
│   ├── PostTypes/Event.php
│   ├── Taxonomies/EventCategory.php
│   ├── Admin/Settings.php      ← Settings API page
│   └── Blocks/FeaturedEvent.php
├── assets/
│   ├── css/admin.css, js/admin.js
│   └── blocks/featured-event/  ← block.json + index.js (no build step) + CSS
└── tests/
    ├── bootstrap.php           ← Brain Monkey
    ├── TestCase.php
    ├── PluginTest.php
    └── PostTypes/EventTest.php

Decisions locked in (from scoping)

# Choice Implementation
1 flavian-starter reference plugin plugins/flavian-starter/
2 PSR-4 namespaces Flavian\Plugins\<PluginClass>, autoload via composer
3 Include one-block example src/Blocks/FeaturedEvent.php + assets/blocks/featured-event/ (server-rendered, no JS build step)
4 Silent on activation Activator::activate() only calls flush_rewrite_rules() — no demo content
5 Extend plugin-developer only, no new agents .claude/agents/plugin-developer.md updated; description + body now point at scaffold and reference plugin
6 CI matrix over plugins/flavian-*/ glob discover job in plugin-validation.yml enumerates with find ... -name 'flavian-*' + jq; adding a new first-party plugin requires no workflow edit
7 Per-plugin composer.json Each plugin has its own composer.json, phpunit.xml.dist, .phpcs.xml.dist, vendor/ — independently testable

CI

.github/workflows/plugin-validation.yml (new) runs on PRs touching plugins/flavian-*/, .claude/templates/plugin/, or the scaffold script:

  1. scaffold-drift — regenerates flavian-starter from templates, git diff --exit-code. Catches template/reference drift in either direction.
  2. discover — emits a matrix of every plugins/flavian-*/ directory.
  3. phpunit — composer install + phpunit per plugin.
  4. phpcs — composer install + phpcs (via cs2pr for inline PR annotations) per plugin.

Implementation notes worth knowing

  • {{PLUGIN_NS_JSON}} token — composer.json's PSR-4 strings need \ per literal backslash in the namespace. The generator computes a JSON-encoded namespace and substitutes it separately from the PHP-source {{PLUGIN_NS}} token. Documented in .claude/templates/plugin/README.md.
  • No JS build step for the blockassets/blocks/featured-event/index.js uses window.wp.blocks / wp.element / wp.blockEditor / wp.i18n runtime globals directly. The plugin is usable the instant composer install finishes; users who want JSX can add @wordpress/scripts themselves.
  • class_exists() guards in Plugin::boot() — so users can rm -rf src/PostTypes/ after scaffolding without breaking the rest of the plugin. Same pattern in Activator.
  • Idempotence verified locally — running the same scaffold command twice produces byte-identical output (confirmed with diff -r). The CI drift check depends on this.
  • .gitignore carve-out.claude/templates/ added to the existing unignore list; .claude/* defaults to ignored.

Test plan

  • commitlint passes on this PR.
  • plugin-validation / scaffold-drift passes — regenerated flavian-starter matches committed copy.
  • plugin-validation / phpunit (flavian-starter) passes — Brain Monkey tests in tests/PluginTest.php and tests/PostTypes/EventTest.php run green after composer install.
  • plugin-validation / phpcs (flavian-starter) passes — no WordPress-Extra violations in the scaffold output.
  • Visual regression workflow (feat: integrate visual regression testing for theme changes #69) and the rest of CI unaffected (no theme files touched).
  • Spot check: scaffold a throwaway plugin locally — bash scripts/scaffold-plugin.sh demo --author "Demo" --force produces a working plugins/demo/ that passes its own composer test.

🤖 Generated with Claude Code

…ugin

Adds scripts/scaffold-plugin.sh which generates new WordPress plugins under
plugins/ from token-substituted templates in .claude/templates/plugin/.

The generator produces a PSR-4 autoloaded plugin with proper headers,
activation/deactivation hooks, a CPT, a taxonomy, a Settings API page, a
server-rendered block (no build step required), PHPUnit tests via Brain
Monkey, and a WordPress-Extra PHPCS ruleset. Feature flags
(--no-cpt / --no-taxonomy / --no-settings / --no-block / --minimal) trim
the output; --force overwrites and --dry-run previews.

plugins/flavian-starter/ is the canonical scaffold output, generated by:

  bash scripts/scaffold-plugin.sh flavian-starter \
    --name "Flavian Starter" \
    --description "..." \
    --author "PMDevSolutions" --force

It doubles as the generator's e2e test — .github/workflows/plugin-validation.yml
regenerates it on every push and fails if the result drifts from the
committed copy.

The validation workflow also runs PHPUnit and PHPCS against every plugin
matching plugins/flavian-*/ via a dynamic matrix (find + jq), so adding a
new first-party plugin requires no workflow edit.

Extends the plugin-developer agent's prompt to point at the scaffold script
and the reference plugin so it stops hand-writing boilerplate.

Note on namespaces in JSON: composer.json templates use {{PLUGIN_NS_JSON}}
instead of {{PLUGIN_NS}} because JSON requires '\' for each literal
backslash in PSR-4 namespace strings.

Note on .gitignore: .claude/templates/ added to the unignore list (previously
caught by the .claude/* default-ignore).

Closes #19

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@PAMulligan PAMulligan linked an issue May 20, 2026 that may be closed by this pull request
3 tasks
Three classes of CI failures from #70 addressed:

1. PHPUnit ran a test against namespaced code that called __() — Brain
   Monkey doesn't auto-stub WP i18n. Fix: TestCase::setUp now calls
   Functions\stubTranslationFunctions() + stubEscapeFunctions().

2. PHPUnit flagged an Actions\expectAdded test as "risky" because Mockery
   expectations don't bump PHPUnit's assertion counter. Fix: TestCase now
   uses Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration trait, which
   bridges Mockery's verified expectations into PHPUnit's count.

3. PHPCS exploded on two fronts:
   - Per-plugin .phpcs.xml.dist allowed PSR-4 filenames but didn't relax
     short array syntax or class doc-comment requirements. Fix: convert
     every short array literal in the templates to long-form array(),
     add class-level docblocks to every class, add method docblocks to
     every public method, fix aligned-spacing in flavian-starter.php's
     define() and register_*_hook() calls, rename $post_id in uninstall.php
     (collides with WP global), broaden the FileName sniff exclusion to
     the whole sniff rather than two sub-sniffs.
   - Repo-root .github/workflows/phpcs.yml scans every plugin with
     WordPress-Extra,WordPress-Docs (without the FileName carve-out the
     per-plugin config has) and so fights its own per-plugin linter. Fix:
     add plugins/flavian-*/* to its --ignore list — first-party plugins
     are now linted exclusively by plugin-validation.yml with their own
     PSR-4-aware ruleset.
   - cs2pr received a mix of progress-bar output and checkstyle XML.
     Fix: pass -q to phpcs in plugin-validation.yml so only the XML
     reaches the action's parser.

Verified locally via php:8.2-cli + composer:2 Docker images:
- PHPUnit: 4 tests, 9 assertions, 0 errors, 0 risky.
- PHPCS (per-plugin config): 9 files, 0 errors, 0 warnings.
- PHPCS (repo-root with new ignore): 0 violations.
- Scaffold idempotence: regen → diff -r → identical.

Refs #19

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@PAMulligan PAMulligan self-assigned this May 20, 2026
@PAMulligan PAMulligan merged commit d8f0335 into main May 20, 2026
13 checks passed
@PAMulligan PAMulligan deleted the 19-add-plugin-scaffold-generator branch May 20, 2026 06:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add plugin scaffold generator

1 participant