diff --git a/README.md b/README.md index 6b1ea33ec..aa5462426 100644 --- a/README.md +++ b/README.md @@ -688,6 +688,16 @@ bun run dev # → open the URL Vite prints: that page is the No build needed first — the built maps are already in the repo. To start a plugin, copy `plugin-designs/template-bundle/` to `plugin-designs//`; the studio finds it automatically. +### The easiest path: one server for your site and its maps + +1. **Work in the studio.** `bun run dev` opens the Plugin Studio (with voice, if you set up Rudder above): + build and try maps there; they update as you save. +2. **Point the drop-in at your site.** In `vite.doxa-maps-build-config.json`, set `dropin.dest` to your + site's `public/` folder (the default is `../doxa-life/public`). +3. **Build and drop in.** `bun run build && bun run dropin` copies the maps into your site's + `public/dx-maps/`, and your site's own server serves them: one server, nothing else to deploy. + The drop-in touches only `dx-maps/`; the rest of your site is left alone. +
Make changes to the studio itself diff --git a/documentation/reference.md b/documentation/reference.md index 7278eb9cc..ddb4632ec 100644 --- a/documentation/reference.md +++ b/documentation/reference.md @@ -145,6 +145,7 @@ i18n, under a **distinct** tag, since `customElements.define()` is one-shot per | `bun run clean` | wipes and recreates the `public//` output tree | | `bun run tree` | writes `manifest.json` + the leaf pages, then copies the tree to every build-config `paths` destination | | `bun run verify` · `bun run verify:full` | data-integrity, shipped-bundle and network-policy guards (fast, no browser) · plus rendered posters and live apps in a browser | +| `bun run dropin` | the one-server drop-in: copies the built tree into your site's `public/` (`dropin.dest`, default `../doxa-life/public`), so the site's own server serves every map. Never prunes; `--prune` into a `public/` folder is refused | | `bun run sync:dropin [--from-config] [--prune]` | mirrors the built `public/` into a host checkout. The allowlist lives in ONE place, `vite.doxa-maps-build-config.json` → `dropin.include`; `dropin.exclude` lists built files no page fetches. Never copies credentials, docs or staging pages; `--dry-run` first | | `bun run check:public-hygiene [] [--strict]` | proves a served folder is fit to publish: no local paths, internal names, dev URLs, token-shaped secrets, source maps or staging pages. Run it on a drop-in before any public push | diff --git a/internal/sync-dropin.mjs b/internal/sync-dropin.mjs index 622ab1a7d..424974e9f 100644 --- a/internal/sync-dropin.mjs +++ b/internal/sync-dropin.mjs @@ -2,8 +2,9 @@ /** * sync-dropin.mjs — mirror the built web-root (public/) into a HOST checkout as a "drop-in". * - * WHY: the website (doxa-life) consumes this bundler's output as generated files under - * public/embed/doxa-maps-build/. Hand-editing there is forbidden — the ONLY way a map changes + * WHY: the website (doxa-life) consumes this bundler's output as generated files: the tree folder + * (dx-maps/) dropped straight into its public/, so one server serves the site and the maps. Hand- + * editing there is forbidden — the ONLY way a map changes * on the site is: change the bundler → `bun run build` → sync. This is that one honest step, * so it is a script and not a remembered rsync incantation. * @@ -40,7 +41,7 @@ * * Usage: node internal/sync-dropin.mjs [--from public] [--tree doxa-maps] [--keep-staging] * [--only a,b | --from-config] [--prune] [--protect a.txt,other] [--dry-run] - * e.g. node internal/sync-dropin.mjs ../doxa-life/public/embed/doxa-maps-build --from-config --prune + * e.g. bun run dropin (= --from-config, into dropin.dest: ../doxa-life/public) * Prints: copied · unchanged · deleted (stale inside the allowlist) · pruned (outside it) · * excluded (credentials/docs/staging) · excluded (not shipped: dropin.exclude), then the * destination's top-level tree after the sync. @@ -48,7 +49,7 @@ * Review the result with `git -C status` — the host repo is the restore point. */ import { readdirSync, statSync, lstatSync, mkdirSync, copyFileSync, rmSync, existsSync, readFileSync, realpathSync } from 'node:fs' -import { join, relative, resolve, dirname, sep } from 'node:path' +import { join, relative, resolve, dirname, sep, basename } from 'node:path' import { fileURLToPath } from 'node:url' import { createHash } from 'node:crypto' import { TREE_NAME } from './_tree-name.mjs' @@ -87,6 +88,14 @@ const excluded = (f) => excludedInternal(f) || isNotShipped(f) const usage = 'usage: node internal/sync-dropin.mjs [] [--from public] [--only a,b | --from-config] [--prune] [--protect a,b] [--dry-run] ( defaults to $DOXA_DROPIN_DEST, then dropin.dest in vite.doxa-maps-build-config.json)' if (!DEST) { console.error(`[sync-dropin] ${usage}`); process.exit(1) } const dest = resolve(DEST) +// ★ --prune INTO A SITE'S WEB-ROOT WOULD DELETE THE SITE. --prune removes every file in outside the +// allowlist, which was right while was a folder of its own (public/embed/doxa-maps-build). The +// one-server drop-in goes straight into the site's public/, so IS the site and everything outside +// dx-maps/ would go. Refuse it outright rather than trust that nobody types it. +if (PRUNE && basename(dest) === 'public') { + console.error(`[sync-dropin] refusing --prune into ${dest}: it is a site's web-root, and --prune would delete every file of the site outside the drop-in. Sync without --prune (bun run dropin); stale files INSIDE the drop-in are still removed.`) + process.exit(1) +} if (!existsSync(dest) || !statSync(dest).isDirectory()) { console.error(`[sync-dropin] dest does not exist or is not a directory: ${dest} — create the drop-in folder in the host first`); process.exit(1) } if (!existsSync(FROM)) { console.error(`[sync-dropin] source missing: ${FROM} — run \`bun run build\` first`); process.exit(1) } diff --git a/package.json b/package.json index ca2de9c21..5382ebe51 100644 --- a/package.json +++ b/package.json @@ -113,6 +113,7 @@ "check:embed-contract": "node internal/check-gospel-embed-contract.mjs", "check:engine-contract": "node internal/check-gospel-engine-contract.mjs", "check:link-coverage": "node internal/check-link-verification-coverage.mjs", + "dropin": "node internal/sync-dropin.mjs --from-config", "gospel:bridge-reach": "node plugin-designs/gospel-resources/scripts/build-bridge-reach-table.mjs", "gospel:field-cards": "node plugin-designs/gospel-resources/scripts/build-field-cards.mjs", "check:link-coverage:poison": "node internal/check-link-verification-coverage.mjs --poison", @@ -309,7 +310,7 @@ "check:zoning-declared": "node internal/check-zoning-declared.mjs", "check:zoning-declared:self-test": "node internal/check-zoning-declared.mjs --self-test", "clean": "node internal/clean.mjs", - "deploy:dev": "bun run build && node internal/sync-dropin.mjs --from-config --prune", + "deploy:dev": "bun run build && bun run dropin", "desktop:build": "node internal/desktop-build.mjs", "dev": "bun x vite", "fix:sri": "node internal/fix-sri.mjs", @@ -558,6 +559,7 @@ "check:zoning-declared": "A project list's ZONING matches what its author DECLARED (data/zoning-declarations.json). Zoning is human judgement — geography first, balanced with language — so a gate cannot decide whether a zoning is GOOD; it can decide whether it still matches what was stated, which is what makes UPG-100 reusable as a PATTERN for any project list (7000/4700/3000/2000/700) zoned by an agent who did not write this code. ★ EVERY OBVIOUS RULE FAILED THE REFERENCE IMPLEMENTATION when measured on the live 28 zones / 2107 records: 'every group in exactly one zone' is wrong (112 double-claims, 110 of them the design-approved deaf bloc double-count, legend-tree.js:151) and 'no group unzoned' is wrong (11 JPN/CHN/MNG groups, plausibly deliberate). So it asserts A1 no zone resolves to zero groups, A2 at most one GEOGRAPHIC zone per group with bloc zones declared as a second partition, A3 the orphan set EQUALS the declared set (a RESOLVED orphan fails too — a stale declaration stops being evidence), A4 every selector type is one selector-evaluator supports, A5 no zone over the declared page budget. A1 and A4 would both have caught this cycle's real defects (deaf + 5 wagf zones resolving to nothing) before a browser did, and A2 caught a third overlap my own hand analysis had missed. Exit 75 without records or declarations. Run :self-test to prove the five still fire.", "deploy:dev": "destination + allowlist come from vite.doxa-maps-build-config.json (dropin.dest / dropin.include); DOXA_DROPIN_DEST= overrides the destination", "desktop:build": "bun run desktop:build : packages ONE plugin whose meta targets include desktop as a desktop app (Tauri): builds it into .desktop//, builds its sidecar engine for this machine, and runs tauri build with an override pointing the window at it. See documentation/HOWTO-build-the-desktop-app.md.", + "dropin": "copy the built tree (dx-maps/) into your site's public/ (vite.doxa-maps-build-config.json dropin.dest, default ../doxa-life/public), so ONE server serves the site and every map. Only the drop-in is touched: the site's own files stay, and stale files inside dx-maps/ are removed. It never prunes: --prune into a folder named public/ is refused, because it would delete the site.", "gallery": "writes public/__gallery/index.html — the ARCHIVED bundle-index gallery, from public/doxa-maps/manifest.json. It is NO LONGER the root page: as of 2026-09-22 `/` mounts in BOTH trees (the built root page is written by `bun run tree`), and the gallery is the opt-in index at /__gallery. Archived, not deleted — the dev server imports renderGallery()/readBuildConfig() from this module.", "gen:iso3-to-alpha2": "derive library/data/iso3-to-alpha2.json from vendored CLDR (48.2.2, Unicode-3.0) joined against the 242 codes in library/data/countries.json, so Intl.DisplayNames can produce a LOCALIZED country name — it takes alpha-2 and our data speaks alpha-3. Offline, no dependency. GRADES ITS OWN OUTPUT and writes nothing on failure: 242 coverage, injectivity, and KOS -> XK as the one documented exception (Kosovo has no ISO assignment). Cross-checks the 176-country research spine map and REPORTS disagreements without rewriting it — that file drives fetches and is not this generator's to edit.", "internet:adm1": "regenerate data/tables/internet-by-adm1.json (internet use by first-level area; India from NFHS-5 2019-21, CC BY 4.0) from data/internet/ through a strict name crosswalk to the shipped gb-IND-ADM1 boundaries. Fails on an unmatched name, never guesses. Generic rows (iso3, adm1, measure, sex, area, value, source, year) so other plugins and go-int can reuse it.", diff --git a/vite.doxa-maps-build-config.json b/vite.doxa-maps-build-config.json index 8e094aee5..bf2a9530e 100644 --- a/vite.doxa-maps-build-config.json +++ b/vite.doxa-maps-build-config.json @@ -7,7 +7,7 @@ "tokenUrl": "", "token": "", "dropin": { - "dest": "../doxa-life/public/embed/doxa-maps-build", + "dest": "../doxa-life/public", "include": [ "README.md" ],