var __defProp = Object.defineProperty; var __getOwnPropNames = Object.getOwnPropertyNames; var __esm = (fn, res) => function __init() { return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; }; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; // internal/i18n-areas.mjs var i18n_areas_exports = {}; __export(i18n_areas_exports, { allAreas: () => allAreas, areasForBundle: () => areasForBundle, bundleGraph: () => bundleGraph }); import { readFileSync as readFileSync6, existsSync as existsSync5, readdirSync as readdirSync4, statSync as statSync3 } from "node:fs"; import { resolve as resolve7, dirname as dirname5, join as join3, relative } from "node:path"; function allAreas(root) { return readdirSync4(join3(root, "library/i18n/locales/en")).filter((f) => f.endsWith(".json")).map((f) => f.replace(/\.json$/, "")).sort(); } function resolveFile(p) { for (const cand of [p, p + ".js", p + ".mjs", p + ".vue", p + ".json", join3(p, "index.js")]) { try { if (existsSync5(cand) && statSync3(cand).isFile()) return cand; } catch { } } return null; } function expandGlob(pattern, fromDir) { const abs = resolve7(fromDir, pattern); const parts = abs.split("/"); const out = []; const walk = (i, dir) => { if (i === parts.length) return; const seg = parts[i]; if (i === 0) return walk(1, "/"); if (!seg.includes("*")) { const next = join3(dir, seg); if (i === parts.length - 1) { if (existsSync5(next)) out.push(next); } else walk(i + 1, next); return; } const re = new RegExp("^" + seg.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*") + "$"); let entries = []; try { entries = readdirSync4(dir); } catch { return; } for (const e of entries) if (re.test(e)) { if (i === parts.length - 1) out.push(join3(dir, e)); else walk(i + 1, join3(dir, e)); } }; walk(0, "/"); return out; } function bundleGraph(root, bundle) { const entry = join3(root, "plugin-designs", bundle, "index.js"); const seen = /* @__PURE__ */ new Set(); const queue = [entry]; const LOCALES = join3(root, "library/i18n/locales"); while (queue.length) { const f = queue.pop(); if (!f || seen.has(f) || f.startsWith(LOCALES) || f.includes("/node_modules/")) continue; seen.add(f); let src2 = ""; try { src2 = readFileSync6(f, "utf8"); } catch { continue; } const dir = dirname5(f); const add = (spec) => { if (!spec) return; let p = null; if (spec.startsWith("@map/")) p = join3(root, "library", spec.slice(5)); else if (spec.startsWith(".") || spec.startsWith("/")) p = spec.startsWith("/") ? join3(root, spec) : resolve7(dir, spec); else return; p = p.replace(/\?.*$/, ""); const r = resolveFile(p); if (r) queue.push(r); }; for (const m of src2.matchAll(IMPORT_RE)) add(m[1] || m[2] || m[3]); for (const m of src2.matchAll(GLOB_RE)) { const pats = m[1].startsWith("[") ? [...m[1].matchAll(/['"]([^'"]+)['"]/g)].map((x) => x[1]) : [m[1].slice(1, -1)]; for (const pat of pats) { if (pat.startsWith("!")) continue; const base = pat.startsWith("@map/") ? join3(root, "library") : dir; for (const hit of expandGlob(pat.replace(/^@map\//, ""), base)) queue.push(hit); } } } return [...seen]; } function areasForBundle(root, bundle, { extra = [] } = {}) { const areas = allAreas(root); const files = bundleGraph(root, bundle); const text = files.map((f) => { try { return readFileSync6(f, "utf8"); } catch { return ""; } }).join("\n"); const used = new Set(extra); const assemblesUnknownKeys = /\b(?:t|te|tm|tStatic|T)\(\s*`\$\{/.test(text); for (const a of areas) { const esc = a.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); if (new RegExp(`['"\`]${esc}\\.`).test(text)) { used.add(a); continue; } if (new RegExp(`\\b(?:useOptionalT|useOptionalI18n|qualify|tm|t|te|tStatic|rt)\\(\\s*['"\`]${esc}['"\`]`).test(text)) { used.add(a); continue; } if (assemblesUnknownKeys && new RegExp(`['"\`]${esc}['"\`]`).test(text)) used.add(a); } return { areas: [...used].filter((a) => areas.includes(a)).sort(), files: files.map((f) => relative(root, f)) }; } var IMPORT_RE, GLOB_RE; var init_i18n_areas = __esm({ "internal/i18n-areas.mjs"() { IMPORT_RE = /(?:import|export)\s[^'"`;]*?from\s*['"]([^'"]+)['"]|import\s*['"]([^'"]+)['"]|import\s*\(\s*['"]([^'"]+)['"]\s*\)/g; GLOB_RE = /import\.meta\.glob\s*\(\s*(\[[^\]]*\]|['"][^'"]+['"])/g; } }); // vite.config.js import { defineConfig, loadEnv } from "file:///home/lk10x/Workspace/eco-dx/root-doxa-maps/doxa-maps-plugin-studio/node_modules/vite/dist/node/index.js"; import vue from "file:///home/lk10x/Workspace/eco-dx/root-doxa-maps/doxa-maps-plugin-studio/node_modules/@vitejs/plugin-vue/dist/index.mjs"; import cssInjectedByJsPlugin from "file:///home/lk10x/Workspace/eco-dx/root-doxa-maps/doxa-maps-plugin-studio/node_modules/vite-plugin-css-injected-by-js/dist/esm/index.js"; // internal/plugin-gallery/dev-plugin.mjs import { resolve as resolve4, extname } from "node:path"; import { spawnSync } from "node:child_process"; import { readdirSync as readdirSync3, statSync as statSync2, existsSync as existsSync3, readFileSync as readFileSync5 } from "node:fs"; // internal/plugin-gallery/discover.mjs import { resolve, dirname } from "node:path"; import { fileURLToPath } from "node:url"; import { readdirSync, statSync, existsSync, readFileSync } from "node:fs"; var __vite_injected_original_import_meta_url = "file:///home/lk10x/Workspace/eco-dx/root-doxa-maps/wt-hf/internal/plugin-gallery/discover.mjs"; var REPO_ROOT = resolve(dirname(fileURLToPath(__vite_injected_original_import_meta_url)), "..", ".."); var PROFILE_TIER = { "nested-mfe-spa": 0, "nested-elements-mfe": 1, "monolithic": 2, "parameterized": 3, "single": 4 }; var TYPE_DISPLAY = { "single": "single-element", "parameterized": "preset-element", "monolithic": "multi-view-element", "nested-elements-mfe": "composite-element", "nested-mfe-spa": "app-shell" }; var TYPE_ALIASES = Object.fromEntries(Object.entries(TYPE_DISPLAY).map(([k, v]) => [v, k])); var normalizeType = (t) => t && PROFILE_TIER[t] != null ? t : TYPE_ALIASES[t] || null; function readJsonSafe(p) { try { return JSON.parse(readFileSync(p, "utf8")); } catch { return null; } } function detectProfileType(dir, profile, ownTag, allTags, hasInstances) { const meta = readJsonSafe(resolve(dir, `${profile}.meta.json`)); const declared = meta && normalizeType(meta.type); if (declared) return { type: declared, label: meta.label || profile }; const type = hasInstances ? "parameterized" : "single"; try { const childVue = countChildVue(dir); if (childVue >= 5) { console.warn( `[discover] ${profile}: type INFERRED as "${type}" but the bundle has ${childVue} child .vue components. Tier 3+ is never inferred \u2014 if this nests multiple views internally it is "monolithic"; if its children are independently embeddable it is "nested-elements-mfe". Declare it in ${profile}.meta.json {"type": ...} or this ships understated.` ); } } catch { } return { type, label: meta && meta.label || profile }; } function countChildVue(dir) { let n = 0; const walk = (d, depth) => { for (const e of readdirSync(d, { withFileTypes: true })) { if (e.name.startsWith(".") || e.name === "node_modules") continue; const p = resolve(d, e.name); if (e.isDirectory()) walk(p, depth + 1); else if (depth > 0 && e.name.endsWith(".vue")) n++; } }; walk(dir, 0); return n; } function discoverSpaRegistry(root = REPO_ROOT) { const APP_DIR = resolve(root, "plugin-designs"); const bundles = []; if (!existsSync(APP_DIR)) return bundles; const dirs = readdirSync(APP_DIR).sort().filter((n) => { const d = resolve(APP_DIR, n); return statSync(d).isDirectory() && n !== "template-bundle" && existsSync(resolve(d, "index.js")); }); const tagOf = {}; for (const n of dirs) { try { const m = readFileSync(resolve(APP_DIR, n, "index.js"), "utf8").match(/customElements\.define\(\s*['"]([^'"]+)['"]/); if (m) tagOf[n] = m[1]; } catch { } } const allTags = Object.values(tagOf); for (const n of dirs) { const dir = resolve(APP_DIR, n), tag = tagOf[n]; if (!tag) continue; const files = readdirSync(dir); const instFile = files.find((f) => /\.instances\.json$/i.test(f)); const rawInst = instFile ? readJsonSafe(resolve(dir, instFile)) : null; const instances = Array.isArray(rawInst) ? rawInst : rawInst && Array.isArray(rawInst.instances) ? rawInst.instances : null; const hasInstances = !!(instances && instances.length); const instApi = rawInst && !Array.isArray(rawInst) && typeof rawInst.apiBaseUrl === "string" ? rawInst.apiBaseUrl : ""; const items = []; for (const profile of files.filter((f) => f.endsWith(".vue")).map((f) => f.replace(/\.vue$/, "")).sort()) { const t = detectProfileType(dir, profile, tag, allTags, hasInstances); if (t.type === "parameterized" && hasInstances) { for (const inst of instances) { items.push({ profile, id: inst.id || inst.title || profile, label: inst.title || inst.id || profile, type: "parameterized", props: inst.props || {}, apiBaseUrl: instApi }); } } else { items.push({ profile, id: profile, label: t.label, type: t.type, props: null }); } } if (!items.length) continue; items.sort((a, b) => (PROFILE_TIER[a.type] ?? 9) - (PROFILE_TIER[b.type] ?? 9)); const bestTier = Math.min(...items.map((i) => PROFILE_TIER[i.type] ?? 9)); bundles.push({ bundle: n, tag, items, bestTier }); } bundles.sort((a, b) => a.bestTier - b.bestTier || a.bundle.localeCompare(b.bundle)); return bundles; } // internal/generate-gallery.mjs import { readFileSync as readFileSync3, writeFileSync, existsSync as existsSync2, readdirSync as readdirSync2, mkdirSync } from "node:fs"; import { join, resolve as resolve3, dirname as dirname3 } from "node:path"; import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url"; // internal/_tree-name.mjs import { readFileSync as readFileSync2 } from "node:fs"; import { resolve as resolve2, dirname as dirname2 } from "node:path"; import { fileURLToPath as fileURLToPath2 } from "node:url"; var __vite_injected_original_import_meta_url2 = "file:///home/lk10x/Workspace/eco-dx/root-doxa-maps/wt-hf/internal/_tree-name.mjs"; var ROOT = resolve2(dirname2(fileURLToPath2(__vite_injected_original_import_meta_url2)), ".."); var BUILD_CONFIG_FILE = resolve2(ROOT, "vite.doxa-maps-build-config.json"); var FALLBACK = "doxa-maps"; function read() { let name = ""; try { const cfg = JSON.parse(readFileSync2(BUILD_CONFIG_FILE, "utf8")); if (cfg && typeof cfg.name === "string") name = cfg.name.trim(); } catch { } if (!name) return FALLBACK; if (!/^[a-z0-9][a-z0-9._-]*$/i.test(name) || name.includes("..")) { throw new Error(`[_tree-name] build config "name" must be a single URL-safe path segment, got ${JSON.stringify(name)}`); } return name; } var TREE_NAME = read(); function readGated() { try { const cfg = JSON.parse(readFileSync2(BUILD_CONFIG_FILE, "utf8")); return Array.isArray(cfg.gatedProfiles) ? cfg.gatedProfiles.filter((x) => typeof x === "string" && x.trim()).map((x) => x.trim()) : []; } catch { return []; } } var GATED_PROFILES = readGated(); var TREE_NAME_RE = TREE_NAME.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); if (process.argv[1] && resolve2(process.argv[1]) === fileURLToPath2(__vite_injected_original_import_meta_url2)) process.stdout.write(TREE_NAME + "\n"); // internal/generate-gallery.mjs var __vite_injected_original_import_meta_url3 = "file:///home/lk10x/Workspace/eco-dx/root-doxa-maps/wt-hf/internal/generate-gallery.mjs"; var ROOT2 = resolve3(dirname3(fileURLToPath3(__vite_injected_original_import_meta_url3)), ".."); function readSourceTiers(appDir = join(ROOT2, "plugin-designs")) { const out = {}; if (!existsSync2(appDir)) return out; let dirs = []; try { dirs = readdirSync2(appDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name); } catch { return out; } for (const bundle of dirs) { const dir = join(appDir, bundle); let files = []; try { files = readdirSync2(dir); } catch { continue; } const hasInstances = files.some((f) => /\.instances\.json$/i.test(f)); for (const f of files) { if (!f.endsWith(".vue")) continue; const profile = f.replace(/\.vue$/, ""); try { const t = detectProfileType(dir, profile, "", [], hasInstances); out[bundle + "/" + profile] = t.type; } catch { } } } return out; } function readBuildConfig() { let cfg = {}; try { cfg = JSON.parse(readFileSync3(join(ROOT2, "vite.doxa-maps-build-config.json"), "utf8")); } catch { } const str = (k) => typeof cfg[k] === "string" ? cfg[k].trim() : ""; let tokenUrl = str("tokenUrl"); if (!tokenUrl) { try { const src2 = JSON.parse(readFileSync3(join(ROOT2, "library/api/sources.json"), "utf8")); const v = src2?._meta?.mapboxTokenUrl || src2?.mapboxTokenUrl; if (typeof v === "string" && v.trim()) tokenUrl = v.trim(); } catch { } } return { name: str("name") || TREE_NAME, title: str("displayTitle") || TREE_NAME, tokenUrl: tokenUrl || "window.MAP_TOKEN_URL", token: str("token"), apiBaseUrl: str("apiBaseUrl").replace(/\/+$/, ""), assetsBaseUrl: str("assetsBaseUrl").replace(/\/+$/, ""), qaWidget: cfg.qaWidget !== false }; } function snip(b, p, x) { var u = (x || p).u, tag = x && x.e || p.e || b.e[0] || b.b; var r = { iframe: '' }; if (!x) r.script = '\n<" + tag + " profile-config='" + JSON.stringify({ profile: p.p, tk: "YOUR_MAPBOX_TOKEN", utmSource: "YOUR_SOURCE" }) + "'>"; return r; } function compact(m, lean, tierMap) { tierMap = tierMap || {}; var PT = { "nested-mfe-spa": 0, "nested-elements-mfe": 1, "monolithic": 2, "parameterized": 3, "single": 4 }; var capFor = function(t) { return PT[t] != null ? 5 - PT[t] : 1; }; return (m.bundles || []).map(function(src2) { var b = { b: src2.bundle, s: src2.scriptUrl || "", i: src2.integrity || "", e: src2.elements || [], p: [] }; b.p = (src2.profiles || []).map(function(sp) { var type = sp.type || tierMap[src2.bundle + "/" + sp.profile] || "single"; var c = capFor(type); var p = { p: sp.profile, e: sp.element || "", u: sp.iframeUrl || "", d: sp.description || "", c }; var em = sp.embed || {}, d = snip(b, p, null); if (!lean || em.iframe !== d.iframe || em.script !== d.script) p.em = em; var ins = (sp.instances || []).map(function(sx) { var x = { id: sx.id, t: sx.title || sx.id, a: sx.apiBaseUrl || "", pr: sx.props || {}, e: sx.element || "", u: sx.iframeUrl || "" }; var xe = sx.embed || {}, xd = snip(b, p, x); if (!lean || xe.iframe !== xd.iframe) x.em = xe; return x; }); if (ins.length) p.in = ins; return p; }); return b; }); } var src = (fn) => fn.toString().replace(/<\/script/gi, "<\\/script"); var LADDER_CSS = String.raw`:root{--acc:#1a73e8}@media(prefers-color-scheme:dark){:root{--acc:#64b5f6}} .ladder{position:relative;margin-left:auto;display:flex;align-items:center;gap:6px;white-space:nowrap} .rung{display:inline-flex;align-items:center;gap:4px}.rname{font:500 12px/1 ui-monospace,Menlo,monospace} .arrow{color:var(--mut);font-size:12px;padding:0 1px} .q{width:18px;height:18px;padding:0;border-radius:50%;font:700 11px/1 system-ui,sans-serif;color:var(--mut);background:var(--bg);display:inline-grid;place-items:center} .start{font:600 10.5px/1 system-ui,sans-serif;letter-spacing:.04em;text-transform:uppercase;color:var(--acc);background:var(--bg);border-radius:999px;padding:4px 9px;margin-right:4px} .q:hover,.q[aria-expanded=true],.start:hover,.start[aria-expanded=true]{color:var(--acc);border-color:var(--acc);background:var(--bg)} .explain{position:absolute;top:calc(100% + 8px);right:0;width:420px;max-width:calc(100vw - 32px);max-height:70vh;overflow:auto;z-index:40;background:var(--bg);border:1px solid var(--bd);border-radius:10px;box-shadow:0 4px 16px rgba(0,0,0,.14);padding:12px 14px;font-size:13px;line-height:1.45;text-align:left;white-space:normal} .explain h3{margin:0 0 5px;font-size:14px;display:flex;align-items:center;gap:8px;flex-wrap:wrap}.explain h4{margin:8px 0 3px;font-size:13px} .explain .tier{font:700 10px/1 system-ui,sans-serif;letter-spacing:.6px;text-transform:uppercase;color:var(--mut)} .explain p{margin:0 0 6px}.explain .aka{font-size:11.5px;color:var(--mut);margin:-2px 0 6px} .explain .how{margin:0 0 6px;font-size:12.5px}.explain .how summary{cursor:pointer;color:var(--acc);font-weight:600;font-size:12px}.explain .how p{margin:6px 0 0;color:var(--mut)} .explain .ex{color:var(--mut);font-size:12px} @media(max-width:1280px){.ladder{display:none}} `; var LADDER_HTML = ''; var LADDER_JS = String.raw` `; function renderGallery(model, opts = {}) { const { mode = "build", title = TREE_NAME, tree = TREE_NAME, tokenUrl = "window.MAP_TOKEN_URL", token = "", apiBaseUrl = "", assetsBaseUrl = "", qaWidget = true, tierMap = {} } = opts; const dev = mode === "dev"; const baked = compact(model, true, tierMap); const json = (v) => JSON.stringify(v).replace(/ String(s).replace(/&/g, "&").replace(/ ${esc(title)}
${esc(title)}{{ countLabel }}${dev ? LADDER_HTML : ""}
{{ ttl }}{{ sub }}
Open page
Choose a map from the list.
${dev ? LADDER_JS : ""} `; } function main() { const CFG = readBuildConfig(); const manifestPath = join(ROOT2, "public", CFG.name, "manifest.json"); if (!existsSync2(manifestPath)) { console.error(`[generate-gallery] missing ${manifestPath} \u2014 run \`bun run tree\` first`); process.exit(1); } const manifest = JSON.parse(readFileSync3(manifestPath, "utf8")); const tierMap = readSourceTiers(); const html = renderGallery(manifest, { mode: "build", title: CFG.title, tree: "../" + CFG.name, tokenUrl: CFG.tokenUrl, token: CFG.token, apiBaseUrl: CFG.apiBaseUrl, assetsBaseUrl: CFG.assetsBaseUrl, qaWidget: CFG.qaWidget, tierMap }); const baked = compact(manifest, true, tierMap); const nProfiles = baked.reduce((n, b) => n + b.p.length, 0); const nInstances = baked.reduce((n, b) => n + b.p.reduce((k, p) => k + (p.in ? p.in.length : 0), 0), 0); const nOverrides = baked.reduce((n, b) => n + b.p.reduce((k, p) => k + (p.em ? 1 : 0) + (p.in || []).filter((x) => x.em).length, 0), 0); const oi = process.argv.indexOf("--out"); const outDir = oi >= 0 && process.argv[oi + 1] ? resolve3(process.argv[oi + 1]) : null; if (!outDir) { console.log("[generate-gallery] ARCHIVED \u2014 not written. The gallery is no longer part of the build or of public/;"); console.log(" the Plugin Studio (/index.html) is the front page. To render it anyway: bun run gallery -- --out "); return; } if (outDir === resolve3(ROOT2, "public") || outDir.startsWith(resolve3(ROOT2, "public") + "/")) { console.error("[generate-gallery] refusing to write into public/ \u2014 it holds only the output folder."); process.exit(1); } const dir = outDir; mkdirSync(dir, { recursive: true }); writeFileSync(join(dir, "index.html"), html); console.log(`[generate-gallery] ${dir}/index.html (ARCHIVED rendering) \u2014 ${(html.length / 1024).toFixed(1)} KB \xB7 ${baked.length} bundle(s) \xB7 ${nProfiles} profile(s) \xB7 ${nInstances} instance(s) \xB7 ${nOverrides} embed snippet(s) baked verbatim (the rest re-derive)`); } if (process.argv[1] && __vite_injected_original_import_meta_url3 === pathToFileURL(process.argv[1]).href) main(); // internal/profile-meta.mjs import { readFileSync as readFileSync4 } from "node:fs"; import { join as join2 } from "node:path"; var CORNERS = ["bottom-right", "bottom-left", "top-right", "top-left"]; var VISIBILITY = ["team", "public"]; var MOBILE = ["desktop-only", "larger-screens"]; function readProfileMeta(dir, profileName) { try { const meta = JSON.parse(readFileSync4(join2(dir, `${profileName}.meta.json`), "utf8")); const corner = meta?.widget?.corner; const name = [meta?.label, meta?.title, meta?.displayName].find((v) => typeof v === "string" && v.trim()); return { stage: meta?.stage === "production" ? "production" : "experimental", heavy: meta?.heavy === true, widget: CORNERS.includes(corner) ? { corner } : null, label: name ? name.trim() : "", // Where the plugin runs ("web", "desktop"…); absent = web. A desktop-only plugin is never a gallery tab. targets: Array.isArray(meta?.targets) ? meta.targets.filter((t) => typeof t === "string") : null, visibility: VISIBILITY.includes(meta?.visibility) ? meta.visibility : "team", // The declared-but-unrecognised value, kept so the generator can name it in the failure. // null when the key is absent (the supported way to say "team") or already valid. visibilityInvalid: meta?.visibility === void 0 || VISIBILITY.includes(meta?.visibility) ? null : String(meta.visibility), mobile: MOBILE.includes(meta?.mobile) ? meta.mobile : null }; } catch { return { stage: "experimental", heavy: false, widget: null, label: "", targets: null, visibility: "team", visibilityInvalid: null, mobile: null }; } } function profileMetaFields(dir, profileName) { const m = readProfileMeta(dir, profileName); return { stage: m.stage, ...m.visibility === "public" ? { visibility: "public" } : {}, ...m.label ? { label: m.label } : {}, ...m.targets ? { targets: m.targets } : {}, ...m.heavy ? { heavy: true } : {}, ...m.widget ? { widget: m.widget } : {}, ...m.mobile ? { mobile: m.mobile } : {} }; } var bundleStage = (profiles) => profiles.some((p) => p.stage === "production") ? "production" : "experimental"; var bundleVisibility = (profiles) => profiles.some((p) => p.visibility === "public") ? "public" : "team"; // internal/plugin-gallery/dev-plugin.mjs var DEV_ASSET_MIME = { ".json": "application/json; charset=utf-8", ".geojson": "application/geo+json; charset=utf-8", ".js": "text/javascript; charset=utf-8", ".css": "text/css; charset=utf-8", ".csv": "text/csv; charset=utf-8", ".txt": "text/plain; charset=utf-8", ".html": "text/html; charset=utf-8", ".svg": "image/svg+xml", ".png": "image/png", ".webp": "image/webp", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".woff2": "font/woff2", ".woff": "font/woff", ".pdf": "application/pdf" }; function resolveDevAsset(rel, root = REPO_ROOT) { const isFile = (p) => existsSync3(p) && statSync2(p).isFile(); const source = resolve4(root, "data", rel); if (isFile(source)) return source; const shipped = resolve4(root, "public", TREE_NAME, "assets", rel); if (isFile(shipped)) return shipped; const profiles = resolve4(root, "plugin-designs"); for (const name of readdirSync3(profiles)) { if (name.startsWith("_") || name.startsWith(".")) continue; const cand = resolve4(profiles, name, "data", rel); if (isFile(cand)) return cand; } return null; } function readComposes(bundle, profile) { try { const meta = JSON.parse(readFileSync5(resolve4(REPO_ROOT, "plugin-designs", bundle, `${profile}.meta.json`), "utf8")); return Array.isArray(meta?.composes) ? meta.composes.map(String).filter(Boolean) : []; } catch { return []; } } function galleryModel(reg) { const iframe = (u) => ``; const link = (...parts) => "/__leaf/" + parts.map(encodeURIComponent).join("/"); let gated = /* @__PURE__ */ new Set(); try { const cfg = JSON.parse(readFileSync5(resolve4(REPO_ROOT, "vite.doxa-maps-build-config.json"), "utf8")); if (Array.isArray(cfg.gatedProfiles)) gated = new Set(cfg.gatedProfiles.map(String)); } catch { } const bundles = [...reg].filter((b) => !gated.has(b.bundle)).sort((a, b) => a.bundle.localeCompare(b.bundle)).map((b) => { const scriptUrl = `/plugin-designs/${b.bundle}/index.js`; const presets = /* @__PURE__ */ new Map(); const typeOf = /* @__PURE__ */ new Map(); for (const it of b.items) { if (!presets.has(it.profile)) presets.set(it.profile, []); if (!typeOf.has(it.profile)) typeOf.set(it.profile, it.type); if (it.type === "parameterized") presets.get(it.profile).push(it); } const profiles = [...presets.keys()].sort().map((profile) => { const p = { profile, file: `${profile}.vue`, description: "", element: b.tag, type: typeOf.get(profile) || "single", ...profileMetaFields(resolve4(REPO_ROOT, "plugin-designs", b.bundle), profile), // same as the built manifest iframeUrl: link(b.bundle, profile), scriptUrl, embed: { iframe: iframe(`./${b.bundle}/${profile}/index.html`), script: ` <${b.tag} profile-config='${JSON.stringify({ profile, tk: "YOUR_MAPBOX_TOKEN", utmSource: "YOUR_SOURCE" })}'>` } }; const instances = presets.get(profile).map((it) => ({ id: it.id, title: it.label, apiBaseUrl: it.apiBaseUrl || "", props: { profile, ...it.props || {} }, element: b.tag, iframeUrl: link(b.bundle, profile, it.id), embed: { iframe: iframe(`./${b.bundle}/${profile}/${it.id}/index.html`) } })); if (instances.length) p.instances = instances; return p; }); const composes = [...new Set([...presets.keys()].flatMap((profile) => readComposes(b.bundle, profile)))]; return { bundle: b.bundle, scriptUrl, integrity: "", elements: [b.tag], stage: bundleStage(profiles), ...bundleVisibility(profiles) === "public" ? { visibility: "public" } : {}, ...composes.length ? { composes } : {}, profiles }; }); return { schema: "doxa-maps-manifest@1", generatedFrom: "plugin-designs/*/*.vue (dev discovery)", embedBase: ".", bundleCount: bundles.length, profileCount: bundles.reduce((n, b) => n + b.profiles.length, 0), bundles }; } function leafRewrite(path, treeName = TREE_NAME) { if (!path.endsWith("/index.html")) return ""; let segs = path.slice(0, -"/index.html".length).split("/").filter(Boolean); if (segs[0] === treeName) segs = segs.slice(1); if (segs.length < 2 || segs.length > 3) return ""; for (const s of segs) { let d = s; try { d = decodeURIComponent(s); } catch { return ""; } if (!d || d === "." || d === ".." || d.includes("/") || d.includes("\\")) return ""; } return "/__leaf/" + segs.join("/"); } function autoSpaDevPlugin(token = "") { const page = () => { const cfg = readBuildConfig(); return renderGallery(galleryModel(discoverSpaRegistry()), { // ★ `/__plugin-gallery`, ABSOLUTE — the SAME move, and the same fix, as assetsBaseUrl below. // `tree` is the folder the live manifest is fetched from and that relative refs resolve // against. It was '__plugin-gallery' (relative), correct only while the gallery WAS the root // page. The gallery now lives at /__gallery, so the relative form resolves one level deeper. // MEASURED 2026-09-22 on the running dev server: // /__plugin-gallery/manifest.json ............. 200 // /__gallery/__plugin-gallery/manifest.json ... 404 // Naming it absolutely makes the value independent of the page depth the gallery sits at — // which is the whole reason assetsBaseUrl was made absolute in the same move. The built tree // passes '../' for the same reason; both now resolve to the real tree root. mode: "dev", title: cfg.title, tree: "/__plugin-gallery", tokenUrl: cfg.tokenUrl, token: token || cfg.token, // ★ ABSOLUTE, NOT RELATIVE — the gallery no longer lives at the root. // This was '' (relative), which was correct while the gallery WAS the root page: a map asked // for assets/tables/x.json and got /assets/tables/x.json. The gallery has since moved to // /__gallery so the studio can own /, and the same relative path now resolves one level deeper // to /__gallery/assets/... — which 404s. MEASURED 2026-09-22 on the running dev server: // /assets/tables/internet-by-country.json ............. 200 // /__gallery/assets/tables/internet-by-country.json ... 404 // Nothing failed at move time; the breakage only appears when a map FETCHES, and it surfaces as // "JSON.parse: unexpected end of data" rather than as a 404, because Vite answers an unknown // path with HTML. The dev server serves the asset tree at /assets/ (see resolveDevAsset), so // naming it absolutely makes the value independent of whatever page depth the gallery sits at. apiBaseUrl: cfg.apiBaseUrl, assetsBaseUrl: "/assets", qaWidget: cfg.qaWidget }); }; const json = (res, body, status = 200) => { res.statusCode = status; res.setHeader("Content-Type", "application/json; charset=utf-8"); res.setHeader("Cache-Control", "no-cache"); res.end(JSON.stringify(body)); }; return { name: "doxa-auto-spa", apply: "serve", configureServer(server) { server.middlewares.use((req, res, next) => { const rawPath = (req.url || "").split("?")[0]; let _decoded = rawPath; try { _decoded = decodeURIComponent(rawPath); } catch { } if (/(^|\/)\.(git|env)(\/|$)|(^|\/)\.(merge-queue|main-capture)(\/|$)/i.test(_decoded)) { res.statusCode = 404; return res.end(); } let treeName = TREE_NAME; try { treeName = readBuildConfig()?.name || treeName; } catch { } const rewritten = leafRewrite(rawPath, treeName); const path = rewritten || rawPath; if (rewritten) req.url = rewritten + (req.url || "").slice(rawPath.length); const iife = /^\/(?:([^/]+)\/)?([a-z0-9][a-z0-9-]*)\/([a-z0-9][a-z0-9-]*)\.js$/.exec(rawPath); if (iife && iife[2] === iife[3] && (!iife[1] || iife[1] === treeName)) { const file = resolve4(REPO_ROOT, "public", treeName, iife[2], iife[2] + ".js"); if (existsSync3(file)) { res.setHeader("Content-Type", "text/javascript; charset=utf-8"); res.setHeader("Cache-Control", "no-cache"); return res.end(readFileSync5(file)); } if (existsSync3(resolve4(REPO_ROOT, "plugin-designs", iife[2]))) { res.statusCode = 404; res.setHeader("Content-Type", "text/plain; charset=utf-8"); return res.end(`${iife[2]}.js is only produced by a build: run \`bun run build\` (or BUNDLE=${iife[2]} bun x vite build), then reload.`); } } const leafAsset = /^\/__leaf\/(?:[^/]+\/){0,3}assets\//.exec(path); const assetPrefix = path.startsWith("/assets/") ? "/assets/" : path.startsWith("/__plugin-gallery/assets/") ? "/__plugin-gallery/assets/" : path.startsWith(`/${treeName}/assets/`) ? `/${treeName}/assets/` : leafAsset ? leafAsset[0] : ""; if (assetPrefix) { let rel = ""; try { rel = decodeURIComponent(path.slice(assetPrefix.length)); } catch { rel = ""; } if (!rel || rel.split("/").includes("..")) { res.statusCode = 400; return res.end("bad asset path"); } const file = resolveDevAsset(rel); if (!file) return json(res, { error: "asset not found", asset: rel, looked: [`public/${TREE_NAME}/assets/`, "data/", "plugin-designs/*/data/"] }, 404); res.statusCode = 200; res.setHeader("Content-Type", DEV_ASSET_MIME[extname(file).toLowerCase()] || "application/octet-stream"); res.setHeader("Cache-Control", "no-cache"); return res.end(readFileSync5(file)); } if (path === "/__dev-sha") { const git2 = (...a) => spawnSync("git", a, { cwd: REPO_ROOT, encoding: "utf8" }).stdout.trim(); const sha = git2("rev-parse", "HEAD"); const dirty = git2("status", "--porcelain", "--untracked-files=no", "--", "library", "plugin-designs", "internal", "vite.config.js", "package.json") ? "+dirty" : ""; res.statusCode = sha ? 200 : 500; res.setHeader("Content-Type", "text/plain; charset=utf-8"); res.setHeader("Cache-Control", "no-store"); return res.end((sha || "unknown") + dirty + "\n"); } if (path === "/__plugin-gallery/registry.json" || path === "/__plugin-gallery/manifest.json") { let reg; try { reg = discoverSpaRegistry(); } catch (e) { return json(res, { error: String(e) }, 500); } return json(res, path.endsWith("/manifest.json") ? galleryModel(reg) : reg); } if (path.startsWith("/__leaf/")) { const parts = path.slice("/__leaf/".length).split("/").filter(Boolean).map((s) => { try { return decodeURIComponent(s); } catch { return ""; } }); const [bundle, profile, instance] = parts; if (!bundle || !profile || parts.some((s) => !s || s.includes("..") || s.includes("/"))) { res.statusCode = 400; return res.end("bad leaf path"); } let reg; try { reg = discoverSpaRegistry(); } catch (e) { return json(res, { error: String(e) }, 500); } const entry = reg.find((b) => b.bundle === bundle); if (!entry) { const named = reg.find((b) => b.bundle === profile); const hint = named ? `this plugin's own page is /__leaf/${named.bundle}/${named.items?.[0]?.profile || named.bundle}` : "a plugin's own page is /__leaf//; every plugin is listed at /__plugin-gallery/manifest.json"; return json(res, { error: "unknown bundle", bundle, hint }, 404); } const tag = entry.tag; let leafCfg = {}; try { leafCfg = readBuildConfig() || {}; } catch { leafCfg = {}; } const leafTok = token || leafCfg.token || ""; const apiBase = leafCfg.apiBaseUrl || ""; const pc = JSON.stringify(instance ? { profile, instanceId: instance, assetsBase: "/assets" } : { profile, assetsBase: "/assets" }); const scrollPage = (entry.items || []).some( (it) => it.profile === profile && it.type === "nested-elements-mfe" ); res.statusCode = 200; res.setHeader("Content-Type", "text/html; charset=utf-8"); res.setHeader("Cache-Control", "no-cache"); return res.end([ "", `${bundle} \xB7 ${profile}`, '', '', leafTok ? `` : "", // ★ MAP_APP_API_URL IS NOT OPTIONAL — without it every dashboard renders EMPTY in dev. // The BUILT leaves bake `window.MAP_APP_API_URL = "https://pray.doxa.life"`; these dev // pages baked only the Mapbox token, so the dashboards fell back to a RELATIVE // `/api/people-groups/list?...`. Vite answers an unknown path with SPA-fallback HTML at // HTTP 200, so the fetch "succeeds", JSON.parse fails or yields nothing, and the facet // rail renders ZERO rows with no console error and no error card. Measured 2026-09-22: // 8 such 404/fallback requests during one studio sweep. That reads as a broken component // and is really a missing global — the worst kind of dev-only divergence, and exactly the // four-surface class (see REFERENCE-the-four-surface-contract.md). apiBase ? `` : "", // ── FIXED-VIEWPORT LEAF vs SCROLL-PAGE LEAF ──────────────────────────────────────── // The BUILT tree makes exactly this distinction (internal/generate-tree.mjs, `scrollPage`) // and this dev leaf did NOT — it served the fixed-viewport style to EVERY bundle. That is // the whole of BUG-CANNOT-SCROLL-DOWN-TO-THE-GRID-UNDER-THE-DASHBOARD: the three-file fix // for BUG-DASHBOARD-CANT-SCROLL-DOWN landed in the built tree only, so the dev URL that // actually gets tested was never covered and still traps the composite. // // WHY `height:100%` ON BODY IS THE TRAP, and why scrollHeight LIES about it. A definite // height + `overflow:visible` lets content SPILL (so scrollHeight inflates — measured 3531 // against a 900 viewport) WITHOUT creating a scrollport, so the viewport has nothing to // scroll. The research-dashboard's own inner `overflow:auto` regions then swallow every // wheel event. MEASURED 2026-09-22 on this leaf: 12 wheel events of 600px moved the page // 0px while 2,631px of content sat below the fold. A geometry check calls that "scrollable"; // only a real wheel exposes it. // // A cap-4 composite (nested-elements-mfe) is a StackLayout scroll-stack, so the DOCUMENT // must own the scroll: `min-height` (a floor, not a cage) + explicit `overflow-y:auto`, // and the element takes its NATURAL height so the page grows and the grid below is // reachable. Every other tier keeps the fixed-viewport leaf — a map IS the page. scrollPage ? `` : ``, "", // tree-base ON THE LEAF TOO — cap-5 shells nested here fetch a manifest of their own. // doxa-maps-spa (like plugin-studio) discovers its tabs from the tree's manifest.json and // resolves it RELATIVE to its own page when no base is given. On a /__leaf/ URL that // resolved to /__leaf/manifest.json and 400'd. Harmless (both shells carry a baked // fallback list) but it silently degrades the tab list to the fallback instead of the // live 19-bundle tree. Passing the base makes a nested shell see exactly what the top // level sees. Elements that ignore tree-base simply drop an unknown attribute. `<${tag} tree-base="/__plugin-gallery" profile-config='${pc}'>`, ``, "" ].filter(Boolean).join("\n")); } if (path === "/" || path === "/index.html") { let devCfg = {}; try { devCfg = readBuildConfig() || {}; } catch { devCfg = {}; } const devTok = token || devCfg.token || ""; const devApiBase = devCfg.apiBaseUrl || ""; res.statusCode = 200; res.setHeader("Content-Type", "text/html; charset=utf-8"); res.setHeader("Cache-Control", "no-cache"); return res.end([ "", '', '', `${TREE_NAME} plugin studio`, '', // The token ladder's baked rung, exactly as the built leaves receive it, so maps paint // in dev without anyone appending ?tk=. devTok ? `` : "", // Same reason as the leaf route above: without MAP_APP_API_URL the dashboards nested in // the studio fetch a relative /api/... that Vite answers with HTML at 200, so they mount // and then show nothing. The value comes from the SAME build config the built leaves use, // so dev and built agree by construction rather than by a second hard-coded copy. devApiBase ? `` : "", // No chrome, no prose: the element fills the viewport. "Without a lot of text garb // that takes up so much space" is a layout requirement, so the host page contributes // ZERO text — every pixel belongs to the studio element. "", "", // tree-base IS REQUIRED IN DEV — without it the studio mounts EMPTY. // The studio builds its tab list at runtime by fetching `manifest.json`, and with // no base it falls back to '../manifest.json' RELATIVE TO ITS SCRIPT URL. In dev the // script is /plugin-designs/plugin-studio/index.js, so that resolves to // /plugin-designs/manifest.json — which does not exist. The shell then renders with zero // rows: 31 shadow descendants, no nested plugin tags. (Measured exactly that before this // line existed; the symptom reads as "no elements in the studio", not as a failed fetch.) // The dev server's manifest lives on its own route instead, serving the SAME // manifest-shaped model the built tree bakes (19 bundles / 30 profiles), so pointing the // studio at it gives dev the same tab list production has. '', '', "" ].filter(Boolean).join("\n")); } if (path !== "/__gallery" && path !== "/__gallery/") return next(); let html; try { html = page(); } catch (e) { console.error(`[plugin-gallery] ${e && e.stack ? e.stack : e}`); return next(); } res.statusCode = 200; res.setHeader("Content-Type", "text/html; charset=utf-8"); res.end(html); }); } }; } // internal/vite-plugin-strip-shadow-css-comments.mjs import { resolve as resolve5, dirname as dirname4, sep } from "node:path"; import { fileURLToPath as fileURLToPath4 } from "node:url"; var __vite_injected_original_import_meta_url4 = "file:///home/lk10x/Workspace/eco-dx/root-doxa-maps/wt-hf/internal/vite-plugin-strip-shadow-css-comments.mjs"; var ROOT3 = resolve5(dirname4(fileURLToPath4(__vite_injected_original_import_meta_url4)), ".."); var SOURCE_ROOTS = ["library", "plugin-designs"].map((d) => resolve5(ROOT3, d) + sep); var TAG = "[strip-shadow-css-comments]"; var CALL = /\buseShadowStyles\(\s*(?:[A-Za-z_$][\w$]*\s*\+\s*)*`|\b(?:export\s+)?const\s+[A-Z][A-Z0-9_]*(?:_CSS|_STYLES)\s*=\s*`/g; function findTemplateEnd(code, from) { for (let i = from; i < code.length; i++) { const c = code[i]; if (c === "\\") { i++; continue; } if (c === "`") return { end: i }; if (c === "$" && code[i + 1] === "{") return { reason: "contains a ${\u2026} substitution", at: i }; } return { reason: "template literal never closes", at: code.length }; } function stripCssComments(css) { const n = css.length; let out = ""; let comments = 0; let i = 0; while (i < n) { const c = css[i]; if (c === '"' || c === "'") { let j = i + 1; while (j < n && css[j] !== c && css[j] !== "\n") { if (css[j] === "\\") j++; j++; } if (j >= n) return { reason: "a CSS string never closes" }; if (css[j] === "\n") { out += css.slice(i, j); i = j; continue; } out += css.slice(i, j + 1); i = j + 1; continue; } if (c === "/" && css[i + 1] === "*") { const j = css.indexOf("*/", i + 2); if (j < 0) return { reason: "a CSS comment never closes" }; comments++; i = j + 2; continue; } if ((c === "u" || c === "U") && /^url\(/i.test(css.slice(i, i + 4)) && (i === 0 || !/[\w-]/.test(css[i - 1]))) { let j = i + 4; while (j < n && (css[j] === " " || css[j] === " ")) j++; if (css[j] === '"' || css[j] === "'") { out += css.slice(i, j); i = j; continue; } const k = css.indexOf(")", j); if (k < 0) return { reason: "an unquoted url() never closes" }; out += css.slice(i, k + 1); i = k + 1; continue; } out += c; i++; } if (comments > 0) { out = out.replace(/[ \t]+(?=\r?\n|$)/g, ""); out = out.replace(/\r?\n(?:[ \t]*\r?\n)+/g, "\n"); } return { css: out, comments }; } function stripShadowStyleComments(code) { const result = { code, changed: false, literals: 0, comments: 0, bytes: 0, skipped: [] }; let out = ""; let last = 0; CALL.lastIndex = 0; let m; while (m = CALL.exec(code)) { const open = m.index + m[0].length; const lit = findTemplateEnd(code, open); if (lit.reason) { result.skipped.push({ line: lineOf(code, m.index), reason: lit.reason }); CALL.lastIndex = lit.at; continue; } result.literals++; const original = code.slice(open, lit.end); const stripped = stripCssComments(original); if (stripped.reason) { result.skipped.push({ line: lineOf(code, m.index), reason: stripped.reason }); } else if (stripped.css !== original) { out += code.slice(last, open) + stripped.css; last = lit.end; result.comments += stripped.comments; result.bytes += original.length - stripped.css.length; result.changed = true; } CALL.lastIndex = lit.end + 1; } if (result.changed) result.code = out + code.slice(last); return result; } function lineOf(code, index) { let line = 1; for (let i = 0; i < index; i++) if (code[i] === "\n") line++; return line; } function isSourceModule(id) { const q = id.indexOf("?"); if (q >= 0) return false; if (!(id.endsWith(".vue") || id.endsWith(".js"))) return false; return SOURCE_ROOTS.some((root) => id.startsWith(root)); } function stripShadowCssCommentsPlugin() { let logger = console; const totals = { files: 0, literals: 0, comments: 0, bytes: 0, skipped: 0 }; return { name: "doxa:strip-shadow-css-comments", apply: "build", enforce: "pre", configResolved(config) { logger = config.logger; }, transform(code, id) { if (!isSourceModule(id)) return null; const r = stripShadowStyleComments(code); for (const s of r.skipped) { totals.skipped++; logger.warn(`${TAG} left untouched ${id.slice(ROOT3.length + 1)}:${s.line} \u2014 ${s.reason}`); } if (!r.changed) return null; totals.files++; totals.literals += r.literals; totals.comments += r.comments; totals.bytes += r.bytes; return { code: r.code, map: null }; }, buildEnd() { logger.info(`${TAG} removed ${totals.comments} CSS comments (${totals.bytes} bytes) from ${totals.literals} shadow-style literals in ${totals.files} files` + (totals.skipped ? `; ${totals.skipped} literal(s) left untouched` : "")); } }; } // internal/backend-dev-plugin.mjs import { spawn } from "node:child_process"; import { existsSync as existsSync4, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, rmSync, chmodSync, renameSync, createWriteStream } from "node:fs"; import { createHash } from "node:crypto"; import { connect } from "node:net"; import { inflateRawSync } from "node:zlib"; import { resolve as resolve6 } from "node:path"; var PB_VERSION = "0.40.4"; var PB_SHA256 = { linux_amd64: "9042ec818570e79c3628dadcd0a756c1496d9e1173918ec409d133c02f82e5fa", linux_arm64: "86095bf8ed9345954f0d2bf0a5fb9b57584ae60b77ebf3b6cd23a8003a3fd418", darwin_amd64: "052906521f09f6f23405cd804c930d2b7a1f8eee06d39b85723b5054b2acb4e4", darwin_arm64: "eeb619ea4f8a06421daedb946d133bed269fea334a760941d147f76befc25ebc" }; function devBackends(env = process.env) { const pbPort = Number(env.DX_PB_PORT || 8090); return [ { name: "rudder-engine", bin: "backend/bin/rudder-engine", build: "cd backend/rudder-engine && go build -o ../bin/rudder-engine .", // What a fresh clone is told at startup when this backend is not built: it is OPTIONAL (desktop voice). optional: "optional: Rudder (desktop voice-to-text) is not built, which is fine for the maps. It needs Go 1.22+; see documentation/HOWTO-build-the-desktop-app.md.", route: "/api/rudder", port: Number(process.env.RUDDER_PORT || 5193), env: { RUDDER_PORT: String(process.env.RUDDER_PORT || 5193), RUDDER_NEMO_PORT: String(process.env.RUDDER_NEMO_PORT || 5194) }, // This server is SUPPOSED to run the engine (a server deployment with the engine switched on): while it // is down, the plugin says "not running yet" instead of "runs on your own computer". expected: () => process.env.RUDDER_ENGINE === "1" || process.env.RUDDER_AUDIO === "browser", // RUDDER_ENGINE=0 (a server where Rudder is laptop-only, 2026-09-27): never start it, even if a // binary built by an earlier engine-on deploy is still there — it would offer to record a server's mic. disabled: () => process.env.RUDDER_ENGINE === "0" }, { // Hooks off (a hooks dir that never exists): only the migrations in git shape the schema, as in the cloud. // DX_PB_URL points this laptop at a shared PocketBase instead (e.g. the cloud pb-dev); none starts here. name: "pocketbase", bin: "backend/bin/pocketbase", args: ["serve", "--http", `127.0.0.1:${pbPort}`, "--dir", ".pb/dev", "--migrationsDir", "backend/pocketbase/pb_migrations", "--hooksDir", ".pb/hooks-off"], build: "it downloads on the next `bun run dev` (or set DX_PB_URL)", fetch: fetchPocketBase, portEnv: "DX_PB_PORT", route: "/api/pb", rewrite: (p) => p.replace(/^\/api\/pb/, "") || "/", port: pbPort, remote: env.DX_PB_URL || "", offline: (res) => { res.writeHead(503, { "Content-Type": "application/json" }); res.end(JSON.stringify({ status: 503, message: "PocketBase is not running on this computer (yet). See the dev server log." })); } } ]; } async function fetchPocketBase(root, logger) { const plat = { "linux-x64": "linux_amd64", "linux-arm64": "linux_arm64", "darwin-x64": "darwin_amd64", "darwin-arm64": "darwin_arm64" }[`${process.platform}-${process.arch}`]; if (!plat) { logger.warn(`[pocketbase] no pinned build for ${process.platform}-${process.arch}: put PocketBase ${PB_VERSION} at backend/bin/pocketbase, or set DX_PB_URL`); return false; } const name = `pocketbase_${PB_VERSION}_${plat}.zip`; const dir = resolve6(root, "backend/bin"); const part = resolve6(dir, `.pocketbase.part`); logger.info(`[pocketbase] downloading ${name} (first run only)`); try { mkdirSync2(dir, { recursive: true }); const r = await fetch(`https://github.com/pocketbase/pocketbase/releases/download/v${PB_VERSION}/${name}`); if (!r.ok) throw new Error(`HTTP ${r.status}`); const buf = Buffer.from(await r.arrayBuffer()); const sha = createHash("sha256").update(buf).digest("hex"); if (sha !== PB_SHA256[plat]) throw new Error(`checksum mismatch (${sha}): refusing to use it`); writeFileSync2(part, unzipOne(buf, "pocketbase")); chmodSync(part, 493); renameSync(part, resolve6(dir, "pocketbase")); return true; } catch (e) { logger.warn(`[pocketbase] download failed (${e.message}); /api/pb answers 503 until the next \`bun run dev\``); return false; } finally { rmSync(part, { force: true }); } } function unzipOne(buf, want) { const end = buf.lastIndexOf(Buffer.from([80, 75, 5, 6])); if (end < 0) throw new Error("not a zip"); for (let i = 0, p = buf.readUInt32LE(end + 16); i < buf.readUInt16LE(end + 10); i++) { const method = buf.readUInt16LE(p + 10), size = buf.readUInt32LE(p + 20); const nameLen = buf.readUInt16LE(p + 28), local = buf.readUInt32LE(p + 42); if (buf.toString("utf8", p + 46, p + 46 + nameLen) === want) { const start2 = local + 30 + buf.readUInt16LE(local + 26) + buf.readUInt16LE(local + 28); const data = buf.subarray(start2, start2 + size); if (method === 8) return inflateRawSync(data); if (method === 0) return data; throw new Error(`unsupported zip method ${method}`); } p += 46 + nameLen + buf.readUInt16LE(p + 30) + buf.readUInt16LE(p + 32); } throw new Error(`${want} not in the zip`); } var portInUse = (port) => new Promise((ok) => { const s = connect({ host: "127.0.0.1", port }, () => { s.destroy(); ok(true); }); s.on("error", () => ok(false)); }); function devBackendProxy(env = process.env) { return Object.fromEntries(devBackends(env).map((b) => [b.route, { target: b.remote || `http://127.0.0.1:${b.port}`, changeOrigin: !!b.remote, ws: true, ...b.rewrite ? { rewrite: b.rewrite } : {} }])); } var REG = globalThis.__dxDevBackends ||= { procs: /* @__PURE__ */ new Map(), exitHooked: false, stopping: false }; var isAlive = (name) => { const e = REG.procs.get(name); return !!e && e.child.exitCode === null && e.child.signalCode === null; }; function stopAll() { REG.stopping = true; for (const { child } of REG.procs.values()) { try { child.kill("SIGTERM"); } catch { } } } function start(root, b, logger, restarts = 0) { const tag = `[${b.name}]`; const child = spawn(resolve6(root, b.bin), b.args || [], { cwd: root, env: { ...process.env, ...b.env }, stdio: ["ignore", "pipe", "pipe"] }); const entry = { child, restarts, logger }; REG.procs.set(b.name, entry); const logDir = resolve6(root, ".pb", "logs"); mkdirSync2(logDir, { recursive: true }); const sink = createWriteStream(resolve6(logDir, `${b.name}.log`), { flags: "a" }); const LOUD = /\b(error|fail(ed|ure)?|panic|fatal|warn(ing)?|refused|denied|in use|cannot|unable|superuser|pbinstall?)\b/i; const out = (d) => { sink.write(d); String(d).trim().split("\n").forEach((l) => l && LOUD.test(l) && entry.logger.info(`${tag} ${l}`)); }; child.stdout.on("data", out); child.stderr.on("data", out); child.on("exit", () => sink.end()); child.on("exit", (code) => { if (REG.procs.get(b.name) !== entry) return; REG.procs.delete(b.name); if (REG.stopping) return; entry.logger.warn(`${tag} exited (${code})`); if (entry.restarts < 5) { setTimeout(() => { if (REG.stopping || REG.procs.has(b.name)) return; entry.logger.warn(`${tag} restarting (${entry.restarts + 1}/5)`); start(root, b, entry.logger, entry.restarts + 1); }, 2e3); } }); logger.info(`${tag} started \u2192 ${b.route} on 127.0.0.1:${b.port} (full log: .pb/logs/${b.name}.log)`); } function backendDevPlugin(root, env = process.env) { const backends = devBackends(env); return { name: "dx-backend-dev", apply: "serve", configureServer(server) { for (const b of backends) { server.middlewares.use(b.route, (req, res, next) => { if (b.remote || isAlive(b.name)) return next(); if (b.offline) return b.offline(res); const status = { engine: "offline", expected: !!(b.expected && b.expected()), error: "The desktop engine is not running." }; if ((req.url || "").startsWith("/events")) { res.writeHead(200, { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive" }); res.write(`data: ${JSON.stringify({ type: "status", status })} `); return; } if ((req.url || "").startsWith("/status")) { res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(status)); return; } res.writeHead(200, { "Content-Type": "application/json" }); res.end("[]"); }); } const logger = server.config.logger; if (!REG.exitHooked) { REG.exitHooked = true; process.once("exit", stopAll); process.once("SIGINT", () => { stopAll(); process.exit(0); }); process.once("SIGTERM", () => { stopAll(); process.exit(0); }); } for (const b of backends) { if (b.remote) { logger.info(`[${b.name}] not started here: ${b.route} \u2192 ${b.remote}`); continue; } if (isAlive(b.name)) { REG.procs.get(b.name).logger = logger; logger.info(`[${b.name}] still running \u2192 ${b.route} (kept across the dev-server restart)`); continue; } if (b.disabled && b.disabled()) { logger.info(`[backend] ${b.name} switched off here (${b.name === "rudder-engine" ? "RUDDER_ENGINE=0" : "disabled"})`); continue; } const launch = async () => { if (b.portEnv && await portInUse(b.port)) { logger.warn(`[${b.name}] port ${b.port} is already in use (another \`bun run dev\`?): not started. Set ${b.portEnv} to use another port.`); return; } if (!REG.stopping && !isAlive(b.name)) start(root, b, logger); }; if (!existsSync4(resolve6(root, b.bin))) { if (b.fetch) { REG.fetching ||= /* @__PURE__ */ new Set(); if (REG.fetching.has(b.name)) continue; REG.fetching.add(b.name); b.fetch(root, logger).then((ok) => { REG.fetching.delete(b.name); if (ok) launch(); }); continue; } if (b.optional) logger.info(`[backend] ${b.optional}`); else logger.warn(`[backend] ${b.name} not built \u2014 its plugin will show "engine offline". Build: ${b.build}`); continue; } launch(); } } }; } // internal/dev-sync-plugin.mjs import { execFile } from "node:child_process"; var git = (root, args) => new Promise((ok) => execFile( "git", args, { cwd: root, timeout: 6e4, env: { ...process.env, GIT_TERMINAL_PROMPT: "0" } }, (e, out, err) => { const all = String(err || e?.message || "").trim(); ok({ ok: !e, out: String(out || "").trim(), err: all.split("\n")[0], errAll: all }); } )); var AUTH_RE = /unauthori[sz]ed|authentication failed|could not read (username|password)|terminal prompts disabled|permission denied \(publickey|\b40[13]\b/i; var shown = (remote) => remote.replace(/\/\/[^/@]*@/, "//"); var label = (remote, branch) => remote.includes("/") ? `${branch} of ${shown(remote)}` : `${remote}/${branch}`; var REG2 = globalThis.__dxDevSync ||= { timer: null, running: false, status: { enabled: false } }; async function syncOnce(root, remote, branch, log = { info() { }, warn() { } }) { if (REG2.running) return REG2.status; REG2.running = true; try { const src2 = label(remote, branch); const s = { enabled: true, remote: shown(remote), branch, checkedAt: (/* @__PURE__ */ new Date()).toISOString() }; const f = await git(root, ["fetch", "-q", remote, branch]); if (!f.ok) { const auth = AUTH_RE.test(f.errAll); const state = auth ? "needs-credentials" : "fetch-failed"; const message = auth ? `dev-sync needs git credentials for ${shown(remote)} (a credential helper, or an SSH remote your key can read); git said: ${shown(f.err)}` : `could not fetch ${src2}: ${shown(f.err)}`; if (REG2.status.state !== state || REG2.status.message !== message) log.warn(`[dev-sync] ${message}`); return REG2.status = { ...s, state, message }; } const up = (await git(root, ["rev-parse", "FETCH_HEAD"])).out; const head = (await git(root, ["rev-parse", "HEAD"])).out; const cur = (await git(root, ["symbolic-ref", "-q", "--short", "HEAD"])).out; const behind = Number((await git(root, ["rev-list", "--count", `HEAD..${up}`])).out || 0); const ahead = Number((await git(root, ["rev-list", "--count", `${up}..HEAD`])).out || 0); const dirty = (await git(root, ["status", "--porcelain", "--untracked-files=no"])).out !== ""; Object.assign(s, { upstream: up, behind, ahead, dirty, currentBranch: cur || "(detached)" }); if (behind === 0) return REG2.status = { ...s, state: "up-to-date", message: `up to date with ${src2}` }; const why = cur !== branch ? `this checkout is on ${cur || "a detached HEAD"}, not ${branch}` : dirty ? "you have local edits" : ahead ? `you have ${ahead} local commit(s) that are not on ${src2}` : ""; const m = why ? null : await git(root, ["merge", "--ff-only", "-q", up]); if (why || !m.ok) { const message = `${branch} is ${behind} commit(s) ahead; ${why || `fast-forward refused (${m.err})`}, so nothing was merged. Pull when ready.`; if (REG2.status.upstream !== up || REG2.status.state !== "behind") log.warn(`[dev-sync] ${message}`); return REG2.status = { ...s, state: "behind", message }; } log.info(`[dev-sync] fast-forwarded ${head.slice(0, 8)} \u2192 ${up.slice(0, 8)} (${behind} commit(s) from ${src2})`); return REG2.status = { ...s, behind: 0, state: "up-to-date", lastMerge: s.checkedAt, message: `fast-forwarded ${behind} commit(s) from ${src2}` }; } finally { REG2.running = false; } } function devSyncPlugin(root, env = process.env) { const remote = env.DX_SYNC_REMOTE || ""; const branch = env.DX_SYNC_BRANCH || "development"; const every = Math.max(2, Number(env.DX_SYNC_INTERVAL) || 5); return { name: "dx-dev-sync", apply: "serve", configureServer(server) { server.middlewares.use("/api/dev-sync", (req, res) => { res.writeHead(200, { "Content-Type": "application/json", "Cache-Control": "no-store" }); res.end(JSON.stringify(REG2.status)); }); clearInterval(REG2.timer); if (!remote) { REG2.status = { enabled: false, message: "off: set DX_SYNC_REMOTE (a remote name like origin, or a repo URL) in .env.local to follow a shared branch" }; return; } const log = server.config.logger; log.info(`[dev-sync] following ${label(remote, branch)} every ${every}s (fast-forward only, never over local edits)`); const tick = () => syncOnce(root, remote, branch, log); REG2.timer = setInterval(tick, every * 1e3); REG2.timer.unref?.(); tick(); } }; } // vite.config.js import { fileURLToPath as fileURLToPath5 } from "node:url"; import { resolve as resolve8, dirname as dirname6, join as join4 } from "node:path"; import { readdirSync as readdirSync5, statSync as statSync4, existsSync as existsSync6, readFileSync as readFileSync7 } from "node:fs"; import { createHash as createHash2 } from "node:crypto"; var __vite_injected_original_import_meta_url5 = "file:///home/lk10x/Workspace/eco-dx/root-doxa-maps/wt-hf/vite.config.js"; var __dirname = dirname6(fileURLToPath5(__vite_injected_original_import_meta_url5)); var SHIP_ONE_LOCALE = process.env.DX_SHIP_ONE_LOCALE === "1"; var profilesDir = resolve8(__dirname, "plugin-designs"); var MFE_NAME = TREE_NAME; var OUT_ROOT = process.env.DOXA_OUT_ROOT ? resolve8(process.env.DOXA_OUT_ROOT) : GATED_PROFILES.includes(process.env.BUNDLE) ? resolve8(__dirname, ".gated", MFE_NAME) : resolve8(__dirname, "public", MFE_NAME); function discoverBundles() { if (!existsSync6(profilesDir)) return {}; const entries = {}; for (const name of readdirSync5(profilesDir)) { if (name.startsWith("_") || name.startsWith(".")) continue; const dir = resolve8(profilesDir, name); if (!statSync4(dir).isDirectory()) continue; const html = resolve8(dir, "index.html"); const js = resolve8(dir, "index.js"); if (!existsSync6(html) || !existsSync6(js)) continue; entries[name] = { html, js }; } return entries; } var localeDirs = () => readdirSync5(join4(__dirname, "library/i18n/locales"), { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).sort(); function localeTree(areas, lc) { const root = join4(__dirname, "library/i18n/locales"); const tree = {}; for (const a of areas) { const p = join4(root, lc, `${a}.json`); if (!existsSync6(p)) continue; try { tree[a] = JSON.parse(readFileSync7(p, "utf8")); } catch { } } return Object.keys(tree).length ? JSON.stringify(tree) : null; } function i18nAreasPlugin(bundle) { let areas = null; return { name: "doxa-i18n-areas", enforce: "pre", async buildStart() { const { areasForBundle: areasForBundle2 } = await Promise.resolve().then(() => (init_i18n_areas(), i18n_areas_exports)); areas = areasForBundle2(__dirname, bundle).areas; this.info?.(`i18n areas for ${bundle}: ${areas.join(", ") || "(none)"}`); }, transform(code, id) { if (!areas || !id.replace(/\\/g, "/").endsWith("/library/i18n/index.js")) return null; const GLOB = "import.meta.glob('./locales/*/*.json'"; if (!code.includes(GLOB)) this.error("i18n catalogue glob not found in library/i18n/index.js; cannot narrow it"); const loc = SHIP_ONE_LOCALE ? "en" : "*"; const list = areas.length ? "[" + areas.map((a) => `'./locales/${loc}/${a}.json'`).join(", ") + "]" : "['./locales/*/__none__.json']"; let out = code.replace(GLOB, "import.meta.glob(" + list); if (SHIP_ONE_LOCALE) { const sri = {}; for (const lc of localeDirs()) { if (lc === "en") continue; const json = localeTree(areas, lc); if (json) sri[lc] = "sha384-" + createHash2("sha384").update(json).digest("base64"); } out = `const __DX_LOCALE_SRI__ = ${JSON.stringify(sri)}; ` + out; } return { code: out, map: null }; }, // Emit /locales/.json for every locale the IIFE no longer carries, holding exactly // the areas this bundle uses — the same areasForBundle() answer the glob above was narrowed // with, so the two dimensions cannot drift apart. Emitted from THIS plugin on purpose: the // composite-freshness, landing-consumers, fix:sri and .gated/ steps already walk the build's // own output, so assets that appear here are covered with no new pipeline step. generateBundle() { if (!SHIP_ONE_LOCALE || !areas?.length) return; for (const lc of localeDirs()) { if (lc === "en") continue; const json = localeTree(areas, lc); if (json) this.emitFile({ type: "asset", fileName: `locales/${lc}.json`, source: json }); } } }; } var vite_config_default = defineConfig(({ command, mode }) => { const env = loadEnv(mode, __dirname, ""); const all = discoverBundles(); const allNames = Object.keys(all); const target = process.env.BUNDLE; const isBuild = command === "build"; if (isBuild && !target) { throw new Error( `[${MFE_NAME}] Set BUNDLE= for build, or use \`npm run build\` (loops all). Available: ${allNames.join(", ") || "(none)"}` ); } if (isBuild && !all[target]) { throw new Error( `[${MFE_NAME}] Bundle "${target}" is not buildable: plugin-designs/${target}/ is missing index.html and/or index.js (the custom-element entry pair every bundle needs). Buildable bundles: ${allNames.join(", ") || "(none)"}` ); } const buildEntries = isBuild ? { [target]: all[target].js } : Object.fromEntries(allNames.map((n) => [n, all[n].html])); return { // COMPILE-TIME QA GATE (release governance): the QA corner widget references // the maintainer's internal platform and must NOT exist in public // artifacts. Default FALSE strips it from every bundle (dead-branch + // treeshake); an explicitly-flagged platform build sets DOXA_QA=1. // The dev server listens on THIS MACHINE ONLY. A reachable one serves the source tree, node_modules // and the token baked into /; a remote viewer reaches it through an ssh tunnel (ssh -L), never a public // port. `--host` on the command line still overrides this for a deliberate LAN session. // Local plugin backends (backend/, e.g. rudder-engine) are started by backendDevPlugin and reached // through these same-origin routes, so a plugin calls /api//… in dev exactly as on desktop. // NOT WATCHING public/ (the first laptop run with DX_SYNC_REMOTE, 2026-09-28): a landing rewrites // dozens of built leaf index.html files there, and Vite answers each changed .html with a FULL page reload // (measured: one edited leaf page -> one full-reload broadcast), so a pull fired a burst of reloads while // git was still writing and the page went white. The dev server never serves public/ through its module // graph (the script-embed route reads it on request), so nothing there needs watching. server: { host: "127.0.0.1", proxy: devBackendProxy(env), watch: { ignored: ["**/public/**"] } }, define: { // ★ NO 'unsafe-eval' (CRITIC-4, 2026-09-26). vue-i18n's default message compiler builds each message // with `new Function`, so a partner with a strict Content-Security-Policy saw every translated string // fail. The JIT compiler (vue-i18n >= 9.3) compiles messages to an AST and evaluates it without eval; // defining the flag lets the bundler drop the eval path entirely (check:no-eval asserts it). __INTLIFY_JIT_COMPILATION__: "true", __INTLIFY_DROP_MESSAGE_COMPILER__: "false", __DOXA_QA__: JSON.stringify(process.env.DOXA_QA === "1"), // The output tree's name (vite.doxa-maps-build-config.json `name`, the same value OUT_ROOT uses), // for the few runtime paths that must name the tree — so renaming the tree needs no source edit. __DOXA_TREE_NAME__: JSON.stringify(MFE_NAME) }, // Build only: shadow-DOM CSS is injected from useShadowStyles(`…`) literals that // ship verbatim, so their block comments are stripped before the Vue compiler // runs (enforce: 'pre'). Dev keeps the comments. plugins: [ // ★ ONLY THE TRANSLATION AREAS THIS BUNDLE USES (CRITIC-10). library/i18n/index.js globs every // locales/*/*.json; in a BUNDLE build the glob is narrowed to the areas internal/i18n-areas.mjs finds // in this bundle's source graph. Dev (no BUNDLE) keeps every area. check:i18n-treeshake verifies it. isBuild && target ? i18nAreasPlugin(target) : null, stripShadowCssCommentsPlugin(), // ─── STRUCTURAL shadow-DOM CSS fix (customElement mode) ────────────────── // WHY: every bundle here is a `defineCustomElement(...)` that renders inside a // SHADOW ROOT. In @vitejs/plugin-vue's DEFAULT mode, each SFC