doxa-maps/docs/README-SECTION-3-cli-commands.md

203 lines
11 KiB
Markdown

## 3. CLI commands + required installs (per deployment method)
Everything runs through `bun` and the scripts in `package.json`. The build loop
iterates every folder under **`plugin-designs/`** (each folder = one bundle), so once a
map exists there, `bun run build` picks it up automatically — no per-map wiring.
> **Install once (all methods):** [Bun](https://bun.sh) (`curl -fsSL https://bun.sh/install | bash`) — Node ≥ 18 also works (`npm run <script>`; the scripts are identical). Then:
>
> ```bash
> bun install
> ```
The scripts these commands are grounded in (from this branch's `package.json`):
```jsonc
// TRANSCRIBED FROM package.json 2026-09-20. The previous copy had DRIFTED: it listed
// `check:drop` twice, omitted `check:template-literal` and `check:sri`, and still showed
// the old inline `build` pipeline — which has since moved behind a LOCK WRAPPER.
// ★ `build` is now `bash internal/build-lock.sh`; the real pipeline is `build:unlocked`.
// The lock SERIALIZES concurrent builds (a `clean` rm -rf's the shared output tree at the
// START of every build, so two builds racing give opposite answers seconds apart). Use
// BUILD_LOCK_WAIT=600 to QUEUE rather than collide. Note the lock serializes but does NOT
// dedupe: N queued builds = N full rebuilds.
// If you edit package.json, re-transcribe this block — a doc that QUOTES a config is
// claiming to be ground truth, and this one had silently stopped being it.
"dev": "bun x vite",
"build": "bash internal/build-lock.sh",
"build:unlocked": "bun run check:template-literal && bun run clean && bun run token && for d in plugin-designs/*/; do BUNDLE=$(basename \"$d\") bun x vite build || exit 1; done && bun run tree && bun run check:drop && bun run check:parse && bun run verify && bun run check:sri && bun run gallery",
"preview": "bun x vite preview",
"serve": "python3 -m http.server 4173 --bind 0.0.0.0 --directory public",
"cap:sync": "bunx cap sync android",
"cap:verify": "test -f capacitor/android/app/src/main/assets/public/index.html && echo \"OK webDir copied\" || (echo \"FAIL: empty assets/public -> blank white screen. Check webDir.\" && exit 1)",
"apk:debug": "bun run build && bun run cap:sync && bun run cap:verify && cd capacitor/android && ./gradlew assembleDebug",
"apk:install": "adb install -r capacitor/android/app/build/outputs/apk/debug/app-debug.apk",
"phone": "bun run apk:debug && bun run apk:install"
```
`bun run build` writes the whole deployable tree to `public/doxa-maps/`, including
`manifest.json` — the machine-readable index that carries the ready-to-paste
**iframe** and **`<script src>` + SRI** embed snippets for every map.
### Summary table
| # | Deploy method | Install first (prereqs) | Command(s) |
|---|---------------|-------------------------|------------|
| 1 | **iframe embed** | Bun | `bun install` → `bun run build` → `bun run serve` (or drop `public/doxa-maps/` on any host) |
| 2 | **Mobile app (Android)** | Bun ALONE for `check:mobile`; + Android SDK · `adb` on PATH · JDK 17 (Gradle) for the APK | `bun run check:mobile` (sync + webDir assertion, NO toolchain needed) · `bun run phone` (= `apk:debug` + `apk:install` to a device/emulator) |
| 3 | **Direct-JS / dashboard** | Bun | `bun run build`, then paste the `<script src> + integrity` (SRI) snippet from `manifest.json`. *Proven same-root AND foreign-host (verified 2026-09-20: gl + attribution, 0 errors, 0 404s). Fill the `YOUR_*` placeholders the snippet ships with* |
| 4 | **WordPress** | Bun (+ `php` CLI to run the contract gate) | `bun run build`, then either paste the iframe/script snippet into a **Custom HTML** block, **or** install `wordpress/doxa-maps-embed/` (shortcode + Gutenberg block). `bun run check:wp-embed` verifies the emitted markup. *(Contract-proven; not yet shot on a live WP install.)* |
| 5 | **Desktop (Tauri)** | Rust toolchain + Tauri CLI *(not yet scaffolded)* | **Deferred POC** — no command exists on this branch yet. |
---
### 1. iframe embed *(required)*
**Install:** Bun.
```bash
bun install
bun run build # builds every plugin-designs/* bundle -> public/doxa-maps/
bun run serve # optional: serves public/ at http://localhost:4173
```
The build writes `public/doxa-maps/manifest.json`. Each profile carries an
`embed.iframe` snippet, e.g.:
```html
<iframe src="./all-people-groups/all-people-groups/index.html"
width="100%" height="600"
style="border:none;border-radius:12px;" allowfullscreen></iframe>
```
URLs are root-relative, so the whole `doxa-maps/` folder is position-independent —
drop it on Cloudflare/Vercel/any static host and every iframe resolves. (For an
external absolute base, set `embedBaseUrl` in the build config before building.)
### 2. Mobile app — Android / Capacitor *(required)*
**Install:** Bun · **Android SDK** (`sdkmanager`, `adb` on your PATH) · **JDK 17**
(the Gradle wrapper needs it) · Capacitor (`@capacitor/*` are already devDependencies,
so `bun install` covers them). A connected device or running emulator for install.
```bash
bun run check:mobile # CONTRACT check — needs NO JDK/SDK/device
bun run phone # full path — needs the toolchain above
```
**`check:mobile` is runnable anywhere.** `cap sync` is a file copy plus plugin wiring, so it
needs no Android toolchain at all; `cap:verify` then asserts
`capacitor/android/app/src/main/assets/public/index.html` exists. That guards the soft
failure `capacitor.config.json` warns about — *a wrong `webDir` yields an EMPTY
`assets/public` and a BLANK WHITE SCREEN at launch, not an error*. Verified 2026-09-20 with
`java`, `gradle`, `adb` and `sdkmanager` all absent: sync EXIT=0, 21 bundles staged,
`cap:verify` OK. Negative-tested by removing `index.html` — EXIT=1.
**Status: built + contract-proven; not shot.** The APK itself (`gradlew assembleDebug`) and
its launch on a device still need JDK 17 + the Android SDK + `adb`.
`phone` chains the whole path: `apk:debug` (which runs `bun run build`, then
`cap:sync` to copy the web output into the Android project, then `cap:verify`, then
`./gradlew assembleDebug`) and finally `apk:install` (`adb install -r ...`) onto the
device. Run the halves separately if you want to inspect between steps:
```bash
bun run apk:debug # build the APK only
bun run apk:install # push the built APK to a connected device
```
Only Android is scaffolded on this branch (`capacitor/android`, `capacitor.config.json`);
iOS is not set up.
### 3. Direct-JS / dashboard *(required)*
**Install:** Bun.
```bash
bun run build
```
Then paste the profile's `embed.script` snippet from `manifest.json` — a `<script src>`
with a Subresource-Integrity (SRI) hash plus the custom-element tag:
```html
<script src="./all-people-groups/all-people-groups.js"
integrity="sha384-j5Ypx+EI9DGlTClzgLs1R8wAnmTnkQ7HgAhHrxyGoVoHDMTlFVY7slU6JNicn77T"
crossorigin="anonymous"></script>
<all-people-groups
profile-config='{"profile":"all-people-groups","tk":"YOUR_MAPBOX_TOKEN"}'>
</all-people-groups>
```
The element accepts props / emits events, so it can wire into a surrounding page as a
unified research dashboard (deploy-3 proof). The SRI hash regenerates on every build.
#### Embedding on a FOREIGN host (a partner site) — the two things that differ
Both embed methods were run to their first failure on a foreign host page (2026-09-20).
**deploy-1 needed nothing**; **deploy-3 needed two values**, and the snippet now ships both.
*Why they differ:* an **iframe carries its own document base**, so the leaf page's relative
asset and API paths resolve correctly. A **direct-JS embed executes in the host's document**,
so those same relative paths resolve against the *host* — and 404. Same bundle, same data,
different containing document.
| | iframe (deploy-1) | direct-JS (deploy-3) |
|---|---|---|
| `assetsBase` | not needed | **required** — in `profile-config` |
| API base | not needed | **required** — `window.MAP_APP_API_URL` (a host global, *not* a profile-config key) |
| Mapbox token | inherited by the leaf | replace `YOUR_MAPBOX_TOKEN` |
Both are emitted into `embed.script` already: the API base uses the configured value, and
`assetsBase` falls back to a `YOUR_TREE_URL/assets` placeholder when `assetsBaseUrl` is unset.
**One rewrite either way.** Snippet URLs are **tree-relative** (`./bundle/...`) because
`embedBaseUrl` is empty by default — that is what keeps a dropped folder position-independent
and the `check:drop` gate green. A partner whose page is not *at* the tree root must either
prefix the path (`./doxa-maps/...`) or set **`embedBaseUrl`** in the build config to emit
absolute URLs. That is a deployment choice, not a defect.
### 4. WordPress *(built + contract-proven; not yet shot on a live WP install)*
> **Corrected 2026-09-20.** This section previously read *"deferred POC — no dedicated
> WordPress build step exists on this branch"*. That is no longer true: a first-class
> plugin exists at `wordpress/doxa-maps-embed/` (a `[doxa_map]` shortcode + a Gutenberg
> block), and its emitted markup is now verified by a gate.
**The simple path** — no plugin needed, same artifacts as methods 1 and 3:
```bash
bun run build
```
Then paste either the iframe snippet **or** the `<script src> + SRI` snippet from
`manifest.json` into a WordPress **Custom HTML** block.
**The plugin path** — `wordpress/doxa-maps-embed/` registers the bundle through WordPress's
own dependency graph, so the SRI hash and `crossorigin` survive and the script loads once
per page rather than once per embed.
```bash
bun run check:wp-embed # renders the REAL shortcode with no WordPress installed
```
That gate stubs the ~20 WordPress functions the plugin actually calls (`wordpress/tests/`),
renders the shortcode against the REAL built `manifest.json`, and asserts the contract
`HOW-TO-TEST.md` defines: the element tag is emitted, `profile-config` decodes to valid
JSON, the script `src` equals the catalog path, the SRI `integrity` is injected by the
`script_loader_tag` filter, `crossorigin="anonymous"` is present — plus a negative test
that a **malformed** integrity is rejected.
**Status honesty — say "contract-proven", not "proven".** What is proven: the plugin emits
the correct markup, repeatably and in CI. What is *not*: that WordPress renders the page,
that the block editor registers it, or that a browser executes the result. Those still need
a real WP install (`wp-env start` — see `wordpress/doxa-maps-embed/HOW-TO-TEST.md`).
### 5. Desktop — Tauri *(deferred POC)*
**Intended prereqs:** the Rust toolchain (`rustup`) + the Tauri CLI, wrapping the same
`bun run build` web output as a native desktop shell.
**Status: deferred POC.** No Tauri scaffolding or build script exists on this branch
yet, so there is no copy-paste command to run today. Documented here for completeness
of the 5-method model.