cwp push
shipped 1.0.0cwp push [env] [flags]
Acts on the environment the project makes obvious: the only one it defines, or the one called dev. Name another to act there. A project with several and no dev has to name one.
| Argument | What it is | Default |
|---|---|---|
[env] | environment to write to (default: default_environment in cwp.yml) |
| Flag | What it does | Default |
|---|---|---|
--only <what> | narrow to one artifact, or to code for the tracked paths | |
--force | override the refusals a crossing makes | off |
--no-backup | skip the remote backup taken before the write | on |
--delete | mirror the tracked paths: also remove remote files that are absent locally | off |
--prune | with —only roles or —only inventory: delete what the file does not list | off |
--exact | with —only inventory: pin to the recorded versions | off |
--replace | with —only bricks: overwrite items that already exist instead of skipping them | off |
--sensitive | does nothing since 2.0: credentials never enter the tree, and customCss travels on its own | off |
--skip-in-use | with —only bricks: leave items the site still references in place | off |
--skip-incomplete-fonts | with —only bricks: push everything except fonts the tree carries no faces for | off |
--keep-superseded-fonts | with —only bricks: leave the font files this import replaces in the media library | off |
--no-snapshot | with —only bricks: skip the local design-system export taken before a replace | on |
--overwrite-conflicts | with —only content: overwrite posts that changed on the target since cwp last saw them | off |
--media <mode> | with —only content: attachments the target lacks, upload | require | |
--publish | with —only content: a post the target does not have yet arrives published | off |
--status <status> | with —only content: set an existing post’s status too | |
--delete-terms | with —only content: remove terms of a declared taxonomy the tree no longer describes | off |
--skip-missing-targets | with —only content: drop a menu entry whose target is absent instead of refusing | off |
--yes | skip the confirmation prompt | off |
--with-agent | with —dry-run on a host with no shell: install and remove the PHP agent so the plan is real | off |
Plus the shared flags --json, -v, --verbose, -q, --quiet and --dry-run.
What it does
cwp push makes an environment match the committed tree. It carries both halves
of what is under management:
- the six artifacts — content, the Bricks design system, settings, widget
areas, roles and the inventory, the same six
cwp pullreads down. Each artifact’s page holds its own rules, under the older spelling that stays as an alias: content, bricks, settings, widgets, roles, inventory; - the
trackedpaths fromcwp.yml: your theme and plugin files. They are code and have no baseline. An operator shipping a change means them as much as the content.
One command carries all of it for a reason. A partial deploy leaves an environment that matches no commit. The provenance record exists to detect that state. One push is one confirmation and one remote backup for what you experience as shipping one change.
--only <what> narrows a run to one of the six, or to code for the tracked
paths alone. cwp refuses a name this project does not carry rather than doing
nothing.
--only code ships your own theme or plugin and touches nothing else: a hotfix,
or the code a page needs before the page can arrive.
The order it sends things in
A supplier goes before what uses it, and the code goes before all of them:
tracked paths → inventory → bricks → content → settings → widgets → roles
A page can point at an element type your plugin registers or at a global class the design system carries. Neither points back at a page. So the run sends them first and writes the pages onto a site that already has what they need.
The reference check reads the environment as it is before the run. A class or element type that this run is about to deliver gets a warning rather than a refusal:
! 4 element types not on "dev" yet: acme/hero, … — they arrive earlier in this
run, and the pages render wrong if they do not
A reference nothing in the run supplies still refuses. The check exists for that case: a page saved with a dangling reference renders wrong rather than failing.
A deploy’s post-steps come last. The cache flush, the provenance record and
post-deploy.sh describe the run as a whole, so they run after the content and
not after the files.
One plan, one decision, one restore point
Each artifact’s survey produces a sub-plan, and cwp concatenates their effects into one plan. The guard ladder reads every remote write this run will make and decides once:
-
Resolve where it goes. The environment you named, or the one
default_environment:declares. A project that declares none refuses rather than picking: cwp does not guess which site a write is for. -
Refuse an environment that was never adopted.
cwp adoptgives the tree its relationship with an environment. -
Refuse if the environment moved since cwp last transferred (below).
-
Refuse if the environment is
protectedand--forcewas not given: exit 5. 3a. Refuse if the environment is inlaunchorliveand a readiness check fails there: the sandbox captcha key, the mail guard on the environment, the scrub’s placeholder accounts. No flag passes it; the line names the fix, orstage: buildwhile the site is not live (stage).✗ "prod" is live and turnstile site key on prod is the sandbox key, and every submission passes → cwp edge captcha prod writes the zone's widget into the builder, or set stage: build while prod is not live -
Show what would move and ask for confirmation, unless
--yes. Exit 6 when cwp cannot prompt.Each artifact says it in its own words and names the destructive parts. The posts it would overwrite, and which of them somebody changed on the site. The design-system items it would delete. The capabilities a role would lose. The plugins a
--prunewould remove.--dry-runprints the same list. Read it before you decide. -
Run
pre-push.sh. A non-zero exit aborts. -
Take one remote backup, unless
--no-backup. -
Write, then verify: per file for the tracked paths, per item for content.
Nothing happens until the confirmation passes: a refused push takes no backup and does not run your hook. One plan means one restore point.
Verification compares a digest per file rather than counting files. A transfer tool decides what to send by size and modification time. A file on the site that differs in content alone reads as up to date. The tool never resends it. Counting files would never see it.
Where the transport has a flag to stop trusting size and time, cwp warns that
it is re-sending that path on content and sends it. When the second pass
matches, the run reports a repair step. It runs once. A second mismatch is a
fact about the host: no room, no permission, something else writing the file.
Cloudron has no such flag, so the run names what to do by hand.
cwp deploy is this command plus the remote post-steps and the
post-deploy hook. They share one implementation, so their flags and guards
cannot drift apart.
It refuses when the environment moved
git push refuses a non-fast-forward, and so does this. The survey reads each
artifact from the environment and compares it against the baseline the last
transfer agreed on. If the two disagree, the push stops and names what does:
The command the line names is not held up by the line: `cwp edge captcha
prod` writes the door the check reads, so the refusal lets it through.
✗ "prod" no longer matches what cwp last recorded for it: roles, settings
→ somebody changed it there, or cwp's last transfer did not fully land — read
it down first (`cwp pull prod`) and commit what you want to keep, or pass
--force to overwrite what is there
It names both readings because one comparison cannot tell them apart. An artifact that did not fully converge records no baseline. That makes the second reading rare. A baseline written by an older cwp is still in the file.
It sees more than a count would. A role renamed, a capability granted, a plugin deactivated at the same version, a widget moved within its area: the survey compares each of them.
Content gets a finer check. Its own push refuses per item when something
changed on both sides. --overwrite-conflicts is the deliberate override.
One artifact has no check, and the run says so:
! not checked for changes on "prod": bricks — these artifacts cannot yet report
what the environment holds, so a change made there would be overwritten
without warning
The design-system push uploads a package and lets the builder merge it. It never reads what the site holds, so there is nothing to compare. The run says so rather than leaving you to assume otherwise.
It creates the accounts the content needs
An item carries the login that wrote it. A push resolves every one of them on the
target before it writes anything. It creates the missing accounts from
content/authors.yml: login, display name, address, roles, and a random
password nobody ever sees. cwp sends no mail. The person gets in through the
site’s own password reset.
cwp never changes an account that is already there. It reports a display
name or an address that differs from the tree and leaves it alone: a push may
not move somebody’s login credential. The next cwp pull brings
the tree in line.
A login the target lacks and the tree does not describe stops the run before cwp writes anything. The refusal names all of them at once:
✗ 2 author(s) cannot be resolved on "prod":
naima — content/authors.yml does not describe this person
arno — content/authors.yml carries no email address for them
→ pull the people into the tree first — `cwp content pull` writes
content/authors.yml — or create the accounts there
Set author on an environment and every item
goes there under that one login instead. The tree still holds who wrote what.
The flags that belong to one artifact
--prune, --exact and --delete are not general. Each answers a question only
some artifacts have. Each is legal only where it means something. Elsewhere the
command refuses it rather than doing nothing:
| Flag | Where it applies |
|---|---|
--prune | --only roles, --only inventory — delete what the file does not list |
--exact | --only inventory — install the recorded version over a different one |
--replace | --only bricks — overwrite items that already exist instead of skipping |
--sensitive | --only bricks — include the api-keys and custom-code tabs |
--skip-in-use | --only bricks — leave items the site still references in place |
--skip-incomplete-fonts | --only bricks — push everything but fonts with no faces |
--keep-superseded-fonts | --only bricks — leave replaced font files in the library |
--no-snapshot | --only bricks — skip the local export taken before a replace |
--overwrite-conflicts | --only content — write over a post that changed on the target |
--media <mode> | --only content — upload attachments the target lacks, or require them |
--publish | --only content — a post the target lacks arrives published |
--status <status> | --only content — set an existing post’s status too |
--delete-terms | --only content — remove terms the tree no longer describes |
--skip-missing-targets | --only content — drop a menu entry with no target |
--delete | an un-narrowed run or --only code — mirror the tracked paths |
✗ --exact does not apply to `roles`
→ use it with the artifact it belongs to: cwp push --only inventory --exact
Two flags are absent on purpose. There is no selection: no --post,
--type or --all. A crossing carries everything under management, and
narrowing to a few items leaves a baseline that means nothing in particular.
--only <artifact> is the one narrowing there is.
There is no --no-verify either. A crossing either converged or it did not, and
verification turns “exited zero” into “the site holds what the tree describes”.
The flag stays on cwp content push, where skipping the
read back on a single artifact is a diagnosis rather than a habit.
Read --delete twice: it mirrors the files, and a run narrowed with
--only does not carry them. An artifact’s own deletions have their own flag.
Additive by default, mirror with --delete
cloudron sync push only ever adds and overwrites. A file you delete locally
stays on the remote. In a plugin directory WordPress keeps loading it, so a
file you think you removed can still be running. The default stays that way:
deleting live files as a side effect of a routine deploy is the surprise this
tool exists to prevent.
That paragraph is about the tracked paths. cwp converges the six artifacts item by item under their own rules. Their pages describe those rules.
The default is not silent about it. When the remote holds files your tracked path
does not, cwp says so and names the count. --delete mirrors instead, and it
sits behind every guard above. Protected environments still refuse. The
confirmation still runs and says outright which files it will delete. cwp still
takes the backup first. cwp push --delete --dry-run lists the exact files it
would remove and changes nothing.
Verification, and why it is per file
After the transfer, cwp compares an md5sum of every remote file against your
local tree. A file that never arrived, or arrived with different contents, fails
the push. The error names it.
cloudron sync decides what to send by size and mtime. A remote file corrupted
to the same length with its mtime intact reads as Already up to date. The
transfer never resends it. Re-running the push does not repair it. Touch the
local file to force a resend, or delete the remote copy. The error says so.
If the remote image has no md5sum, cwp warns and falls back to comparing file
counts. That is weaker, but it does not fail a good push.
What the dry run can and cannot tell you
The plan is the tracked paths, their local file counts and their remote targets.
On the Cloudron provider it is not a file-level diff. Cloudron has no
transfer dry run, so only a transfer shows what differs. The dry run says that
in as many words. On the ssh provider it is a file-level diff, because
rsync has one.
With --delete the dry run lists the deletion set exactly. A remote listing is
enough to compute it.
Files on the far side that are not yours
A transfer made by hand from macOS leaves a ._file beside every real one.
cloudron push packs a directory with the BSD tar of the machine it runs on,
and GNU tar on the far side unpacks the extended attributes as separate files.
On every transfer cwp makes itself, it sets COPYFILE_DISABLE=1. That is no
help for a transfer you make yourself, so cwp names the files instead:
! 36 AppleDouble file(s) under /app/data/wp-content/plugins/site on dev
(._site.php, ._hooks.php, …) — macOS `tar` writes them when a directory is
transferred by hand. WordPress loads none of them; remove them with
`cwp deploy dev --delete`, which mirrors
They are rubbish rather than a danger. WordPress loads none of them, because
glob('*.php') matches a leading dot only when the pattern writes one. Removing
them takes --delete: cwp does not delete remote files without it.
What an artifact could not do
An artifact converges what it can and reports what it cannot. A plugin cwp could not fetch. A role the site would not take a capability from. A page the target stored differently from what cwp sent. The run names each one under its artifact and says how many did not land:
! inventory — 0 item(s) converged, 1 did not
my-theme 2.4 (source: media/my-theme.zip) — the far side could not resolve it
An artifact that did not fully converge records no baseline. The next push
therefore does not accuse the environment of having moved: cwp writes down what
happened, not what it intended. --json carries the same lines under
unfinished, keyed by artifact.
--json records which guards ran
Every push and deploy carries a guards object. It says whether the
protected-environment refusal applied, whether cwp took or waived a restore
point, and whether the change had a committed source:
{ "guards": { "protectedEnvironment": "passed-with-force",
"restorePoint": "taken",
"source": "present" } }
A guard that silently did not run shows as a missing line there.
Example
cwp push dev --dry-run # see what would be sent
cwp push dev --delete --dry-run # see what would be sent *and removed*
cwp push prod --force # prod is protected; --force is required
What lands before what
A full push sends the tracked paths first. Then come the inventory, the design system, the content, the settings, the widgets and the roles: a supplier before what uses it. Settings come after the content because the options holding a post id need the post to be there.
A settings group marked before: content in cwp.yml is the exception. It
holds the options a plugin registers post types, taxonomies or fields from.
That half of the settings lands before the content, and the run shows it as
settings (schema). The rest of the settings stays after the content.
What the target needs afterwards
A push writes data past the forms a plugin would have used. What the plugin
does when its own form saves, it does not do after a push. The transient it
holds a table in stays stale. The counter row it updates with a bare UPDATE
never exists. The rewrite rule for a sitemap is not there until somebody
flushes.
A family names those steps (settle in its file, see the cwp.yml
reference). The push runs them after the
artifacts and before the ref moves. Each step is an effect in the plan: the dry
run lists it and the confirmation names it. A step that fails is a push that
did not arrive. Two families naming the same step get one. A flush of the
rewrite rules runs last.
settle example-child/redirects: delete transient ex_all_redirects
settle example-child/redirects: redirect_clicks = 0 on every ex_redirect without one
settle example-child/seo: flush rewrite rules
A run in which no artifact writes settles nothing. What only a project knows
stays in its post-deploy hook.
The page builder has steps of its own. On a site loading its CSS from files, a
design-system push that carried classes, variables, theme styles or components
writes Bricks’ per-post CSS again. After a content push onto a site with query
filters on, cwp rebuilds the filter index through bricks/reindex-filters. A
target that gained the filter tables after its content had arrived had empty
ones until then. The run names both steps.
What it does not do
It does not push the database. cwp has no upward database command, and
cwp deploy is not one. The project deferred a whole-database push;
Safety records that decision and why. This command carries the tree
and the tracked files: the things that have a reviewed, committed source.
It does not push media. The files an attachment points at travel downward in the tree and never go back up.
It does not push anything outside the tracked list, run hooks during a dry run,
or delete anything remotely unless you pass --delete.
It does not merge. Like the pull, it refuses and hands the decision back to
you, with git diff as the surface you make it on.