/** * profile-meta.mjs — the per-profile fields a host reads from .meta.json, in ONE place. * * generate-tree (the built manifest.json) and the dev server's /__plugin-gallery/manifest.json both call * this, so the dev Studio and the built Studio cannot disagree (rega: the corner widget never mounted in * dev because only the built manifest carried "widget"). * stage "production" | "experimental" default experimental * heavy true when the element holds live WebGL contexts * widget { corner } for a corner widget the Plugin Studio mounts once, fixed */ import { readFileSync } from 'node:fs' import { join } from 'node:path' const CORNERS = ['bottom-right', 'bottom-left', 'top-right', 'top-left'] export function readProfileMeta(dir, profileName) { try { const meta = JSON.parse(readFileSync(join(dir, `${profileName}.meta.json`), 'utf8')) const corner = meta?.widget?.corner // The plugin's own display name (label, or title / displayName): what the Plugin Studio rail calls it. 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() : '', } } catch { return { stage: 'experimental', heavy: false, widget: null, label: '' } } } /** The optional manifest keys for one profile (stage always; heavy / widget only when set). */ export function profileMetaFields(dir, profileName) { const m = readProfileMeta(dir, profileName) return { stage: m.stage, ...(m.label ? { label: m.label } : {}), ...(m.heavy ? { heavy: true } : {}), ...(m.widget ? { widget: m.widget } : {}) } } /** A bundle is production if ANY of its profiles says so. */ export const bundleStage = (profiles) => (profiles.some((p) => p.stage === 'production') ? 'production' : 'experimental')