Skip to content

Close the v2 re-audit's template findings - #20

Merged
ahegyes merged 9 commits into
trunkfrom
fix/reaudit-followups
Oct 29, 2026
Merged

ahegyes merged 9 commits into
trunkfrom
fix/reaudit-followups

Conversation

@ahegyes

@ahegyes ahegyes commented Oct 29, 2026 •

Copy link
Copy Markdown
Owner

Summary

Fixes from the v2 re-audit:

  • three partial-deploy paths escaped the plugin's build-error handling;

  • the greeting's translated default was saved in the saving admin's language;

  • the scaffold accepted names that break the block's JSX and never built the filled-in scripts, and a plugin generated with a short name failed its first lint:scripts;

  • TypeScript and the Node types were undeclared;

  • two recipes lacked a default or a typed read;

  • several documents described what the template no longer ships;

  • the template's own release zip shipped README.scaffold.md;

  • the configs pins lagged behind configs' trunk.

  • Partial deploys:

    • uninstall.php called the uninstall engine after its try, so a build missing the engine fataled the uninstall request; it now checks for the engine inside the try.
    • dws_tpl_deactivate() reports a composition root that cannot be built, as dws_tpl_boot() does.
    • functions.php adds its hooks after loading includes/, so an include that throws leaves nothing hooked.
  • Greeting: the Message field's default is empty and its placeholder shows the translated greeting. dws_tpl_get_string_option() reads an empty value of an option with a translated default as that default, so an untouched field follows the site's language. includes/migrations.php already keeps translated defaults out of the database for this reason.

  • Scaffold:

    • the fill-in also refuses <, >, { and }, which land in the example block's JSX;
    • after the sweep, the fill-in installs the plugin's npm packages with install scripts off, since its job holds a token that can push, and formats the JS and TS files lint:scripts covers. A name shorter than the template's own leaves lines that Prettier joins, so "Acme Orders" had failed lint:scripts;
    • fill-in-scaffold.yml and the Scaffold rehearsal set up Node before the fill-in, and the rehearsal builds the filled-in plugin and runs lint:scripts and lint:types.
  • TypeScript: typescript and @types/node, which lint:types and the end-to-end specs need, are declared at the versions the lock already resolved.

  • Recipes: both settings recipes' limit fields take their default from DWS_TPL_SETTING_DEFAULTS, and the recurring-jobs recipe reads its repair timestamp through dws_tpl_get_int_option().

  • Prose: these now match what the template ships:

    • CONTRIBUTING's release steps, which now set the Version: header;
    • the issue templates' runtime area;
    • the README's empty safeguards slot;
    • the entry file's Description, which takes composer.json's sentence;
    • the scaffold TODO's list of descriptions;
    • SECURITY.md's audit scope;
    • convention 5's boolean read;
    • the tests README's job order.
  • Release zip: .distignore excludes README.scaffold.md, and the fill-in drops that line when it moves the file to README.md.

  • Configs pins: the workflows, package.json and both locks move from 9f64dc7 to configs 3cbd985. The release now installs its WP-CLI commands from configs' php/release lock, through the template's own development install of configs.

Code +26 −13 (5 files), tests +106 −5 (5 files), workflows, issue templates and fill-in +38 −17 (11 files), docs +13 −9 (7 files), .distignore +1, npm manifest and the two locks +16 −14.

Type of change

  • Bug fix
  • New feature
  • Refactor
  • Build / CI / tooling
  • Documentation
  • Dependency update

Checklist

  • Changelog fragment added (composer changelog:add): none; the v2 rebuild ships as "Initial release."
  • Tests added/updated:
    • PartialDeployTest runs a missing blocks manifest, an include that throws, a deactivation whose composition root cannot be built, and an uninstall whose engine cannot load, each in its own request;
    • placeholder, untouched-save and empty-read rows are added;
    • the empty-greeting row expects the default.
  • composer quality-check passes locally
  • Plugin header updated if runtime requirements changed: no runtime requirement changed; the Description takes composer.json's sentence

Breaking changes for forks

  • Empty values: dws_tpl_get_string_option() reads an empty value of an option with a translated default as that default, so a cleared Message greets with the default instead of printing nothing. The feature's toggle turns the greeting off.
  • Plugin names: the fill-in refuses <, >, { and } in the plugin name.
  • Fill-in needs Node: the fill-in runs npm ci and the formatter, so a fork's own fill-in workflow must set up Node first.

Review points.

  • Changed files in the checkout: the partial-deploy rows rename or add a file in the checkout for one request and restore it in finally. A run killed mid-row can leave a .hidden file or includes/zz-throws.php behind.
  • First CI run of the release tools: this PR's Release dry run is the first CI run of the lock-based WP-CLI install.

Testing.

  • composer quality-check passes: validate, changelog validation, PHPCS on both profiles, PHPStan, Unit 6 and Integration 75.
  • npm run test:e2e passes (3 of 3). lint:scripts, lint:styles and lint:types are clean, and so are actionlint, zizmor and shellcheck.
  • Before the fixes:
    • the include, deactivation, uninstall and greeting rows failed;
    • the deactivation row also fails with only its catch reverted.
  • Scaffold names: a scratch fill-in named Acme <Orders> passes trunk's script, then fails the new build and type-lint steps; this branch's script refuses it.
  • Short and long names: scratch fill-ins named "Acme Orders" and "Acme Orders for WooCommerce" both pass build, lint:scripts, lint:styles and lint:types. The formatter changes only the two lines the short name shortened.
  • Release tools and zip: configs' tool step, run in the template's checkout, installs WP-CLI 2.12.0 from the template's copy of configs, and make-pot and dist-archive run from it. The archive holds no README, and a scratch fill-in drops the .distignore line and keeps its leftover checks green.

