fix(bones): work from any folder, rename only what changes, safe update, a real format:check, unique widgets - #117
Merged
Merged
Conversation
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.
There was a problem hiding this comment.
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
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.
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.
This was referenced Sep 25, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Six defects from the
bonesCLI audit (2026-09-24), the ones planned for 2.1. Each commit fixes one, with a process test intests/Consolethat fails on v2.0.12 (BONES_SOURCE=<v2.0.12 tree>, seetests/README.md) and passes here.What changes
namespace,readme.txt, the stubs and everymake:*target against the shell's directory, sophp my-plugin/bones <anything>died on a TypeError. It nowchdir()s into the plugin first. A relativedeploypath 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 intowp-content/pluginskeeps working..mocatalogues 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, rewritesvendor/and nothing else. In a renamed plugin with anindex.phpin its root, it used to moveindex.phpover the main plugin file. The language files are now renamed by the rename itself: they used to be renamed only by that--updatepass, and never on--reset.Domain Path: /languages, with the slash, is found too.vendor/wpbones/wpbonesand then ran a fullcomposer update, so a failing Composer left the plugin with no framework. It now runscomposer update wpbones/wpbones --with-dependenciesand 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.format:checkis a check.wp-scripts formatdrops--checkand always writes, so the "check" rewrote four files of the base boilerplate,public/apps/app.jsamong them, and exited 0. It is nowprettier --checkon the same globs and ignore file.migrate:to-v2also writes a.prettierignore(public/,build/,vendor/, Composer's files,languages/*.json), soformatstops rewriting the bundles too. Thewebpack.config.jsstub is Prettier-formatted, so a migrated plugin passes its own check.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 underdeccriptionand never showed.updatenow updates only the framework and its dependencies, which is what "Update the Framework" in the help has always said. The help now points tocomposer updatefor 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).WPKirk-Developingbench and in scratch copies of the boilerplates: 17 checks, 14 ✗ on v2.0.12, 17 ✓ here. The checks: WordPress's ownMOreader reads the renamed catalogue as before;tinkerreaches 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:checkwrites nothing and fails on an unformatted file; two widgets asWP_Widgetsees them.The 14 boilerplates still carry
wp-scripts format --check; they get the new script and a.prettierignorewith the release cascade.