From 7b36255dc254dbb54b7061c2350ff568930ca44e Mon Sep 17 00:00:00 2001 From: swift-river542 Date: Wed, 16 Sep 2026 12:43:56 -0500 Subject: [PATCH] sync:dropin never mirrors documentation into a served folder; protects nothing by default --- docs/reference.md | 2 +- internal/sync-dropin.mjs | 26 ++++++++++++++++++-------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/docs/reference.md b/docs/reference.md index ad1361c7e..091d6847e 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -153,7 +153,7 @@ each bundle registers a **distinct** tag. | `bun run dev` | Vite dev server + HMR (default `http://localhost:5173`, or the next free port — read the terminal). `/` is the **auto-generated SPA** (playground) of every element, built from `internal/playground/{discover,render,dev-plugin}.mjs`; every bundle is also served at its own html path. Dev-only routes: `/__playground/registry.json` (the registry), `/assets/*` (served from `public/assets/`, else `app-profiles/*/data/`). **Writes nothing to `public/`** — no HMR across that boundary. | | `bun run smoke:playground` | Opens every element of the auto-generated SPA by deep link in Firefox and proves it boots: element defined, shadow DOM populated, no error card, a WebGL canvas for map profiles (checked through nested shadow roots), no HTML-for-JSON data responses. `--json ` writes the evidence; exit 1 on any failure. The pre-handoff gate. | | `bun run smoke:share` | The share round-trip gate (Firefox): opens a host page (`--playground` = every canvas element of the dev SPA; `--url --tag ` = the website or any CDN page), clicks Share, reads the URL, fetches it, reopens it in a fresh page and asserts the SAME custom element mounts with a WebGL canvas. `--json `; exit 1 on any failure — proves REQ-1 ("links work wherever the tree is hosted"). | -| `bun run sync:dropin ` | Mirrors the built web-root `public/` into a host checkout (e.g. the website's `public/js/doxa-maps-build/`): adds, updates and deletes to match; **never copies credentials** (`api/maps/token.json`, `token.local.js`); protects host-owned files (`README.md`); `--dry-run` first. The only sanctioned way a map changes on the site: change the bundler → `bun run build` → sync → commit in the host. | +| `bun run sync:dropin ` | Mirrors the built web-root `public/` into a host checkout (e.g. the website's `public/js/doxa-maps-build/`): adds, updates and deletes to match; **never copies credentials** (`api/maps/token.json`, `token.local.js`) **nor documentation** (`*.md`, `*.txt` — the README and docs stay private with the bundler; a stray doc in the destination is removed); `--protect a,b` keeps host-owned files; `--dry-run` first. The only sanctioned way a map changes on the site: change the bundler → `bun run build` → sync → commit in the host. | | `bun run build` | `clean` → one `vite build` per `app-profiles/*/` folder → `tree` → `check:drop` → `check:parse`. | | `bun run build:bundle` | Builds ONE bundle (needs `BUNDLE=`). | | `bun run build:watch` | Rebuilds into `public/` on every change (integration loop). | diff --git a/internal/sync-dropin.mjs b/internal/sync-dropin.mjs index 5633e3239..ef43f0d52 100644 --- a/internal/sync-dropin.mjs +++ b/internal/sync-dropin.mjs @@ -11,11 +11,14 @@ * files that no longer exist in the source. EXCEPT: * · credentials are NEVER copied — api/maps/token.json and token.local.js (the site serves * its own /api/maps/token; the leaf pages' token ladder falls through to it); - * · host-owned files are PROTECTED (default: README.md at the drop-in root) — never - * overwritten, never deleted. + * · documentation is NEVER copied — *.md / *.markdown / *.txt: the served folder is product, + * the README and docs stay private with the bundler (a stray doc already in is + * removed like any other stale file); + * · host-owned files can be PROTECTED with --protect (none by default) — never overwritten, + * never deleted. * Refuses to CREATE : the drop-in folder is the host's decision, not this script's. * - * Usage: node internal/sync-dropin.mjs [--from public] [--protect README.md,other] + * Usage: node internal/sync-dropin.mjs [--from public] [--protect a.txt,other] * [--dry-run] * e.g. node internal/sync-dropin.mjs /home/qwerty/Workspace/doxa-life/public/js/doxa-maps-build * Exit: 0 synced (or dry-run printed) · 1 bad arguments / dest missing @@ -32,9 +35,12 @@ const argOf = (k, d) => { const i = args.indexOf(k); return i >= 0 ? args[i + 1] const DEST = args.find(a => !a.startsWith('--') && a !== argOf('--from') && a !== argOf('--protect')) const FROM = resolve(ROOT, argOf('--from', 'public')) const DRY = args.includes('--dry-run') -const PROTECT = new Set(String(argOf('--protect', 'README.md')).split(',').map(s => s.trim()).filter(Boolean)) +const PROTECT = new Set(String(argOf('--protect', '')).split(',').map(s => s.trim()).filter(Boolean)) // Never ship a credential into a host repo — these are gitignored here for the same reason. const EXCLUDE = new Set(['api/maps/token.json', 'token.local.js']) +// Never ship documentation into a served folder: the README/story stays private with the bundler. +const isDoc = (f) => /\.(md|markdown|txt)$/i.test(f) +const excluded = (f) => EXCLUDE.has(f) || isDoc(f) if (!DEST) { console.error('[sync-dropin] usage: node internal/sync-dropin.mjs [--from public] [--protect a,b] [--dry-run]'); process.exit(1) } const dest = resolve(DEST) @@ -51,9 +57,11 @@ function walk(dir, base = dir, out = []) { } const sha = p => createHash('sha1').update(readFileSync(p)).digest('hex') -const src = walk(FROM).filter(f => !EXCLUDE.has(f)) +const allSrc = walk(FROM) +const src = allSrc.filter(f => !excluded(f)) const have = new Set(walk(dest)) -let copied = 0, unchanged = 0, deleted = 0, protectedSkips = 0, excluded = walk(FROM).length - src.length +let copied = 0, unchanged = 0, deleted = 0, protectedSkips = 0 +const excludedCount = allSrc.length - src.length for (const f of src) { if (PROTECT.has(f)) { protectedSkips++; continue } const s = join(FROM, f), d = join(dest, f) @@ -63,7 +71,9 @@ for (const f of src) { } const srcSet = new Set(src) for (const f of have) { - if (srcSet.has(f) || PROTECT.has(f) || EXCLUDE.has(f)) continue + // stale = not in the source, not protected. Credentials and docs found in are stale too: + // they must not sit in a served folder (a stray README, a token file). + if (srcSet.has(f) || PROTECT.has(f)) continue if (!DRY) rmSync(join(dest, f)) deleted++ } @@ -73,4 +83,4 @@ if (!DRY) { for (const d of dirs) if (readdirSync(d).length === 0) rmSync(d, { recursive: true }) } console.log(`[sync-dropin] ${DRY ? 'DRY RUN — ' : ''}${norm(relative(ROOT, FROM))}/ → ${dest}`) -console.log(`[sync-dropin] copied ${copied} · unchanged ${unchanged} · deleted ${deleted} · protected ${protectedSkips} (${[...PROTECT].join(', ')}) · credentials excluded ${excluded}`) +console.log(`[sync-dropin] copied ${copied} · unchanged ${unchanged} · deleted ${deleted} · protected ${protectedSkips}${PROTECT.size ? ` (${[...PROTECT].join(', ')})` : ''} · credentials/docs excluded ${excludedCount}`)