Skip to content

Commit 8f742e0

Browse files
authored
🚀 chore(release): migrate to WPBones v2.0.1 infrastructure + CLAUDE.md (#2)
Ran `php bones migrate:to-v2` — drops gulp, moves to webpack auto-discovery with unified yarn scripts (dev/build/test/format/lint). Composer constraint bumped to ^2.0.1 to pull the inline-scripts routing fix. Plugin version aligned to 2.0.0 across readme.txt, wp-kirk.php, package.json. Also adds CLAUDE.md at repo root documenting what this boilerplate demos (admin menu routing + custom page routes), the smoke test, how to use as a GitHub template, and the framework surface it exercises.
1 parent 5740309 commit 8f742e0

21 files changed

Lines changed: 11862 additions & 27086 deletions

‎.prettierrc‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"printWidth": 100,
3+
"tabWidth": 2,
4+
"useTabs": false,
5+
"semi": true,
6+
"singleQuote": true,
7+
"trailingComma": "es5",
8+
"bracketSpacing": true,
9+
"arrowParens": "always",
10+
"endOfLine": "lf"
11+
}

‎CLAUDE.md‎

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# WPKirk-Routes-Boilerplate
2+
3+
Focused demo of the **menu routing + custom page routing** layer in WP Bones: admin menus
4+
mapped to controllers via `config/menus.php`, standalone "custom page" routes via
5+
`config/routes.php`, named routes resolved with `$plugin->getMenuUrl()` / `$plugin->getPageUrl()`.
6+
This is the WP Bones take on Laravel-style URL generation for WordPress.
7+
8+
## What this demos
9+
10+
Five admin menu items dispatched to different controllers, plus a custom page route:
11+
12+
- `config/menus.php` — top-level menu `WP Kirk Routes` with 5 sub-items wiring different
13+
controllers (`MenuRouting\MenuRoutingController`, `AnotherController`).
14+
- `config/routes.php` — a named `my_custom_page_route` mapped to
15+
`Routes\RoutesController@myRoute` for building links with `$plugin->getPageUrl()`.
16+
- Named menu entry `second_view` demonstrates `$plugin->getMenuUrl('second_view')` for
17+
type-safe link generation elsewhere in the plugin.
18+
19+
**Key files to read first:**
20+
21+
| File | What to look at |
22+
| --- | --- |
23+
| `config/menus.php` | Full menu tree, named entry + inline entries side by side |
24+
| `config/routes.php` | Custom page route definition |
25+
| `plugin/Http/Controllers/MenuRouting/MenuRoutingController.php` | Multi-method controller dispatching `index`, `routes`, `second`, `pages` |
26+
| `plugin/Http/Controllers/AnotherController.php` | Minimal controller to show direct namespace routing |
27+
| `plugin/Http/Controllers/CustomPageController.php` | Referenced from `routes.php` |
28+
29+
## Smoke test (manual, ~30s)
30+
31+
With the plugin active:
32+
33+
1. Log in to `wp-admin` and confirm the **WP Kirk Routes** top-level menu is visible.
34+
2. Click each of the 5 sub-items in turn:
35+
- **Menu Routing** → `MenuRoutingController@index`
36+
- **Routes** → `MenuRoutingController@routes`
37+
- **Second View** → `MenuRoutingController@second`
38+
- **Another Menu View** → `AnotherController@index`
39+
- **Pages Folder** → `MenuRoutingController@pages`
40+
41+
Each should render its view without fatal/PHP errors.
42+
3. On the "Routes" page, follow any generated link — it should resolve to the correct admin
43+
URL (confirms `$plugin->getMenuUrl()` / `$plugin->getPageUrl()` resolution).
44+
4. `wp-content/debug.log` should stay clean.
45+
46+
## Use as a template
47+
48+
```sh
49+
# 1. clone from the GitHub template
50+
gh repo create my-routes-plugin --template wpbones/WPKirk-Routes-Boilerplate --public --clone
51+
cd my-routes-plugin
52+
53+
# 2. rename the PHP namespace + plugin slug
54+
composer install
55+
php bones rename "My Routes Plugin"
56+
57+
# 3. build + activate
58+
yarn install && yarn build
59+
wp plugin activate my-routes-plugin
60+
```
61+
62+
Edit `config/menus.php` for your admin menu tree, add controllers under `plugin/Http/Controllers/`.
63+
Use `config/routes.php` for routes that don't live in the menu (public-facing custom pages,
64+
thank-you pages, etc.). Resolve named URLs via `$plugin->getMenuUrl('key')` /
65+
`$plugin->getPageUrl('key')` — never hard-code `admin.php?page=...`.
66+
67+
## Framework surface exercised
68+
69+
This boilerplate is the **regression bed** for URL generation and menu dispatching:
70+
71+
- `WPKirk\WPBones\Routing\AdminMenuProvider` reading `config/menus.php` and wiring handlers
72+
- `WPKirk\WPBones\Routing\Pages\PageProvider` reading `config/routes.php`
73+
- `Plugin::getMenuUrl()` / `Plugin::getPageUrl()` named-URL resolution
74+
- Controller dispatch with nested namespace support (`MenuRouting\MenuRoutingController`)
75+
- Multiple methods on the same controller via the `get => 'Controller@method'` config shape

