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

9.1 KiB

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 (curl -fsSL https://bun.sh/install | bash) — Node ≥ 18 also works (npm run <script>; the scripts are identical). Then:

bun install

The scripts these commands are grounded in (from this branch's package.json):

// 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
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.

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.:

<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.

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:

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.

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:

<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.

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:

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.

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.