@xumana/cwp
v4.4.0
Published
Controlled WordPress — a CLI that moves WordPress sites between a local DDEV container and their host (Cloudron, ssh or local), guarding every upward write
Readme
cwp
Controlled WordPress: a CLI that moves WordPress sites between a local DDEV container and their host, guarding every upward write.
The name says what the tool does. Moving a WordPress site between environments
is normally an unpredictable sequence of half-remembered commands. cwp makes
every one of those movements controlled, and refuses the ones that are not.
cwp is middleware. It orchestrates DDEV, WP-CLI and your host, and adds the
WordPress- and builder-specific glue those tools don't provide. It never
reimplements what they already do: DDEV owns the local container, the host owns
remote transport, WP-CLI owns WordPress. cwp owns sequencing, safety and
project conventions.
Three hosts ship: Cloudron, a plain VPS over ssh, and
local only. Two page builders ship: Bricks, and none, which is plain WordPress
and a supported configuration rather than a degraded one.
You manage a handful of builder sites. Every time you refresh a local copy you
hand-run wp db export, search-replace, upload copies and mail-guard plugins.
This is that workflow with the footguns removed.
The manual is at cwp.sh/docs
One document rather than two, so there is no second copy to fall out of date. Below is what you need before deciding to click.
| | |
|---|---|
| Start here | What cwp is and is not · Requirements · Install · Quickstart · Choose your host |
| Concepts | The two-layer config · Asymmetric data flow · Reproducibility · The safety model · The output contract · Providers · Builders · The site plugin |
| Commands | One page each, generated from the CLI's own manifest |
| Guides | The build loop · Onboarding an existing site · Uploads strategies · The scrub pipeline · Bricks integration · Hooks |
| Reference | cwp.yml · Machine config · Exit codes |
| Troubleshooting | Common failures and the command that fixes each |
The pages are in this repository too, under docs/, laid out so the
directory structure is the site's route structure. A link between two of them
works when you read them here and when you read them there.
Install
npm i -g @xumana/cwp
cwp --version
cwp doctorInstalled globally, once per machine. Project state lives in cwp.yml in the
repository and in ~/.config/cwp/config.yml on your machine.
Requires Node ≥ 22, a Docker runtime, DDEV and mkcert, plus whatever your host
needs: the Cloudron CLI and a login, or an ssh key. cwp doctor checks all of
it at once, and every failure prints the command that fixes it.
Quickstart
From nothing to a working local copy of a production site:
mkdir -p ~/Code/example/example.com && cd ~/Code/example/example.com
cwp init # scaffold: cwp.yml, .ddev/, the site plugin
ddev start
ddev wp core download # core is gitignored, never in the repo
cwp fetch # the themes and plugins the site needs to render
cwp adopt dev # mint the relationship, derive the scope, build the tree
cwp db pull dev # the database, scrubbed, with uploads proxied
cwp openThe first pull on an image-heavy site takes about two minutes, because uploads are proxied rather than copied.
Then take a baseline before you touch anything:
cwp db snapshot baselineThe full version, with what each step does, is at cwp.sh/docs/start/quickstart.
Day to day it is a loop rather than a setup: cwp db pull to start from what is
live, build in the builder, cwp pull what you built, read the diff, commit,
cwp deploy. Step three is the one people skip. Skipping it makes a deploy ship
nothing you did: your work is in the database until something pulls it out.
The two pulls are different commands on purpose. cwp db pull replaces your
local database with the site's; cwp pull reads the site's state into files
you can diff and commit, and writes nothing to your local WordPress. The loop
end to end is at
cwp.sh/docs/guides/build-loop.
The safety model
Six rules, not configurable away. They are the whole reason this tool exists.
- Bulk data flows down, code flows up. Individually named items may flow upward under the full guard set, with every affected item printed first.
- Protected environments refuse upward writes without
--force. cwp reads that list off the effects rather than off the command's name. That keeps the list complete rather than remembered. - There is no bulk database push. Designed, then deferred.
- A remote restore point is taken before any upward write. On a host that cannot take one, disabling it makes the write refused.
- A local snapshot is taken before any downward write, and a snapshot that fails aborts the pull rather than warning.
- Every pull installs the mail guard, even with scrubbing switched off.
And one that is not a refusal: never raw SQL for URL replacement. WordPress
stores serialised data, so it is always wp search-replace.
The source records twelve invariants as data, each naming the test that asserts
it. The build fails when a named test is renamed or deleted. The long version is
at cwp.sh/safety, and the catalogue is in
src/domain/policy/invariants.ts.
Working on cwp itself
| | |
|---|---|
| docs/ARCHITECTURE.md | The design document for v1.0: the five layers, the seven-step spine, the two seams, the effect model. Start here. |
| docs/SPEC.md | The world cwp runs against: what the external tools do, verified rather than derived. |
| CLAUDE.md | Working conventions, and which document is authoritative for what. |
| docs/features/, docs/bugs/ | What is planned, and what is broken, one file each; aimd next shows what is up. Everything is entered there before code. |
npm ci
npm run typecheck && npm run lint && npm test
npm run build # tsup → dist/cli.jsThe site is in site/, with its own package.json. The rules that
govern what may be written on it are in
site/CONTENT-RULES.md, and docs/WEBSITE.md is the
whole record of how it was built.
Versioning and licence
Semantic versioning, tracked in VERSION.txt; changes in CHANGELOG.md, in
Keep a Changelog format. GitLab CI publishes through npm's trusted publishing;
no npm token exists anywhere in this project.
PolyForm Noncommercial 1.0.0 from 2.0.0 (LICENSE). Free for
any noncommercial purpose: personal use, study, hobby projects, and use by
charities, schools, public research and government. Commercial use needs a
separate licence; write to [email protected].
Everything up to and including 1.0.0 is MIT and stays MIT
(LICENSE-MIT). A licence already given cannot be taken back,
and forks of those versions are legitimate.
Either licence covers cwp itself, not the tools it orchestrates: Cloudron,
DDEV, WP-CLI and WordPress carry their own, and Bricks is commercial and
licensed per site.