‎bones‎

Lines changed: 255 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -708,7 +708,7 @@ namespace Bones {
708708
define('WPBONES_MINIMAL_PHP_VERSION', '7.4');
709709

710710
/* MARK: The WP Bones command line version. */
711-
define('WPBONES_COMMAND_LINE_VERSION', '1.11.1');
711+
define('WPBONES_COMMAND_LINE_VERSION', '2.0.0');
712712

713713
use Bones\SemVer\Exceptions\InvalidVersionException;
714714
use Bones\SemVer\Version;
@@ -1315,9 +1315,11 @@ namespace Bones {
13151315
$this->line(' version Update the Plugin version');
13161316
$this->info('migrate');
13171317
$this->line(' migrate:create Create a new Migration');
1318+
$this->line(' migrate:to-v2 Migrate gulp-based plugin to v2 webpack infrastructure');
13181319
$this->info('make');
13191320
$this->line(' make:ajax Create a new Ajax service provider class');
13201321
$this->line(' make:api Create a new API controller class');
1322+
$this->line(' make:app Create a new React/TS app in resources/assets/apps');
13211323
$this->line(' make:console Create a new Bones command');
13221324
$this->line(' make:controller Create a new controller class');
13231325
$this->line(' make:cpt Create a new Custom Post Type service provider class');
@@ -1374,6 +1376,10 @@ namespace Bones {
13741376
elseif ($this->isCommand('migrate:create')) {
13751377
$this->createMigrate($this->getCommandParams(0));
13761378
}
1379+
// migrate:to-v2
1380+
elseif ($this->isCommand('migrate:to-v2')) {
1381+
$this->migrateToV2();
1382+
}
13771383
// -- make ---------------------------------------------
13781384
//
13791385
// make:ajax {className}
@@ -1384,6 +1390,10 @@ namespace Bones {
13841390
elseif ($this->isCommand('make:api')) {
13851391
$this->createAPIController($this->getCommandParams(0));
13861392
}
1393+
// make:app {appName}
1394+
elseif ($this->isCommand('make:app')) {
1395+
$this->createApp($this->getCommandParams(0));
1396+
}
13871397
// make:console {command_name}
13881398
elseif ($this->isCommand('make:console')) {
13891399
$this->createCommand($this->getCommandParams(0));
@@ -3161,6 +3171,250 @@ namespace Bones {
31613171
$this->optimize();
31623172
}
31633173

3174+
/**
3175+
* Scaffold a React/TS app under resources/assets/apps.
3176+
*
3177+
* Default (folder-based, best for apps with sub-components):
3178+
* resources/assets/apps/<name>/index.tsx
3179+
*
3180+
* With --flat (single-file, best for tiny apps):
3181+
* resources/assets/apps/<name>.tsx
3182+
*
3183+
* The entry is auto-discovered by the v2 webpack config — no package.json edits needed.
3184+
*
3185+
* @param string|null $appName Lowercase name, dashes allowed (e.g. "dashboard", "billing-widget").
3186+
*/
3187+
protected function createApp(?string $appName = '')
3188+
{
3189+
if ($this->isHelp($appName)) {
3190+
$this->info('Usage:');
3191+
$this->line(' php bones make:app <name> [--flat]');
3192+
$this->info('Options:');
3193+
$this->line(' --flat Create a single-file app (apps/<name>.tsx) instead of folder-based');
3194+
return;
3195+
}
3196+
3197+
if (empty($appName)) {
3198+
$appName = $this->ask('App name (lowercase, e.g. "dashboard")');
3199+
}
3200+
3201+
if (!preg_match('/^[a-z][a-z0-9-]*$/', $appName)) {
3202+
$this->error('Invalid app name. Use lowercase letters, digits, and dashes (must start with a letter).');
3203+
return;
3204+
}
3205+
3206+
// Names that collide with scripts WordPress core registers. If we let
3207+
// the user pick one of these, wp_enqueue_script silently skips our
3208+
// bundle because the handle is already taken and the app never mounts
3209+
// in the browser. Block the name up-front instead of letting the user
3210+
// debug a ghost bug.
3211+
$reservedHandles = [
3212+
'dashboard', 'post', 'postbox', 'common', 'user', 'user-profile',
3213+
'utils', 'admin-bar', 'admin-comments', 'media-upload', 'media-views',
3214+
'jquery', 'jquery-core', 'jquery-ui-core', 'backbone', 'underscore',
3215+
'react', 'react-dom', 'react-jsx-runtime',
3216+
'wp-api', 'wp-element', 'wp-components', 'wp-data', 'wp-hooks', 'wp-i18n',
3217+
'wp-util', 'wp-a11y', 'wp-date',
3218+
];
3219+
if (in_array($appName, $reservedHandles, true)) {
3220+
$this->error("'{$appName}' collides with a reserved WordPress script handle — wp_enqueue_script would silently drop your bundle.");
3221+
$this->line(" Pick a different name, e.g. {$appName}-app, my-{$appName}, or a plugin-specific prefix.");
3222+
return;
3223+
}
3224+
3225+
$flat = in_array('--flat', $this->arguments(), true);
3226+
3227+
// PascalCase component name (dashboard-widget → DashboardWidget)
3228+
$componentName = str_replace(' ', '', ucwords(str_replace('-', ' ', $appName)));
3229+
3230+
// Resolve text domain from plugin header, fall back to a placeholder the user can replace.
3231+
$header = $this->extractPluginHeaderInfo(['Text Domain']);
3232+
$textDomain = $header['Text Domain'] ?? 'your-text-domain';
3233+
3234+
$content = $this->prepareStub('app', [
3235+
'{AppName}' => $appName,
3236+
'{ComponentName}' => $componentName,
3237+
'{TextDomain}' => $textDomain,
3238+
]);
3239+
3240+
$this->mkdirIfNotExists('resources/assets/apps');
3241+
3242+
if ($flat) {
3243+
$filepath = "resources/assets/apps/{$appName}.tsx";
3244+
if (file_exists($filepath)) {
3245+
$this->error("File already exists: {$filepath}");
3246+
return;
3247+
}
3248+
file_put_contents($filepath, $content);
3249+
$this->line(" Created {$filepath}");
3250+
} else {
3251+
$folder = "resources/assets/apps/{$appName}";
3252+
if (file_exists($folder)) {
3253+
$this->error("Folder already exists: {$folder}");
3254+
return;
3255+
}
3256+
mkdir($folder, 0755, true);
3257+
$filepath = "{$folder}/index.tsx";
3258+
file_put_contents($filepath, $content);
3259+
$this->line(" Created {$filepath}");
3260+
}
3261+
3262+
$this->info("\nNext steps:");
3263+
$this->line(" 1. Add the mount point to your view:");
3264+
$this->line(" <div id=\"{$appName}-root\"></div>");
3265+
$this->line(" 2. Enqueue it from your controller:");
3266+
$this->line(" ->withAdminAppsScript('{$appName}')");
3267+
$this->line(" 3. Run yarn dev — webpack auto-discovers the new entry.");
3268+
}
3269+
3270+
/**
3271+
* Migrate a v1.x plugin (gulp + run-s + wp-scripts split) to the v2 unified
3272+
* webpack infrastructure.
3273+
*
3274+
* The migration:
3275+
* - Deletes gulpfile.js and package-lock.json (switching to yarn)
3276+
* - Creates webpack.config.js, tsconfig.json, .prettierrc, jest.config.js
3277+
* - Rewrites package.json scripts to the unified dev/build/test/format block
3278+
* - Drops gulp-* and npm-run-all devDependencies
3279+
* - Adds the v2 devDependency set (@wordpress/scripts 31+, typescript, glob,
3280+
* less/less-loader, webpack-remove-empty-scripts, @wordpress/jest-preset-default,
3281+
* @types/react, @types/react-dom)
3282+
*
3283+
* The migration does NOT touch resources/assets/ — the developer's code stays
3284+
* as-is. After the migration, run `yarn install && yarn build` to verify.
3285+
*
3286+
* @since 2.0.0
3287+
*/
3288+
protected function migrateToV2(): void
3289+
{
3290+
$this->info('WP Bones — migrate to v2');
3291+
$this->line('');
3292+
$this->warning('This will modify your plugin build infrastructure:');
3293+
$this->line(' • Delete gulpfile.js and package-lock.json (switching to yarn)');
3294+
$this->line(' • Create webpack.config.js, tsconfig.json, .prettierrc, jest.config.js');
3295+
$this->line(' • Rewrite package.json scripts and devDependencies');
3296+
$this->line('');
3297+
$this->warning('Commit your current work first. The resources/assets/ folder is left untouched.');
3298+
$this->line('');
3299+
3300+
$answer = $this->ask('Continue? (y/N)');
3301+
if (strtolower(trim($answer)) !== 'y') {
3302+
$this->line('Migration aborted.');
3303+
return;
3304+
}
3305+
3306+
// 1. Delete gulp-era files
3307+
foreach (['gulpfile.js', 'package-lock.json'] as $file) {
3308+
if (file_exists($file)) {
3309+
unlink($file);
3310+
$this->line(" Removed {$file}");
3311+
}
3312+
}
3313+
3314+
// 2. Create v2 config files (skip if already present — don't clobber customizations)
3315+
$configs = [
3316+
'webpack.config.js' => 'webpack-config',
3317+
'tsconfig.json' => 'tsconfig',
3318+
'.prettierrc' => 'prettierrc',
3319+
'jest.config.js' => 'jest-config',
3320+
];
3321+
foreach ($configs as $file => $stub) {
3322+
if (file_exists($file)) {
3323+
$this->warning(" Kept existing {$file} (review manually)");
3324+
continue;
3325+
}
3326+
file_put_contents($file, $this->prepareStub($stub, []));
3327+
$this->line(" Created {$file}");
3328+
}
3329+
3330+
// 3. Rewrite package.json
3331+
if (file_exists('package.json')) {
3332+
$pkg = json_decode(file_get_contents('package.json'), true);
3333+
if (!is_array($pkg)) {
3334+
$this->error('package.json is not valid JSON, skipping rewrite.');
3335+
} else {
3336+
$previousScripts = $pkg['scripts'] ?? [];
3337+
3338+
$pkg['scripts'] = [
3339+
'dev' => 'wp-scripts start',
3340+
'build' => 'wp-scripts build',
3341+
'test' => 'wp-scripts test-unit-js',
3342+
'test:watch' => 'wp-scripts test-unit-js --watch',
3343+
'format' => 'wp-scripts format',
3344+
'format:check' => 'wp-scripts format --check',
3345+
'lint' => 'wp-scripts lint-js resources/',
3346+
'lint:style' => "wp-scripts lint-style 'resources/**/*.{css,scss}'",
3347+
'check-engines' => 'wp-scripts check-engines',
3348+
'check-licenses' => 'wp-scripts check-licenses',
3349+
'packages-update' => 'wp-scripts packages-update',
3350+
];
3351+
3352+
// Preserve make-pot / make-json if the plugin had them
3353+
foreach (['make-pot', 'make-json'] as $preserve) {
3354+
if (isset($previousScripts[$preserve])) {
3355+
$pkg['scripts'][$preserve] = $previousScripts[$preserve];
3356+
}
3357+
}
3358+
3359+
// Drop gulp-era devDeps
3360+
$dropDevDeps = [
3361+
'@babel/core', '@babel/preset-env', '@babel/preset-react',
3362+
'gulp', 'gulp-babel', 'gulp-clean-css', 'gulp-less',
3363+
'gulp-sass', 'gulp-typescript', 'gulp-uglify', 'gulp-watch',
3364+
'sass', 'npm-run-all',
3365+
];
3366+
foreach ($dropDevDeps as $dep) {
3367+
unset($pkg['devDependencies'][$dep]);
3368+
}
3369+
3370+
// v2-required devDeps — always overwritten so an old pinned version
3371+
// (e.g. @wordpress/scripts ^27) is bumped to the range v2 needs.
3372+
$addDevDeps = [
3373+
'@types/react' => '^18.3.0',
3374+
'@types/react-dom' => '^18.3.0',
3375+
'@wordpress/jest-preset-default' => '^12.44.0',
3376+
'@wordpress/scripts' => '^31.7.0',
3377+
'glob' => '^11.0.0',
3378+
'less' => '^4.6.4',
3379+
'less-loader' => '^12.2.0',
3380+
'typescript' => '^5.9.3',
3381+
'webpack-remove-empty-scripts' => '^1.1.0',
3382+
];
3383+
foreach ($addDevDeps as $dep => $version) {
3384+
$pkg['devDependencies'][$dep] = $version;
3385+
}
3386+
3387+
if (isset($pkg['devDependencies'])) {
3388+
ksort($pkg['devDependencies']);
3389+
}
3390+
if (isset($pkg['dependencies'])) {
3391+
ksort($pkg['dependencies']);
3392+
}
3393+
3394+
file_put_contents(
3395+
'package.json',
3396+
json_encode($pkg, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n"
3397+
);
3398+
$this->line(' Rewrote package.json');
3399+
}
3400+
}
3401+
3402+
// 4. Summary + manual steps
3403+
$this->line('');
3404+
$this->success('✅ Migration to v2 complete.');
3405+
$this->line('');
3406+
$this->info('Next steps:');
3407+
$this->line(' 1. yarn install');
3408+
$this->line(' 2. yarn build # verify everything compiles');
3409+
$this->line(' 3. yarn test # verify tests still pass, if any');
3410+
$this->line('');
3411+
$this->warning('Review manually:');
3412+
$this->line(' • Custom gulp tasks (if you had any) must be re-implemented as webpack plugins');
3413+
$this->line(' • Old build:<name> / start:<name> scripts for individual apps are gone — ');
3414+
$this->line(' webpack auto-discovers everything under resources/assets/apps/.');
3415+
$this->line(' • File extensions: .jsx/.tsx files in apps/ are fine; plain .js with JSX must be renamed.');
3416+
}
3417+
31643418
/**
31653419
* Let's roll
31663420
*

‎composer.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
},
2121
"require": {
2222
"php": ">=7.4",
23-
"wpbones/wpbones": "^1.11.1",
23+
"wpbones/wpbones": "^2.0.1",
2424
"wpbones/wpkirk-helpers": "~2.0"
2525
},
2626
"autoload": {

0 commit comments

Comments
 (0)