@at-flux/astro-feature-flags 1.0.3 → 1.0.4
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.
- package/README.md +13 -12
- package/dist/index.d.mts +74 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +195 -12
- package/dist/index.mjs.map +1 -1
- package/docs/how-to/hide-from-sitemaps.md +56 -10
- package/package.json +1 -1
- package/src/dev-head-inject.ts +2 -0
- package/src/dev-inline-runtimes.ts +29 -1
- package/src/dev-outline-css.ts +42 -5
- package/src/index.ts +49 -3
- package/src/sitemap-prune.ts +171 -0
package/dist/index.mjs.map
CHANGED
|
@@ -1 +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(\n (line, idx, arr) => line.length > 0 || (idx > 0 && idx < arr.length - 1),\n );\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\n // Inject the dev-chrome stylesheet. Astro's ClientRouter swaps <head> on a\n // view-transition navigation, and a runtime-injected <style> is dropped on\n // the first swap unless marked persistent — which is why the element\n // outline/badge chrome vanished after navigating. Mark it\n // data-astro-transition-persist so ClientRouter keeps a single copy, and\n // keep the injector idempotent + re-run it on every nav so the styles stay\n // constant across pages even where a swap removes them.\n const ensureFeatureFlagStyles = () => {\n if (document.querySelector(\"style[data-astro-feature-flags]\")) return;\n const s = document.createElement(\"style\");\n s.setAttribute(\"data-astro-feature-flags\", \"\");\n s.setAttribute(\"data-astro-transition-persist\", \"astro-feature-flags\");\n s.textContent = featureFlagStyles;\n (document.head || document.documentElement).appendChild(s);\n };\n ensureFeatureFlagStyles();\n\n const affPath = () => {\n let p =\n typeof location !== \"undefined\" && location.pathname\n ? location.pathname\n : \"/\";\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\")\n 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 ensureFeatureFlagStyles();\n document.documentElement.setAttribute(\n \"data-ff-route\",\n affTokens(affPath()).join(\" \"),\n );\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 (\n a === \"data-ff-route\" ||\n (a && a.indexOf(\"data-ff-enabled-\") === 0)\n ) {\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) =>\n 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 =\n 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(\n \"--ff-combo-gradient\",\n `linear-gradient(90deg, ${strong.join(\", \")})`,\n );\n el.style.setProperty(\n \"--ff-combo-gradient-soft\",\n `linear-gradient(90deg, ${soft.join(\", \")})`,\n );\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(\n \"--ff-combo-badge-gradient\",\n `linear-gradient(90deg, ${soft.join(\", \")})`,\n );\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(\n (c) => `color-mix(in oklab, white 86%, ${c} 14%)`,\n );\n root.style.setProperty(\n `--${N}-route-gradient`,\n `linear-gradient(90deg, ${soft.join(\", \")})`,\n );\n root.style.setProperty(\n \"--ff-route-outline-gradient\",\n `linear-gradient(90deg, ${strong.join(\", \")})`,\n );\n root.style.setProperty(\n \"--ff-route-badge-gradient\",\n `linear-gradient(90deg, ${soft.join(\", \")})`,\n );\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, {\n once: true,\n });\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 ??\n css?.badgeLabelDev ??\n 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 <html> 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\n .map((s) => `${s}:not([${nsAttr}*=\" \"])`)\n .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<\n 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(\n \"affRoutePatternToPrefix\",\n );\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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"}
|
|
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/sitemap-prune.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(\n (line, idx, arr) => line.length > 0 || (idx > 0 && idx < arr.length - 1),\n );\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 devLayer: 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 {\n featureFlagStyles,\n routeFlags: RF,\n flagNameToToken: M,\n devLayer: L,\n } = payload;\n\n // Layer priority is fixed by the order layers are first declared, in\n // document order. The dev sheet is appended, so it lands after the app's\n // own stylesheets and its layer would sort last — above Tailwind's\n // `utilities` — which is the opposite of what the layer is for. A style\n // element holding nothing but the order statement, inserted as the first\n // child of <head>, declares the layer before any app sheet and so pins it\n // below everything the app writes.\n const ensureLayerOrder = () => {\n if (document.querySelector(\"style[data-astro-feature-flags-layer]\"))\n return;\n const head = document.head || document.documentElement;\n const s = document.createElement(\"style\");\n s.setAttribute(\"data-astro-feature-flags-layer\", \"\");\n s.setAttribute(\n \"data-astro-transition-persist\",\n \"astro-feature-flags-layer\",\n );\n s.textContent = `@layer ${L};`;\n head.insertBefore(s, head.firstChild);\n };\n\n // Inject the dev-chrome stylesheet. Astro's ClientRouter swaps <head> on a\n // view-transition navigation, and a runtime-injected <style> is dropped on\n // the first swap unless marked persistent — which is why the element\n // outline/badge chrome vanished after navigating. Mark it\n // data-astro-transition-persist so ClientRouter keeps a single copy, and\n // keep the injector idempotent + re-run it on every nav so the styles stay\n // constant across pages even where a swap removes them.\n const ensureFeatureFlagStyles = () => {\n ensureLayerOrder();\n if (document.querySelector(\"style[data-astro-feature-flags]\")) return;\n const s = document.createElement(\"style\");\n s.setAttribute(\"data-astro-feature-flags\", \"\");\n s.setAttribute(\"data-astro-transition-persist\", \"astro-feature-flags\");\n s.textContent = featureFlagStyles;\n (document.head || document.documentElement).appendChild(s);\n };\n ensureFeatureFlagStyles();\n\n const affPath = () => {\n let p =\n typeof location !== \"undefined\" && location.pathname\n ? location.pathname\n : \"/\";\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\")\n 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 ensureFeatureFlagStyles();\n document.documentElement.setAttribute(\n \"data-ff-route\",\n affTokens(affPath()).join(\" \"),\n );\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 (\n a === \"data-ff-route\" ||\n (a && a.indexOf(\"data-ff-enabled-\") === 0)\n ) {\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) =>\n 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 =\n 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(\n \"--ff-combo-gradient\",\n `linear-gradient(90deg, ${strong.join(\", \")})`,\n );\n el.style.setProperty(\n \"--ff-combo-gradient-soft\",\n `linear-gradient(90deg, ${soft.join(\", \")})`,\n );\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(\n \"--ff-combo-badge-gradient\",\n `linear-gradient(90deg, ${soft.join(\", \")})`,\n );\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(\n (c) => `color-mix(in oklab, white 86%, ${c} 14%)`,\n );\n root.style.setProperty(\n `--${N}-route-gradient`,\n `linear-gradient(90deg, ${soft.join(\", \")})`,\n );\n root.style.setProperty(\n \"--ff-route-outline-gradient\",\n `linear-gradient(90deg, ${strong.join(\", \")})`,\n );\n root.style.setProperty(\n \"--ff-route-badge-gradient\",\n `linear-gradient(90deg, ${soft.join(\", \")})`,\n );\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, {\n once: true,\n });\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\n/**\n * Cascade layer for the declarations the app must be able to override without\n * writing `!important`. Named rather than anonymous so the order statement in\n * {@link affHeadInlineRuntime} can name it too.\n */\nexport const DEV_LAYER = \"aff-dev\";\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 ??\n css?.badgeLabelDev ??\n 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 <html> 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 *\n * The badge is an absolutely positioned `::before`, so its host has to be a\n * containing block, and the host also wants rounding for the outline to follow.\n * Both of those are the app's business, not ours: a flagged element that the app\n * positions itself, or rounds itself, must keep doing so. Marking a flagged\n * element must not move it.\n *\n * So `position` and `border-radius` are emitted inside `:where()` *and* inside\n * the `aff-dev` cascade layer. `:where()` alone is not enough: an unlayered rule\n * beats a layered one before specificity is ever consulted, so a zero-specificity\n * unlayered rule still overrides Tailwind's `.rounded-full` and `.absolute`,\n * which live in `@layer utilities`. Layering ours is what lets the app win.\n *\n * Layer priority follows the order layers are first declared, so the layer only\n * sorts below the app's own layers if `@layer aff-dev;` appears before them in\n * document order. The sheet opens with that statement for the case where it\n * lands first, and {@link affHeadInlineRuntime} also prepends a style element\n * carrying it as the first child of `<head>`, which is what actually guarantees\n * the order at runtime.\n *\n * Everything else here is dev chrome the app has no opinion about, and stays at\n * its natural specificity.\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\n .map((s) => `${s}:not([${nsAttr}*=\" \"])`)\n .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@layer ${DEV_LAYER} {\n :where(${selOutline}) {\n position: relative;\n border-radius: ${opts.borderRadius};\n }\n}\n${selOutline} {\n outline: ${opts.outlineWidth} solid var(--aff-outline-c-${token});\n outline-offset: ${opts.outlineOffset};\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@layer ${DEV_LAYER} {\n :where([${nsAttr}*=\" \"]) {\n position: relative;\n border-radius: ${opts.borderRadius};\n }\n}\n[${nsAttr}*=\" \"] {\n outline: none !important;\n outline-offset: 0 !important;\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@layer ${DEV_LAYER};\\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 { readdirSync, readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport { basename, join } from \"node:path\";\nimport type { ResolvedFeatureRuntime } from \"./runtime\";\nimport { shouldIncludeRoute } from \"./runtime\";\n\n/**\n * `@astrojs/sitemap` names its output `sitemap-0.xml`, `sitemap-1.xml`, … alongside a\n * `sitemap-index.xml`. Other generators write a plain `sitemap.xml`. Match the family.\n */\nconst SITEMAP_FILE = /^sitemap[\\w.-]*\\.xml$/i;\n\nfunction walkSitemapFiles(dir: string): string[] {\n const out: string[] = [];\n for (const ent of readdirSync(dir, { withFileTypes: true })) {\n const p = join(dir, ent.name);\n if (ent.isDirectory()) out.push(...walkSitemapFiles(p));\n else if (ent.isFile() && SITEMAP_FILE.test(ent.name)) out.push(p);\n }\n return out;\n}\n\n/**\n * `<loc>` is XML, so the five predefined entities and numeric references are all\n * legal in it. `&` is the one that actually turns up (a query string with two\n * parameters), but a path that came through an escaper wholesale can carry the\n * others, and an entity left undecoded turns into a pathname that matches no\n * route and is silently kept.\n */\nfunction decodeXmlEntities(value: string): string {\n return value\n .replace(/&#x([0-9a-f]+);/gi, (_, hex: string) =>\n String.fromCodePoint(Number.parseInt(hex, 16)),\n )\n .replace(/&#(\\d+);/g, (_, dec: string) =>\n String.fromCodePoint(Number.parseInt(dec, 10)),\n )\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/"/g, '\"')\n .replace(/'/g, \"'\")\n .replace(/&/g, \"&\");\n}\n\nfunction locPathname(entry: string): string | null {\n const loc = /<loc>\\s*([\\s\\S]*?)\\s*<\\/loc>/i.exec(entry)?.[1];\n if (!loc) return null;\n const href = decodeXmlEntities(loc).trim();\n try {\n return new URL(href).pathname;\n } catch {\n return href.startsWith(\"/\") ? href : null;\n }\n}\n\n/**\n * Drop every `<url>` whose `<loc>` points at a route this runtime prunes.\n *\n * The alternative is asking every site to duplicate the flag decision in its own\n * `sitemap({ filter })`, which is what the docs used to say and what the site this\n * package was written for got subtly wrong: a substring test culled `/blog/about-x/`\n * along with `/about/`. Deciding it here, from the same runtime that deletes the\n * files, means the two answers cannot drift.\n *\n * String surgery rather than an XML parse, so the untouched entries come back\n * byte-identical and the diff of a rebuild stays readable.\n */\nexport function pruneSitemapXml(\n xml: string,\n runtime: ResolvedFeatureRuntime,\n): string {\n return xml.replace(/[ \\t]*<url>[\\s\\S]*?<\\/url>\\s*/gi, (entry) => {\n const pathname = locPathname(entry);\n if (!pathname) return entry;\n const keep = shouldIncludeRoute({\n pathname,\n routeFlags: runtime.routeFlags,\n flags: runtime.flags,\n isDev: false,\n });\n return keep ? entry : \"\";\n });\n}\n\n/** Number of `<url>` entries left in a urlset. */\nexport function sitemapUrlCount(xml: string): number {\n return (xml.match(/<url>/gi) ?? []).length;\n}\n\n/**\n * Remove the `<sitemap>` entries of an index that point at files which no longer exist.\n */\nexport function pruneSitemapIndexXml(\n xml: string,\n removedFiles: readonly string[],\n): string {\n if (!removedFiles.length) return xml;\n const removed = new Set(removedFiles);\n return xml.replace(/[ \\t]*<sitemap>[\\s\\S]*?<\\/sitemap>\\s*/gi, (entry) => {\n const pathname = locPathname(entry);\n if (!pathname) return entry;\n return removed.has(basename(pathname)) ? \"\" : entry;\n });\n}\n\nexport interface SitemapPruneResult {\n /** Sitemap files rewritten with fewer entries. */\n rewritten: string[];\n /** Sitemap files deleted because nothing was left in them. */\n removed: string[];\n /** Whether any sitemap file was found at all. */\n found: boolean;\n}\n\n/**\n * Walk `outDir` (Astro `dist/`) and take the pruned routes out of every sitemap.\n *\n * Runs in `astro:build:done`, which means this integration has to sit **after**\n * `@astrojs/sitemap` in `integrations` — Astro runs the hook in array order, and a\n * sitemap written after this pass would keep its dead URLs.\n */\nexport function applySitemapPruneToDist(\n outDir: string,\n runtime: ResolvedFeatureRuntime,\n): SitemapPruneResult {\n const result: SitemapPruneResult = {\n rewritten: [],\n removed: [],\n found: false,\n };\n let files: string[];\n try {\n files = walkSitemapFiles(outDir);\n } catch {\n return result;\n }\n result.found = files.length > 0;\n\n const indexes: string[] = [];\n for (const file of files) {\n const before = readFileSync(file, \"utf8\");\n if (!/<urlset[\\s>]/i.test(before)) {\n if (/<sitemapindex[\\s>]/i.test(before)) indexes.push(file);\n continue;\n }\n const after = pruneSitemapXml(before, runtime);\n if (after === before) continue;\n if (sitemapUrlCount(after) === 0) {\n rmSync(file, { force: true });\n result.removed.push(file);\n } else {\n writeFileSync(file, after, \"utf8\");\n result.rewritten.push(file);\n }\n }\n\n const removedNames = result.removed.map((file) => basename(file));\n for (const file of indexes) {\n const before = readFileSync(file, \"utf8\");\n const after = pruneSitemapIndexXml(before, removedNames);\n if (after === before) continue;\n if (!/<sitemap>/i.test(after)) {\n rmSync(file, { force: true });\n result.removed.push(file);\n } else {\n writeFileSync(file, after, \"utf8\");\n result.rewritten.push(file);\n }\n }\n\n return result;\n}\n","import type { ResolvedFeatureRuntime } from \"./runtime\";\nimport { toToken } from \"./runtime\";\nimport { inlineInvoke } from \"./inline-script\";\nimport { affHeadInlineRuntime } from \"./dev-inline-runtimes\";\nimport { DEV_LAYER } from \"./dev-outline-css\";\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 devLayer: DEV_LAYER,\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 DEV_LAYER,\n} from \"./dev-outline-css\";\nimport { applyProductionHtmlCullToDist } from \"./production-html-cull\";\nimport { applySitemapPruneToDist } from \"./sitemap-prune\";\nimport { buildAffDevHeadInline } from \"./dev-head-inject\";\nimport { routePrefixJsHelper } from \"./route-prefix-js\";\n\nexport { createFeatureFlagStyles, createProductionGateStyles, DEV_LAYER };\nexport {\n cullProductionHtml,\n applyProductionHtmlCullToDist,\n} from \"./production-html-cull\";\nexport type { SitemapPruneResult } from \"./sitemap-prune\";\nexport {\n applySitemapPruneToDist,\n pruneSitemapIndexXml,\n pruneSitemapXml,\n sitemapUrlCount,\n} from \"./sitemap-prune\";\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 * When false, leaves generated sitemaps alone. On by default: a pruned route that is\n * still advertised in `sitemap-0.xml` is a 404 handed to every crawler that reads it.\n *\n * Needs this integration to sit **after** `@astrojs/sitemap` in `integrations`, because\n * Astro runs `astro:build:done` in array order.\n */\n pruneSitemap?: 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<\n 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(\n \"affRoutePatternToPrefix\",\n );\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 {\n css,\n staticMinify = true,\n pruneSitemap = true,\n ...flagOpts\n } = 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 /**\n * Set in `astro:config:setup`, read in `astro:build:done`, so that a sitemap this\n * pass never saw can be reported as an ordering mistake rather than silently skipped.\n */\n let sitemapIntegrationPresent = false;\n\n return {\n name: \"astro-feature-flags\",\n hooks: {\n \"astro:config:setup\": ({\n config,\n updateConfig,\n addDevToolbarApp,\n command,\n injectScript,\n }: {\n config?: { integrations?: { name?: string }[] };\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 sitemapIntegrationPresent = (config?.integrations ?? []).some(\n (integration) => integration?.name === \"@astrojs/sitemap\",\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\": ({\n dir,\n logger,\n }: {\n dir: URL;\n logger?: { warn: (message: string) => void };\n }) => {\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 if (!pruneSitemap) return;\n const sitemaps = applySitemapPruneToDist(outDir, runtime);\n if (!sitemaps.found && sitemapIntegrationPresent && prunePaths.length) {\n logger?.warn(\n \"@astrojs/sitemap is configured but no sitemap was on disk yet, so pruned routes may still be listed. Move astroFeatureFlags() after sitemap() in `integrations`.\",\n );\n }\n },\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -1,20 +1,62 @@
|
|
|
1
1
|
# Hide Flagged Routes From Sitemaps
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A route disabled by a flag is deleted from the build. If the sitemap still lists it,
|
|
4
|
+
every crawler that reads the sitemap is handed a 404.
|
|
5
|
+
|
|
6
|
+
## Automatic (the default)
|
|
7
|
+
|
|
8
|
+
Put `astroFeatureFlags()` **after** `sitemap()` in `integrations` and there is nothing
|
|
9
|
+
else to do. Astro runs `astro:build:done` in array order, so by the time the flags
|
|
10
|
+
integration prunes the routes the sitemap is already on disk, and it takes the dead
|
|
11
|
+
`<url>` entries out of it. A sitemap left with no URLs is deleted, along with its entry
|
|
12
|
+
in `sitemap-index.xml`.
|
|
4
13
|
|
|
5
14
|
```js
|
|
6
15
|
// astro.config.mjs
|
|
7
16
|
|
|
8
17
|
import sitemap from "@astrojs/sitemap";
|
|
18
|
+
import astroFeatureFlags from "@at-flux/astro-feature-flags";
|
|
19
|
+
|
|
20
|
+
export default defineConfig({
|
|
21
|
+
integrations: [
|
|
22
|
+
sitemap(),
|
|
23
|
+
// after sitemap(), so the sitemap exists when the prune runs
|
|
24
|
+
astroFeatureFlags({
|
|
25
|
+
flags: {
|
|
26
|
+
wip: { colour: "rgb(220 38 38)", routes: ["/blog/*"] },
|
|
27
|
+
},
|
|
28
|
+
environments: {
|
|
29
|
+
prod: {
|
|
30
|
+
when: process.env.NODE_ENV === "production",
|
|
31
|
+
flags: { wip: false },
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
}),
|
|
35
|
+
],
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Get the order wrong and the build warns: the sitemap is written after the prune, so
|
|
40
|
+
nothing is found to prune. Pass `pruneSitemap: false` to opt out, and
|
|
41
|
+
`staticMinify: false` to turn off route pruning and the HTML cull with it.
|
|
42
|
+
|
|
43
|
+
Matching is by route prefix, the same decision that deletes the files, so `/blog/*`
|
|
44
|
+
takes out `/blog/post/` and leaves `/blogroll/` alone.
|
|
45
|
+
|
|
46
|
+
## By hand
|
|
47
|
+
|
|
48
|
+
Filter in `@astrojs/sitemap` instead when the URL list needs to differ from what is
|
|
49
|
+
built — a route that ships but should not be advertised, say. `getResolvedFeatures()`
|
|
50
|
+
resolves the flags once and `featureRouteIncluded()` answers per URL:
|
|
51
|
+
|
|
52
|
+
```js
|
|
9
53
|
import astroFeatureFlags, {
|
|
10
54
|
featureRouteIncluded,
|
|
11
55
|
getResolvedFeatures,
|
|
12
56
|
} from "@at-flux/astro-feature-flags";
|
|
13
57
|
|
|
14
58
|
const featureFlagOptions = {
|
|
15
|
-
flags: {
|
|
16
|
-
wip: { colour: "rgb(220 38 38)", routes: ["/blog/*"] },
|
|
17
|
-
},
|
|
59
|
+
flags: { wip: { routes: ["/blog/*"] } },
|
|
18
60
|
environments: {
|
|
19
61
|
prod: {
|
|
20
62
|
when: process.env.NODE_ENV === "production",
|
|
@@ -27,23 +69,27 @@ const featureRuntime = getResolvedFeatures(featureFlagOptions);
|
|
|
27
69
|
|
|
28
70
|
export default defineConfig({
|
|
29
71
|
integrations: [
|
|
30
|
-
astroFeatureFlags(featureFlagOptions),
|
|
31
72
|
sitemap({
|
|
32
73
|
filter: (page) => {
|
|
33
74
|
try {
|
|
34
|
-
|
|
35
|
-
return featureRouteIncluded(pathname, featureRuntime);
|
|
75
|
+
return featureRouteIncluded(new URL(page).pathname, featureRuntime);
|
|
36
76
|
} catch {
|
|
37
77
|
return true;
|
|
38
78
|
}
|
|
39
79
|
},
|
|
40
80
|
}),
|
|
81
|
+
astroFeatureFlags(featureFlagOptions),
|
|
41
82
|
],
|
|
42
83
|
});
|
|
43
84
|
```
|
|
44
85
|
|
|
45
|
-
`featureRuntime` only needs to be declared before the `sitemap` filter uses it.
|
|
86
|
+
`featureRuntime` only needs to be declared before the `sitemap` filter uses it. Both
|
|
87
|
+
mechanisms can run together: the filter never emits the URL, and the prune then finds
|
|
88
|
+
nothing to remove.
|
|
46
89
|
|
|
47
|
-
|
|
90
|
+
If the sitemap runs under a `mode` whose active layer is not the one you want for URL
|
|
91
|
+
filtering, pass **`forceEnvironment`** (it wins over **`AFF_ENVIRONMENT`** when both are
|
|
92
|
+
set), for example
|
|
93
|
+
`getResolvedFeatures({ ...featureFlagOptions, mode: "development", forceEnvironment: "prod" })`.
|
|
48
94
|
|
|
49
|
-
|
|
95
|
+
Covered by **`test/sitemap-prune.test.ts`** and **`test/sitemap-route-filter.test.ts`**.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@at-flux/astro-feature-flags",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Typed Astro feature flags from JSON config. Includes environment specific rendering and pruning from elements to entire routes",
|
|
6
6
|
"author": "atflux <dev@atflux.uk>",
|
package/src/dev-head-inject.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { ResolvedFeatureRuntime } from "./runtime";
|
|
|
2
2
|
import { toToken } from "./runtime";
|
|
3
3
|
import { inlineInvoke } from "./inline-script";
|
|
4
4
|
import { affHeadInlineRuntime } from "./dev-inline-runtimes";
|
|
5
|
+
import { DEV_LAYER } from "./dev-outline-css";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Single `injectScript('head-inline', …)` payload for `astro dev`: dev-only outline CSS,
|
|
@@ -21,6 +22,7 @@ export function buildAffDevHeadInline(args: {
|
|
|
21
22
|
featureFlagStyles,
|
|
22
23
|
routeFlags: runtime.routeFlags,
|
|
23
24
|
flagNameToToken,
|
|
25
|
+
devLayer: DEV_LAYER,
|
|
24
26
|
});
|
|
25
27
|
return `${setup}\n${affDevBootstrap}`;
|
|
26
28
|
}
|
|
@@ -2,6 +2,7 @@ type HeadInlinePayload = {
|
|
|
2
2
|
featureFlagStyles: string;
|
|
3
3
|
routeFlags: Record<string, string[]>;
|
|
4
4
|
flagNameToToken: Record<string, string>;
|
|
5
|
+
devLayer: string;
|
|
5
6
|
};
|
|
6
7
|
|
|
7
8
|
type BootstrapPayload = {
|
|
@@ -23,7 +24,33 @@ export function affHeadInlineRuntime(payload: HeadInlinePayload): void {
|
|
|
23
24
|
return p.endsWith("/") ? p : `${p}/`;
|
|
24
25
|
};
|
|
25
26
|
try {
|
|
26
|
-
const {
|
|
27
|
+
const {
|
|
28
|
+
featureFlagStyles,
|
|
29
|
+
routeFlags: RF,
|
|
30
|
+
flagNameToToken: M,
|
|
31
|
+
devLayer: L,
|
|
32
|
+
} = payload;
|
|
33
|
+
|
|
34
|
+
// Layer priority is fixed by the order layers are first declared, in
|
|
35
|
+
// document order. The dev sheet is appended, so it lands after the app's
|
|
36
|
+
// own stylesheets and its layer would sort last — above Tailwind's
|
|
37
|
+
// `utilities` — which is the opposite of what the layer is for. A style
|
|
38
|
+
// element holding nothing but the order statement, inserted as the first
|
|
39
|
+
// child of <head>, declares the layer before any app sheet and so pins it
|
|
40
|
+
// below everything the app writes.
|
|
41
|
+
const ensureLayerOrder = () => {
|
|
42
|
+
if (document.querySelector("style[data-astro-feature-flags-layer]"))
|
|
43
|
+
return;
|
|
44
|
+
const head = document.head || document.documentElement;
|
|
45
|
+
const s = document.createElement("style");
|
|
46
|
+
s.setAttribute("data-astro-feature-flags-layer", "");
|
|
47
|
+
s.setAttribute(
|
|
48
|
+
"data-astro-transition-persist",
|
|
49
|
+
"astro-feature-flags-layer",
|
|
50
|
+
);
|
|
51
|
+
s.textContent = `@layer ${L};`;
|
|
52
|
+
head.insertBefore(s, head.firstChild);
|
|
53
|
+
};
|
|
27
54
|
|
|
28
55
|
// Inject the dev-chrome stylesheet. Astro's ClientRouter swaps <head> on a
|
|
29
56
|
// view-transition navigation, and a runtime-injected <style> is dropped on
|
|
@@ -33,6 +60,7 @@ export function affHeadInlineRuntime(payload: HeadInlinePayload): void {
|
|
|
33
60
|
// keep the injector idempotent + re-run it on every nav so the styles stay
|
|
34
61
|
// constant across pages even where a swap removes them.
|
|
35
62
|
const ensureFeatureFlagStyles = () => {
|
|
63
|
+
ensureLayerOrder();
|
|
36
64
|
if (document.querySelector("style[data-astro-feature-flags]")) return;
|
|
37
65
|
const s = document.createElement("style");
|
|
38
66
|
s.setAttribute("data-astro-feature-flags", "");
|