Skip to content

fix(bones): work from any folder, rename only what changes, safe update, a real format:check, unique widgets - #117

Merged
gfazioli merged 9 commits into
masterfrom
fix/bones-robustness
Sep 25, 2026
Merged

gfazioli merged 9 commits into
masterfrom
fix/bones-robustness

Conversation

@gfazioli

Copy link
Copy Markdown
Collaborator

Six defects from the bones CLI audit (2026-09-24), the ones planned for 2.1. Each commit fixes one, with a process test in tests/Console that fails on v2.0.12 (BONES_SOURCE=<v2.0.12 tree>, see tests/README.md) and passes here.

What changes

  • bones works from any folder. Up to 2.0.12 it read namespace, readme.txt, the stubs and every make:* target against the shell's directory, so php my-plugin/bones <anything> died on a TypeError. It now chdir()s into the plugin first. A relative deploy path still means the folder it was typed in, and WordPress is still found through the plugin path as the shell spells it, so a plugin symlinked into wp-content/plugins keeps working.
  • rename writes only what changes. It used to rewrite every file under the plugin. On the Internationalization boilerplate that meant 187 files written, 99 of them byte-identical, and both .mo catalogues corrupted (a longer id shifts the strings, the offset table stays). Files with a NUL byte are now skipped. rename --update, which Composer runs after every install, rewrites vendor/ and nothing else. In a renamed plugin with an index.php in its root, it used to move index.php over the main plugin file. The language files are now renamed by the rename itself: they used to be renamed only by that --update pass, and never on --reset. Domain Path: /languages, with the slash, is found too.
  • update no longer deletes the framework first. It deleted vendor/wpbones/wpbones and then ran a full composer update, so a failing Composer left the plugin with no framework. It now runs composer update wpbones/wpbones --with-dependencies and exits with Composer's status. Measured with Composer 2.10.3: Composer replaces a renamed package by itself, both as a dist install and as a git clone with 82 changed files.
  • The migrated format:check is a check. wp-scripts format drops --check and always writes, so the "check" rewrote four files of the base boilerplate, public/apps/app.js among them, and exited 0. It is now prettier --check on the same globs and ignore file. migrate:to-v2 also writes a .prettierignore (public/, build/, vendor/, Composer's files, languages/*.json), so format stops rewriting the bundles too. The webpack.config.js stub is Prettier-formatted, so a migrated plugin passes its own check.
  • Every generated widget has its own id_base, name and description. All widgets of a plugin used to get {slug}-demo-widget, and so shared one settings option. The description sat under deccription and never showed.

update now updates only the framework and its dependencies, which is what "Update the Framework" in the help has always said. The help now points to composer update for everything else.

Verified

  • composer test: 173 tests, 15 new; the new ones fail on v2.0.12 except two regression guards (a relative deploy path from the plugin root and from the parent folder, which already worked).
  • Live, in the WPKirk-Developing bench and in scratch copies of the boilerplates: 17 checks, 14 ✗ on v2.0.12, 17 ✓ here. The checks: WordPress's own MO reader reads the renamed catalogue as before; tinker reaches WordPress from the plugins folder and from a symlinked copy; the real Composer takes a renamed plugin from 2.0.11 to 2.0.12; yarn format:check writes nothing and fails on an unformatted file; two widgets as WP_Widget sees them.

The 14 boilerplates still carry wp-scripts format --check; they get the new script and a .prettierignore with the release cascade.

Up to 2.0.12 bones read namespace, readme.txt, the stubs and every make:*
target against the shell's directory, so php my-plugin/bones <anything> died
on a TypeError from getNamespace(). It now moves into the plugin first. A
relative deploy path keeps meaning the folder it was typed in, and WordPress
is still found through the plugin path as the shell spells it, so a plugin
symlinked into wp-content/plugins keeps working.

The console harness can run bones from the parent folder, put a fake
composer on PATH, and take bones and its stubs from another tree
(BONES_SOURCE) to show a test failing on a previous release.
…date

Up to 2.0.12 rename read and rewrote every file under the plugin, and
rename --update, which Composer runs after every install and update, went
through the whole rename again. On the Internationalization boilerplate that
was 187 files written, 99 of them byte-identical, and both .mo catalogues
corrupted: replacing wp-kirk with a longer id shifts every string while the
offset table stays put. In a renamed plugin wp-kirk.php never exists, so an
index.php in the root was taken for a pre-1.5 main file and moved over the
real one on the next composer install.

- only files whose content changes are written; files with a NUL byte are
  skipped
- rename --update rewrites vendor/ and nothing else
- the namespace file is saved before Domain Path is read, so the language
  files are renamed by the rename itself (they were renamed only by the
  --update pass Composer ran afterwards, and never on --reset)
- Domain Path: /languages, the form WordPress documents, is found
- recursiveScan() and its nested _rglob() give way to filesToRename(), which
  visits each real folder once
Up to 2.0.12 update deleted vendor/wpbones/wpbones and then ran a full
composer update: when Composer failed, the plugin was left with no framework
at all. Composer replaces the package by itself, renamed files included
(measured with Composer 2.10.3 on a dist install and on a git clone with 82
files changed by the rename), so nothing is deleted any more.

It now runs composer update wpbones/wpbones --with-dependencies, which is
what the help has always promised ("Update the Framework"), and exits with
Composer's status.
migrate:to-v2 wrote "format:check": "wp-scripts format --check", and
wp-scripts format drops --check and always passes --write (scripts/format.js
in 31.8.0): on a boilerplate the check exited 0 after rewriting four files,
public/apps/app.js among them.

- format:check is prettier --check on the globs and the ignore file that
  wp-scripts format uses, so it fails where format would change something
  and writes nothing
- migrate:to-v2 creates a .prettierignore (public/, build/, vendor/,
  composer.json/lock, languages/*.json), so format stops rewriting the
  compiled bundles too
- the webpack.config.js stub is Prettier-formatted, so a migrated plugin
  passes its own check
…ption

The widget stub gave every widget of a plugin the id_base
"{slug}-demo-widget" and the name "{Plugin} Widget": a second widget
shared the first one's settings, which WordPress stores in the option
widget_{id_base}, and both showed under one name on the Widgets screen. The
description sat under the key deccription, so it never showed at all.

make:widget Shop/RecentPosts now writes the id_base
"{slug}-shop-recent-posts", the name "{Plugin} Recent Posts" and a
description under the right key.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Unresolved critical and moderate safety, correctness, and coverage issues remain.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 Medium severity · 1 Low severity

Open (2)
What changed in this PR

Updates the bones CLI for safer execution, renaming, framework updates, formatting migration, and widget generation.

Changes:

  • Supports invocation from arbitrary directories.
  • Makes renames selective and binary-safe.
  • Limits updates to the framework and dependencies.
  • Adds formatting migration rules and process-based regression tests.
File Summary Review status
tests/​Support/​BonesProcess.php Adds process and Composer test helpers. Reviewed
tests/​README.md Documents console test workflows. Nit: extraction requires creating /tmp/v2012 first (2 votes).
tests/​Console/​WorkingDirectoryTest.php Tests execution from different directories. Reviewed
tests/​Console/​UpdateTest.php Tests safe framework updates. Reviewed
tests/​Console/​RenameTest.php Tests selective and binary-safe renaming. Reviewed
tests/​Console/​MigrateToV2Test.php Tests migration formatting configuration. Moderate: does not execute formatting commands or verify ignored files (2 votes).
tests/​Console/​MakeCommandsTest.php Tests generated widget metadata. Reviewed
src/​Console/​stubs/​widget.stub Generates widget metadata. Reviewed
src/​Console/​stubs/​webpack-config.stub Provides a formatted webpack stub. Reviewed
src/​Console/​stubs/​prettierignore.stub Defines formatting exclusions. Reviewed
src/​Console/​bin/​bones Implements the CLI changes. Critical symlink traversal issue (2 votes); moderate widget-ID collisions (2 votes), ignore-file handling (1 vote), and symlinked deploy paths (2 votes).

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread tests/Console/MigrateToV2Test.php Outdated
Comment thread tests/README.md Outdated
Command::loadWordPress(), which custom commands use, looks for WordPress and
vendor/autoload.php through PWD, so moving into the plugin was not enough:
from the plugins folder a command that loads WordPress still looked three
folders above the shell's directory. bones now leaves PWD as a cd into the
plugin would, spelled as the shell spells it, for itself and for what it
runs.
format:check runs prettier directly, and pnpm 12 links only the binaries of
direct dependencies: measured, pnpm 12.6.0 answered "prettier: command not
found" (exit 127), while 10.34.5 did link it. migrate:to-v2 now adds
prettier as npm:[email protected], the alias @wordpress/scripts 31 declares
and the one its format script asks projects to install. Reported by the
Codex review of #117.
…fore tar

Review of #117: the migrate test inspects the scripts and the ignore file the
migration writes, it does not run Prettier (that needs node_modules, which
this suite does not install), so its name says so. The documented
BONES_SOURCE recipe now creates its folder before tar -x -C.
@gfazioli
gfazioli merged commit 1313f0f into master Sep 25, 2026
4 checks passed
@gfazioli
gfazioli deleted the fix/bones-robustness branch September 25, 2026 12: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.

2 participants