Skip to content

fix: two template bugs that shipped in every scaffolded plugin - #4

Merged
pluginslab merged 1 commit into
mainfrom
fix/template-activation-and-substitution
Sep 21, 2026
Merged

pluginslab merged 1 commit into
mainfrom
fix/template-activation-and-substitution

Conversation

@pluginslab

Copy link
Copy Markdown
Owner

Stacked on #3 — base is feat/playground-verifier, since both bugs were found by that agent and the justification below cites its output. Merge #3 first.

Both of these shipped in every plugin the kit has ever scaffolded.

1. A fresh clone fatals on activation

pl-example.php:27 required vendor/autoload.php unconditionally. .gitignore:10 excludes vendor/. So the plugin as cloned — which is what a git-based deploy installs — died at require before anything else ran.

PHP Warning:  require_once(.../vendor/autoload.php): Failed to open stream:
No such file or directory in .../pl-example.php on line 27
Error: Failed opening required '.../vendor/autoload.php'

get_option('active_plugins') after the attempt: array(). Activation aborted.

The scaffolder runs composer install immediately, so the happy path always worked — which is why this survived this long. The failure is specifically deploying the generated repo via git without a composer install step, i.e. Ploi's default if nobody edits the deploy script.

What makes it pure liability: composer.json's require block is {"php": ">=8.2"}. Every package is require-dev. The plugin has zero third-party runtime dependencies, and the verifier confirmed it runs completely with that line gone.

Now guarded with is_readable(). The guard is damage control; composer install --no-dev in the deploy lane is the actual fix once a runtime dependency exists, and the comment says so.

2. Every scaffolded plugin ships a class named PL_Example_Plugin

The CLI's replacement keys are case-sensitive and mutually disjoint. PL_EXAMPLE does not match PL_Example; neither matches PLExample. The template's bootstrap class uses underscore-separated PascalCase — WordPress's convention for global class names — which had no entry, so it shipped verbatim.

Cosmetic in isolation. Fatal when two kit-scaffolded plugins are active on the same site: Cannot declare class PL_Example_Plugin.

Fixed by deriving a classPrefix (acme-order-tracker → Acme_Order_Tracker). More usefully, generalised: tests/cli/test-substitutions.sh scans every file the CLI will rewrite, collects each casing of the example identity present, and asserts each has a replacement entry. A sixth casing introduced by a future template edit fails there instead of in someone else's plugin.

Negative control: removing the entry turns the suite red and names the offending file.

Also: dropping the Composer autoload classmap

Not a bug report — a judgement call, and the one I'd most like challenged.

The v1.0.2 fix added both a classmap in composer.json and the WordPress-style resolver in the main file. Composer's registers first, so the resolver the plugin actually depends on may never execute. The verifier had to give a scratch copy a 56-byte no-op autoload.php to establish the resolver works at all, because the classmap would have covered for a broken one.

A classmap that quietly covers for a broken resolver is worse than no classmap: it fails on someone else's machine, after they add a class and forget to re-dump. One loader now, always exercised.

Argument against: a composer.json with no autoload key looks wrong to a WordPress developer skimming the template, and this file's job is partly to teach. There's a comment explaining the absence, but a comment explaining an absence is weaker than one explaining a presence. Happy to revert this hunk if you'd rather keep belt-and-braces.

Verification

./scripts/quality.sh green, 109 assertions. composer validate passes. composer dump-autoload confirms the plugin's classes are no longer in the classmap, so the resolver is genuinely the only path.

Caveat: phpunit still doesn't run in this repo (no phpunit.xml), so dropping the classmap is unverified against the test suite. The runtime evidence is stronger than a unit test would be, but it isn't the same thing.

🤖 Generated with Claude Code

@pluginslab
pluginslab changed the base branch from feat/playground-verifier to main September 21, 2026 16:40
Both found by playground-verifier running against the kit's own template.

1. Fresh clone fatals on activation.

pl-example.php required vendor/autoload.php unconditionally, while
.gitignore:10 excludes vendor/. The plugin as cloned -- which is what a
git-based deploy installs -- died at require before anything else ran.
Guarded with is_readable().

composer.json has no runtime requires at all; every package is in
require-dev. So that line was buying nothing and costing a fatal. The
guard is damage control; composer install --no-dev in the deploy lane is
the real fix once a runtime dependency exists.

2. Every scaffolded plugin ships a class named PL_Example_Plugin.

The CLI's replacement keys are case-sensitive and mutually disjoint, and
PL_Example -- underscore-separated PascalCase, WordPress's convention for
global class names -- matched none of the four existing entries. Two
kit-scaffolded plugins active on the same site collide with "Cannot
declare class PL_Example_Plugin".

Fixed by deriving a classPrefix, and generalised by a test that scans
every file the CLI rewrites and asserts each casing present has a
replacement entry -- so a sixth casing fails here rather than in someone
else's plugin.

Also drops composer.json's autoload classmap. It covered the same classes
as the resolver in the main file and registered first, so the resolver
the plugin actually depends on might never run -- and a classmap that
quietly covers for a broken resolver fails on someone else's machine,
after they add a class and forget to re-dump. One loader, always
exercised.

Suite: 99 -> 109 assertions.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@pluginslab
pluginslab force-pushed the fix/template-activation-and-substitution branch from 079721e to b0b3fb5 Compare September 21, 2026 16:42
@pluginslab
pluginslab merged commit 0751a24 into main Sep 21, 2026
1 check passed
@pluginslab
pluginslab deleted the fix/template-activation-and-substitution branch September 21, 2026 16:43
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.

1 participant