WordPress plugin rebrand/conventions/remote-CLI patterns, Gitea release workflow, bastille jail provisioning, remote shell quoting safety, server fleet map, delegate brief writing, and verification discipline -- all derived from real incidents this session, plus two skills adapted (MIT license, attributed) from obra/superpowers and andrej-karpathy-skills.
6.6 KiB
name, description
| name | description |
|---|---|
| wordpress-plugin-rebrand | Use when forking and rebranding an existing WordPress plugin under a new name/prefix (e.g. for a white-label or iWP-branded resale product). Covers identifier renaming, repo flattening, updater bundling, and license-field wiring. |
WordPress Plugin Rebrand
Process for taking an existing WordPress plugin and rebranding it under a new name/prefix, without breaking anything or missing a spot. Validated live across 5 real plugins in one session (WooList, WooBC, WooSol, SlickWoo, MetAI → iWP-branded forks), each independently re-verified end-to-end afterward with zero defects found.
Inputs you need before starting
- Source repo (clone URL + credentials).
- New brand prefix (e.g.
iWP), new product slug (e.g.iwp-woolist) — get the exact slug from wherever the catalog/product list already defines it, never invent your own. - The updater/license-client file to bundle, if the target platform has
one (e.g. a shared
IWP_Updaterclass), and its exact integration snippet (api_url, plugin_slug, license_option names).
Step 1 — Learn the actual existing convention, don't assume
Read the plugin's main file first. Every plugin has its own prefix scheme — don't assume it matches a previous rebrand. Identify:
- Constant prefix (
PLUGINNAME_...) - Class prefix (
PluginName_...) - Function prefix (
pluginname_...) - Text domain string (usually lowercase-hyphenated, e.g.
pluginname) - Whether the plugin lives at the repo root or in a subdirectory
Step 2 — Flatten to repo root
If the plugin's files live in a subdirectory (not the repo root), move
everything up to root. Reason: many git hosts' repo-archive endpoint
(/archive/{tag}.zip-style) names the zip's top-level folder after the
repo, so a nested subdirectory inside the repo produces a
double-nested folder in the resulting distributable zip
(reponame/plugin-subdir/... instead of reponame/...). Flattening
avoids this categorically rather than requiring special-case handling
downstream.
Step 3 — Rename, in this order
- Text-domain / hyphenated contexts first: every
oldname-(CSS classes, script/style handles, asset file paths, admin page slugs) →newprefix-oldname-. A safe global find/replace, catches the vast majority of hyphenated identifiers correctly in one pass. - Constants:
OLDNAME_→NEWPREFIX_OLDNAME_. - Classes:
OldName_→NewPrefix_OldName_. - Functions / hooks / underscore contexts:
oldname_→newprefix_oldname_. - camelCase-after-underscore contexts (JS localize-object names are
the common case:
oldnameDataused as the 2nd arg towp_localize_script()) →newprefix_oldnameData. Grep the JS files for the exact existing spelling before finalizing — don't guess the casing, the PHPwp_localize_script()call must match byte-for-byte or the JS breaks silently at runtime. - Remaining bare prose occurrences of the product name (page titles,
UI labels, doc-comments) → the new display name (e.g.
MetAI→iWP MetAI). Do this LAST, after class renaming, or you'll double- prefix the class name. - File/directory renames to match:
class-oldname-admin.php→class-newprefix-oldname-admin.php, etc. - Update the plugin header block:
Plugin Name,Author,Author URI,Plugin URI,Text Domain(always hyphenated, never underscored — WordPress text domains are always hyphen-form even when everything else in that context uses underscores).
Step 4 — Cross-check enqueue calls against actual filenames
Every wp_enqueue_script(), wp_enqueue_style(), wp_localize_script()
handle/path argument must match the ACTUAL renamed filenames on disk
exactly, or the admin UI silently fails to load its JS/CSS with no error
message anywhere obvious. Check this explicitly, don't assume the
renames were consistent.
Step 5 — Bundle the updater / license client (if applicable)
Copy the shared updater class unchanged into the plugin's includes/
directory. Wire it in the main file (near the top, after constants):
require_once <PATH_CONSTANT> . 'includes/class-<updater-file>.php';
add_action( 'plugins_loaded', function () {
new <UpdaterClass>( [
'api_url' => '<the licensing server's API base>',
'plugin_file' => __FILE__,
'plugin_slug' => '<the confirmed catalog slug>',
'version' => <VERSION_CONSTANT>,
'license_option' => '<newprefix>_<name>_license_key',
] );
}, 5 );
Then add ONE settings field for the license key (option name matching
license_option above) into wherever the plugin already has an admin
settings UI — follow that file's existing code style/pattern for how
other fields are registered/rendered, don't introduce a different
pattern. If the plugin has no settings UI at all, add a minimal one
rather than skipping this step — license-gated updates need somewhere
for the customer to enter the key.
Step 6 — Verify before pushing
php -levery.phpfile. Zero tolerance for syntax errors.- Case-insensitive grep the whole tree for the bare old name:
grep -rniE '(^|[^a-z_-])oldname' --include='*.php' --include='*.js' --include='*.css' .— every hit must be part of a correctly-prefixed token (newprefix-oldname,newprefix_oldname,NewPrefix_OldName, or the new display name). Zero unprefixed exceptions, except possibly an original-authorship credit comment if one genuinely exists. - Sanity-check for double-prefixing bugs (
iWP iWP,IWP_IWP,iwp-iwp,iwp_iwpor equivalent for your prefix) — a real mistake that happens when step 3.6 runs before 3.3, or runs twice.
Step 7 — Ship
Commit, push (force-push is fine for a freshly-created fork with no
other history depending on it — confirm that's actually the case before
force-pushing anything else). Tag a release. See the
gitea-release-workflow skill for the release/verification steps if the
git host is Gitea.
Common failure modes (all hit at least once during validation)
- Forgetting the flatten-to-root step → double-nested zip.
- JS localize-object name mismatch (hyphen where an underscore is required for a valid JS identifier) → admin UI JS silently breaks with no visible error.
- Trusting the delegate's self-report instead of re-verifying — every
single delegate run in the validating session initially reported
success; independent re-verification (re-running
php -lyourself, a fresh grep, an actual functional test through the real serving pipeline) is what actually confirms it, not the report.