Skip to content

cwp adopt

shipped 1.0.0-rc.2
cwp adopt <env> [flags]

Acts on the environment you name. There is no local default, so you name one.

tree → site

ArgumentWhat it isDefault
<env>environment to adopt
FlagWhat it doesDefault
--releasetake it back: remove cwp’s identity and recordsoff
--rescopere-derive what is under management, as a diff to consent tooff
--forceoverride the protected-environment refusaloff
--no-backupskip the remote backup taken before the writeon
--yesskip the confirmation promptoff
--with-agentwith —dry-run on a host with no shell: install and remove the PHP agent so the plan is realoff

Plus the shared flags --json, -v, --verbose, -q, --quiet and --dry-run.

What it does

Declares that this project manages a site, and gives its content the identity that makes every later crossing possible.

It writes to the site. A uid changes what the site is and stays there until something removes it, so cwp adopt asks before it does, naming the posts it would adopt.

cwp adopt prod --dry-run

The dry run answers a question nothing else does: this is what I would manage, and this is what I would write, before cwp writes anything. It prints the scope it would derive (post types, taxonomies, widget areas, roles) and names the types it would not take without a decision:

✓ would adopt — "prod" — 10 post(s) would be given an identity
    page/home → #17, claimed by the tree on its slug
    page/about → #11, claimed by the tree on its slug
    page/careers → #2, a new identity — the tree does not describe it
    …and 7 more
✓ scope — 2 post type(s) · 2 taxonom(ies) · 1 widget area(s) · 5 role(s)
  registered by a plugin or the theme, and not taken without a decision:
    product     1,284 entries
    shop_order    412 entries

The two answers per post are not the same decision. A page claimed by the tree becomes that tree item, and stays it: the next push writes the file over that page. A page the tree does not describe gets an identity of its own. cwp manages it from then on, with nothing in the repository describing it yet.

Read them before the yes. A slug matched to the wrong post duplicates a page and leaves the original holding the URL.

That list is where transactional data belongs: visible, unmanaged, and a decision somebody makes rather than four thousand orders arriving in a repository.

Adoption is standing, not repeated. Afterwards, content cwp has not seen before gets an identity without a new prompt. You took the decision once. The output still names every minting.

That naming carries the same answers the plan carried, in the same words:

✓ page/home — adopted as 5f5a9e54-… (claimed by the tree on its slug)
✓ page/d9f77deb-… — adopted as d9f77deb-… (claimed by the tree on the last transfer's record)
✓ page/careers — adopted as f3c1b204-… (a new identity — the tree does not describe it)

There are three answers because the pairing has three keys. cwp reads them in this order: the identity the post already carries, then what the last transfer to this environment recorded, then the slug. The middle one carries a site that lost its _cwp_uid to a restore or a migration while the tree stayed intact. cwp wrote those posts, and its ref remembers which post each item was. It never reads another environment’s ref, and it declines where the post type disagrees.

What the target does not hold

Adoption pairs two sides. The run names the other side too: the tree items no post on the environment claims.

! 4 tree item(s) have no counterpart on "prod" — the next push creates each of them as a new post
    page/d9f77deb-…  content/page/d9f77deb-….yml
      → same title as #4711: cwp wp --env prod -- post meta update 4711 _cwp_uid d9f77deb-…

That list is the set a push would create a second time. A draft has no slug: wp_insert_post derives post_name only for a status that is publicly addressable. So nothing pairs the draft with the post it already is on the site, and pushing makes a second one.

Where a leftover item and a post this run gave a fresh identity to carry the same title, cwp names the pair and prints the command that settles it. It does not run it. A title is not an identity. The slug key declines a guess because nobody can undo adopting the wrong post. So the decision goes to whoever can recognise the post. The cleanup is one paste rather than four duplicates. A title two items share, or two posts share, names nothing, and cwp does not offer it.

Taking it back

cwp adopt prod --release

Removes the identity from every post, the sync marker beside it, the adoption record, the provenance options and the dashboard widget. It says how many of each it took off:

✓ release — "dev" is no longer adopted — removed 7 identities, 6 sync marker(s), cwp_pushes, cwp_last_push, the dashboard widget

The count is there because a release that cleared nothing must not read like one that cleared everything. Anything left on the site afterwards is a warning, not a tick.

It refuses while the tree still holds items tracked against that environment. Releasing then would orphan those identities in silence. The files keep their uids and the site loses them. The next push then adopts by slug and duplicates everything.

The ref survives, marked released, so the log of what happened stays true.

What it does not do

  • It does not adopt a source environment. That mode is read-only by declaration, and --force does not override it. The refusal is a target that does not exist rather than a guard that fired.
  • It does not resolve a disagreement. If the site carries an adoption record this checkout knows nothing about, another checkout adopted it. cwp says so and chooses nothing. Adopting again would mint a second identity over the first.
  • It does not pull anything. Adoption writes identity and records the scope. Reading the environment into the tree is cwp pull: a separate decision and a separate command.