Three paths of a partial deploy escaped the catches the template
documents. uninstall.php called the framework's Uninstaller after its
try, so a build missing the engine fataled the uninstall request.
dws_tpl_deactivate() built the composition root without the catch
dws_tpl_boot() has, so a deactivation on such a build fataled too.
functions.php added its hooks before the includes loop, so an include
that threw still left the boot and deactivation hooked to a plugin that
had not loaded.

uninstall.php checks for the engine inside its try, the deactivation
reports a build error as the boot does, and the hooks are added after
the includes. Rows run each case in its own request, as a missing
blocks manifest already gated, which no row covered.

Assisted-by: Claude Code:claude-opus-5-5
The Message field took the translated greeting as its default, so the
first save of the tab stored it in the language of the admin who saved,
and every customer then read that language. includes/migrations.php
already keeps translated defaults out of the database for this reason;
the field put one back.

The field's default is empty and its placeholder shows the translated
greeting, and the string reader reads an empty value of an option with
a translated default as that default, so an untouched field keeps
following the site's language. A cleared Message therefore greets with
the default instead of printing nothing. Rows render the placeholder,
save the untouched field and read the empty value.

Assisted-by: Claude Code:claude-opus-5-5
The fill-in refused only the characters that break the single-quoted
PHP and JavaScript strings, but the display name also lands in the
example block's JSX text, where <, >, { and } break the build. The
Quality rehearsal never built or type-checked the filled-in scripts, so
such a name passed it and the generated plugin failed its first build.

The fill-in refuses <, >, { and } as well, and the rehearsal installs
the filled-in plugin's npm packages, builds it and runs its type lint.

Assisted-by: Claude Code:claude-opus-5-5
lint:types runs tsc over the blocks, assets and end-to-end specs, and
the specs read Node's types, but neither typescript nor @types/node was
declared: both arrived through @wordpress/scripts, so a change in its
tree could swap the compiler's major or drop the types without a change
here.

Both are declared at the versions the lock already resolves, so the
lock moves only its root entries.

Assisted-by: Claude Code:claude-opus-5-5
The two settings recipes declared their limit field without a default,
so a fresh settings page rendered it empty instead of at the default
the recipe's constant holds, and the recurring-jobs recipe read its
repair timestamp with a raw filter_var() on get_option(), which
AGENTS.md reserves to the typed readers.

Both number fields take the default from DWS_TPL_SETTING_DEFAULTS, and
the repair gate reads through dws_tpl_get_int_option().

Assisted-by: Claude Code:claude-opus-5-5
Several documents described a template that no longer exists or
overstated what it checks. CONTRIBUTING's release steps used arrows
and never set the entry file's Version header, which the release
refuses to tag without. The issue templates offered a Plugin singleton
and a WelcomeNotice the template no longer has. The README listed
safeguards where the template ships an empty slot. The entry's
Description differed from composer.json's sentence, and the scaffold's
TODO left package.json's description out. SECURITY.md said the audit
fails on any npm advisory, though it audits only runtime dependencies,
of which there are none. Convention 5 implied an absent boolean reads
its default, though FILTER_VALIDATE_BOOL reads '' as false. The tests
README said Unit runs before the WordPress matrix, which does not wait
for it.

Each now states what the template does.

Assisted-by: Claude Code:claude-opus-5-5
@ahegyes ahegyes changed the title Fix/reaudit followups Close the v2 re-audit's template findings Oct 29, 2026
The fill-in swaps the plugin's name and slug into Prettier-formatted
TSX. A name or slug shorter than the template's own leaves lines that
Prettier would now join, so a plugin generated as "Acme Orders" failed
lint:scripts on its first Quality run, while a long name passed, and
the rehearsal ran no script lint to notice.

After the sweep, the fill-in installs the plugin's npm packages with
install scripts off, since its job holds a token that can push, and
formats the JS and TS files lint:scripts covers with wp-scripts format.
Both the Fill in scaffold workflow and the rehearsal set up Node before
the fill-in, and the rehearsal runs lint:scripts with the build and
type lint. A short and a long name each pass all three, and formatting
changes only the two lines the shorter name shortened.

Assisted-by: Claude Code:claude-opus-5-5
The workflows, package.json and the locks pinned configs at 9f64dc7,
before configs merged its re-audit follow-ups and the release's tool
lock. The template's own release still took WP-CLI from setup-php and
checked neither the plugin's own PHP floor nor phpunit.dist.xml.

Every workflow ref, the npm dependency and both locks move to 3cbd985.
The release now installs its WP-CLI commands from configs'
php/release lock, which the template's development install provides,
since it requires configs under require-dev; no reusable workflow's
inputs change, and each lock changes only the configs entry.

Assisted-by: Claude Code:claude-opus-5-5
The template's .distignore excluded README.md but not README.scaffold.md,
the README a generated plugin starts from, so the template's own release
archive shipped a file that describes filling in a plugin.

.distignore excludes README.scaffold.md, and the fill-in drops that line
when it moves the file to README.md, which .distignore already excludes,
so a generated plugin keeps no exclusion for a file it lacks.

Assisted-by: Claude Code:claude-opus-5-5
@ahegyes
ahegyes merged commit a2c1d65 into trunk Oct 29, 2026
35 checks passed
@ahegyes
ahegyes deleted the fix/reaudit-followups branch October 29, 2026 15:25
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