> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wpos.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Deploy

> The build, lint, deploy pipeline: idempotent REST deploys with drift protection and a foreign-store merge guard.

The pipeline is three commands, in order, every time:

```bash theme={null}
npx exjsx build site          # JSX -> site/site.bundle.json
npx exjsx lint site           # conventions check; --strict for CI
WP_URL=... WP_USER=... WP_APP_PASSWORD=... \
npx exjsx deploy site/site.bundle.json
```

## Lint before every deploy

`exjsx lint` enforces the mechanical subset of the authoring conventions. Rule
families, each with a rule id you can grep for:

* **Errors**: `invalid-envelope` (a style value the server validator would
  reject: numbers, colors, units, flex enums).
* **Warnings**: `env-baked-url`, `img-alt`, `raw-atomic-overlap`,
  `placeholder-link`, `font-not-loaded`, `heading-structure`, `page-seo`,
  `duplicate-page-slug`.
* **Info**: `oversized-raw`, `unnamed-shared-class`, `deep-nesting`,
  `empty-container`.

Every finding prints its fix inline. Real output from a production project:

```text theme={null}
project site: 1 page file(s), theme
built site: 1 page(s), 12 variables, 2 fonts
  dedup: 237 local styles -> 123 shared classes
lint: 0 error(s), 14 warning(s), 33 info
WARN  img-alt [home#e00061]: URL-src image has no alt text
      fix: pass alt="..." on <img> (URL sources carry inline alt)
WARN  raw-atomic-overlap [class c-1qwa1g]: raw CSS sets [border-radius] which atomic props cover
      fix: move these to tw/sx props; keep raw for nested selectors, pseudo-classes and non-atomic CSS
...
verified: 1313 style envelopes valid (numbers/colors/units/flex-enums) · 3 font families covered (native enqueue or loader): Laygrotesk, Mesotrial, Geistmono · 1 page slug(s) unique
```

The final `verified:` line is a set of **positive assertions**: facts the linter
proved about the bundle (every style envelope valid, every referenced font
family covered, slugs unique). When a gate prints a `verified:` fact, it is
proven; do not re-verify it with hand-rolled scripts.

## Deploy

`exjsx deploy <bundle.json>` performs two kit writes (variables + the shared
class registry) and N page upserts over REST. No wp-cli is needed: the deployer
detects the Elementor version over REST and adapts prop formats per version. It
is **idempotent**: re-run it any time, and unchanged pages are simply
re-stamped.

```text theme={null}
deployed: 12 variables + 123 classes (2 kit writes), 1 page(s)
```

### Flags

| Flag                   | What it does                                                                                                                                                                                                       |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `--dry`                | Plan only. Prints what would be written and per-page actions, no writes                                                                                                                                            |
| `--fast`               | Skips the in-deploy CSS self-priming. Use it when a post-deploy `eu-studio check` follows: the check's page visits re-prime the CSS anyway                                                                         |
| `--only <slug[,slug]>` | Deploys only the named pages. **All kit writes are skipped** (variables, classes, parts), so shared-class changes lag until a full deploy; the CLI warns exactly about this. Structural changes need a full deploy |
| `--force`              | Overwrites pages that drift protection would skip. Never reflex-force; see below                                                                                                                                   |
| `--own-classes`        | Declares this bundle the owner of the site's class store, replacing resident classes. See the merge guard below                                                                                                    |

`--dry` output:

```text theme={null}
dry-run : would write 8 variables + 4 classes, and:
  UPDATE "Home" (/home/) -> existing id 15
```

`--only` warning, verbatim:

```text theme={null}
WARN: --only: shared class registry (123 classes) NOT updated : styles may lag; run a full deploy without --only to sync
```

### Drift protection

On deploy, each page's live tree is compared against the hash stamped at the
last deploy. A mismatch means a human edited the page in the Elementor editor,
and the page is reported as `skipped-drifted` instead of being overwritten.

**Stop when you see it.** Reconcile (use `exjsx decompile` to adopt the live
tree back into JSX) or consciously `--force`; never reflex-force. Drift
detection reads the live tree through wp-cli; without wp-cli the deploy still
works, and the report says
`wp-cli unavailable : drift detection skipped (hand-edit protection off; set EXJSX_WPCLI)`.

### The foreign-store merge guard

Deploying a small side bundle (a probe page, a one-off) to a site that already
hosts another project used to let orphan cleanup delete the resident project's
classes. Now the deployer detects a foreign class store and **merges instead of
wiping**: resident classes the bundle does not declare are preserved, and the
report says so:

```text theme={null}
123 resident class(es) preserved (foreign store : use --own-classes to replace)
```

If the full resident store cannot be read for a safe merge, the deploy refuses
with:

```text theme={null}
deploy: this site's class store holds N classes this bundle doesn't declare : it looks like
another project lives here, and the full store couldn't be read for a safe merge. Deploy to
a fresh site, or pass --own-classes to intentionally replace the store.
```

### Multi-tenant targets: --inline

On shared or multi-site targets, build with `exjsx build site --inline`:
self-contained pages, zero kit writes, no registry or variable clobber.
Single-owned sites should use normal deploys, which own the class registry
namespace (and give you the Class Manager names from `cls=`).

## The loop

Build, lint, deploy, then verify with one call:

```bash theme={null}
npx eu-studio check --pages /,/about/
```

See [Verify](/ultra/verify) for what the gate asserts, and
[Recipes: agent briefs](/ultra/recipe-agent-briefs) for how to budget fix
rounds.
