@at-flux/astro-feature-flags 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +297 -0
  3. package/dist/dev-toolbar-app.d.mts +16 -0
  4. package/dist/dev-toolbar-app.d.mts.map +1 -0
  5. package/dist/dev-toolbar-app.mjs +407 -0
  6. package/dist/dev-toolbar-app.mjs.map +1 -0
  7. package/dist/dev-toolbar-flag-icon-BHCQJ53N.mjs +10 -0
  8. package/dist/dev-toolbar-flag-icon-BHCQJ53N.mjs.map +1 -0
  9. package/dist/index.d.mts +103 -0
  10. package/dist/index.d.mts.map +1 -0
  11. package/dist/index.mjs +1107 -0
  12. package/dist/index.mjs.map +1 -0
  13. package/dist/runtime.d.mts +115 -0
  14. package/dist/runtime.d.mts.map +1 -0
  15. package/dist/runtime.mjs +280 -0
  16. package/dist/runtime.mjs.map +1 -0
  17. package/docs/assets/element-gating.png +0 -0
  18. package/docs/assets/page-gating.png +0 -0
  19. package/docs/assets/toolbar-feature-flags.png +0 -0
  20. package/docs/how-to/hide-from-sitemaps.md +49 -0
  21. package/docs/testing.md +40 -0
  22. package/example/README.md +18 -0
  23. package/example/astro.config.mjs +26 -0
  24. package/example/ff.json +26 -0
  25. package/example/package.json +22 -0
  26. package/example/pnpm-lock.yaml +4022 -0
  27. package/example/src/env.d.ts +2 -0
  28. package/example/src/layouts/Layout.astro +21 -0
  29. package/example/src/pages/hot/index.astro +23 -0
  30. package/example/src/pages/hot-dev/index.astro +18 -0
  31. package/example/src/pages/hot-dev/sub/index.astro +21 -0
  32. package/example/src/pages/hot-feature-1/index.astro +38 -0
  33. package/example/src/pages/index.astro +102 -0
  34. package/example/src/styles/global.css +22 -0
  35. package/example/tsconfig.json +4 -0
  36. package/package.json +68 -0
  37. package/src/badge-layout.ts +146 -0
  38. package/src/dev-head-inject.ts +26 -0
  39. package/src/dev-inline-runtimes.ts +397 -0
  40. package/src/dev-outline-css.ts +449 -0
  41. package/src/dev-toolbar-app.ts +516 -0
  42. package/src/dev-toolbar-flag-icon.ts +9 -0
  43. package/src/index.ts +417 -0
  44. package/src/inline-script.ts +14 -0
  45. package/src/production-html-cull.ts +133 -0
  46. package/src/route-prefix-js.ts +7 -0
  47. package/src/runtime.ts +575 -0
  48. package/virtual-astro-feature-flags.d.ts +67 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/badge-layout.ts","../src/inline-script.ts","../src/dev-inline-runtimes.ts","../src/dev-outline-css.ts","../src/production-html-cull.ts","../src/dev-head-inject.ts","../src/route-prefix-js.ts","../src/index.ts"],"sourcesContent":["export type ElementBadgeVerticalAnchor = \"top\" | \"bottom\";\n\n/** Horizontal placement of the element pill: `end` = top-right (LTR), `start` = top-left, `center` = centred. */\nexport type ElementBadgeHorizontalAlign = \"start\" | \"center\" | \"end\";\n\n/** Subset of dev CSS options used for element-badge geometry (also on `DevOutlineCssOptions`). */\nexport interface ElementBadgeLayoutOptions {\n /**\n * Horizontal alignment along the element’s top or bottom edge. Default `end` (top-right in LTR).\n * Ignored when `elementBadgeHorizontalPercent` is set.\n */\n elementBadgeHorizontalAlign?: ElementBadgeHorizontalAlign;\n /**\n * Optional: anchor at a percentage along the element width (0–100), with the pill centred on that point (`translateX(-50%)`).\n * When set, overrides `elementBadgeHorizontalAlign`.\n */\n elementBadgeHorizontalPercent?: number;\n /**\n * Vertical shift as a percentage of the **badge’s own height** (CSS transform %).\n * With anchor `top`, moves the badge upward. Default `80` (~20% of the pill overlaps the element top edge).\n */\n elementBadgeVerticalShiftPercent?: number;\n /** Anchor the badge to the top or bottom edge of the element. Default `top`. */\n elementBadgeVerticalAnchor?: ElementBadgeVerticalAnchor;\n}\n\nexport type NormalizedElementBadgeLayout =\n | {\n mode: \"align\";\n horizontalAlign: ElementBadgeHorizontalAlign;\n verticalShiftPercent: number;\n verticalAnchor: ElementBadgeVerticalAnchor;\n }\n | {\n mode: \"percent\";\n horizontalPercent: number;\n verticalShiftPercent: number;\n verticalAnchor: ElementBadgeVerticalAnchor;\n };\n\nfunction clampPercent(n: number, fallback: number): number {\n if (typeof n !== \"number\" || Number.isNaN(n)) return fallback;\n return Math.min(100, Math.max(0, n));\n}\n\nexport function normalizeElementBadgeLayout(\n input: ElementBadgeLayoutOptions | undefined,\n): NormalizedElementBadgeLayout {\n const v = clampPercent(input?.elementBadgeVerticalShiftPercent ?? 80, 80);\n const anchor =\n input?.elementBadgeVerticalAnchor === \"bottom\" ? \"bottom\" : \"top\";\n const hp = input?.elementBadgeHorizontalPercent;\n if (hp !== undefined && hp !== null && typeof hp === \"number\") {\n return {\n mode: \"percent\",\n horizontalPercent: clampPercent(hp, 50),\n verticalShiftPercent: v,\n verticalAnchor: anchor,\n };\n }\n const align: ElementBadgeHorizontalAlign =\n input?.elementBadgeHorizontalAlign === \"start\" ||\n input?.elementBadgeHorizontalAlign === \"center\"\n ? input.elementBadgeHorizontalAlign\n : \"end\";\n return {\n mode: \"align\",\n horizontalAlign: align,\n verticalShiftPercent: v,\n verticalAnchor: anchor,\n };\n}\n\nconst INSET = \"0.35rem\";\n\n/**\n * `left` / `right` / `top` | `bottom` / `transform` for the element `::before` badge (no trailing newline).\n */\nexport function elementBadgePositionBlock(\n layout: NormalizedElementBadgeLayout,\n): string {\n const { verticalShiftPercent: v, verticalAnchor } = layout;\n const y =\n verticalAnchor === \"top\"\n ? `translateY(calc(-1 * ${v}%))`\n : `translateY(${v}%)`;\n\n if (layout.mode === \"percent\") {\n const h = layout.horizontalPercent;\n if (verticalAnchor === \"top\") {\n return ` left: ${h}%;\n right: auto;\n top: 0;\n bottom: auto;\n transform: translateX(-50%) ${y};`;\n }\n return ` left: ${h}%;\n right: auto;\n bottom: 0;\n top: auto;\n transform: translateX(-50%) ${y};`;\n }\n\n const { horizontalAlign } = layout;\n if (horizontalAlign === \"center\") {\n if (verticalAnchor === \"top\") {\n return ` left: 50%;\n right: auto;\n top: 0;\n bottom: auto;\n transform: translateX(-50%) ${y};`;\n }\n return ` left: 50%;\n right: auto;\n bottom: 0;\n top: auto;\n transform: translateX(-50%) ${y};`;\n }\n if (horizontalAlign === \"start\") {\n if (verticalAnchor === \"top\") {\n return ` left: ${INSET};\n right: auto;\n top: 0;\n bottom: auto;\n transform: ${y};`;\n }\n return ` left: ${INSET};\n right: auto;\n bottom: 0;\n top: auto;\n transform: ${y};`;\n }\n /* end — top-right / bottom-right */\n if (verticalAnchor === \"top\") {\n return ` left: auto;\n right: ${INSET};\n top: 0;\n bottom: auto;\n transform: ${y};`;\n }\n return ` left: auto;\n right: ${INSET};\n bottom: 0;\n top: auto;\n transform: ${y};`;\n}\n","export function compactInlineScript(source: string): string {\n const lines = source\n .split(\"\\n\")\n .map((line) => line.trim())\n .filter((line, idx, arr) => line.length > 0 || (idx > 0 && idx < arr.length - 1));\n return lines.join(\"\\n\").trim();\n}\n\nexport function inlineInvoke<Payload>(\n fn: (payload: Payload) => void,\n payload: Payload,\n): string {\n return compactInlineScript(`(${fn.toString()})(${JSON.stringify(payload)});`);\n}\n","type HeadInlinePayload = {\n featureFlagStyles: string;\n routeFlags: Record<string, string[]>;\n flagNameToToken: Record<string, string>;\n};\n\ntype BootstrapPayload = {\n flagTokens: string[];\n colors: Record<string, string>;\n outlineDefaults: Record<string, boolean>;\n badgeDefaults: Record<string, boolean>;\n routeFlags: Record<string, string[]>;\n flagNameToToken: Record<string, string>;\n namespace: string;\n};\n\nexport function affHeadInlineRuntime(payload: HeadInlinePayload): void {\n const toPrefix = (pattern: string): string => {\n let p = String(pattern || \"\").trim();\n if (p.endsWith(\"/**\")) p = p.slice(0, -3);\n else if (p.endsWith(\"/*\")) p = p.slice(0, -2);\n else if (p.length > 1 && p.endsWith(\"*\")) p = p.slice(0, -1);\n return p.endsWith(\"/\") ? p : `${p}/`;\n };\n try {\n const { featureFlagStyles, routeFlags: RF, flagNameToToken: M } = payload;\n const s = document.createElement(\"style\");\n s.setAttribute(\"data-astro-feature-flags\", \"\");\n s.textContent = featureFlagStyles;\n (document.head || document.documentElement).appendChild(s);\n\n const affPath = () => {\n let p = typeof location !== \"undefined\" && location.pathname ? location.pathname : \"/\";\n if (!p.endsWith(\"/\")) p += \"/\";\n return p;\n };\n\n const affFlagNames = (pathname: string) => {\n let p = pathname || \"/\";\n if (!p.endsWith(\"/\")) p += \"/\";\n const out: string[] = [];\n const seen = new Set<string>();\n for (const rp in RF) {\n if (!Object.prototype.hasOwnProperty.call(RF, rp)) continue;\n const route = toPrefix(rp);\n if (!(p === route || p.indexOf(route) === 0)) continue;\n const fns = RF[rp] || [];\n for (let i = 0; i < fns.length; i++) {\n const fn = fns[i];\n if (fn && !seen.has(fn)) {\n seen.add(fn);\n out.push(fn);\n }\n }\n }\n return out;\n };\n\n const affTokens = (pathname: string) =>\n affFlagNames(pathname)\n .map((name) => M[name])\n .filter(Boolean);\n\n const affSyncDisabledOverlay = () => {\n const root = document.documentElement;\n if (!document.body) return;\n const names = affFlagNames(affPath());\n const disabled: string[] = [];\n for (let i = 0; i < names.length; i++) {\n const tk = M[names[i]];\n if (!tk) continue;\n if (root.getAttribute(`data-ff-enabled-${tk}`) === \"off\") disabled.push(tk);\n }\n const id = \"aff-route-disabled-overlay\";\n let el = document.getElementById(id);\n if (!disabled.length) {\n if (el) el.remove();\n root.removeAttribute(\"data-aff-route-disabled\");\n return;\n }\n root.setAttribute(\"data-aff-route-disabled\", \"\");\n if (!el) {\n el = document.createElement(\"div\");\n el.id = id;\n el.className = \"aff-route-disabled-overlay\";\n const p = document.createElement(\"p\");\n el.appendChild(p);\n document.body.appendChild(el);\n }\n const pEl = el.querySelector(\"p\") || el;\n const flagsText = disabled.join(\", \");\n const line1 =\n disabled.length === 1\n ? `Page disabled: ${flagsText} flag is disabled.`\n : `Page disabled: ${flagsText} flags are disabled.`;\n const line2 =\n \"In production builds, pages with disabled flags like this will not be emitted\";\n pEl.textContent = `${line1}\\n${line2}`;\n };\n\n const affApply = () => {\n document.documentElement.setAttribute(\"data-ff-route\", affTokens(affPath()).join(\" \"));\n affSyncDisabledOverlay();\n };\n\n affApply();\n if (typeof MutationObserver === \"function\") {\n const obs = new MutationObserver((muts) => {\n for (let i = 0; i < muts.length; i++) {\n const a = muts[i] && muts[i].attributeName;\n if (a === \"data-ff-route\" || (a && a.indexOf(\"data-ff-enabled-\") === 0)) {\n affSyncDisabledOverlay();\n break;\n }\n }\n });\n obs.observe(document.documentElement, { attributes: true });\n }\n document.addEventListener(\"astro:page-load\", affApply);\n document.addEventListener(\"astro:after-swap\", affApply);\n } catch {\n // no-op in injected runtime\n }\n}\n\nexport function affDevBootstrapRuntime(payload: BootstrapPayload): void {\n const toPrefix = (pattern: string): string => {\n let p = String(pattern || \"\").trim();\n if (p.endsWith(\"/**\")) p = p.slice(0, -3);\n else if (p.endsWith(\"/*\")) p = p.slice(0, -2);\n else if (p.length > 1 && p.endsWith(\"*\")) p = p.slice(0, -1);\n return p.endsWith(\"/\") ? p : `${p}/`;\n };\n const T = payload.flagTokens;\n const C = payload.colors;\n const OD = payload.outlineDefaults;\n const BD = payload.badgeDefaults;\n const RF = payload.routeFlags;\n const F2T = payload.flagNameToToken;\n const N = payload.namespace;\n const K = `${N}.dev.v1`;\n\n const root = document.documentElement;\n const isEnabled = (tk: string) => root.getAttribute(`data-ff-enabled-${tk}`) !== \"off\";\n const currentTokenColor = (\n tk: string,\n cols: Record<string, string>,\n ): string => {\n const cssVar = root.style.getPropertyValue(`--${N}-c-${tk}`).trim();\n return cssVar || cols[tk] || C[tk] || \"#94a3b8\";\n };\n const unique = (a: string[]) => [...new Set(a.filter(Boolean))];\n\n const comboTokens = (el: Element): string[] => {\n let out: string[] = [];\n const raw = (el.getAttribute(`data-${N}`) || \"\").trim();\n if (raw) out = out.concat(raw.split(/\\s+/).filter(Boolean));\n for (let i = 0; i < T.length; i++) {\n const tk = T[i];\n if (el.hasAttribute(`data-${N}-${tk}`)) out.push(tk);\n }\n return unique(out);\n };\n\n const stripBooleanFlagAttrs = (el: Element) => {\n for (let i = 0; i < T.length; i++) {\n el.removeAttribute(`data-${N}-${T[i]}`);\n }\n };\n const clearComboVisualState = (el: HTMLElement) => {\n el.removeAttribute(\"data-ff-label\");\n el.style.removeProperty(\"--ff-combo-gradient\");\n el.style.removeProperty(\"--ff-combo-gradient-soft\");\n el.style.removeProperty(\"--ff-combo-outline\");\n el.style.removeProperty(\"--ff-combo-text\");\n el.style.removeProperty(\"--ff-combo-badge-border\");\n el.style.removeProperty(\"--ff-combo-badge-gradient\");\n };\n\n const applyCombo = (el: HTMLElement, cols: Record<string, string>) => {\n const allTokens = comboTokens(el);\n const hasCombo = allTokens.length >= 2;\n const hasDisabledInCombo = hasCombo && allTokens.some((tk) => !isEnabled(tk));\n const visual = allTokens.filter((tk) => isEnabled(tk));\n if (hasDisabledInCombo) {\n // Fail-closed for combos: if any member token is disabled, hide the whole element.\n // Keep all combo tokens mirrored in data-<ns> so enabled-off CSS selectors can hide it.\n stripBooleanFlagAttrs(el);\n el.setAttribute(`data-${N}`, allTokens.join(\" \"));\n clearComboVisualState(el);\n return;\n }\n if (visual.length < 2) {\n clearComboVisualState(el);\n if (hasCombo) {\n // Only normalize attrs for true combos. Single-flag shorthand markers\n // (e.g. data-ff-wip) must stay intact so re-enabling can recover styling.\n stripBooleanFlagAttrs(el);\n if (visual.length === 1) {\n el.setAttribute(`data-${N}`, visual[0]!);\n } else {\n el.removeAttribute(`data-${N}`);\n }\n }\n return;\n }\n stripBooleanFlagAttrs(el);\n // Mirror all enabled tokens into the primary namespace attribute so `[data-<ns>*=\" \"]`\n // matches (required for boolean-only combos like data-ff-a + data-ff-b).\n el.setAttribute(`data-${N}`, visual.join(\" \"));\n el.setAttribute(\"data-ff-label\", visual.join(\" | \"));\n const strong = visual.map((tk) => currentTokenColor(tk, cols));\n const soft = strong.map((c) => `color-mix(in oklab, white 86%, ${c} 14%)`);\n el.style.setProperty(\"--ff-combo-gradient\", `linear-gradient(90deg, ${strong.join(\", \")})`);\n el.style.setProperty(\"--ff-combo-gradient-soft\", `linear-gradient(90deg, ${soft.join(\", \")})`);\n el.style.setProperty(\"--ff-combo-outline\", strong[0] || \"#64748b\");\n el.style.setProperty(\"--ff-combo-text\", strong[0] || \"#111827\");\n el.style.setProperty(\n \"--ff-combo-badge-border\",\n `color-mix(in oklab, ${strong[0] || \"#64748b\"} 35%, transparent)`,\n );\n el.style.setProperty(\"--ff-combo-badge-gradient\", `linear-gradient(90deg, ${soft.join(\", \")})`);\n };\n\n const routeTokensForPath = (path: string): string[] => {\n const out: string[] = [];\n const seen = new Set<string>();\n for (const rp in RF) {\n if (!Object.prototype.hasOwnProperty.call(RF, rp)) continue;\n const cnd = toPrefix(rp);\n if (!(path === cnd || path.indexOf(cnd) === 0)) continue;\n const fns = RF[rp] || [];\n for (let i = 0; i < fns.length; i++) {\n const tk = F2T[fns[i]];\n if (tk && !seen.has(tk)) {\n seen.add(tk);\n out.push(tk);\n }\n }\n }\n return out;\n };\n\n const routeLabelFor = (path: string, toks: string[]): string => {\n if (!toks.length) return \"\";\n const byFlag: Record<string, string[]> = {};\n for (const rp in RF) {\n if (!Object.prototype.hasOwnProperty.call(RF, rp)) continue;\n const cnd = toPrefix(rp);\n if (!(path === cnd || path.indexOf(cnd) === 0)) continue;\n const fns = RF[rp] || [];\n for (let i = 0; i < fns.length; i++) {\n const tk = F2T[fns[i]];\n if (!tk) continue;\n if (!byFlag[tk]) byFlag[tk] = [];\n byFlag[tk].push(rp);\n }\n }\n return `Page: ${toks\n .map((tk) => `${tk} - ${(byFlag[tk] || []).join(\", \") || \"matched\"}`)\n .join(\" | \")}`;\n };\n\n const affSyncRouteChrome = (path: string) => {\n if (!document.body) return;\n // Keep every matched route token on <html> so per-token toolbar rules\n // (`[data-ff-route~=\"tk\"]`) still apply; gradients use enabled-only below.\n const routeAll = routeTokensForPath(path);\n if (!routeAll.length) {\n document.getElementById(\"aff-route-frame\")?.remove();\n document.getElementById(\"aff-route-badge\")?.remove();\n root.removeAttribute(\"data-ff-route\");\n root.removeAttribute(\"data-ff-route-label\");\n return;\n }\n root.setAttribute(\"data-ff-route\", routeAll.join(\" \"));\n root.setAttribute(\"data-ff-route-label\", routeLabelFor(path, routeAll));\n if (!document.getElementById(\"aff-route-frame\")) {\n const frame = document.createElement(\"div\");\n frame.id = \"aff-route-frame\";\n frame.className = \"aff-route-frame\";\n frame.setAttribute(\"aria-hidden\", \"true\");\n document.body.appendChild(frame);\n }\n let badge = document.getElementById(\"aff-route-badge\");\n if (!badge) {\n badge = document.createElement(\"div\");\n badge.id = \"aff-route-badge\";\n badge.className = \"aff-route-badge\";\n badge.setAttribute(\"role\", \"status\");\n document.body.appendChild(badge);\n }\n badge.textContent = root.getAttribute(\"data-ff-route-label\") || \"\";\n badge.setAttribute(\"aria-label\", badge.textContent || \"\");\n };\n\n try {\n (window as Window & { __AFF__?: unknown }).__AFF__ = {\n tokens: T,\n colors: C,\n namespace: N,\n };\n const raw = localStorage.getItem(K) || localStorage.getItem(\"aff.dev.v1\");\n const st = raw ? JSON.parse(raw) : {};\n const o = st.outline || st.chrome || {};\n const e = st.enabled || st.render || {};\n const b = st.badge || {};\n const cols = st.colors || {};\n\n for (let i = 0; i < T.length; i++) {\n const t = T[i];\n const od = OD[t] !== false;\n const bd = BD[t] !== false;\n if (o[t] === false || (!od && o[t] !== true))\n root.setAttribute(`data-ff-outline-${t}`, \"off\");\n else root.removeAttribute(`data-ff-outline-${t}`);\n if (e[t] === false) root.setAttribute(`data-ff-enabled-${t}`, \"off\");\n else root.removeAttribute(`data-ff-enabled-${t}`);\n if (b[t] === false || (!bd && b[t] !== true))\n root.setAttribute(`data-ff-badge-${t}`, \"off\");\n else root.removeAttribute(`data-ff-badge-${t}`);\n const col = cols[t] || C[t];\n const vn = `--${N}-c-${t}`;\n if (col) root.style.setProperty(vn, col);\n else root.style.removeProperty(vn);\n }\n\n const applyAll = () => {\n const map = new Set<HTMLElement>();\n document\n .querySelectorAll<HTMLElement>(`[data-${N}]`)\n .forEach((el) => map.add(el));\n for (let i = 0; i < T.length; i++) {\n document\n .querySelectorAll<HTMLElement>(`[data-${N}-${T[i]}]`)\n .forEach((el) => map.add(el));\n }\n map.forEach((el) => applyCombo(el, cols));\n };\n\n const affAfterNav = () => {\n if (!document.body) return;\n let p = window.location?.pathname || \"/\";\n if (!p.endsWith(\"/\")) p += \"/\";\n const toks = routeTokensForPath(p).filter((tk) => isEnabled(tk));\n if (toks.length) {\n const strong = toks.map((tk) => currentTokenColor(tk, cols));\n const soft = strong.map((c) => `color-mix(in oklab, white 86%, ${c} 14%)`);\n root.style.setProperty(`--${N}-route-gradient`, `linear-gradient(90deg, ${soft.join(\", \")})`);\n root.style.setProperty(\"--ff-route-outline-gradient\", `linear-gradient(90deg, ${strong.join(\", \")})`);\n root.style.setProperty(\"--ff-route-badge-gradient\", `linear-gradient(90deg, ${soft.join(\", \")})`);\n root.style.setProperty(\n \"--ff-route-badge-border\",\n `color-mix(in oklab, ${strong[0] || \"#64748b\"} 35%, transparent)`,\n );\n root.style.setProperty(\"--ff-route-outline\", strong[0] || \"#64748b\");\n root.style.setProperty(\"--ff-route-text\", strong[0] || \"#111827\");\n } else {\n root.style.removeProperty(`--${N}-route-gradient`);\n root.style.removeProperty(\"--ff-route-outline-gradient\");\n root.style.removeProperty(\"--ff-route-badge-gradient\");\n root.style.removeProperty(\"--ff-route-badge-border\");\n root.style.removeProperty(\"--ff-route-outline\");\n root.style.removeProperty(\"--ff-route-text\");\n }\n applyAll();\n affSyncRouteChrome(p);\n };\n\n affAfterNav();\n if (document.readyState === \"loading\") {\n document.addEventListener(\"DOMContentLoaded\", affAfterNav, { once: true });\n }\n document.addEventListener(\"astro:page-load\", affAfterNav);\n document.addEventListener(\"astro:after-swap\", affAfterNav);\n if (typeof MutationObserver === \"function\") {\n const obs = new MutationObserver((muts) => {\n for (let i = 0; i < muts.length; i++) {\n const a = muts[i]?.attributeName;\n if (!a) continue;\n if (\n a === \"style\" ||\n a.indexOf(\"data-ff-enabled-\") === 0 ||\n a.indexOf(\"data-ff-outline-\") === 0 ||\n a.indexOf(\"data-ff-badge-\") === 0\n ) {\n affAfterNav();\n break;\n }\n }\n });\n obs.observe(root, { attributes: true });\n }\n } catch {\n // no-op in injected runtime\n }\n}\n","import {\n elementBadgePositionBlock,\n normalizeElementBadgeLayout,\n} from \"./badge-layout\";\nimport type { ElementBadgeLayoutOptions } from \"./badge-layout\";\nimport { inlineInvoke } from \"./inline-script\";\nimport { affDevBootstrapRuntime } from \"./dev-inline-runtimes\";\nimport type { ResolvedFeatureRuntime } from \"./runtime\";\nimport { toToken } from \"./runtime\";\n\nexport type DevOutlineHiddenStrategy = \"visibility\" | \"display\";\n\nexport interface DevOutlineCssOptions extends ElementBadgeLayoutOptions {\n outlineWidth?: string;\n /** Fallback when no per-token colour is set. */\n outlineColor?: string;\n /** Per-flag-token outline / badge / route-badge colour (overrides JSON `colors`). */\n outlineColorByToken?: Record<string, string>;\n outlineOffset?: string;\n borderRadius?: string;\n\n /** Default pill text for the `wip` flag token in the dev toolbar. */\n badgeLabelWip?: string;\n /** @deprecated Use {@link badgeLabelWip}. */\n badgeLabelDev?: string;\n badgeLabelByToken?: Record<string, string>;\n\n hiddenStrategy?: DevOutlineHiddenStrategy;\n\n /**\n * @deprecated Unused: badge does not use container queries.\n */\n badgeMinInlineSize?: string;\n}\ntype NormalizedDevOutlineCssOptions = {\n outlineWidth: string;\n outlineColor: string;\n outlineColorByToken: Record<string, string>;\n outlineOffset: string;\n borderRadius: string;\n badgeLabelWip: string;\n badgeLabelByToken: Record<string, string>;\n hiddenStrategy: DevOutlineHiddenStrategy;\n badgeMinInlineSize: string;\n};\n\nconst defaultDevOutlineCssOptions: Omit<\n NormalizedDevOutlineCssOptions,\n \"outlineColorByToken\"\n> = {\n outlineWidth: \"2px\",\n outlineColor: \"rgb(220 38 38)\",\n outlineOffset: \"-2px\",\n borderRadius: \"0.5rem\",\n badgeLabelWip: \"wip\",\n badgeLabelByToken: {},\n hiddenStrategy: \"visibility\",\n badgeMinInlineSize: \"8rem\",\n};\n\nfunction normalizeCssOptions(\n css: DevOutlineCssOptions | undefined,\n runtime: ResolvedFeatureRuntime,\n): NormalizedDevOutlineCssOptions {\n const byToken = {\n ...runtime.flagColorsByToken,\n ...(css?.outlineColorByToken ?? {}),\n };\n const badgeLabelWip =\n css?.badgeLabelWip ?? css?.badgeLabelDev ?? defaultDevOutlineCssOptions.badgeLabelWip;\n return {\n ...defaultDevOutlineCssOptions,\n ...(css ?? {}),\n outlineColorByToken: byToken,\n badgeLabelWip,\n badgeLabelByToken: {\n ...defaultDevOutlineCssOptions.badgeLabelByToken,\n ...(css?.badgeLabelByToken ?? {}),\n },\n };\n}\n\nfunction colorForToken(\n token: string,\n opts: NormalizedDevOutlineCssOptions,\n): string {\n return opts.outlineColorByToken[token] ?? opts.outlineColor;\n}\n\n/** CSS variable per token, set on &lt;html&gt; from ff.json + dev-toolbar overrides. */\nfunction featureColorVar(token: string, namespace: string): string {\n const ns = toToken(namespace) || \"ff\";\n return `--${ns}-c-${token}`;\n}\n\n/**\n * Dev-only styles: `data-ff=\"token\"` (or space-separated tokens) gates outlines/badges.\n * `html[data-ff-route=\"<token>\"]` (dev) adds a fixed top-right route badge (label only).\n * Toolbar toggles: `data-ff-enabled-*`, `data-ff-outline-*`, `data-ff-badge-*`, `--<namespace>-c-*`.\n */\nexport function createFeatureFlagStyles(\n runtime: ResolvedFeatureRuntime,\n css?: DevOutlineCssOptions,\n): string {\n const nsAttr = `data-${toToken(runtime.namespace) || \"ff\"}`;\n const selectorsForFlag = (flag: string) => {\n const token = toToken(flag);\n const valueSelectors = [`[${nsAttr}~=\"${token}\"]`];\n if (flag !== token) valueSelectors.push(`[${nsAttr}~=\"${flag}\"]`);\n const baseSelectors = [...valueSelectors, `[${nsAttr}-${token}]`];\n return {\n token,\n baseSelectors,\n selOutline: baseSelectors.map((s) => `${s}:not([${nsAttr}*=\" \"])`).join(\", \"),\n selIs: `:is(${baseSelectors.join(\", \")})`,\n };\n };\n const opts = normalizeCssOptions(css, runtime);\n const badgeLayout = normalizeElementBadgeLayout(css);\n const badgePos = elementBadgePositionBlock(badgeLayout);\n const chunks: string[] = [];\n\n for (const flag of Object.keys(runtime.flags)) {\n const { token, selOutline, selIs } = selectorsForFlag(flag);\n /** Combo hosts use `[${nsAttr}*=\" \"]` + `data-ff-label`; skip per-token ::before so combo rules win. */\n const selIsSingleBadge = `${selIs}:not([${nsAttr}*=\" \"])`;\n const col = colorForToken(token, opts);\n const v = featureColorVar(token, runtime.namespace);\n const label =\n opts.badgeLabelByToken[token] ??\n (token === \"wip\" ? opts.badgeLabelWip : token);\n const labelEscaped = String(label).replace(/'/g, \"\\\\'\");\n\n chunks.push(`\nhtml {\n --aff-outline-c-${token}: transparent;\n}\nhtml:not([data-ff-outline-${token}=\"off\"]) {\n --aff-outline-c-${token}: var(${v}, ${col});\n}\n${selOutline} {\n position: relative;\n outline: ${opts.outlineWidth} solid var(--aff-outline-c-${token});\n outline-offset: ${opts.outlineOffset};\n border-radius: ${opts.borderRadius};\n}\nhtml:not([data-ff-badge-${token}=\"off\"]) ${selIsSingleBadge}::before {\n box-sizing: border-box;\n position: absolute;\n inset: auto;\n margin: 0;\n width: max-content;\n max-width: min(18rem, calc(100% - 0.75rem));\n pointer-events: auto;\n z-index: 20;\n${badgePos}\n display: block;\n content: '${labelEscaped}';\n border: 1px solid color-mix(in oklab, var(${v}, ${col}) 60%, transparent);\n border-radius: 9999px;\n padding: 0.125rem 0.45rem;\n font-size: 0.625rem;\n line-height: 1.2;\n letter-spacing: 0.02em;\n font-weight: 600;\n color: var(${v}, ${col});\n background: color-mix(in oklab, white 85%, var(${v}, ${col}) 15%);\n white-space: nowrap;\n text-overflow: ellipsis;\n overflow: hidden;\n transition: opacity 0.14s ease, background 0.14s ease, border-color 0.14s ease;\n}\nhtml:not([data-ff-badge-${token}=\"off\"]) ${selIsSingleBadge}:hover::before {\n opacity: 0.16;\n background: color-mix(in oklab, white 94%, var(${v}, ${col}) 6%);\n border-color: color-mix(in oklab, var(${v}, ${col}) 35%, transparent);\n}\nhtml:not([data-ff-badge-${token}=\"off\"]) ${selIsSingleBadge}[data-ff-align=\"start\"]::before {\n left: 0.35rem;\n right: auto;\n transform: translateY(calc(-1 * 80%));\n}\nhtml:not([data-ff-badge-${token}=\"off\"]) ${selIsSingleBadge}[data-ff-align=\"center\"]::before {\n left: 50%;\n right: auto;\n transform: translate(-50%, calc(-1 * 80%));\n}\nhtml:not([data-ff-badge-${token}=\"off\"]) ${selIsSingleBadge}[data-ff-align=\"end\"]::before {\n left: auto;\n right: 0.35rem;\n transform: translateY(calc(-1 * 80%));\n}\nhtml:not([data-ff-badge-${token}=\"off\"]) ${selIsSingleBadge}[data-ff-horizontal]::before {\n left: calc(attr(data-ff-horizontal number, 50) * 1%);\n right: auto;\n transform: translate(-50%, calc(-1 * 80%));\n}\nhtml:not([data-ff-badge-${token}=\"off\"]) ${selIsSingleBadge}[data-ff-horizontal][data-ff-anchor=\"bottom\"]::before {\n transform: translate(-50%, 80%);\n}\nhtml:not([data-ff-badge-${token}=\"off\"]) ${selIsSingleBadge}[data-ff-anchor=\"bottom\"]::before {\n top: auto;\n bottom: 0;\n transform: translateY(80%);\n}\nhtml:not([data-ff-badge-${token}=\"off\"]) ${selIsSingleBadge}[data-ff-anchor=\"bottom\"][data-ff-align=\"center\"]::before {\n transform: translate(-50%, 80%);\n}\nhtml:not([data-ff-badge-${token}=\"off\"]) ${selIsSingleBadge}[data-ff-vertical]::before {\n transform: translateY(calc(attr(data-ff-vertical number, 80) * -1%));\n}\nhtml:not([data-ff-badge-${token}=\"off\"]) ${selIsSingleBadge}[data-ff-horizontal][data-ff-vertical]::before {\n transform: translate(-50%, calc(attr(data-ff-vertical number, 80) * -1%));\n}\nhtml:not([data-ff-badge-${token}=\"off\"]) ${selIsSingleBadge}[data-ff-anchor=\"bottom\"][data-ff-vertical]::before {\n transform: translateY(calc(attr(data-ff-vertical number, 80) * 1%));\n}\nhtml:not([data-ff-badge-${token}=\"off\"]) ${selIsSingleBadge}[data-ff-anchor=\"bottom\"][data-ff-horizontal][data-ff-vertical]::before {\n transform: translate(-50%, calc(attr(data-ff-vertical number, 80) * 1%));\n}\nhtml[data-ff-enabled-${token}=\"off\"] ${selIs} {\n display: none !important;\n}\n`);\n }\n\n // Multi-token value on a single element: show combined badge text and a gradient outline.\n chunks.push(`\n[${nsAttr}*=\" \"] {\n position: relative;\n outline: none !important;\n outline-offset: 0 !important;\n border-radius: ${opts.borderRadius};\n}\nhtml [${nsAttr}*=\" \"]::after {\n content: \"\";\n position: absolute;\n inset: 0;\n border-radius: inherit;\n pointer-events: none;\n z-index: 10;\n box-sizing: border-box;\n padding: ${opts.outlineWidth};\n background: var(--ff-combo-gradient, linear-gradient(90deg, #fecaca, #bfdbfe, #bbf7d0));\n -webkit-mask:\n linear-gradient(#000 0 0) content-box,\n linear-gradient(#000 0 0);\n -webkit-mask-composite: xor;\n mask-composite: exclude;\n}\nhtml [${nsAttr}*=\" \"]::before {\n box-sizing: border-box;\n position: absolute;\n inset: auto;\n margin: 0;\n width: max-content;\n max-width: min(18rem, calc(100% - 0.75rem));\n pointer-events: auto;\n z-index: 20;\n${badgePos}\n display: block;\n content: attr(${nsAttr});\n background: var(--ff-combo-badge-gradient, var(--ff-combo-gradient-soft, linear-gradient(90deg, #fff1f2, #eff6ff, #f0fdf4))) !important;\n color: var(--ff-combo-text, #111827) !important;\n border: 1px solid var(--ff-combo-badge-border, color-mix(in oklab, #94a3b8 50%, transparent)) !important;\n border-radius: 9999px;\n padding: 0.125rem 0.45rem;\n font-size: 0.625rem;\n line-height: 1.2;\n letter-spacing: 0.02em;\n font-weight: 600;\n white-space: nowrap;\n text-overflow: ellipsis;\n overflow: hidden;\n transition: opacity 0.14s ease, background 0.14s ease, border-color 0.14s ease;\n}\nhtml [${nsAttr}*=\" \"][data-ff-label]::before {\n content: attr(data-ff-label) !important;\n}\nhtml [${nsAttr}*=\" \"]:hover::before {\n opacity: 0.2;\n background: linear-gradient(90deg, #fff8f9, #f8fbff, #f8fff9);\n border-color: color-mix(in oklab, #94a3b8 35%, transparent);\n}\n`);\n\n // Combo chrome must be suppressed *after* generic combo rules so `!important` / order cannot be\n // overridden by `html [...][data-ff-label]::before { content: ... !important }`.\n for (const flag of Object.keys(runtime.flags)) {\n const { token, selIs } = selectorsForFlag(flag);\n chunks.push(`\nhtml[data-ff-badge-${token}=\"off\"] ${selIs}[${nsAttr}*=\" \"]::before,\nhtml[data-ff-badge-${token}=\"off\"] ${selIs}[${nsAttr}*=\" \"][data-ff-label]::before {\n display: none !important;\n content: none !important;\n}\nhtml[data-ff-badge-${token}=\"off\"] ${selIs}[${nsAttr}*=\" \"]:hover::before,\nhtml[data-ff-badge-${token}=\"off\"] ${selIs}[${nsAttr}*=\" \"][data-ff-label]:hover::before {\n display: none !important;\n opacity: 1 !important;\n background: none !important;\n border-color: transparent !important;\n}\nhtml[data-ff-outline-${token}=\"off\"] ${selIs}[${nsAttr}*=\" \"] {\n outline: none !important;\n outline-offset: 0 !important;\n}\nhtml[data-ff-outline-${token}=\"off\"] ${selIs}[${nsAttr}*=\" \"]::after {\n display: none !important;\n}\n`);\n }\n\n chunks.push(`\n.aff-route-frame {\n position: fixed;\n inset: 0.6rem;\n pointer-events: none;\n z-index: 10040;\n box-sizing: border-box;\n border-radius: 0.5rem;\n overflow: hidden;\n}\n.aff-route-frame::before {\n content: \"\";\n position: absolute;\n inset: 0;\n border-radius: inherit;\n padding: 2px;\n background: var(--ff-route-outline-gradient, linear-gradient(90deg, #ef4444, #3b82f6, #22c55e));\n -webkit-mask:\n linear-gradient(#000 0 0) content-box,\n linear-gradient(#000 0 0);\n -webkit-mask-composite: xor;\n mask-composite: exclude;\n}\n.aff-route-badge {\n position: fixed;\n top: 0.75rem;\n right: 0.75rem;\n z-index: 10060;\n pointer-events: auto;\n box-sizing: border-box;\n display: inline-flex;\n align-items: center;\n max-width: min(34rem, calc(100vw - 1.5rem));\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n border: 1px solid var(--ff-route-badge-border, color-mix(in oklab, #94a3b8 55%, transparent));\n border-radius: 9999px;\n padding: 0.15rem 0.5rem;\n font-size: 0.625rem;\n line-height: 1.2;\n letter-spacing: 0.02em;\n font-weight: 600;\n color: var(--ff-route-text, #111827);\n background: var(--ff-route-badge-gradient, linear-gradient(90deg, #fff1f2));\n opacity: 1;\n transition: opacity 0.14s ease, border-color 0.14s ease, background 0.14s ease;\n}\n.aff-route-badge:hover {\n opacity: 0.2;\n border-color: color-mix(in oklab, #94a3b8 35%, transparent);\n}\n.aff-route-disabled-overlay {\n position: fixed;\n inset: 0;\n z-index: 10050;\n box-sizing: border-box;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: clamp(1rem, 4vw, 2.5rem);\n text-align: center;\n font: 600 0.95rem/1.45 system-ui, -apple-system, Segoe UI, sans-serif;\n color: #f8fafc;\n background: rgba(15, 23, 42, 0.52);\n backdrop-filter: blur(2px);\n pointer-events: auto;\n}\n.aff-route-disabled-overlay > p {\n margin: 0;\n max-width: min(44rem, calc(100vw - 2rem));\n white-space: pre-line;\n}\n`);\n\n for (const flag of Object.keys(runtime.flags)) {\n const token = toToken(flag);\n chunks.push(`\n[data-ff-route~=\"${token}\"][data-ff-outline-${token}=\"off\"] #aff-route-frame {\n display: none !important;\n}\n[data-ff-route~=\"${token}\"][data-ff-badge-${token}=\"off\"] #aff-route-badge {\n display: none !important;\n}\n[data-ff-route~=\"${token}\"][data-ff-enabled-${token}=\"off\"] #aff-route-frame {\n display: none !important;\n}\n[data-ff-route~=\"${token}\"][data-ff-enabled-${token}=\"off\"] #aff-route-badge {\n display: none !important;\n}\n`);\n }\n\n return \"\\n\" + chunks.join(\"\\n\") + \"\\n\";\n}\n\n/**\n * CSS-only hiding for disabled flags. The Astro integration no longer injects this into\n * production builds — static HTML is culled instead. Kept for custom tooling or tests.\n */\nexport function createProductionGateStyles(\n runtime: ResolvedFeatureRuntime,\n): string {\n const nsAttr = `data-${toToken(runtime.namespace) || \"ff\"}`;\n const chunks: string[] = [];\n for (const [flag, enabled] of Object.entries(runtime.flags)) {\n if (enabled) continue;\n const token = toToken(flag);\n const valueSelectors = [`[${nsAttr}~=\"${token}\"]`];\n if (flag !== token) valueSelectors.push(`[${nsAttr}~=\"${flag}\"]`);\n chunks.push(\n `${[...valueSelectors, `[${nsAttr}-${token}]`].join(\", \")} { display: none !important; }`,\n );\n }\n return chunks.length ? `\\n${chunks.join(\"\\n\")}\\n` : \"\";\n}\n\nexport function buildAffDevBootstrapScript(\n flagTokens: string[],\n colors: Record<string, string>,\n outlineDefaults: Record<string, boolean>,\n badgeDefaults: Record<string, boolean>,\n routeFlags: Record<string, string[]>,\n flagNameToToken: Record<string, string>,\n namespace: string,\n): string {\n return inlineInvoke(affDevBootstrapRuntime, {\n flagTokens,\n colors,\n outlineDefaults,\n badgeDefaults,\n routeFlags,\n flagNameToToken,\n namespace: toToken(namespace) || \"ff\",\n });\n}\n","import { readdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { parse, type HTMLElement } from \"node-html-parser\";\nimport type { ResolvedFeatureRuntime } from \"./runtime\";\nimport { toToken } from \"./runtime\";\n\nfunction walkHtmlFiles(dir: string): string[] {\n const out: string[] = [];\n const entries = readdirSync(dir, { withFileTypes: true });\n for (const ent of entries) {\n const p = join(dir, ent.name);\n if (ent.isDirectory()) out.push(...walkHtmlFiles(p));\n else if (ent.isFile() && ent.name.endsWith(\".html\")) out.push(p);\n }\n return out;\n}\n\nfunction namespaceAttrName(namespace: string): string {\n return `data-${toToken(namespace) || \"ff\"}`;\n}\n\nfunction elementFlagSlugs(\n el: HTMLElement,\n nsAttr: string,\n knownTokens: readonly string[],\n): string[] {\n const slugSet = new Set<string>();\n const combined = el.getAttribute(nsAttr)?.trim();\n if (combined) {\n for (const part of combined.split(/\\s+/).filter(Boolean)) {\n slugSet.add(part);\n }\n }\n for (const tk of knownTokens) {\n if (el.hasAttribute(`${nsAttr}-${tk}`)) slugSet.add(tk);\n }\n return [...slugSet];\n}\n\nfunction slugToFlagNames(slug: string, flagNames: readonly string[]): string[] {\n const out: string[] = [];\n for (const name of flagNames) {\n if (slug === name || slug === toToken(name)) out.push(name);\n }\n return out;\n}\n\nfunction shouldCullElement(\n el: HTMLElement,\n runtime: ResolvedFeatureRuntime,\n nsAttr: string,\n knownTokens: readonly string[],\n flagNames: readonly string[],\n): boolean {\n const slugs = elementFlagSlugs(el, nsAttr, knownTokens);\n if (slugs.length === 0) return false;\n for (const slug of slugs) {\n const names = slugToFlagNames(slug, flagNames);\n if (names.length === 0) return false;\n }\n for (const slug of slugs) {\n const names = slugToFlagNames(slug, flagNames);\n for (const name of names) {\n if (!runtime.flags[name]) return true;\n }\n }\n return false;\n}\n\nfunction elementDepth(el: HTMLElement): number {\n let d = 0;\n let n: HTMLElement | null = el.parentNode as HTMLElement | null;\n while (n) {\n d++;\n n = n.parentNode as HTMLElement | null;\n }\n return d;\n}\n\n/**\n * Production static HTML: remove nodes gated by disabled flags (same rules as dev\n * `data-*` / `data-*-<token>`), strip dev-only route attributes on `<html>`, and drop\n * empty `<style>` tags left after `featureFlagStyles` is empty.\n */\nexport function cullProductionHtml(\n html: string,\n runtime: ResolvedFeatureRuntime,\n): string {\n const root = parse(html) as HTMLElement;\n const nsAttr = namespaceAttrName(runtime.namespace);\n const flagNames = Object.keys(runtime.flags);\n const knownTokens = flagNames.map((n) => toToken(n));\n\n const candidates = root.querySelectorAll(\"*\") as unknown as HTMLElement[];\n const toRemove: HTMLElement[] = [];\n for (const el of candidates) {\n if (shouldCullElement(el, runtime, nsAttr, knownTokens, flagNames)) {\n toRemove.push(el);\n }\n }\n toRemove.sort((a, b) => elementDepth(b) - elementDepth(a));\n for (const el of toRemove) {\n el.remove();\n }\n\n const htmlEl = root.querySelector(\"html\") as HTMLElement | null;\n if (htmlEl) {\n htmlEl.removeAttribute(\"data-ff-route\");\n htmlEl.removeAttribute(\"data-ff-route-label\");\n }\n\n const styles = root.querySelectorAll(\"style\") as unknown as HTMLElement[];\n for (const st of styles) {\n const text = st.textContent ?? \"\";\n if (!text.trim()) st.remove();\n }\n\n return root.outerHTML;\n}\n\n/**\n * Walk `outDir` (e.g. Astro `dist/`) and apply {@link cullProductionHtml} to every `.html` file.\n */\nexport function applyProductionHtmlCullToDist(\n outDir: string,\n runtime: ResolvedFeatureRuntime,\n): void {\n for (const file of walkHtmlFiles(outDir)) {\n const before = readFileSync(file, \"utf8\");\n const after = cullProductionHtml(before, runtime);\n if (after !== before) writeFileSync(file, after, \"utf8\");\n }\n}\n","import type { ResolvedFeatureRuntime } from \"./runtime\";\nimport { toToken } from \"./runtime\";\nimport { inlineInvoke } from \"./inline-script\";\nimport { affHeadInlineRuntime } from \"./dev-inline-runtimes\";\n\n/**\n * Single `injectScript('head-inline', …)` payload for `astro dev`: dev-only outline CSS,\n * `data-ff-route` on `<html>` (pathname + {@link ResolvedFeatureRuntime.routeFlags}), then\n * the feature-flag dev bootstrap (toolbar state, combo badges, route chrome).\n */\nexport function buildAffDevHeadInline(args: {\n runtime: ResolvedFeatureRuntime;\n featureFlagStyles: string;\n affDevBootstrap: string;\n}): string {\n const { runtime, featureFlagStyles, affDevBootstrap } = args;\n const flagNameToToken = Object.fromEntries(\n Object.keys(runtime.flags).map((name) => [name, toToken(name)]),\n );\n const setup = inlineInvoke(affHeadInlineRuntime, {\n featureFlagStyles,\n routeFlags: runtime.routeFlags,\n flagNameToToken,\n });\n return `${setup}\\n${affDevBootstrap}`;\n}\n","/**\n * Shared JS helper source used by generated browser scripts.\n * Keep this in sync with runtime.routePatternToPrefix semantics.\n */\nexport function routePrefixJsHelper(functionName: string): string {\n return `function ${functionName}(pattern){var p=String(pattern||'').trim();if(p.endsWith('/**'))p=p.slice(0,-3);else if(p.endsWith('/*'))p=p.slice(0,-2);else if(p.length>1&&p.endsWith('*'))p=p.slice(0,-1);return p.endsWith('/')?p:p+'/';}`;\n}\n","import { rmSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport {\n elementBadgePositionBlock,\n normalizeElementBadgeLayout,\n} from \"./badge-layout\";\nimport { DEV_TOOLBAR_FLAG_ICON_SVG } from \"./dev-toolbar-flag-icon\";\nimport type {\n ElementBadgeHorizontalAlign,\n ElementBadgeLayoutOptions,\n ElementBadgeVerticalAnchor,\n NormalizedElementBadgeLayout,\n} from \"./badge-layout\";\nimport {\n type FeatureFlagMap,\n type ResolveFeatureRuntimeOptions,\n type ResolvedFeatureRuntime,\n resolveFeatureFlagsByEnvironment,\n resolveFeatureRuntime,\n routePathsToPrune,\n shouldIncludeRoute,\n toEnumKey,\n toToken,\n} from \"./runtime\";\n\nexport type {\n ElementBadgeHorizontalAlign,\n ElementBadgeLayoutOptions,\n ElementBadgeVerticalAnchor,\n NormalizedElementBadgeLayout,\n};\nexport { elementBadgePositionBlock, normalizeElementBadgeLayout };\n\nexport type {\n DevOutlineCssOptions,\n DevOutlineHiddenStrategy,\n} from \"./dev-outline-css\";\n\nimport type { DevOutlineCssOptions } from \"./dev-outline-css\";\nimport {\n buildAffDevBootstrapScript,\n createFeatureFlagStyles,\n createProductionGateStyles,\n} from \"./dev-outline-css\";\nimport { applyProductionHtmlCullToDist } from \"./production-html-cull\";\nimport { buildAffDevHeadInline } from \"./dev-head-inject\";\nimport { routePrefixJsHelper } from \"./route-prefix-js\";\n\nexport { createFeatureFlagStyles, createProductionGateStyles };\nexport {\n cullProductionHtml,\n applyProductionHtmlCullToDist,\n} from \"./production-html-cull\";\n\nexport interface AstroFeatureFlagsOptions extends ResolveFeatureRuntimeOptions {\n css?: DevOutlineCssOptions;\n /** When false, keeps static build output untouched (no route pruning / HTML cull). */\n staticMinify?: boolean;\n}\n\n/** Prefer `prod` if present; else first non-`dev` key (sorted); else `\"prod\"`. */\nexport function primaryNonDevEnvironmentKey(\n flagsByEnvironment: Record<string, FeatureFlagMap>,\n): string {\n const keys = Object.keys(flagsByEnvironment);\n if (keys.includes(\"prod\")) return \"prod\";\n const nonDev = keys.filter((k) => k !== \"dev\").sort();\n return nonDev[0] ?? \"prod\";\n}\n\nfunction withDefaultEnvironments(\n options: ResolveFeatureRuntimeOptions = {},\n): ResolveFeatureRuntimeOptions {\n const mode = options.mode ?? process.env.NODE_ENV ?? \"development\";\n const raw =\n options.environments &&\n typeof options.environments === \"object\" &&\n !Array.isArray(options.environments)\n ? { ...options.environments }\n : {};\n delete raw.dev;\n const environments: NonNullable<ResolveFeatureRuntimeOptions[\"environments\"]> =\n {\n dev: {\n when: mode !== \"production\",\n },\n ...raw,\n };\n if (Object.keys(environments).length < 2) {\n environments.prod = environments.prod ?? {\n when: mode === \"production\",\n flags: {},\n };\n }\n return { ...options, environments };\n}\n\nexport function createVirtualModuleSource(\n runtime: ResolvedFeatureRuntime,\n css?: DevOutlineCssOptions,\n flagsByEnvironment: Record<string, FeatureFlagMap> = {},\n devBootstrap?: string,\n): string {\n const flagNames = Object.keys(runtime.flags);\n const flagTokens = flagNames.map((name) => toToken(name));\n const enumEntries = flagNames\n .map((name) => ` ${toEnumKey(name)}: ${JSON.stringify(name)}`)\n .join(\",\\n\");\n const tokenEntries = flagNames\n .map((name) => ` ${toEnumKey(name)}: ${JSON.stringify(toToken(name))}`)\n .join(\",\\n\");\n const styles = createFeatureFlagStyles(runtime, css);\n const colorsJson = JSON.stringify(runtime.flagColorsByToken, null, 2);\n const flagNameToToken = JSON.stringify(\n Object.fromEntries(flagNames.map((name) => [name, toToken(name)])),\n null,\n 2,\n );\n const defaultNonDevEnvironment =\n primaryNonDevEnvironmentKey(flagsByEnvironment);\n const bootstrap =\n devBootstrap ??\n buildAffDevBootstrapScript(\n flagTokens,\n { ...runtime.flagColorsByToken },\n { ...runtime.flagOutlineDefaultsByToken },\n { ...runtime.flagBadgeDefaultsByToken },\n { ...runtime.routeFlags },\n Object.fromEntries(flagNames.map((name) => [name, toToken(name)])),\n runtime.namespace,\n );\n const affRoutePatternToPrefix = routePrefixJsHelper(\"affRoutePatternToPrefix\");\n\n return `${affRoutePatternToPrefix}\n\nexport const FeatureFlag = Object.freeze({\n${enumEntries}\n});\n\nexport const FeatureToken = Object.freeze({\n${tokenEntries}\n});\n\nexport const featureFlagTokens = Object.freeze(${JSON.stringify(flagTokens)});\nexport const featureFlagColors = Object.freeze(${colorsJson});\nexport const featureFlags = Object.freeze(${JSON.stringify(runtime.flags, null, 2)});\nexport const featureFlagsByEnvironment = Object.freeze(${JSON.stringify(flagsByEnvironment, null, 2)});\nexport const defaultNonDevEnvironment = ${JSON.stringify(defaultNonDevEnvironment)};\nexport const featureRouteFlags = Object.freeze(${JSON.stringify(runtime.routeFlags, null, 2)});\nexport const featureNamespace = ${JSON.stringify(runtime.namespace)};\n/** True when Astro is running the dev server or dev build (import.meta.env.DEV). Not the same as the reserved \\`dev\\` environment layer — see \\`activeEnvironmentKey\\`. */\nexport const isAstroDev = import.meta.env.DEV;\nexport const activeEnvironmentKey = ${JSON.stringify(runtime.activeEnvironment)};\n\nexport const affDevBootstrap = import.meta.env.DEV ? ${JSON.stringify(bootstrap)} : '';\n\nexport function isFeatureEnabled(flag) {\n return Boolean(featureFlags[flag]);\n}\n\nexport function shouldRenderFeature(flag) {\n return isFeatureEnabled(flag);\n}\n\nexport function isFeatureRoute(pathname) {\n const normalized = pathname.endsWith('/') ? pathname : pathname + '/';\n return Object.keys(featureRouteFlags).some((route) => {\n const candidate = affRoutePatternToPrefix(route);\n return normalized === candidate || normalized.startsWith(candidate);\n });\n}\n\nexport function shouldIncludePath(pathname) {\n if (import.meta.env.DEV) return true;\n const normalized = pathname.endsWith('/') ? pathname : pathname + '/';\n for (const [route, flags] of Object.entries(featureRouteFlags)) {\n const candidate = affRoutePatternToPrefix(route);\n if (normalized === candidate || normalized.startsWith(candidate)) {\n return flags.every((flag) => isFeatureEnabled(flag));\n }\n }\n return true;\n}\n\nexport function flagsForEnvironment(envName) {\n var F = featureFlagsByEnvironment[envName];\n return F != null ? F : null;\n}\n\nexport function isFeatureEnabledForEnvironment(flag, envName) {\n var F = featureFlagsByEnvironment[envName];\n return !!(F && F[flag]);\n}\n\nexport function shouldIncludePathForEnvironment(pathname, envName) {\n var F = featureFlagsByEnvironment[envName];\n if (!F) return true;\n const normalized = pathname.endsWith('/') ? pathname : pathname + '/';\n for (const [route, flagList] of Object.entries(featureRouteFlags)) {\n const candidate = affRoutePatternToPrefix(route);\n if (normalized === candidate || normalized.startsWith(candidate)) {\n return flagList.every((flag) => Boolean(F[flag]));\n }\n }\n return true;\n}\n\nconst flagNameToToken = Object.freeze(${flagNameToToken});\n\nexport function matchedFeatureRoutePrefix(pathname) {\n if (!pathname) return null;\n const normalized = pathname.endsWith('/') ? pathname : pathname + '/';\n let best = null;\n let bestLen = -1;\n for (const routePrefix of Object.keys(featureRouteFlags)) {\n const route = affRoutePatternToPrefix(routePrefix);\n if (normalized === route || normalized.startsWith(route)) {\n if (route.length > bestLen) {\n bestLen = route.length;\n best = routePrefix;\n }\n }\n }\n return best;\n}\n\nexport function routeFeatureTokenForPath(pathname) {\n const prefix = matchedFeatureRoutePrefix(pathname);\n if (!prefix) return undefined;\n const flagNames = featureRouteFlags[prefix] || [];\n const first = flagNames[0];\n return first != null ? flagNameToToken[first] : undefined;\n}\n\nexport function routeFeatureTokensForPath(pathname) {\n if (!pathname) return [];\n const normalized = pathname.endsWith('/') ? pathname : pathname + '/';\n const out = [];\n const seen = new Set();\n for (const [routePrefix, flagNames] of Object.entries(featureRouteFlags)) {\n const route = affRoutePatternToPrefix(routePrefix);\n if (!(normalized === route || normalized.startsWith(route))) continue;\n for (const name of flagNames || []) {\n const token = flagNameToToken[name];\n if (!token || seen.has(token)) continue;\n seen.add(token);\n out.push(token);\n }\n }\n return out;\n}\n\nexport function routeFeatureMatchesForPath(pathname) {\n if (!pathname) return [];\n const normalized = pathname.endsWith('/') ? pathname : pathname + '/';\n var byFlag = {};\n for (const [routePattern, flagNames] of Object.entries(featureRouteFlags)) {\n const candidate = affRoutePatternToPrefix(routePattern);\n if (!(normalized === candidate || normalized.startsWith(candidate))) continue;\n for (const flagName of flagNames || []) {\n const token = flagNameToToken[flagName];\n if (!token) continue;\n if (!byFlag[flagName]) byFlag[flagName] = { flag: flagName, token, routes: [] };\n byFlag[flagName].routes.push(routePattern);\n }\n }\n return Object.values(byFlag);\n}\n\nexport const featureFlagStyles = import.meta.env.DEV ? ${JSON.stringify(styles)} : \"\";\n`;\n}\n\nexport function getResolvedFeatures(\n options: ResolveFeatureRuntimeOptions = {},\n): ResolvedFeatureRuntime {\n return resolveFeatureRuntime(withDefaultEnvironments(options));\n}\n\nexport function featureRouteIncluded(\n pathname: string,\n runtime: ResolvedFeatureRuntime,\n): boolean {\n return shouldIncludeRoute({\n pathname,\n routeFlags: runtime.routeFlags,\n flags: runtime.flags,\n isDev: runtime.isDev,\n });\n}\n\nexport type { FeatureFlagMap } from \"./runtime\";\nexport {\n longestMatchingRoutePrefix,\n mergeFlagsWithProcessEnvOverrides,\n resolveFeatureFlagsByEnvironment,\n routePatternToPrefix,\n} from \"./runtime\";\n\nexport default function astroFeatureFlags(\n options: AstroFeatureFlagsOptions = {},\n): any {\n const { css, staticMinify = true, ...flagOpts } = options;\n const opts = withDefaultEnvironments(flagOpts);\n const mode = opts.mode ?? process.env.NODE_ENV ?? \"development\";\n\n const runtime = resolveFeatureRuntime({\n ...opts,\n mode,\n });\n\n return {\n name: \"astro-feature-flags\",\n hooks: {\n \"astro:config:setup\": ({\n updateConfig,\n addDevToolbarApp,\n command,\n injectScript,\n }: {\n updateConfig: (config: unknown) => void;\n addDevToolbarApp?: (opts: {\n id: string;\n name: string;\n icon: string;\n entrypoint: string;\n }) => void;\n command?: string;\n injectScript?: (stage: string, content: string) => void;\n }) => {\n const flagNames = Object.keys(runtime.flags);\n const flagTokens = flagNames.map((name) => toToken(name));\n const flagsByEnvironment = resolveFeatureFlagsByEnvironment(opts);\n const flagNameToToken = Object.fromEntries(\n flagNames.map((name) => [name, toToken(name)]),\n );\n const bootstrap = buildAffDevBootstrapScript(\n flagTokens,\n { ...runtime.flagColorsByToken },\n { ...runtime.flagOutlineDefaultsByToken },\n { ...runtime.flagBadgeDefaultsByToken },\n { ...runtime.routeFlags },\n flagNameToToken,\n runtime.namespace,\n );\n\n if (\n command === \"dev\" &&\n runtime.isDev &&\n typeof addDevToolbarApp === \"function\"\n ) {\n addDevToolbarApp({\n id: \"astro-feature-flags\",\n name: \"Feature flags\",\n icon: DEV_TOOLBAR_FLAG_ICON_SVG,\n entrypoint: fileURLToPath(\n new URL(\"./dev-toolbar-app.mjs\", import.meta.url),\n ),\n });\n }\n if (\n command === \"dev\" &&\n runtime.isDev &&\n typeof injectScript === \"function\"\n ) {\n const styles = createFeatureFlagStyles(runtime, css);\n injectScript(\n \"head-inline\",\n buildAffDevHeadInline({\n runtime,\n featureFlagStyles: styles,\n affDevBootstrap: bootstrap,\n }),\n );\n }\n updateConfig({\n vite: {\n plugins: [\n {\n name: \"astro-feature-flags:virtual-module\",\n resolveId(id: string) {\n if (id === \"virtual:astro-feature-flags\")\n return \"\\0virtual:astro-feature-flags\";\n return null;\n },\n load(id: string) {\n if (id === \"\\0virtual:astro-feature-flags\") {\n return createVirtualModuleSource(\n runtime,\n css,\n flagsByEnvironment,\n bootstrap,\n );\n }\n return null;\n },\n },\n ],\n },\n });\n },\n \"astro:build:done\": ({ dir }: { dir: URL }) => {\n if (runtime.isDev || !staticMinify) return;\n const outDir = fileURLToPath(dir);\n const prunePaths = routePathsToPrune({\n routeFlags: runtime.routeFlags,\n flags: runtime.flags,\n });\n for (const routePath of prunePaths) {\n rmSync(join(outDir, routePath), { recursive: true, force: true });\n }\n applyProductionHtmlCullToDist(outDir, runtime);\n },\n },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,115 @@
1
+ //#region src/runtime.d.ts
2
+ type FeatureFlagMap = Record<string, boolean>;
3
+ type FeatureRouteMap = Record<string, string[]>;
4
+ type FeatureColorMap = Record<string, string>;
5
+ type FeatureBoolByTokenMap = Record<string, boolean>;
6
+ interface FeatureConfig {
7
+ namespace: string;
8
+ flags: FeatureFlagMap;
9
+ routeFlags: FeatureRouteMap;
10
+ colors: FeatureColorMap;
11
+ outlineDefaultsByToken: FeatureBoolByTokenMap;
12
+ badgeDefaultsByToken: FeatureBoolByTokenMap;
13
+ mode: string;
14
+ /** Which `environments` entry was used (reserved `dev` = all flags on, toolbar layer). */
15
+ activeEnvironment: string;
16
+ }
17
+ /**
18
+ * Declarative feature flags for Astro. Merge order: inline options → optional root
19
+ * {@link ResolveFeatureRuntimeOptions.jsonConfigPath} → optional per-environment
20
+ * `environments.<name>.jsonConfigPath` (merged when that layer is active).
21
+ *
22
+ * Exactly one `when: true` across environments unless `forceEnvironment` or `AFF_ENVIRONMENT`
23
+ * selects the layer. A reserved **`dev`** environment is required (all flags on at resolve time;
24
+ * toggles are dev-toolbar only) plus at least one other environment (e.g. `prod`).
25
+ */
26
+ interface ResolveFeatureRuntimeOptions {
27
+ /**
28
+ * Directory used to resolve relative {@link jsonConfigPath} values (root and per-environment).
29
+ * Defaults to `process.cwd()`.
30
+ */
31
+ configRoot?: string;
32
+ /** Optional main JSON file merged after inline `flags` / `environments`. */
33
+ jsonConfigPath?: string;
34
+ /** Passed through to `ResolvedFeatureRuntime.mode` (e.g. `process.env.NODE_ENV`). */
35
+ mode?: string;
36
+ /** Process env for `AFF_FEATURE_*` and `ASTRO_FEATURE_FLAGS` (skipped when active env is `dev`). */
37
+ env?: Record<string, string | undefined>;
38
+ /**
39
+ * Use this `environments` entry regardless of `when` / `AFF_ENVIRONMENT` (e.g. sitemaps, tests).
40
+ */
41
+ forceEnvironment?: string;
42
+ tokenNamespace?: string;
43
+ flags?: Record<string, {
44
+ color?: string;
45
+ colour?: string;
46
+ outline?: boolean;
47
+ badge?: boolean;
48
+ routes?: string[];
49
+ }>;
50
+ environments?: Record<string, {
51
+ when?: boolean;
52
+ flags?: Record<string, boolean>; /** Optional JSON merged after the root file when this environment is active. */
53
+ jsonConfigPath?: string;
54
+ }>;
55
+ }
56
+ declare function normalizePath(pathname: string): string;
57
+ /**
58
+ * Normalizes a route key from `ff.json` for prefix matching.
59
+ * Supports wildcards: `/blog/*`, `/docs/**` → prefix `/blog/`, `/docs/`.
60
+ */
61
+ declare function routePatternToPrefix(pattern: string): string;
62
+ declare function toEnumKey(flagName: string): string;
63
+ declare function toToken(flagName: string): string;
64
+ declare function loadFeatureConfig(options?: ResolveFeatureRuntimeOptions): FeatureConfig;
65
+ /** Applies `AFF_FEATURE_*` and `ASTRO_FEATURE_FLAGS` on top of a resolved flag map. */
66
+ declare function mergeFlagsWithProcessEnvOverrides(flags: FeatureFlagMap, env?: Record<string, string | undefined>): FeatureFlagMap;
67
+ /**
68
+ * Resolved flag booleans for each `environments` key, after the same process-env overrides
69
+ * as {@link resolveFeatureRuntime} (overrides skipped for the reserved `dev` layer).
70
+ */
71
+ declare function resolveFeatureFlagsByEnvironment(options?: ResolveFeatureRuntimeOptions): Record<string, FeatureFlagMap>;
72
+ /** Normalize colour map keys (flag names or tokens) to CSS tokens. */
73
+ declare function colorsToTokenMap(colors: FeatureColorMap): Record<string, string>;
74
+ interface ResolvedFeatureRuntime {
75
+ namespace: string;
76
+ mode: string;
77
+ /** True when the active environment is the reserved `dev` layer (toolbar + all flags on). */
78
+ isDev: boolean;
79
+ activeEnvironment: string;
80
+ flags: FeatureFlagMap;
81
+ routeFlags: FeatureRouteMap;
82
+ /** Outline/badge/route-frame colour per flag token (CSS colour strings). */
83
+ flagColorsByToken: Record<string, string>;
84
+ /** Default dev-toolbar outline/badge state per flag token. */
85
+ flagOutlineDefaultsByToken: Record<string, boolean>;
86
+ flagBadgeDefaultsByToken: Record<string, boolean>;
87
+ }
88
+ declare function resolveFeatureRuntime(options?: ResolveFeatureRuntimeOptions): ResolvedFeatureRuntime;
89
+ declare function isFlagEnabled(flags: FeatureFlagMap, flag: string): boolean;
90
+ declare function isRouteFlagged(pathname: string, routeFlags: FeatureRouteMap): boolean;
91
+ declare function shouldIncludeRoute({
92
+ pathname,
93
+ routeFlags,
94
+ flags,
95
+ isDev
96
+ }: {
97
+ pathname: string;
98
+ routeFlags: FeatureRouteMap;
99
+ flags: FeatureFlagMap;
100
+ isDev: boolean;
101
+ }): boolean;
102
+ declare function routePathsToPrune({
103
+ routeFlags,
104
+ flags
105
+ }: {
106
+ routeFlags: FeatureRouteMap;
107
+ flags: FeatureFlagMap;
108
+ }): string[];
109
+ /**
110
+ * Longest `routeFlags` key that matches `pathname` (prefix match, trailing slashes normalized).
111
+ */
112
+ declare function longestMatchingRoutePrefix(pathname: string, routeFlags: FeatureRouteMap): string | null;
113
+ //#endregion
114
+ export { FeatureBoolByTokenMap, FeatureColorMap, FeatureConfig, FeatureFlagMap, FeatureRouteMap, ResolveFeatureRuntimeOptions, ResolvedFeatureRuntime, colorsToTokenMap, isFlagEnabled, isRouteFlagged, loadFeatureConfig, longestMatchingRoutePrefix, mergeFlagsWithProcessEnvOverrides, normalizePath, resolveFeatureFlagsByEnvironment, resolveFeatureRuntime, routePathsToPrune, routePatternToPrefix, shouldIncludeRoute, toEnumKey, toToken };
115
+ //# sourceMappingURL=runtime.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.d.mts","names":[],"sources":["../src/runtime.ts"],"mappings":";KAGY,cAAA,GAAiB,MAAA;AAAA,KACjB,eAAA,GAAkB,MAAA;AAAA,KAClB,eAAA,GAAkB,MAAA;AAAA,KAClB,qBAAA,GAAwB,MAAA;AAAA,UAEnB,aAAA;EACf,SAAA;EACA,KAAA,EAAO,cAAA;EACP,UAAA,EAAY,eAAA;EACZ,MAAA,EAAQ,eAAA;EACR,sBAAA,EAAwB,qBAAA;EACxB,oBAAA,EAAsB,qBAAA;EACtB,IAAA;EAVyB;EAYzB,iBAAA;AAAA;;AAXF;;;;;AAEA;;;UAqBiB,4BAAA;EAlBH;;;;EAuBZ,UAAA;EApB2C;EAsB3C,cAAA;EA1BA;EA4BA,IAAA;EA3BA;EA6BA,GAAA,GAAM,MAAA;EA5BN;;;EAgCA,gBAAA;EACA,cAAA;EACA,KAAA,GAAQ,MAAA;IAGJ,KAAA;IACA,MAAA;IACA,OAAA;IACA,KAAA;IACA,MAAA;EAAA;EAGJ,YAAA,GAAe,MAAA;IAGX,IAAA;IACA,KAAA,GAAQ,MAAA;IAER,cAAA;EAAA;AAAA;AAAA,iBAmBU,aAAA,CAAc,QAAA;;;;;iBASd,oBAAA,CAAqB,OAAA;AAAA,iBAYrB,SAAA,CAAU,QAAA;AAAA,iBASV,OAAA,CAAQ,QAAA;AAAA,iBAiPR,iBAAA,CACd,OAAA,GAAS,4BAAA,GACR,aAAA;;iBA2Ca,iCAAA,CACd,KAAA,EAAO,cAAA,EACP,GAAA,GAAK,MAAA,+BAIJ,cAAA;;;;;iBAyBa,gCAAA,CACd,OAAA,GAAS,4BAAA,GACR,MAAA,SAAe,cAAA;;iBAkBF,gBAAA,CACd,MAAA,EAAQ,eAAA,GACP,MAAA;AAAA,UAQc,sBAAA;EACf,SAAA;EACA,IAAA;EA9YkB;EAgZlB,KAAA;EACA,iBAAA;EACA,KAAA,EAAO,cAAA;EACP,UAAA,EAAY,eAAA;EAhYgC;EAkY5C,iBAAA,EAAmB,MAAA;EAzXe;EA2XlC,0BAAA,EAA4B,MAAA;EAC5B,wBAAA,EAA0B,MAAA;AAAA;AAAA,iBAGZ,qBAAA,CACd,OAAA,GAAS,4BAAA,GACR,sBAAA;AAAA,iBAuBa,aAAA,CAAc,KAAA,EAAO,cAAA,EAAgB,IAAA;AAAA,iBAIrC,cAAA,CACd,QAAA,UACA,UAAA,EAAY,eAAA;AAAA,iBASE,kBAAA,CAAA;EACd,QAAA;EACA,UAAA;EACA,KAAA;EACA;AAAA;EAEA,QAAA;EACA,UAAA,EAAY,eAAA;EACZ,KAAA,EAAO,cAAA;EACP,KAAA;AAAA;AAAA,iBAec,iBAAA,CAAA;EACd,UAAA;EACA;AAAA;EAEA,UAAA,EAAY,eAAA;EACZ,KAAA,EAAO,cAAA;AAAA;;;;iBAeO,0BAAA,CACd,QAAA,UACA,UAAA,EAAY,eAAA"}
@@ -0,0 +1,280 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { isAbsolute, join } from "node:path";
3
+ //#region src/runtime.ts
4
+ function toBoolean(value, fallback = false) {
5
+ if (typeof value === "boolean") return value;
6
+ if (typeof value !== "string") return fallback;
7
+ const normalized = value.trim().toLowerCase();
8
+ if ([
9
+ "1",
10
+ "true",
11
+ "yes",
12
+ "on"
13
+ ].includes(normalized)) return true;
14
+ if ([
15
+ "0",
16
+ "false",
17
+ "no",
18
+ "off"
19
+ ].includes(normalized)) return false;
20
+ return fallback;
21
+ }
22
+ function readJsonIfExists(pathname) {
23
+ if (!existsSync(pathname)) return {};
24
+ return JSON.parse(readFileSync(pathname, "utf8"));
25
+ }
26
+ function normalizePath(pathname) {
27
+ if (!pathname) return "/";
28
+ return pathname.endsWith("/") ? pathname : `${pathname}/`;
29
+ }
30
+ /**
31
+ * Normalizes a route key from `ff.json` for prefix matching.
32
+ * Supports wildcards: `/blog/*`, `/docs/**` → prefix `/blog/`, `/docs/`.
33
+ */
34
+ function routePatternToPrefix(pattern) {
35
+ let p = pattern.trim();
36
+ if (p.endsWith("/**")) p = p.slice(0, -3);
37
+ else if (p.endsWith("/*")) p = p.slice(0, -2);
38
+ else if (p.endsWith("*") && p.length > 1) p = p.slice(0, -1);
39
+ return normalizePath(p);
40
+ }
41
+ function toEnumKey(flagName) {
42
+ return flagName.replace(/[^a-zA-Z0-9]+/g, " ").trim().split(/\s+/).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
43
+ }
44
+ function toToken(flagName) {
45
+ return flagName.replace(/[^a-zA-Z0-9]+/g, "-").replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/([a-zA-Z])([0-9])/g, "$1-$2").replace(/([0-9])([a-zA-Z])/g, "$1-$2").toLowerCase().replace(/-+/g, "-").replace(/^-|-$/g, "");
46
+ }
47
+ function mergeRecords(base, ...layers) {
48
+ const out = { ...base };
49
+ for (const layer of layers) for (const [k, v] of Object.entries(layer)) {
50
+ if (v === void 0) continue;
51
+ const cur = out[k];
52
+ if (v && typeof v === "object" && !Array.isArray(v) && cur && typeof cur === "object" && !Array.isArray(cur)) out[k] = mergeRecords(cur, v);
53
+ else out[k] = v;
54
+ }
55
+ return out;
56
+ }
57
+ function resolvePath(relativeOrAbsolute, configRoot) {
58
+ return isAbsolute(relativeOrAbsolute) ? relativeOrAbsolute : join(configRoot, relativeOrAbsolute);
59
+ }
60
+ function normalizeDeclarativeConfig(raw, mode = process.env.NODE_ENV ?? "development") {
61
+ const tokenNamespaceRaw = (typeof raw.tokenNamespace === "string" ? raw.tokenNamespace : void 0) ?? (typeof raw.namespace === "string" ? raw.namespace : void 0) ?? "ff";
62
+ const flagsRaw = raw.flags && typeof raw.flags === "object" && !Array.isArray(raw.flags) ? raw.flags : {};
63
+ const flags = {};
64
+ for (const [flagName, maybeDef] of Object.entries(flagsRaw)) {
65
+ if (!maybeDef || typeof maybeDef !== "object" || Array.isArray(maybeDef)) continue;
66
+ const def = maybeDef;
67
+ flags[flagName] = {
68
+ color: (typeof def.colour === "string" ? def.colour : void 0) ?? (typeof def.color === "string" ? def.color : void 0),
69
+ outline: typeof def.outline === "boolean" ? def.outline : true,
70
+ badge: typeof def.badge === "boolean" ? def.badge : true,
71
+ routes: Array.isArray(def.routes) ? def.routes.filter((v) => typeof v === "string") : []
72
+ };
73
+ }
74
+ const envRaw = raw.environments && typeof raw.environments === "object" && !Array.isArray(raw.environments) ? raw.environments : {};
75
+ const environments = {};
76
+ for (const [envName, maybeEnv] of Object.entries(envRaw)) {
77
+ if (envName === "dev") continue;
78
+ if (!maybeEnv || typeof maybeEnv !== "object" || Array.isArray(maybeEnv)) continue;
79
+ const env = maybeEnv;
80
+ const envFlagsRaw = env.flags && typeof env.flags === "object" && !Array.isArray(env.flags) ? env.flags : {};
81
+ const envFlags = {};
82
+ for (const [flagName, value] of Object.entries(envFlagsRaw)) envFlags[flagName] = Boolean(value);
83
+ const when = typeof env.when === "boolean" ? env.when : void 0;
84
+ const layerJson = typeof env.jsonConfigPath === "string" ? env.jsonConfigPath : void 0;
85
+ environments[envName] = {
86
+ ...when !== void 0 ? { when } : {},
87
+ ...layerJson ? { jsonConfigPath: layerJson } : {},
88
+ flags: envFlags
89
+ };
90
+ }
91
+ environments.dev = {
92
+ when: mode !== "production",
93
+ flags: {}
94
+ };
95
+ return {
96
+ tokenNamespace: tokenNamespaceRaw,
97
+ flags,
98
+ environments
99
+ };
100
+ }
101
+ function assertEnvironmentShape(environments) {
102
+ const keys = Object.keys(environments);
103
+ if (!environments.dev) throw new Error("astro-feature-flags: environments must include a reserved \"dev\" entry.");
104
+ if (keys.length < 2) throw new Error("astro-feature-flags: environments must include `dev` plus at least one other layer (e.g. `prod`).");
105
+ }
106
+ function countActiveWhen(environments) {
107
+ return Object.values(environments).filter((e) => e.when === true).length;
108
+ }
109
+ function resolveAffEnvironment(options) {
110
+ return (options.env ?? process.env).AFF_ENVIRONMENT?.trim();
111
+ }
112
+ function resolveActiveEnvironmentName(normalized, options) {
113
+ const { environments } = normalized;
114
+ assertEnvironmentShape(environments);
115
+ const forced = options.forceEnvironment?.trim();
116
+ if (forced && environments[forced]) return forced;
117
+ const aff = resolveAffEnvironment(options);
118
+ if (aff && environments[aff]) return aff;
119
+ const n = countActiveWhen(environments);
120
+ if (n !== 1) {
121
+ const activeNames = Object.entries(environments).filter(([, e]) => e.when === true).map(([k]) => k);
122
+ const devWhen = environments.dev?.when;
123
+ const hint = n > 1 && environments.dev && devWhen === true ? " The reserved `dev` environment is active for this `mode` while another layer also has `when: true`. Pin one layer with `forceEnvironment` or `AFF_ENVIRONMENT`, or adjust non-dev `when` predicates." : "";
124
+ throw new Error(`astro-feature-flags: exactly one environment must have when: true (found ${n}${activeNames.length ? `: ${activeNames.join(", ")}` : ""}).${hint} Set forceEnvironment or AFF_ENVIRONMENT to pick a layer explicitly.`);
125
+ }
126
+ for (const [name, cfg] of Object.entries(environments)) if (cfg.when === true) return name;
127
+ throw new Error("astro-feature-flags: internal error resolving environment.");
128
+ }
129
+ function readBaseMergedRaw(options) {
130
+ const configRoot = options.configRoot ?? process.cwd();
131
+ const { tokenNamespace = "ff", flags = {}, environments = {} } = options;
132
+ let merged = {
133
+ tokenNamespace,
134
+ flags,
135
+ environments
136
+ };
137
+ if (options.jsonConfigPath) {
138
+ const p = resolvePath(options.jsonConfigPath, configRoot);
139
+ merged = mergeRecords(merged, readJsonIfExists(p));
140
+ }
141
+ return merged;
142
+ }
143
+ function mergePerEnvironmentJson(baseRaw, envName, configRoot, mode) {
144
+ if (envName === "dev") return baseRaw;
145
+ const rel = normalizeDeclarativeConfig(baseRaw, mode).environments[envName]?.jsonConfigPath;
146
+ if (!rel) return baseRaw;
147
+ return mergeRecords(baseRaw, readJsonIfExists(resolvePath(rel, configRoot)));
148
+ }
149
+ function readMergedRawForActiveEnv(options, envName) {
150
+ const configRoot = options.configRoot ?? process.cwd();
151
+ const mode = options.mode ?? process.env.NODE_ENV ?? "development";
152
+ return mergePerEnvironmentJson(readBaseMergedRaw(options), envName, configRoot, mode);
153
+ }
154
+ function loadFeatureConfig(options = {}) {
155
+ const mode = options.mode ?? process.env.NODE_ENV ?? "development";
156
+ const envName = resolveActiveEnvironmentName(normalizeDeclarativeConfig(readBaseMergedRaw(options), mode), options);
157
+ const normalized = normalizeDeclarativeConfig(readMergedRawForActiveEnv(options, envName), mode);
158
+ const envFlags = normalized.environments[envName]?.flags ?? {};
159
+ const routeFlags = {};
160
+ const colors = {};
161
+ const outlineDefaultsByToken = {};
162
+ const badgeDefaultsByToken = {};
163
+ const resolvedFlags = {};
164
+ for (const [flagName, flagDef] of Object.entries(normalized.flags)) {
165
+ if (envName === "dev") resolvedFlags[flagName] = true;
166
+ else resolvedFlags[flagName] = envFlags[flagName] === true;
167
+ if (flagDef.color) colors[flagName] = flagDef.color;
168
+ const token = toToken(flagName);
169
+ outlineDefaultsByToken[token] = flagDef.outline;
170
+ badgeDefaultsByToken[token] = flagDef.badge;
171
+ for (const route of flagDef.routes) routeFlags[route] = [...routeFlags[route] ?? [], flagName];
172
+ }
173
+ return {
174
+ namespace: normalized.tokenNamespace || "ff",
175
+ flags: resolvedFlags,
176
+ routeFlags,
177
+ colors,
178
+ outlineDefaultsByToken,
179
+ badgeDefaultsByToken,
180
+ mode,
181
+ activeEnvironment: envName
182
+ };
183
+ }
184
+ /** Applies `AFF_FEATURE_*` and `ASTRO_FEATURE_FLAGS` on top of a resolved flag map. */
185
+ function mergeFlagsWithProcessEnvOverrides(flags, env = process.env) {
186
+ const resolvedFlags = { ...flags };
187
+ for (const [key, value] of Object.entries(resolvedFlags)) resolvedFlags[key] = toBoolean(env[`AFF_FEATURE_${toToken(key).replace(/-/g, "_").toUpperCase()}`], Boolean(value));
188
+ const envMapRaw = env.ASTRO_FEATURE_FLAGS;
189
+ if (envMapRaw) try {
190
+ const envMap = JSON.parse(envMapRaw);
191
+ for (const [flag, value] of Object.entries(envMap)) resolvedFlags[flag] = Boolean(value);
192
+ } catch {}
193
+ return resolvedFlags;
194
+ }
195
+ /**
196
+ * Resolved flag booleans for each `environments` key, after the same process-env overrides
197
+ * as {@link resolveFeatureRuntime} (overrides skipped for the reserved `dev` layer).
198
+ */
199
+ function resolveFeatureFlagsByEnvironment(options = {}) {
200
+ const normalized = normalizeDeclarativeConfig(readBaseMergedRaw(options), options.mode ?? process.env.NODE_ENV ?? "development");
201
+ const envKeys = Object.keys(normalized.environments);
202
+ const proc = options.env ?? process.env;
203
+ const out = {};
204
+ for (const envName of envKeys) {
205
+ const cfg = loadFeatureConfig({
206
+ ...options,
207
+ forceEnvironment: envName
208
+ });
209
+ out[envName] = envName === "dev" ? { ...cfg.flags } : mergeFlagsWithProcessEnvOverrides({ ...cfg.flags }, proc);
210
+ }
211
+ return out;
212
+ }
213
+ /** Normalize colour map keys (flag names or tokens) to CSS tokens. */
214
+ function colorsToTokenMap(colors) {
215
+ const out = {};
216
+ for (const [key, value] of Object.entries(colors)) out[toToken(key)] = value;
217
+ return out;
218
+ }
219
+ function resolveFeatureRuntime(options = {}) {
220
+ const env = options.env ?? process.env;
221
+ const config = loadFeatureConfig(options);
222
+ const isDevLayer = config.activeEnvironment === "dev";
223
+ const resolvedFlags = isDevLayer ? { ...config.flags } : mergeFlagsWithProcessEnvOverrides(config.flags, env);
224
+ const colorByToken = colorsToTokenMap(config.colors);
225
+ return {
226
+ namespace: config.namespace,
227
+ mode: config.mode,
228
+ isDev: isDevLayer,
229
+ activeEnvironment: config.activeEnvironment,
230
+ flags: resolvedFlags,
231
+ routeFlags: config.routeFlags,
232
+ flagColorsByToken: colorByToken,
233
+ flagOutlineDefaultsByToken: config.outlineDefaultsByToken,
234
+ flagBadgeDefaultsByToken: config.badgeDefaultsByToken
235
+ };
236
+ }
237
+ function isFlagEnabled(flags, flag) {
238
+ return Boolean(flags?.[flag]);
239
+ }
240
+ function isRouteFlagged(pathname, routeFlags) {
241
+ const normalized = normalizePath(pathname);
242
+ return Object.keys(routeFlags || {}).some((routePrefix) => {
243
+ const route = routePatternToPrefix(routePrefix);
244
+ return normalized === route || normalized.startsWith(route);
245
+ });
246
+ }
247
+ function shouldIncludeRoute({ pathname, routeFlags, flags, isDev }) {
248
+ if (isDev) return true;
249
+ const normalized = normalizePath(pathname);
250
+ for (const [routePrefix, flagNames] of Object.entries(routeFlags || {})) {
251
+ const route = routePatternToPrefix(routePrefix);
252
+ if (normalized === route || normalized.startsWith(route)) return (flagNames || []).every((flagName) => isFlagEnabled(flags, flagName));
253
+ }
254
+ return true;
255
+ }
256
+ function routePathsToPrune({ routeFlags, flags }) {
257
+ return Object.entries(routeFlags || {}).filter(([, flagNames]) => (flagNames || []).some((flagName) => !isFlagEnabled(flags, flagName))).map(([routePath]) => routePatternToPrefix(routePath).replace(/^\/+|\/+$/g, "")).filter(Boolean);
258
+ }
259
+ /**
260
+ * Longest `routeFlags` key that matches `pathname` (prefix match, trailing slashes normalized).
261
+ */
262
+ function longestMatchingRoutePrefix(pathname, routeFlags) {
263
+ const normalized = normalizePath(pathname);
264
+ let best = null;
265
+ let bestLen = -1;
266
+ for (const routePrefix of Object.keys(routeFlags || {})) {
267
+ const route = routePatternToPrefix(routePrefix);
268
+ if (normalized === route || normalized.startsWith(route)) {
269
+ if (route.length > bestLen) {
270
+ bestLen = route.length;
271
+ best = routePrefix;
272
+ }
273
+ }
274
+ }
275
+ return best;
276
+ }
277
+ //#endregion
278
+ export { colorsToTokenMap, isFlagEnabled, isRouteFlagged, loadFeatureConfig, longestMatchingRoutePrefix, mergeFlagsWithProcessEnvOverrides, normalizePath, resolveFeatureFlagsByEnvironment, resolveFeatureRuntime, routePathsToPrune, routePatternToPrefix, shouldIncludeRoute, toEnumKey, toToken };
279
+
280
+ //# sourceMappingURL=runtime.mjs.map