@at-flux/astro-feature-flags 1.0.2 → 1.0.3
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 +31 -31
- package/dist/dev-toolbar-app.d.mts +0 -1
- package/dist/dev-toolbar-app.d.mts.map +1 -1
- package/dist/dev-toolbar-app.mjs +1 -1
- package/dist/dev-toolbar-app.mjs.map +1 -1
- package/dist/{dev-toolbar-flag-icon-BHCQJ53N.mjs → dev-toolbar-flag-icon-MQsIFekv.mjs} +1 -1
- package/dist/{dev-toolbar-flag-icon-BHCQJ53N.mjs.map → dev-toolbar-flag-icon-MQsIFekv.mjs.map} +1 -1
- package/dist/index.d.mts +0 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +9 -6
- package/dist/index.mjs.map +1 -1
- package/dist/runtime.d.mts +4 -11
- package/dist/runtime.d.mts.map +1 -1
- package/dist/runtime.mjs.map +1 -1
- package/package.json +7 -7
- package/src/dev-inline-runtimes.ts +48 -14
- package/src/dev-outline-css.ts +6 -2
- package/src/dev-toolbar-app.ts +2 -4
- package/src/index.ts +11 -8
- package/src/inline-script.ts +3 -1
- package/src/runtime.ts +18 -11
package/README.md
CHANGED
|
@@ -24,10 +24,10 @@ Feature flags for Astro with a declarative config:
|
|
|
24
24
|
|
|
25
25
|
## Terminology (short)
|
|
26
26
|
|
|
27
|
-
| Term
|
|
28
|
-
|
|
|
29
|
-
| **Astro dev**
|
|
30
|
-
| **Non-dev build**
|
|
27
|
+
| Term | Meaning |
|
|
28
|
+
| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
29
|
+
| **Astro dev** | Local `astro dev` — the built-in dev layer is active; all flags on at resolve time; dev toolbar only affects the browser. |
|
|
30
|
+
| **Non-dev build** | `astro build` / preview / deploy with a shipped layer (`prod`, `staging`, …): `environments.<key>.flags` drives SSR, HTML culling, and route pruning. |
|
|
31
31
|
| **`isAstroDev`** (virtual module) | `import.meta.env.DEV` — Vite’s compile-time flag for components. Layer selection still comes from `environments` / `forceEnvironment`; use `isAstroDev` when you need Astro’s literal dev detection. |
|
|
32
32
|
|
|
33
33
|
You cannot add an environment key named **`dev`** — that name is reserved and merged automatically (including its `when`; you do not set it in config).
|
|
@@ -180,18 +180,18 @@ The dev toolbar changes client-side preview state only.
|
|
|
180
180
|
|
|
181
181
|
### Top-level options
|
|
182
182
|
|
|
183
|
-
| Option | Type
|
|
184
|
-
| ------------------ |
|
|
185
|
-
| `configRoot` | `string`
|
|
186
|
-
| `jsonConfigPath` | `string`
|
|
187
|
-
| `forceEnvironment` | `string`
|
|
188
|
-
| `mode` | `string`
|
|
189
|
-
| `env` | `Record<string, string \| undefined>` | `process.env`
|
|
190
|
-
| `tokenNamespace` | `string`
|
|
191
|
-
| `flags` | `Record<string, FlagConfig>`
|
|
192
|
-
| `environments` | `Record<string, EnvironmentConfig>`
|
|
193
|
-
| `css` | `DevOutlineCssOptions`
|
|
194
|
-
| `staticMinify` | `boolean`
|
|
183
|
+
| Option | Type | Default | Notes |
|
|
184
|
+
| ------------------ | ------------------------------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
185
|
+
| `configRoot` | `string` | `process.cwd()` | Resolves relative `jsonConfigPath` values (root + per-environment). |
|
|
186
|
+
| `jsonConfigPath` | `string` | unset | Optional **root** JSON file merged after inline config (see merge order for per-environment files). |
|
|
187
|
+
| `forceEnvironment` | `string` | unset | Pin the active layer (skips `when` / `AFF_ENVIRONMENT` validation). |
|
|
188
|
+
| `mode` | `string` | `NODE_ENV` | Optional advanced override for runtime resolution (mainly tests/tooling). Most apps should omit this and rely on `NODE_ENV` + `environments.when`. |
|
|
189
|
+
| `env` | `Record<string, string \| undefined>` | `process.env` | `AFF_FEATURE_*` / `ASTRO_FEATURE_FLAGS` (not applied in `dev` layer). |
|
|
190
|
+
| `tokenNamespace` | `string` | `'ff'` | CSS var namespace (`--ff-c-*`). |
|
|
191
|
+
| `flags` | `Record<string, FlagConfig>` | `{}` | Flag declarations. |
|
|
192
|
+
| `environments` | `Record<string, EnvironmentConfig>` | _(see below)_ | Declare non-`dev` layers only; reserved `dev` is injected. At least one other key; exactly one `when: true` unless forced. |
|
|
193
|
+
| `css` | `DevOutlineCssOptions` | defaults | Global badge/outline layout and styling. |
|
|
194
|
+
| `staticMinify` | `boolean` | `true` | For static builds: route-prune disabled prefixes + cull gated HTML in `dist/`. Set `false` to keep emitted files untouched. |
|
|
195
195
|
|
|
196
196
|
If you omit `environments`, the integration injects a minimal reserved `dev` plus **`prod`** tied to `NODE_ENV` (or `mode` only if you explicitly override it) so `astroFeatureFlags()` still runs in small demos.
|
|
197
197
|
|
|
@@ -210,10 +210,10 @@ The key **`dev`** is reserved: do not list it under `environments`. The integrat
|
|
|
210
210
|
|
|
211
211
|
### `EnvironmentConfig`
|
|
212
212
|
|
|
213
|
-
| Field | Type | Default | Notes
|
|
214
|
-
| ---------------- | ------------------------- | ------- |
|
|
215
|
-
| `when` | `boolean` | unset | Exactly one environment must have `when: true` (unless forced).
|
|
216
|
-
| `flags` | `Record<string, boolean>` | `{}` | For non-`dev` layers: booleans per flag. Ignored for reserved `dev`.
|
|
213
|
+
| Field | Type | Default | Notes |
|
|
214
|
+
| ---------------- | ------------------------- | ------- | -------------------------------------------------------------------------------------------- |
|
|
215
|
+
| `when` | `boolean` | unset | Exactly one environment must have `when: true` (unless forced). |
|
|
216
|
+
| `flags` | `Record<string, boolean>` | `{}` | For non-`dev` layers: booleans per flag. Ignored for reserved `dev`. |
|
|
217
217
|
| `jsonConfigPath` | `string` | unset | Optional JSON merged when this environment is the active layer (not used on reserved `dev`). |
|
|
218
218
|
|
|
219
219
|
Merge order:
|
|
@@ -227,12 +227,12 @@ Layer select override: `AFF_ENVIRONMENT=prod`. **`forceEnvironment`** on the int
|
|
|
227
227
|
|
|
228
228
|
### DevOutlineCssOptions
|
|
229
229
|
|
|
230
|
-
| Field
|
|
231
|
-
|
|
|
232
|
-
| `elementBadgeHorizontalAlign`
|
|
233
|
-
| `elementBadgeHorizontalPercent`
|
|
234
|
-
| `elementBadgeVerticalShiftPercent`
|
|
235
|
-
| `elementBadgeVerticalAnchor`
|
|
230
|
+
| Field | Default | Meaning |
|
|
231
|
+
| ---------------------------------- | --------- | ----------------------------------------------------------------------------------------------- |
|
|
232
|
+
| `elementBadgeHorizontalAlign` | `'end'` | `'start'` \| `'center'` \| `'end'` — LTR: **`end`** = top-right. |
|
|
233
|
+
| `elementBadgeHorizontalPercent` | _(unset)_ | 0–100: horizontal anchor with pill centred (`translateX(-50%)`); overrides align. |
|
|
234
|
+
| `elementBadgeVerticalShiftPercent` | `80` | Vertical shift as **% of the pill height** (default keeps most of the label above the element). |
|
|
235
|
+
| `elementBadgeVerticalAnchor` | `'top'` | `'top'` or `'bottom'`. |
|
|
236
236
|
|
|
237
237
|
Per-element badge overrides (markup):
|
|
238
238
|
|
|
@@ -250,12 +250,12 @@ If your editor misses virtual module types, add one reference in `src/env.d.ts`.
|
|
|
250
250
|
|
|
251
251
|
## Dev toolbar
|
|
252
252
|
|
|
253
|
-
| Control | Effect
|
|
254
|
-
| ----------- |
|
|
253
|
+
| Control | Effect |
|
|
254
|
+
| ----------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
255
255
|
| **Enabled** | Off → hide nodes carrying that token in `data-ff` (including combined hosts/pages when any member token is Off). |
|
|
256
|
-
| **Outline** | Toggle outlines only (element and route frame). For combined hosts this is a non-layout-shifting overlay ring.
|
|
257
|
-
| **Badges** | Toggle badges only (element pills + route pill).
|
|
258
|
-
| **Colour** | `--<namespace>-c-<token>` (persisted).
|
|
256
|
+
| **Outline** | Toggle outlines only (element and route frame). For combined hosts this is a non-layout-shifting overlay ring. |
|
|
257
|
+
| **Badges** | Toggle badges only (element pills + route pill). |
|
|
258
|
+
| **Colour** | `--<namespace>-c-<token>` (persisted). |
|
|
259
259
|
|
|
260
260
|
## Virtual module
|
|
261
261
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev-toolbar-app.d.mts","names":[],"sources":["../src/dev-toolbar-app.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"dev-toolbar-app.d.mts","names":[],"sources":["../src/dev-toolbar-app.ts"],"mappings":";;QASQ;YACI;IACR;MACE;MACA,SAAS;MACT;;;;cAmHA,kBAAkB"}
|
package/dist/dev-toolbar-app.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as DEV_TOOLBAR_FLAG_ICON_SVG } from "./dev-toolbar-flag-icon-
|
|
1
|
+
import { t as DEV_TOOLBAR_FLAG_ICON_SVG } from "./dev-toolbar-flag-icon-MQsIFekv.mjs";
|
|
2
2
|
import { defineToolbarApp } from "astro/toolbar";
|
|
3
3
|
import { closeOnOutsideClick, createWindowElement, synchronizePlacementOnUpdate } from "astro/runtime/client/dev-toolbar/apps/utils/window.js";
|
|
4
4
|
//#region src/dev-toolbar-app.ts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev-toolbar-app.mjs","names":[],"sources":["../src/dev-toolbar-app.ts"],"sourcesContent":["import type { DevToolbarApp } from \"astro\";\nimport { defineToolbarApp } from \"astro/toolbar\";\nimport {\n closeOnOutsideClick,\n createWindowElement,\n synchronizePlacementOnUpdate,\n} from \"astro/runtime/client/dev-toolbar/apps/utils/window.js\";\nimport { DEV_TOOLBAR_FLAG_ICON_SVG } from \"./dev-toolbar-flag-icon\";\n\ndeclare global {\n interface Window {\n __AFF__?: {\n tokens: string[];\n colors?: Record<string, string>;\n namespace?: string;\n };\n }\n}\n\ntype AffState = {\n enabled: Record<string, boolean>;\n outline: Record<string, boolean>;\n badge: Record<string, boolean>;\n colors: Record<string, string>;\n};\n\nfunction toHexColor(raw: string | undefined, fallback: string): string {\n if (!raw) return fallback;\n const s = raw.trim();\n const hex7 = s.match(/^#([0-9a-fA-F]{6})$/);\n if (hex7) return `#${hex7[1]!.toLowerCase()}`;\n const rgb = s.match(/rgba?\\(\\s*([\\d.]+)[,\\s]+([\\d.]+)[,\\s]+([\\d.]+)/);\n if (rgb) {\n const r = Math.min(255, Math.round(Number(rgb[1])));\n const g = Math.min(255, Math.round(Number(rgb[2])));\n const b = Math.min(255, Math.round(Number(rgb[3])));\n return `#${[r, g, b].map((x) => x.toString(16).padStart(2, \"0\")).join(\"\")}`;\n }\n return fallback;\n}\n\nfunction readMeta(): {\n tokens: string[];\n colors: Record<string, string>;\n namespace: string;\n} {\n try {\n const raw = window.__AFF__;\n const tokens = raw && Array.isArray(raw.tokens) ? raw.tokens : [];\n const colors =\n raw?.colors && typeof raw.colors === \"object\" ? raw.colors : {};\n const namespace = raw?.namespace?.trim() ? raw.namespace.trim() : \"ff\";\n return { tokens, colors, namespace };\n } catch {\n return { tokens: [], colors: {}, namespace: \"ff\" };\n }\n}\n\nfunction storageKey(namespace: string): string {\n return `${namespace}.dev.v1`;\n}\n\nfunction loadState(tokens: string[], namespace: string): AffState {\n const next: AffState = {\n enabled: {},\n outline: {},\n badge: {},\n colors: {},\n };\n for (const t of tokens) {\n next.enabled[t] = true;\n next.outline[t] = true;\n next.badge[t] = true;\n }\n try {\n const raw =\n localStorage.getItem(storageKey(namespace)) ||\n localStorage.getItem(\"aff.dev.v1\");\n if (!raw) return next;\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n const outline = (parsed.outline ?? parsed.chrome) as\n | Record<string, boolean>\n | undefined;\n const enabled = (parsed.enabled ?? parsed.render) as\n | Record<string, boolean>\n | undefined;\n const badge = parsed.badge as Record<string, boolean> | undefined;\n const colors = parsed.colors as Record<string, string> | undefined;\n if (outline) Object.assign(next.outline, outline);\n if (enabled) Object.assign(next.enabled, enabled);\n if (badge) Object.assign(next.badge, badge);\n if (colors) Object.assign(next.colors, colors);\n } catch {\n /* ignore */\n }\n return next;\n}\n\nfunction persist(state: AffState, namespace: string) {\n try {\n localStorage.setItem(storageKey(namespace), JSON.stringify(state));\n } catch {\n /* ignore */\n }\n}\n\nfunction applyDom(\n tokens: string[],\n state: AffState,\n defaults: Record<string, string>,\n namespace: string,\n) {\n const root = document.documentElement;\n for (const t of tokens) {\n if (state.outline[t] !== false)\n root.removeAttribute(`data-ff-outline-${t}`);\n else root.setAttribute(`data-ff-outline-${t}`, \"off\");\n\n if (state.enabled[t] !== false)\n root.removeAttribute(`data-ff-enabled-${t}`);\n else root.setAttribute(`data-ff-enabled-${t}`, \"off\");\n\n if (state.badge[t] !== false) root.removeAttribute(`data-ff-badge-${t}`);\n else root.setAttribute(`data-ff-badge-${t}`, \"off\");\n\n const col = state.colors[t] ?? defaults[t];\n if (col) root.style.setProperty(`--${namespace}-c-${t}`, col);\n else root.style.removeProperty(`--${namespace}-c-${t}`);\n }\n}\n\nconst affDevToolbarApp: DevToolbarApp = defineToolbarApp({\n init(canvas, eventTarget) {\n let state = loadState([], \"ff\");\n let tokens: string[] = [];\n let namespace = \"ff\";\n\n function mount() {\n canvas.querySelector(\"astro-dev-toolbar-window\")?.remove();\n const {\n tokens: nextTokens,\n colors: defaultColors,\n namespace: ns,\n } = readMeta();\n tokens = nextTokens;\n namespace = ns;\n state = loadState(tokens, namespace);\n applyDom(tokens, state, defaultColors, namespace);\n\n const windowElement = createWindowElement(`\n\t\t\t\t<style>\n\t\t\t\t\t:host astro-dev-toolbar-window {\n\t\t\t\t\t\theight: min(520px, 78vh);\n\t\t\t\t\t\toverflow-y: auto;\n\t\t\t\t\t\tcolor-scheme: dark;\n\t\t\t\t\t\t--color-muted: rgba(191, 193, 201, 1);\n\t\t\t\t\t\t--color-purple: rgba(224, 204, 250, 1);\n\t\t\t\t\t}\n\t\t\t\t\theader {\n\t\t\t\t\t\tdisplay: flex;\n\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\tjustify-content: space-between;\n\t\t\t\t\t\tgap: 12px;\n\t\t\t\t\t}\n\t\t\t\t\th1 {\n\t\t\t\t\t\tdisplay: flex;\n\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\tgap: 8px;\n\t\t\t\t\t\tfont-weight: 600;\n\t\t\t\t\t\tcolor: #fff;\n\t\t\t\t\t\tmargin: 0;\n\t\t\t\t\t\tfont-size: 22px;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-flag-toolbar-icon {\n\t\t\t\t\t\tdisplay: inline-flex;\n\t\t\t\t\t\twidth: 1em;\n\t\t\t\t\t\theight: 1em;\n\t\t\t\t\t\tflex-shrink: 0;\n\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\tjustify-content: center;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-flag-toolbar-icon svg {\n\t\t\t\t\t\twidth: 100%;\n\t\t\t\t\t\theight: 100%;\n\t\t\t\t\t\tdisplay: block;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-intro {\n\t\t\t\t\t\tmargin: 0;\n\t\t\t\t\t\tfont-size: 14px;\n\t\t\t\t\t\tline-height: 1.5rem;\n\t\t\t\t\t\tcolor: var(--color-muted);\n margin-bottom: 1rem;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-top {\n\t\t\t\t\t\tdisplay: flex;\n\t\t\t\t\t\talign-items: flex-start;\n\t\t\t\t\t\tjustify-content: space-between;\n\t\t\t\t\t\tgap: 12px;\n\t\t\t\t\t\tmargin: 1rem 0;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-top-left {\n\t\t\t\t\t\tflex: 1;\n\t\t\t\t\t\tmin-width: 0;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-help {\n\t\t\t\t\t\tdisplay: grid;\n\t\t\t\t\t\tgrid-template-columns: minmax(7.5rem, auto) 1fr;\n\t\t\t\t\t\tgap: 0.45rem 0.75rem;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-help code,\n\t\t\t\t\t.aff-help span {\n\t\t\t\t\t\tfont-size: 12px;\n\t\t\t\t\t\tline-height: 1.35;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-help code {\n\t\t\t\t\t\tjustify-self: start;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-actions {\n\t\t\t\t\t\tdisplay: flex;\n\t\t\t\t\t\tjustify-content: flex-end;\n\t\t\t\t\t\tmargin: 0;\n\t\t\t\t\t\tflex-shrink: 0;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-reset {\n\t\t\t\t\t\tborder: 1px solid #343841;\n\t\t\t\t\t\tborder-radius: 0.5rem;\n\t\t\t\t\t\tbackground: #24262d;\n\t\t\t\t\t\tcolor: #fff;\n\t\t\t\t\t\tpadding: 0.3rem 0.55rem;\n\t\t\t\t\t\tfont-size: 12px;\n\t\t\t\t\t\tcursor: pointer;\n\t\t\t\t\t}\n\t\t\t\t\thr {\n\t\t\t\t\t\tborder: 1px solid rgba(27, 30, 36, 1);\n\t\t\t\t\t\tmargin: 1em 0;\n\t\t\t\t\t}\n\t\t\t\t\t.setting-row {\n\t\t\t\t\t\tdisplay: flex;\n\t\t\t\t\t\tjustify-content: space-between;\n\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\tgap: 12px;\n\t\t\t\t\t\tmargin: 0 0 8px;\n\t\t\t\t\t}\n\t\t\t\t\t.setting-row section { flex: 1; min-width: 0; max-width: 54%; }\n\t\t\t\t\th3.aff-row-title {\n\t\t\t\t\t\tfont-size: 12px;\n\t\t\t\t\t\tfont-weight: 600;\n\t\t\t\t\t\tcolor: #fff;\n\t\t\t\t\t\tletter-spacing: 0.05em;\n\t\t\t\t\t\ttext-transform: uppercase;\n\t\t\t\t\t\tmargin: 0;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-radios {\n\t\t\t\t\t\tdisplay: flex;\n\t\t\t\t\t\tgap: 8px;\n\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\tflex-shrink: 0;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-radios label {\n\t\t\t\t\t\tdisplay: inline-flex;\n\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\tgap: 4px;\n\t\t\t\t\t\tfont-size: 11px;\n\t\t\t\t\t\tcursor: pointer;\n\t\t\t\t\t\tcolor: var(--color-muted);\n\t\t\t\t\t\tletter-spacing: 0.04em;\n\t\t\t\t\t\ttext-transform: uppercase;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-radios input { accent-color: var(--color-purple); cursor: pointer; }\n\t\t\t\t\t.aff-flag {\n\t\t\t\t\t\tmargin: 0 0 12px;\n\t\t\t\t\t\tborder: 1px solid rgba(52, 56, 65, 1);\n\t\t\t\t\t\tborder-radius: 10px;\n\t\t\t\t\t\tpadding: 0 10px 10px;\n\t\t\t\t\t\tbackground: rgba(19, 21, 26, 0.55);\n\t\t\t\t\t}\n\t\t\t\t\t.aff-flag summary {\n\t\t\t\t\t\tlist-style: none;\n\t\t\t\t\t\tcursor: pointer;\n\t\t\t\t\t\tuser-select: none;\n\t\t\t\t\t\tdisplay: flex;\n\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\tgap: 8px;\n\t\t\t\t\t\tpadding: 10px 0;\n\t\t\t\t\t\tfont-weight: 600;\n\t\t\t\t\t\tfont-size: 11px;\n\t\t\t\t\t\tletter-spacing: 0.06em;\n\t\t\t\t\t\ttext-transform: uppercase;\n\t\t\t\t\t\tcolor: #fff;\n\t\t\t\t\t\tjustify-content: space-between;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-flag summary::-webkit-details-marker { display: none; }\n\t\t\t\t\t.aff-flag summary::after {\n\t\t\t\t\t\tcontent: '▸';\n\t\t\t\t\t\tdisplay: inline-flex;\n\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\tjustify-content: center;\n\t\t\t\t\t\tmargin-left: auto;\n\t\t\t\t\t\tcolor: var(--color-muted);\n\t\t\t\t\t\tfont-size: 12px;\n\t\t\t\t\t\ttransform: rotate(0deg);\n\t\t\t\t\t\ttransition: transform 0.12s ease;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-flag[open] summary::after {\n\t\t\t\t\t\ttransform: rotate(90deg);\n\t\t\t\t\t}\n\t\t\t\t\t.aff-flag-summary-left {\n\t\t\t\t\t\tdisplay: inline-flex;\n\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\tgap: 8px;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-swatch {\n\t\t\t\t\t\twidth: 10px;\n\t\t\t\t\t\theight: 10px;\n\t\t\t\t\t\tborder-radius: 9999px;\n\t\t\t\t\t\tflex-shrink: 0;\n\t\t\t\t\t\tbox-shadow: inset 0 0 0 1px rgba(255,255,255,0.12);\n\t\t\t\t\t}\n\t\t\t\t\t.aff-flag-body { padding-top: 2px; }\n\t\t\t\t\t.aff-empty {\n\t\t\t\t\t\tmargin: 0;\n\t\t\t\t\t\tfont-size: 14px;\n\t\t\t\t\t\tline-height: 1.5rem;\n\t\t\t\t\t\tcolor: var(--color-muted);\n\t\t\t\t\t}\n\t\t\t\t\tcode {\n\t\t\t\t\t\tcolor: var(--color-purple);\n\t\t\t\t\t\tborder: 1px solid #343841;\n\t\t\t\t\t\tborder-radius: 0.35em;\n\t\t\t\t\t\tbackground-color: #24262d;\n\t\t\t\t\t\tpadding: 0.15em 0.35em;\n\t\t\t\t\t\tfont-size: 0.9em;\n\t\t\t\t\t}\n\t\t\t\t\tinput[type=\"color\"].aff-color {\n\t\t\t\t\t\twidth: 2.25rem;\n\t\t\t\t\t\theight: 1.75rem;\n\t\t\t\t\t\tpadding: 0;\n\t\t\t\t\t\tborder: 1px solid #343841;\n\t\t\t\t\t\tborder-radius: 6px;\n\t\t\t\t\t\tcursor: pointer;\n\t\t\t\t\t\tbackground: transparent;\n\t\t\t\t\t}\n\t\t\t\t</style>\n\t\t\t\t<header>\n\t\t\t\t\t<h1><span class=\"aff-flag-toolbar-icon\">${DEV_TOOLBAR_FLAG_ICON_SVG}</span> Feature flags</h1>\n\t\t\t\t\t<div class=\"aff-actions\"><button id=\"aff-reset\" class=\"aff-reset\" type=\"button\" title=\"Reset all flags to defaults\">Reset all</button></div>\n\t\t\t\t</header>\n\t\t\t\t<div class=\"aff-top\">\n\t\t\t\t\t<div class=\"aff-top-left\">\n\t\t\t\t\t\t<p class=\"aff-intro\">Per-flag preview for <code>data-${namespace}</code> regions.</p>\n\t\t\t\t<div class=\"aff-help\">\n\t\t\t\t\t<code>Enabled</code><span>Show/hide flagged regions in preview.</span>\n\t\t\t\t\t<code>Outline</code><span>Toggle flag outlines without changing layout.</span>\n\t\t\t\t\t<code>Badges</code><span>Toggle element and route labels.</span>\n\t\t\t\t\t<code>Colour</code><span>Set <code>--${namespace}-c-<token></code> on <code><html></code>.</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<hr />\n\t\t\t\t<div id=\"aff-body\"></div>\n\t\t\t`);\n\n const body = windowElement.querySelector(\"#aff-body\");\n const resetBtn = windowElement.querySelector(\n \"#aff-reset\",\n ) as HTMLButtonElement | null;\n\n if (body) {\n if (tokens.length === 0) {\n const p = document.createElement(\"p\");\n p.className = \"aff-empty\";\n p.textContent = \"No flags in ff.json.\";\n body.append(p);\n } else {\n for (const token of tokens) {\n const details = document.createElement(\"details\");\n details.className = \"aff-flag\";\n details.open = tokens.length <= 8;\n\n const summary = document.createElement(\"summary\");\n const summaryLeft = document.createElement(\"span\");\n summaryLeft.className = \"aff-flag-summary-left\";\n const swatch = document.createElement(\"span\");\n swatch.className = \"aff-swatch\";\n const name = document.createElement(\"span\");\n name.textContent = token;\n summaryLeft.append(swatch, name);\n summary.append(summaryLeft);\n\n const syncSwatch = () => {\n const c =\n state.colors[token] ?? defaultColors[token] ?? \"#888888\";\n swatch.style.background = c;\n };\n syncSwatch();\n\n const inner = document.createElement(\"div\");\n inner.className = \"aff-flag-body\";\n\n const row = (\n title: string,\n tooltip: string,\n key: keyof Pick<AffState, \"enabled\" | \"outline\" | \"badge\">,\n onLab: string,\n offLab: string,\n ) => {\n const label = document.createElement(\"label\");\n label.className = \"setting-row\";\n const section = document.createElement(\"section\");\n const h = document.createElement(\"h3\");\n h.className = \"aff-row-title\";\n h.textContent = title;\n section.append(h);\n const radios = document.createElement(\"div\");\n radios.className = \"aff-radios\";\n const mk = (on: boolean, lab: string) => {\n const wrap = document.createElement(\"label\");\n const inp = document.createElement(\"input\");\n inp.type = \"radio\";\n inp.name = `aff-${token}-${key}`;\n const cur = state[key][token] !== false;\n inp.checked = on ? cur : !cur;\n inp.addEventListener(\"change\", () => {\n if (!inp.checked) return;\n state[key][token] = on;\n applyDom(tokens, state, defaultColors, namespace);\n persist(state, namespace);\n });\n wrap.append(inp, document.createTextNode(lab));\n return wrap;\n };\n radios.append(mk(true, onLab), mk(false, offLab));\n label.append(section, radios);\n return label;\n };\n\n inner.append(\n row(\n \"Enabled\",\n \"When off, nodes with this flag token are hidden in preview.\",\n \"enabled\",\n \"On\",\n \"Off\",\n ),\n row(\n \"Outline\",\n \"Toggles dashed outline around flagged elements.\",\n \"outline\",\n \"On\",\n \"Off\",\n ),\n row(\n \"Badges\",\n \"Toggles element badges and top-right route pill.\",\n \"badge\",\n \"On\",\n \"Off\",\n ),\n );\n\n const colorRow = document.createElement(\"label\");\n colorRow.className = \"setting-row\";\n const csection = document.createElement(\"section\");\n const ch = document.createElement(\"h3\");\n ch.className = \"aff-row-title\";\n ch.textContent = \"Colour\";\n ch.title = `Maps to CSS --${namespace}-c-<token> on <html>.`;\n csection.append(ch);\n const cinp = document.createElement(\"input\");\n cinp.type = \"color\";\n cinp.className = \"aff-color\";\n cinp.title = `Set --${namespace}-c-${token}`;\n cinp.value = toHexColor(\n state.colors[token] ?? defaultColors[token],\n \"#888888\",\n );\n cinp.addEventListener(\"input\", () => {\n state.colors[token] = cinp.value;\n syncSwatch();\n applyDom(tokens, state, defaultColors, namespace);\n persist(state, namespace);\n });\n colorRow.append(csection, cinp);\n inner.append(colorRow);\n\n details.append(summary, inner);\n body.append(details);\n }\n }\n }\n\n if (resetBtn) {\n resetBtn.addEventListener(\"click\", () => {\n try {\n localStorage.removeItem(storageKey(namespace));\n localStorage.removeItem(\"aff.dev.v1\");\n } catch {\n // ignore\n }\n state = loadState(tokens, namespace);\n applyDom(tokens, state, defaultColors, namespace);\n mount();\n });\n }\n\n canvas.append(windowElement);\n }\n\n mount();\n document.addEventListener(\"astro:after-swap\", mount);\n closeOnOutsideClick(eventTarget);\n synchronizePlacementOnUpdate(eventTarget, canvas);\n },\n});\n\nexport default affDevToolbarApp;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"dev-toolbar-app.mjs","names":[],"sources":["../src/dev-toolbar-app.ts"],"sourcesContent":["import type { DevToolbarApp } from \"astro\";\nimport { defineToolbarApp } from \"astro/toolbar\";\nimport {\n closeOnOutsideClick,\n createWindowElement,\n synchronizePlacementOnUpdate,\n} from \"astro/runtime/client/dev-toolbar/apps/utils/window.js\";\nimport { DEV_TOOLBAR_FLAG_ICON_SVG } from \"./dev-toolbar-flag-icon\";\n\ndeclare global {\n interface Window {\n __AFF__?: {\n tokens: string[];\n colors?: Record<string, string>;\n namespace?: string;\n };\n }\n}\n\ntype AffState = {\n enabled: Record<string, boolean>;\n outline: Record<string, boolean>;\n badge: Record<string, boolean>;\n colors: Record<string, string>;\n};\n\nfunction toHexColor(raw: string | undefined, fallback: string): string {\n if (!raw) return fallback;\n const s = raw.trim();\n const hex7 = s.match(/^#([0-9a-fA-F]{6})$/);\n if (hex7) return `#${hex7[1]!.toLowerCase()}`;\n const rgb = s.match(/rgba?\\(\\s*([\\d.]+)[,\\s]+([\\d.]+)[,\\s]+([\\d.]+)/);\n if (rgb) {\n const r = Math.min(255, Math.round(Number(rgb[1])));\n const g = Math.min(255, Math.round(Number(rgb[2])));\n const b = Math.min(255, Math.round(Number(rgb[3])));\n return `#${[r, g, b].map((x) => x.toString(16).padStart(2, \"0\")).join(\"\")}`;\n }\n return fallback;\n}\n\nfunction readMeta(): {\n tokens: string[];\n colors: Record<string, string>;\n namespace: string;\n} {\n try {\n const raw = window.__AFF__;\n const tokens = raw && Array.isArray(raw.tokens) ? raw.tokens : [];\n const colors =\n raw?.colors && typeof raw.colors === \"object\" ? raw.colors : {};\n const namespace = raw?.namespace?.trim() ? raw.namespace.trim() : \"ff\";\n return { tokens, colors, namespace };\n } catch {\n return { tokens: [], colors: {}, namespace: \"ff\" };\n }\n}\n\nfunction storageKey(namespace: string): string {\n return `${namespace}.dev.v1`;\n}\n\nfunction loadState(tokens: string[], namespace: string): AffState {\n const next: AffState = {\n enabled: {},\n outline: {},\n badge: {},\n colors: {},\n };\n for (const t of tokens) {\n next.enabled[t] = true;\n next.outline[t] = true;\n next.badge[t] = true;\n }\n try {\n const raw =\n localStorage.getItem(storageKey(namespace)) ||\n localStorage.getItem(\"aff.dev.v1\");\n if (!raw) return next;\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n const outline = (parsed.outline ?? parsed.chrome) as\n Record<string, boolean> | undefined;\n const enabled = (parsed.enabled ?? parsed.render) as\n Record<string, boolean> | undefined;\n const badge = parsed.badge as Record<string, boolean> | undefined;\n const colors = parsed.colors as Record<string, string> | undefined;\n if (outline) Object.assign(next.outline, outline);\n if (enabled) Object.assign(next.enabled, enabled);\n if (badge) Object.assign(next.badge, badge);\n if (colors) Object.assign(next.colors, colors);\n } catch {\n /* ignore */\n }\n return next;\n}\n\nfunction persist(state: AffState, namespace: string) {\n try {\n localStorage.setItem(storageKey(namespace), JSON.stringify(state));\n } catch {\n /* ignore */\n }\n}\n\nfunction applyDom(\n tokens: string[],\n state: AffState,\n defaults: Record<string, string>,\n namespace: string,\n) {\n const root = document.documentElement;\n for (const t of tokens) {\n if (state.outline[t] !== false)\n root.removeAttribute(`data-ff-outline-${t}`);\n else root.setAttribute(`data-ff-outline-${t}`, \"off\");\n\n if (state.enabled[t] !== false)\n root.removeAttribute(`data-ff-enabled-${t}`);\n else root.setAttribute(`data-ff-enabled-${t}`, \"off\");\n\n if (state.badge[t] !== false) root.removeAttribute(`data-ff-badge-${t}`);\n else root.setAttribute(`data-ff-badge-${t}`, \"off\");\n\n const col = state.colors[t] ?? defaults[t];\n if (col) root.style.setProperty(`--${namespace}-c-${t}`, col);\n else root.style.removeProperty(`--${namespace}-c-${t}`);\n }\n}\n\nconst affDevToolbarApp: DevToolbarApp = defineToolbarApp({\n init(canvas, eventTarget) {\n let state = loadState([], \"ff\");\n let tokens: string[] = [];\n let namespace = \"ff\";\n\n function mount() {\n canvas.querySelector(\"astro-dev-toolbar-window\")?.remove();\n const {\n tokens: nextTokens,\n colors: defaultColors,\n namespace: ns,\n } = readMeta();\n tokens = nextTokens;\n namespace = ns;\n state = loadState(tokens, namespace);\n applyDom(tokens, state, defaultColors, namespace);\n\n const windowElement = createWindowElement(`\n\t\t\t\t<style>\n\t\t\t\t\t:host astro-dev-toolbar-window {\n\t\t\t\t\t\theight: min(520px, 78vh);\n\t\t\t\t\t\toverflow-y: auto;\n\t\t\t\t\t\tcolor-scheme: dark;\n\t\t\t\t\t\t--color-muted: rgba(191, 193, 201, 1);\n\t\t\t\t\t\t--color-purple: rgba(224, 204, 250, 1);\n\t\t\t\t\t}\n\t\t\t\t\theader {\n\t\t\t\t\t\tdisplay: flex;\n\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\tjustify-content: space-between;\n\t\t\t\t\t\tgap: 12px;\n\t\t\t\t\t}\n\t\t\t\t\th1 {\n\t\t\t\t\t\tdisplay: flex;\n\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\tgap: 8px;\n\t\t\t\t\t\tfont-weight: 600;\n\t\t\t\t\t\tcolor: #fff;\n\t\t\t\t\t\tmargin: 0;\n\t\t\t\t\t\tfont-size: 22px;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-flag-toolbar-icon {\n\t\t\t\t\t\tdisplay: inline-flex;\n\t\t\t\t\t\twidth: 1em;\n\t\t\t\t\t\theight: 1em;\n\t\t\t\t\t\tflex-shrink: 0;\n\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\tjustify-content: center;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-flag-toolbar-icon svg {\n\t\t\t\t\t\twidth: 100%;\n\t\t\t\t\t\theight: 100%;\n\t\t\t\t\t\tdisplay: block;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-intro {\n\t\t\t\t\t\tmargin: 0;\n\t\t\t\t\t\tfont-size: 14px;\n\t\t\t\t\t\tline-height: 1.5rem;\n\t\t\t\t\t\tcolor: var(--color-muted);\n margin-bottom: 1rem;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-top {\n\t\t\t\t\t\tdisplay: flex;\n\t\t\t\t\t\talign-items: flex-start;\n\t\t\t\t\t\tjustify-content: space-between;\n\t\t\t\t\t\tgap: 12px;\n\t\t\t\t\t\tmargin: 1rem 0;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-top-left {\n\t\t\t\t\t\tflex: 1;\n\t\t\t\t\t\tmin-width: 0;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-help {\n\t\t\t\t\t\tdisplay: grid;\n\t\t\t\t\t\tgrid-template-columns: minmax(7.5rem, auto) 1fr;\n\t\t\t\t\t\tgap: 0.45rem 0.75rem;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-help code,\n\t\t\t\t\t.aff-help span {\n\t\t\t\t\t\tfont-size: 12px;\n\t\t\t\t\t\tline-height: 1.35;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-help code {\n\t\t\t\t\t\tjustify-self: start;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-actions {\n\t\t\t\t\t\tdisplay: flex;\n\t\t\t\t\t\tjustify-content: flex-end;\n\t\t\t\t\t\tmargin: 0;\n\t\t\t\t\t\tflex-shrink: 0;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-reset {\n\t\t\t\t\t\tborder: 1px solid #343841;\n\t\t\t\t\t\tborder-radius: 0.5rem;\n\t\t\t\t\t\tbackground: #24262d;\n\t\t\t\t\t\tcolor: #fff;\n\t\t\t\t\t\tpadding: 0.3rem 0.55rem;\n\t\t\t\t\t\tfont-size: 12px;\n\t\t\t\t\t\tcursor: pointer;\n\t\t\t\t\t}\n\t\t\t\t\thr {\n\t\t\t\t\t\tborder: 1px solid rgba(27, 30, 36, 1);\n\t\t\t\t\t\tmargin: 1em 0;\n\t\t\t\t\t}\n\t\t\t\t\t.setting-row {\n\t\t\t\t\t\tdisplay: flex;\n\t\t\t\t\t\tjustify-content: space-between;\n\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\tgap: 12px;\n\t\t\t\t\t\tmargin: 0 0 8px;\n\t\t\t\t\t}\n\t\t\t\t\t.setting-row section { flex: 1; min-width: 0; max-width: 54%; }\n\t\t\t\t\th3.aff-row-title {\n\t\t\t\t\t\tfont-size: 12px;\n\t\t\t\t\t\tfont-weight: 600;\n\t\t\t\t\t\tcolor: #fff;\n\t\t\t\t\t\tletter-spacing: 0.05em;\n\t\t\t\t\t\ttext-transform: uppercase;\n\t\t\t\t\t\tmargin: 0;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-radios {\n\t\t\t\t\t\tdisplay: flex;\n\t\t\t\t\t\tgap: 8px;\n\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\tflex-shrink: 0;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-radios label {\n\t\t\t\t\t\tdisplay: inline-flex;\n\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\tgap: 4px;\n\t\t\t\t\t\tfont-size: 11px;\n\t\t\t\t\t\tcursor: pointer;\n\t\t\t\t\t\tcolor: var(--color-muted);\n\t\t\t\t\t\tletter-spacing: 0.04em;\n\t\t\t\t\t\ttext-transform: uppercase;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-radios input { accent-color: var(--color-purple); cursor: pointer; }\n\t\t\t\t\t.aff-flag {\n\t\t\t\t\t\tmargin: 0 0 12px;\n\t\t\t\t\t\tborder: 1px solid rgba(52, 56, 65, 1);\n\t\t\t\t\t\tborder-radius: 10px;\n\t\t\t\t\t\tpadding: 0 10px 10px;\n\t\t\t\t\t\tbackground: rgba(19, 21, 26, 0.55);\n\t\t\t\t\t}\n\t\t\t\t\t.aff-flag summary {\n\t\t\t\t\t\tlist-style: none;\n\t\t\t\t\t\tcursor: pointer;\n\t\t\t\t\t\tuser-select: none;\n\t\t\t\t\t\tdisplay: flex;\n\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\tgap: 8px;\n\t\t\t\t\t\tpadding: 10px 0;\n\t\t\t\t\t\tfont-weight: 600;\n\t\t\t\t\t\tfont-size: 11px;\n\t\t\t\t\t\tletter-spacing: 0.06em;\n\t\t\t\t\t\ttext-transform: uppercase;\n\t\t\t\t\t\tcolor: #fff;\n\t\t\t\t\t\tjustify-content: space-between;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-flag summary::-webkit-details-marker { display: none; }\n\t\t\t\t\t.aff-flag summary::after {\n\t\t\t\t\t\tcontent: '▸';\n\t\t\t\t\t\tdisplay: inline-flex;\n\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\tjustify-content: center;\n\t\t\t\t\t\tmargin-left: auto;\n\t\t\t\t\t\tcolor: var(--color-muted);\n\t\t\t\t\t\tfont-size: 12px;\n\t\t\t\t\t\ttransform: rotate(0deg);\n\t\t\t\t\t\ttransition: transform 0.12s ease;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-flag[open] summary::after {\n\t\t\t\t\t\ttransform: rotate(90deg);\n\t\t\t\t\t}\n\t\t\t\t\t.aff-flag-summary-left {\n\t\t\t\t\t\tdisplay: inline-flex;\n\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\tgap: 8px;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-swatch {\n\t\t\t\t\t\twidth: 10px;\n\t\t\t\t\t\theight: 10px;\n\t\t\t\t\t\tborder-radius: 9999px;\n\t\t\t\t\t\tflex-shrink: 0;\n\t\t\t\t\t\tbox-shadow: inset 0 0 0 1px rgba(255,255,255,0.12);\n\t\t\t\t\t}\n\t\t\t\t\t.aff-flag-body { padding-top: 2px; }\n\t\t\t\t\t.aff-empty {\n\t\t\t\t\t\tmargin: 0;\n\t\t\t\t\t\tfont-size: 14px;\n\t\t\t\t\t\tline-height: 1.5rem;\n\t\t\t\t\t\tcolor: var(--color-muted);\n\t\t\t\t\t}\n\t\t\t\t\tcode {\n\t\t\t\t\t\tcolor: var(--color-purple);\n\t\t\t\t\t\tborder: 1px solid #343841;\n\t\t\t\t\t\tborder-radius: 0.35em;\n\t\t\t\t\t\tbackground-color: #24262d;\n\t\t\t\t\t\tpadding: 0.15em 0.35em;\n\t\t\t\t\t\tfont-size: 0.9em;\n\t\t\t\t\t}\n\t\t\t\t\tinput[type=\"color\"].aff-color {\n\t\t\t\t\t\twidth: 2.25rem;\n\t\t\t\t\t\theight: 1.75rem;\n\t\t\t\t\t\tpadding: 0;\n\t\t\t\t\t\tborder: 1px solid #343841;\n\t\t\t\t\t\tborder-radius: 6px;\n\t\t\t\t\t\tcursor: pointer;\n\t\t\t\t\t\tbackground: transparent;\n\t\t\t\t\t}\n\t\t\t\t</style>\n\t\t\t\t<header>\n\t\t\t\t\t<h1><span class=\"aff-flag-toolbar-icon\">${DEV_TOOLBAR_FLAG_ICON_SVG}</span> Feature flags</h1>\n\t\t\t\t\t<div class=\"aff-actions\"><button id=\"aff-reset\" class=\"aff-reset\" type=\"button\" title=\"Reset all flags to defaults\">Reset all</button></div>\n\t\t\t\t</header>\n\t\t\t\t<div class=\"aff-top\">\n\t\t\t\t\t<div class=\"aff-top-left\">\n\t\t\t\t\t\t<p class=\"aff-intro\">Per-flag preview for <code>data-${namespace}</code> regions.</p>\n\t\t\t\t<div class=\"aff-help\">\n\t\t\t\t\t<code>Enabled</code><span>Show/hide flagged regions in preview.</span>\n\t\t\t\t\t<code>Outline</code><span>Toggle flag outlines without changing layout.</span>\n\t\t\t\t\t<code>Badges</code><span>Toggle element and route labels.</span>\n\t\t\t\t\t<code>Colour</code><span>Set <code>--${namespace}-c-<token></code> on <code><html></code>.</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<hr />\n\t\t\t\t<div id=\"aff-body\"></div>\n\t\t\t`);\n\n const body = windowElement.querySelector(\"#aff-body\");\n const resetBtn = windowElement.querySelector(\n \"#aff-reset\",\n ) as HTMLButtonElement | null;\n\n if (body) {\n if (tokens.length === 0) {\n const p = document.createElement(\"p\");\n p.className = \"aff-empty\";\n p.textContent = \"No flags in ff.json.\";\n body.append(p);\n } else {\n for (const token of tokens) {\n const details = document.createElement(\"details\");\n details.className = \"aff-flag\";\n details.open = tokens.length <= 8;\n\n const summary = document.createElement(\"summary\");\n const summaryLeft = document.createElement(\"span\");\n summaryLeft.className = \"aff-flag-summary-left\";\n const swatch = document.createElement(\"span\");\n swatch.className = \"aff-swatch\";\n const name = document.createElement(\"span\");\n name.textContent = token;\n summaryLeft.append(swatch, name);\n summary.append(summaryLeft);\n\n const syncSwatch = () => {\n const c =\n state.colors[token] ?? defaultColors[token] ?? \"#888888\";\n swatch.style.background = c;\n };\n syncSwatch();\n\n const inner = document.createElement(\"div\");\n inner.className = \"aff-flag-body\";\n\n const row = (\n title: string,\n tooltip: string,\n key: keyof Pick<AffState, \"enabled\" | \"outline\" | \"badge\">,\n onLab: string,\n offLab: string,\n ) => {\n const label = document.createElement(\"label\");\n label.className = \"setting-row\";\n const section = document.createElement(\"section\");\n const h = document.createElement(\"h3\");\n h.className = \"aff-row-title\";\n h.textContent = title;\n section.append(h);\n const radios = document.createElement(\"div\");\n radios.className = \"aff-radios\";\n const mk = (on: boolean, lab: string) => {\n const wrap = document.createElement(\"label\");\n const inp = document.createElement(\"input\");\n inp.type = \"radio\";\n inp.name = `aff-${token}-${key}`;\n const cur = state[key][token] !== false;\n inp.checked = on ? cur : !cur;\n inp.addEventListener(\"change\", () => {\n if (!inp.checked) return;\n state[key][token] = on;\n applyDom(tokens, state, defaultColors, namespace);\n persist(state, namespace);\n });\n wrap.append(inp, document.createTextNode(lab));\n return wrap;\n };\n radios.append(mk(true, onLab), mk(false, offLab));\n label.append(section, radios);\n return label;\n };\n\n inner.append(\n row(\n \"Enabled\",\n \"When off, nodes with this flag token are hidden in preview.\",\n \"enabled\",\n \"On\",\n \"Off\",\n ),\n row(\n \"Outline\",\n \"Toggles dashed outline around flagged elements.\",\n \"outline\",\n \"On\",\n \"Off\",\n ),\n row(\n \"Badges\",\n \"Toggles element badges and top-right route pill.\",\n \"badge\",\n \"On\",\n \"Off\",\n ),\n );\n\n const colorRow = document.createElement(\"label\");\n colorRow.className = \"setting-row\";\n const csection = document.createElement(\"section\");\n const ch = document.createElement(\"h3\");\n ch.className = \"aff-row-title\";\n ch.textContent = \"Colour\";\n ch.title = `Maps to CSS --${namespace}-c-<token> on <html>.`;\n csection.append(ch);\n const cinp = document.createElement(\"input\");\n cinp.type = \"color\";\n cinp.className = \"aff-color\";\n cinp.title = `Set --${namespace}-c-${token}`;\n cinp.value = toHexColor(\n state.colors[token] ?? defaultColors[token],\n \"#888888\",\n );\n cinp.addEventListener(\"input\", () => {\n state.colors[token] = cinp.value;\n syncSwatch();\n applyDom(tokens, state, defaultColors, namespace);\n persist(state, namespace);\n });\n colorRow.append(csection, cinp);\n inner.append(colorRow);\n\n details.append(summary, inner);\n body.append(details);\n }\n }\n }\n\n if (resetBtn) {\n resetBtn.addEventListener(\"click\", () => {\n try {\n localStorage.removeItem(storageKey(namespace));\n localStorage.removeItem(\"aff.dev.v1\");\n } catch {\n // ignore\n }\n state = loadState(tokens, namespace);\n applyDom(tokens, state, defaultColors, namespace);\n mount();\n });\n }\n\n canvas.append(windowElement);\n }\n\n mount();\n document.addEventListener(\"astro:after-swap\", mount);\n closeOnOutsideClick(eventTarget);\n synchronizePlacementOnUpdate(eventTarget, canvas);\n },\n});\n\nexport default affDevToolbarApp;\n"],"mappings":"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"}
|
package/dist/{dev-toolbar-flag-icon-BHCQJ53N.mjs.map → dev-toolbar-flag-icon-MQsIFekv.mjs.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev-toolbar-flag-icon-
|
|
1
|
+
{"version":3,"file":"dev-toolbar-flag-icon-MQsIFekv.mjs","names":[],"sources":["../src/dev-toolbar-flag-icon.ts"],"sourcesContent":["/**\n * Inline SVG for the dev toolbar app button and panel header (Heroicons-style flag, 20×20 in a 22×22 box).\n * `currentColor` matches built-in Astro toolbar icons.\n */\nexport const DEV_TOOLBAR_FLAG_ICON_SVG =\n '<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 22 22\" fill=\"currentColor\" aria-hidden=\"true\">' +\n '<path d=\"M4.25 3.25v15.5a.75.75 0 1 1-1.5 0V3.25a.75.75 0 0 1 1.5 0Z\"/>' +\n '<path d=\"M6.5 4.75v9.2a1 1 0 0 0 1.5.87l7.5-4.6a1 1 0 0 0 0-1.74l-7.5-4.6a1 1 0 0 0-1.5.87Z\"/>' +\n \"</svg>\";\n"],"mappings":";;;;;AAIA,MAAa,4BACX"}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { FeatureFlagMap, ResolveFeatureRuntimeOptions, ResolvedFeatureRuntime, longestMatchingRoutePrefix, mergeFlagsWithProcessEnvOverrides, resolveFeatureFlagsByEnvironment, routePatternToPrefix } from "./runtime.mjs";
|
|
2
|
-
|
|
3
2
|
//#region src/badge-layout.d.ts
|
|
4
3
|
type ElementBadgeVerticalAnchor = "top" | "bottom";
|
|
5
4
|
/** Horizontal placement of the element pill: `end` = top-right (LTR), `start` = top-left, `center` = centred. */
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/badge-layout.ts","../src/dev-outline-css.ts","../src/production-html-cull.ts","../src/index.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/badge-layout.ts","../src/dev-outline-css.ts","../src/production-html-cull.ts","../src/index.ts"],"mappings":";;KAAY;;KAGA;;UAGK;;;;;EAKf,8BAA8B;;;;;EAK9B;;;;;EAKA;;EAEA,6BAA6B;;KAGnB;EAEN;EACA,iBAAiB;EACjB;EACA,gBAAgB;;EAGhB;EACA;EACA;EACA,gBAAgB;;iBAQN,4BACd,OAAO,wCACN;;;;iBA+Ba,0BACd,QAAQ;;;KCrEE;UAEK,6BAA6B;EAC5C;;EAEA;;EAEA,sBAAsB;EACtB;EACA;;EAGA;;EAEA;EACA,oBAAoB;EAEpB,iBAAiB;;;;EAKjB;;;;;;;iBAsEc,wBACd,SAAS,wBACT,MAAM;;;;;iBAyTQ,2BACd,SAAS;;;;;;;;iBC9UK,mBACd,cACA,SAAS;;;;iBAqCK,8BACd,gBACA,SAAS;;;UCtEM,iCAAiC;EAChD,MAAM;;EAEN;;;iBAIc,4BACd,oBAAoB,eAAe;iBAoCrB,0BACd,SAAS,wBACT,MAAM,sBACN,qBAAoB,eAAe,iBACnC;iBA8Kc,oBACd,UAAS,+BACR;iBAIa,qBACd,kBACA,SAAS;iBAkBa,kBACtB,UAAS"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as DEV_TOOLBAR_FLAG_ICON_SVG } from "./dev-toolbar-flag-icon-
|
|
1
|
+
import { t as DEV_TOOLBAR_FLAG_ICON_SVG } from "./dev-toolbar-flag-icon-MQsIFekv.mjs";
|
|
2
2
|
import { longestMatchingRoutePrefix, mergeFlagsWithProcessEnvOverrides, resolveFeatureFlagsByEnvironment, resolveFeatureRuntime, routePathsToPrune, routePatternToPrefix, shouldIncludeRoute, toEnumKey, toToken } from "./runtime.mjs";
|
|
3
3
|
import { readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
@@ -1074,11 +1074,14 @@ function astroFeatureFlags(options = {}) {
|
|
|
1074
1074
|
icon: DEV_TOOLBAR_FLAG_ICON_SVG,
|
|
1075
1075
|
entrypoint: fileURLToPath(new URL("./dev-toolbar-app.mjs", import.meta.url))
|
|
1076
1076
|
});
|
|
1077
|
-
if (command === "dev" && runtime.isDev && typeof injectScript === "function")
|
|
1078
|
-
runtime,
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1077
|
+
if (command === "dev" && runtime.isDev && typeof injectScript === "function") {
|
|
1078
|
+
const styles = createFeatureFlagStyles(runtime, css);
|
|
1079
|
+
injectScript("head-inline", buildAffDevHeadInline({
|
|
1080
|
+
runtime,
|
|
1081
|
+
featureFlagStyles: styles,
|
|
1082
|
+
affDevBootstrap: bootstrap
|
|
1083
|
+
}));
|
|
1084
|
+
}
|
|
1082
1085
|
updateConfig({ vite: { plugins: [{
|
|
1083
1086
|
name: "astro-feature-flags:virtual-module",
|
|
1084
1087
|
resolveId(id) {
|
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((line, idx, arr) => line.length > 0 || (idx > 0 && idx < arr.length - 1));\n return lines.join(\"\\n\").trim();\n}\n\nexport function inlineInvoke<Payload>(\n fn: (payload: Payload) => void,\n payload: Payload,\n): string {\n return compactInlineScript(`(${fn.toString()})(${JSON.stringify(payload)});`);\n}\n","type HeadInlinePayload = {\n featureFlagStyles: string;\n routeFlags: Record<string, string[]>;\n flagNameToToken: Record<string, string>;\n};\n\ntype BootstrapPayload = {\n flagTokens: string[];\n colors: Record<string, string>;\n outlineDefaults: Record<string, boolean>;\n badgeDefaults: Record<string, boolean>;\n routeFlags: Record<string, string[]>;\n flagNameToToken: Record<string, string>;\n namespace: string;\n};\n\nexport function affHeadInlineRuntime(payload: HeadInlinePayload): void {\n const toPrefix = (pattern: string): string => {\n let p = String(pattern || \"\").trim();\n if (p.endsWith(\"/**\")) p = p.slice(0, -3);\n else if (p.endsWith(\"/*\")) p = p.slice(0, -2);\n else if (p.length > 1 && p.endsWith(\"*\")) p = p.slice(0, -1);\n return p.endsWith(\"/\") ? p : `${p}/`;\n };\n try {\n const { featureFlagStyles, routeFlags: RF, flagNameToToken: M } = payload;\n\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 = typeof location !== \"undefined\" && location.pathname ? location.pathname : \"/\";\n if (!p.endsWith(\"/\")) p += \"/\";\n return p;\n };\n\n const affFlagNames = (pathname: string) => {\n let p = pathname || \"/\";\n if (!p.endsWith(\"/\")) p += \"/\";\n const out: string[] = [];\n const seen = new Set<string>();\n for (const rp in RF) {\n if (!Object.prototype.hasOwnProperty.call(RF, rp)) continue;\n const route = toPrefix(rp);\n if (!(p === route || p.indexOf(route) === 0)) continue;\n const fns = RF[rp] || [];\n for (let i = 0; i < fns.length; i++) {\n const fn = fns[i];\n if (fn && !seen.has(fn)) {\n seen.add(fn);\n out.push(fn);\n }\n }\n }\n return out;\n };\n\n const affTokens = (pathname: string) =>\n affFlagNames(pathname)\n .map((name) => M[name])\n .filter(Boolean);\n\n const affSyncDisabledOverlay = () => {\n const root = document.documentElement;\n if (!document.body) return;\n const names = affFlagNames(affPath());\n const disabled: string[] = [];\n for (let i = 0; i < names.length; i++) {\n const tk = M[names[i]];\n if (!tk) continue;\n if (root.getAttribute(`data-ff-enabled-${tk}`) === \"off\") disabled.push(tk);\n }\n const id = \"aff-route-disabled-overlay\";\n let el = document.getElementById(id);\n if (!disabled.length) {\n if (el) el.remove();\n root.removeAttribute(\"data-aff-route-disabled\");\n return;\n }\n root.setAttribute(\"data-aff-route-disabled\", \"\");\n if (!el) {\n el = document.createElement(\"div\");\n el.id = id;\n el.className = \"aff-route-disabled-overlay\";\n const p = document.createElement(\"p\");\n el.appendChild(p);\n document.body.appendChild(el);\n }\n const pEl = el.querySelector(\"p\") || el;\n const flagsText = disabled.join(\", \");\n const line1 =\n disabled.length === 1\n ? `Page disabled: ${flagsText} flag is disabled.`\n : `Page disabled: ${flagsText} flags are disabled.`;\n const line2 =\n \"In production builds, pages with disabled flags like this will not be emitted\";\n pEl.textContent = `${line1}\\n${line2}`;\n };\n\n const affApply = () => {\n ensureFeatureFlagStyles();\n document.documentElement.setAttribute(\"data-ff-route\", affTokens(affPath()).join(\" \"));\n affSyncDisabledOverlay();\n };\n\n affApply();\n if (typeof MutationObserver === \"function\") {\n const obs = new MutationObserver((muts) => {\n for (let i = 0; i < muts.length; i++) {\n const a = muts[i] && muts[i].attributeName;\n if (a === \"data-ff-route\" || (a && a.indexOf(\"data-ff-enabled-\") === 0)) {\n affSyncDisabledOverlay();\n break;\n }\n }\n });\n obs.observe(document.documentElement, { attributes: true });\n }\n document.addEventListener(\"astro:page-load\", affApply);\n document.addEventListener(\"astro:after-swap\", affApply);\n } catch {\n // no-op in injected runtime\n }\n}\n\nexport function affDevBootstrapRuntime(payload: BootstrapPayload): void {\n const toPrefix = (pattern: string): string => {\n let p = String(pattern || \"\").trim();\n if (p.endsWith(\"/**\")) p = p.slice(0, -3);\n else if (p.endsWith(\"/*\")) p = p.slice(0, -2);\n else if (p.length > 1 && p.endsWith(\"*\")) p = p.slice(0, -1);\n return p.endsWith(\"/\") ? p : `${p}/`;\n };\n const T = payload.flagTokens;\n const C = payload.colors;\n const OD = payload.outlineDefaults;\n const BD = payload.badgeDefaults;\n const RF = payload.routeFlags;\n const F2T = payload.flagNameToToken;\n const N = payload.namespace;\n const K = `${N}.dev.v1`;\n\n const root = document.documentElement;\n const isEnabled = (tk: string) => root.getAttribute(`data-ff-enabled-${tk}`) !== \"off\";\n const currentTokenColor = (\n tk: string,\n cols: Record<string, string>,\n ): string => {\n const cssVar = root.style.getPropertyValue(`--${N}-c-${tk}`).trim();\n return cssVar || cols[tk] || C[tk] || \"#94a3b8\";\n };\n const unique = (a: string[]) => [...new Set(a.filter(Boolean))];\n\n const comboTokens = (el: Element): string[] => {\n let out: string[] = [];\n const raw = (el.getAttribute(`data-${N}`) || \"\").trim();\n if (raw) out = out.concat(raw.split(/\\s+/).filter(Boolean));\n for (let i = 0; i < T.length; i++) {\n const tk = T[i];\n if (el.hasAttribute(`data-${N}-${tk}`)) out.push(tk);\n }\n return unique(out);\n };\n\n const stripBooleanFlagAttrs = (el: Element) => {\n for (let i = 0; i < T.length; i++) {\n el.removeAttribute(`data-${N}-${T[i]}`);\n }\n };\n const clearComboVisualState = (el: HTMLElement) => {\n el.removeAttribute(\"data-ff-label\");\n el.style.removeProperty(\"--ff-combo-gradient\");\n el.style.removeProperty(\"--ff-combo-gradient-soft\");\n el.style.removeProperty(\"--ff-combo-outline\");\n el.style.removeProperty(\"--ff-combo-text\");\n el.style.removeProperty(\"--ff-combo-badge-border\");\n el.style.removeProperty(\"--ff-combo-badge-gradient\");\n };\n\n const applyCombo = (el: HTMLElement, cols: Record<string, string>) => {\n const allTokens = comboTokens(el);\n const hasCombo = allTokens.length >= 2;\n const hasDisabledInCombo = hasCombo && allTokens.some((tk) => !isEnabled(tk));\n const visual = allTokens.filter((tk) => isEnabled(tk));\n if (hasDisabledInCombo) {\n // Fail-closed for combos: if any member token is disabled, hide the whole element.\n // Keep all combo tokens mirrored in data-<ns> so enabled-off CSS selectors can hide it.\n stripBooleanFlagAttrs(el);\n el.setAttribute(`data-${N}`, allTokens.join(\" \"));\n clearComboVisualState(el);\n return;\n }\n if (visual.length < 2) {\n clearComboVisualState(el);\n if (hasCombo) {\n // Only normalize attrs for true combos. Single-flag shorthand markers\n // (e.g. data-ff-wip) must stay intact so re-enabling can recover styling.\n stripBooleanFlagAttrs(el);\n if (visual.length === 1) {\n el.setAttribute(`data-${N}`, visual[0]!);\n } else {\n el.removeAttribute(`data-${N}`);\n }\n }\n return;\n }\n stripBooleanFlagAttrs(el);\n // Mirror all enabled tokens into the primary namespace attribute so `[data-<ns>*=\" \"]`\n // matches (required for boolean-only combos like data-ff-a + data-ff-b).\n el.setAttribute(`data-${N}`, visual.join(\" \"));\n el.setAttribute(\"data-ff-label\", visual.join(\" | \"));\n const strong = visual.map((tk) => currentTokenColor(tk, cols));\n const soft = strong.map((c) => `color-mix(in oklab, white 86%, ${c} 14%)`);\n el.style.setProperty(\"--ff-combo-gradient\", `linear-gradient(90deg, ${strong.join(\", \")})`);\n el.style.setProperty(\"--ff-combo-gradient-soft\", `linear-gradient(90deg, ${soft.join(\", \")})`);\n el.style.setProperty(\"--ff-combo-outline\", strong[0] || \"#64748b\");\n el.style.setProperty(\"--ff-combo-text\", strong[0] || \"#111827\");\n el.style.setProperty(\n \"--ff-combo-badge-border\",\n `color-mix(in oklab, ${strong[0] || \"#64748b\"} 35%, transparent)`,\n );\n el.style.setProperty(\"--ff-combo-badge-gradient\", `linear-gradient(90deg, ${soft.join(\", \")})`);\n };\n\n const routeTokensForPath = (path: string): string[] => {\n const out: string[] = [];\n const seen = new Set<string>();\n for (const rp in RF) {\n if (!Object.prototype.hasOwnProperty.call(RF, rp)) continue;\n const cnd = toPrefix(rp);\n if (!(path === cnd || path.indexOf(cnd) === 0)) continue;\n const fns = RF[rp] || [];\n for (let i = 0; i < fns.length; i++) {\n const tk = F2T[fns[i]];\n if (tk && !seen.has(tk)) {\n seen.add(tk);\n out.push(tk);\n }\n }\n }\n return out;\n };\n\n const routeLabelFor = (path: string, toks: string[]): string => {\n if (!toks.length) return \"\";\n const byFlag: Record<string, string[]> = {};\n for (const rp in RF) {\n if (!Object.prototype.hasOwnProperty.call(RF, rp)) continue;\n const cnd = toPrefix(rp);\n if (!(path === cnd || path.indexOf(cnd) === 0)) continue;\n const fns = RF[rp] || [];\n for (let i = 0; i < fns.length; i++) {\n const tk = F2T[fns[i]];\n if (!tk) continue;\n if (!byFlag[tk]) byFlag[tk] = [];\n byFlag[tk].push(rp);\n }\n }\n return `Page: ${toks\n .map((tk) => `${tk} - ${(byFlag[tk] || []).join(\", \") || \"matched\"}`)\n .join(\" | \")}`;\n };\n\n const affSyncRouteChrome = (path: string) => {\n if (!document.body) return;\n // Keep every matched route token on <html> so per-token toolbar rules\n // (`[data-ff-route~=\"tk\"]`) still apply; gradients use enabled-only below.\n const routeAll = routeTokensForPath(path);\n if (!routeAll.length) {\n document.getElementById(\"aff-route-frame\")?.remove();\n document.getElementById(\"aff-route-badge\")?.remove();\n root.removeAttribute(\"data-ff-route\");\n root.removeAttribute(\"data-ff-route-label\");\n return;\n }\n root.setAttribute(\"data-ff-route\", routeAll.join(\" \"));\n root.setAttribute(\"data-ff-route-label\", routeLabelFor(path, routeAll));\n if (!document.getElementById(\"aff-route-frame\")) {\n const frame = document.createElement(\"div\");\n frame.id = \"aff-route-frame\";\n frame.className = \"aff-route-frame\";\n frame.setAttribute(\"aria-hidden\", \"true\");\n document.body.appendChild(frame);\n }\n let badge = document.getElementById(\"aff-route-badge\");\n if (!badge) {\n badge = document.createElement(\"div\");\n badge.id = \"aff-route-badge\";\n badge.className = \"aff-route-badge\";\n badge.setAttribute(\"role\", \"status\");\n document.body.appendChild(badge);\n }\n badge.textContent = root.getAttribute(\"data-ff-route-label\") || \"\";\n badge.setAttribute(\"aria-label\", badge.textContent || \"\");\n };\n\n try {\n (window as Window & { __AFF__?: unknown }).__AFF__ = {\n tokens: T,\n colors: C,\n namespace: N,\n };\n const raw = localStorage.getItem(K) || localStorage.getItem(\"aff.dev.v1\");\n const st = raw ? JSON.parse(raw) : {};\n const o = st.outline || st.chrome || {};\n const e = st.enabled || st.render || {};\n const b = st.badge || {};\n const cols = st.colors || {};\n\n for (let i = 0; i < T.length; i++) {\n const t = T[i];\n const od = OD[t] !== false;\n const bd = BD[t] !== false;\n if (o[t] === false || (!od && o[t] !== true))\n root.setAttribute(`data-ff-outline-${t}`, \"off\");\n else root.removeAttribute(`data-ff-outline-${t}`);\n if (e[t] === false) root.setAttribute(`data-ff-enabled-${t}`, \"off\");\n else root.removeAttribute(`data-ff-enabled-${t}`);\n if (b[t] === false || (!bd && b[t] !== true))\n root.setAttribute(`data-ff-badge-${t}`, \"off\");\n else root.removeAttribute(`data-ff-badge-${t}`);\n const col = cols[t] || C[t];\n const vn = `--${N}-c-${t}`;\n if (col) root.style.setProperty(vn, col);\n else root.style.removeProperty(vn);\n }\n\n const applyAll = () => {\n const map = new Set<HTMLElement>();\n document\n .querySelectorAll<HTMLElement>(`[data-${N}]`)\n .forEach((el) => map.add(el));\n for (let i = 0; i < T.length; i++) {\n document\n .querySelectorAll<HTMLElement>(`[data-${N}-${T[i]}]`)\n .forEach((el) => map.add(el));\n }\n map.forEach((el) => applyCombo(el, cols));\n };\n\n const affAfterNav = () => {\n if (!document.body) return;\n let p = window.location?.pathname || \"/\";\n if (!p.endsWith(\"/\")) p += \"/\";\n const toks = routeTokensForPath(p).filter((tk) => isEnabled(tk));\n if (toks.length) {\n const strong = toks.map((tk) => currentTokenColor(tk, cols));\n const soft = strong.map((c) => `color-mix(in oklab, white 86%, ${c} 14%)`);\n root.style.setProperty(`--${N}-route-gradient`, `linear-gradient(90deg, ${soft.join(\", \")})`);\n root.style.setProperty(\"--ff-route-outline-gradient\", `linear-gradient(90deg, ${strong.join(\", \")})`);\n root.style.setProperty(\"--ff-route-badge-gradient\", `linear-gradient(90deg, ${soft.join(\", \")})`);\n root.style.setProperty(\n \"--ff-route-badge-border\",\n `color-mix(in oklab, ${strong[0] || \"#64748b\"} 35%, transparent)`,\n );\n root.style.setProperty(\"--ff-route-outline\", strong[0] || \"#64748b\");\n root.style.setProperty(\"--ff-route-text\", strong[0] || \"#111827\");\n } else {\n root.style.removeProperty(`--${N}-route-gradient`);\n root.style.removeProperty(\"--ff-route-outline-gradient\");\n root.style.removeProperty(\"--ff-route-badge-gradient\");\n root.style.removeProperty(\"--ff-route-badge-border\");\n root.style.removeProperty(\"--ff-route-outline\");\n root.style.removeProperty(\"--ff-route-text\");\n }\n applyAll();\n affSyncRouteChrome(p);\n };\n\n affAfterNav();\n if (document.readyState === \"loading\") {\n document.addEventListener(\"DOMContentLoaded\", affAfterNav, { once: true });\n }\n document.addEventListener(\"astro:page-load\", affAfterNav);\n document.addEventListener(\"astro:after-swap\", affAfterNav);\n if (typeof MutationObserver === \"function\") {\n const obs = new MutationObserver((muts) => {\n for (let i = 0; i < muts.length; i++) {\n const a = muts[i]?.attributeName;\n if (!a) continue;\n if (\n a === \"style\" ||\n a.indexOf(\"data-ff-enabled-\") === 0 ||\n a.indexOf(\"data-ff-outline-\") === 0 ||\n a.indexOf(\"data-ff-badge-\") === 0\n ) {\n affAfterNav();\n break;\n }\n }\n });\n obs.observe(root, { attributes: true });\n }\n } catch {\n // no-op in injected runtime\n }\n}\n","import {\n elementBadgePositionBlock,\n normalizeElementBadgeLayout,\n} from \"./badge-layout\";\nimport type { ElementBadgeLayoutOptions } from \"./badge-layout\";\nimport { inlineInvoke } from \"./inline-script\";\nimport { affDevBootstrapRuntime } from \"./dev-inline-runtimes\";\nimport type { ResolvedFeatureRuntime } from \"./runtime\";\nimport { toToken } from \"./runtime\";\n\nexport type DevOutlineHiddenStrategy = \"visibility\" | \"display\";\n\nexport interface DevOutlineCssOptions extends ElementBadgeLayoutOptions {\n outlineWidth?: string;\n /** Fallback when no per-token colour is set. */\n outlineColor?: string;\n /** Per-flag-token outline / badge / route-badge colour (overrides JSON `colors`). */\n outlineColorByToken?: Record<string, string>;\n outlineOffset?: string;\n borderRadius?: string;\n\n /** Default pill text for the `wip` flag token in the dev toolbar. */\n badgeLabelWip?: string;\n /** @deprecated Use {@link badgeLabelWip}. */\n badgeLabelDev?: string;\n badgeLabelByToken?: Record<string, string>;\n\n hiddenStrategy?: DevOutlineHiddenStrategy;\n\n /**\n * @deprecated Unused: badge does not use container queries.\n */\n badgeMinInlineSize?: string;\n}\ntype NormalizedDevOutlineCssOptions = {\n outlineWidth: string;\n outlineColor: string;\n outlineColorByToken: Record<string, string>;\n outlineOffset: string;\n borderRadius: string;\n badgeLabelWip: string;\n badgeLabelByToken: Record<string, string>;\n hiddenStrategy: DevOutlineHiddenStrategy;\n badgeMinInlineSize: string;\n};\n\nconst defaultDevOutlineCssOptions: Omit<\n NormalizedDevOutlineCssOptions,\n \"outlineColorByToken\"\n> = {\n outlineWidth: \"2px\",\n outlineColor: \"rgb(220 38 38)\",\n outlineOffset: \"-2px\",\n borderRadius: \"0.5rem\",\n badgeLabelWip: \"wip\",\n badgeLabelByToken: {},\n hiddenStrategy: \"visibility\",\n badgeMinInlineSize: \"8rem\",\n};\n\nfunction normalizeCssOptions(\n css: DevOutlineCssOptions | undefined,\n runtime: ResolvedFeatureRuntime,\n): NormalizedDevOutlineCssOptions {\n const byToken = {\n ...runtime.flagColorsByToken,\n ...(css?.outlineColorByToken ?? {}),\n };\n const badgeLabelWip =\n css?.badgeLabelWip ?? css?.badgeLabelDev ?? defaultDevOutlineCssOptions.badgeLabelWip;\n return {\n ...defaultDevOutlineCssOptions,\n ...(css ?? {}),\n outlineColorByToken: byToken,\n badgeLabelWip,\n badgeLabelByToken: {\n ...defaultDevOutlineCssOptions.badgeLabelByToken,\n ...(css?.badgeLabelByToken ?? {}),\n },\n };\n}\n\nfunction colorForToken(\n token: string,\n opts: NormalizedDevOutlineCssOptions,\n): string {\n return opts.outlineColorByToken[token] ?? opts.outlineColor;\n}\n\n/** CSS variable per token, set on <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.map((s) => `${s}:not([${nsAttr}*=\" \"])`).join(\", \"),\n selIs: `:is(${baseSelectors.join(\", \")})`,\n };\n };\n const opts = normalizeCssOptions(css, runtime);\n const badgeLayout = normalizeElementBadgeLayout(css);\n const badgePos = elementBadgePositionBlock(badgeLayout);\n const chunks: string[] = [];\n\n for (const flag of Object.keys(runtime.flags)) {\n const { token, selOutline, selIs } = selectorsForFlag(flag);\n /** Combo hosts use `[${nsAttr}*=\" \"]` + `data-ff-label`; skip per-token ::before so combo rules win. */\n const selIsSingleBadge = `${selIs}:not([${nsAttr}*=\" \"])`;\n const col = colorForToken(token, opts);\n const v = featureColorVar(token, runtime.namespace);\n const label =\n opts.badgeLabelByToken[token] ??\n (token === \"wip\" ? opts.badgeLabelWip : token);\n const labelEscaped = String(label).replace(/'/g, \"\\\\'\");\n\n chunks.push(`\nhtml {\n --aff-outline-c-${token}: transparent;\n}\nhtml:not([data-ff-outline-${token}=\"off\"]) {\n --aff-outline-c-${token}: var(${v}, ${col});\n}\n${selOutline} {\n position: relative;\n outline: ${opts.outlineWidth} solid var(--aff-outline-c-${token});\n outline-offset: ${opts.outlineOffset};\n border-radius: ${opts.borderRadius};\n}\nhtml:not([data-ff-badge-${token}=\"off\"]) ${selIsSingleBadge}::before {\n box-sizing: border-box;\n position: absolute;\n inset: auto;\n margin: 0;\n width: max-content;\n max-width: min(18rem, calc(100% - 0.75rem));\n pointer-events: auto;\n z-index: 20;\n${badgePos}\n display: block;\n content: '${labelEscaped}';\n border: 1px solid color-mix(in oklab, var(${v}, ${col}) 60%, transparent);\n border-radius: 9999px;\n padding: 0.125rem 0.45rem;\n font-size: 0.625rem;\n line-height: 1.2;\n letter-spacing: 0.02em;\n font-weight: 600;\n color: var(${v}, ${col});\n background: color-mix(in oklab, white 85%, var(${v}, ${col}) 15%);\n white-space: nowrap;\n text-overflow: ellipsis;\n overflow: hidden;\n transition: opacity 0.14s ease, background 0.14s ease, border-color 0.14s ease;\n}\nhtml:not([data-ff-badge-${token}=\"off\"]) ${selIsSingleBadge}:hover::before {\n opacity: 0.16;\n background: color-mix(in oklab, white 94%, var(${v}, ${col}) 6%);\n border-color: color-mix(in oklab, var(${v}, ${col}) 35%, transparent);\n}\nhtml:not([data-ff-badge-${token}=\"off\"]) ${selIsSingleBadge}[data-ff-align=\"start\"]::before {\n left: 0.35rem;\n right: auto;\n transform: translateY(calc(-1 * 80%));\n}\nhtml:not([data-ff-badge-${token}=\"off\"]) ${selIsSingleBadge}[data-ff-align=\"center\"]::before {\n left: 50%;\n right: auto;\n transform: translate(-50%, calc(-1 * 80%));\n}\nhtml:not([data-ff-badge-${token}=\"off\"]) ${selIsSingleBadge}[data-ff-align=\"end\"]::before {\n left: auto;\n right: 0.35rem;\n transform: translateY(calc(-1 * 80%));\n}\nhtml:not([data-ff-badge-${token}=\"off\"]) ${selIsSingleBadge}[data-ff-horizontal]::before {\n left: calc(attr(data-ff-horizontal number, 50) * 1%);\n right: auto;\n transform: translate(-50%, calc(-1 * 80%));\n}\nhtml:not([data-ff-badge-${token}=\"off\"]) ${selIsSingleBadge}[data-ff-horizontal][data-ff-anchor=\"bottom\"]::before {\n transform: translate(-50%, 80%);\n}\nhtml:not([data-ff-badge-${token}=\"off\"]) ${selIsSingleBadge}[data-ff-anchor=\"bottom\"]::before {\n top: auto;\n bottom: 0;\n transform: translateY(80%);\n}\nhtml:not([data-ff-badge-${token}=\"off\"]) ${selIsSingleBadge}[data-ff-anchor=\"bottom\"][data-ff-align=\"center\"]::before {\n transform: translate(-50%, 80%);\n}\nhtml:not([data-ff-badge-${token}=\"off\"]) ${selIsSingleBadge}[data-ff-vertical]::before {\n transform: translateY(calc(attr(data-ff-vertical number, 80) * -1%));\n}\nhtml:not([data-ff-badge-${token}=\"off\"]) ${selIsSingleBadge}[data-ff-horizontal][data-ff-vertical]::before {\n transform: translate(-50%, calc(attr(data-ff-vertical number, 80) * -1%));\n}\nhtml:not([data-ff-badge-${token}=\"off\"]) ${selIsSingleBadge}[data-ff-anchor=\"bottom\"][data-ff-vertical]::before {\n transform: translateY(calc(attr(data-ff-vertical number, 80) * 1%));\n}\nhtml:not([data-ff-badge-${token}=\"off\"]) ${selIsSingleBadge}[data-ff-anchor=\"bottom\"][data-ff-horizontal][data-ff-vertical]::before {\n transform: translate(-50%, calc(attr(data-ff-vertical number, 80) * 1%));\n}\nhtml[data-ff-enabled-${token}=\"off\"] ${selIs} {\n display: none !important;\n}\n`);\n }\n\n // Multi-token value on a single element: show combined badge text and a gradient outline.\n chunks.push(`\n[${nsAttr}*=\" \"] {\n position: relative;\n outline: none !important;\n outline-offset: 0 !important;\n border-radius: ${opts.borderRadius};\n}\nhtml [${nsAttr}*=\" \"]::after {\n content: \"\";\n position: absolute;\n inset: 0;\n border-radius: inherit;\n pointer-events: none;\n z-index: 10;\n box-sizing: border-box;\n padding: ${opts.outlineWidth};\n background: var(--ff-combo-gradient, linear-gradient(90deg, #fecaca, #bfdbfe, #bbf7d0));\n -webkit-mask:\n linear-gradient(#000 0 0) content-box,\n linear-gradient(#000 0 0);\n -webkit-mask-composite: xor;\n mask-composite: exclude;\n}\nhtml [${nsAttr}*=\" \"]::before {\n box-sizing: border-box;\n position: absolute;\n inset: auto;\n margin: 0;\n width: max-content;\n max-width: min(18rem, calc(100% - 0.75rem));\n pointer-events: auto;\n z-index: 20;\n${badgePos}\n display: block;\n content: attr(${nsAttr});\n background: var(--ff-combo-badge-gradient, var(--ff-combo-gradient-soft, linear-gradient(90deg, #fff1f2, #eff6ff, #f0fdf4))) !important;\n color: var(--ff-combo-text, #111827) !important;\n border: 1px solid var(--ff-combo-badge-border, color-mix(in oklab, #94a3b8 50%, transparent)) !important;\n border-radius: 9999px;\n padding: 0.125rem 0.45rem;\n font-size: 0.625rem;\n line-height: 1.2;\n letter-spacing: 0.02em;\n font-weight: 600;\n white-space: nowrap;\n text-overflow: ellipsis;\n overflow: hidden;\n transition: opacity 0.14s ease, background 0.14s ease, border-color 0.14s ease;\n}\nhtml [${nsAttr}*=\" \"][data-ff-label]::before {\n content: attr(data-ff-label) !important;\n}\nhtml [${nsAttr}*=\" \"]:hover::before {\n opacity: 0.2;\n background: linear-gradient(90deg, #fff8f9, #f8fbff, #f8fff9);\n border-color: color-mix(in oklab, #94a3b8 35%, transparent);\n}\n`);\n\n // Combo chrome must be suppressed *after* generic combo rules so `!important` / order cannot be\n // overridden by `html [...][data-ff-label]::before { content: ... !important }`.\n for (const flag of Object.keys(runtime.flags)) {\n const { token, selIs } = selectorsForFlag(flag);\n chunks.push(`\nhtml[data-ff-badge-${token}=\"off\"] ${selIs}[${nsAttr}*=\" \"]::before,\nhtml[data-ff-badge-${token}=\"off\"] ${selIs}[${nsAttr}*=\" \"][data-ff-label]::before {\n display: none !important;\n content: none !important;\n}\nhtml[data-ff-badge-${token}=\"off\"] ${selIs}[${nsAttr}*=\" \"]:hover::before,\nhtml[data-ff-badge-${token}=\"off\"] ${selIs}[${nsAttr}*=\" \"][data-ff-label]:hover::before {\n display: none !important;\n opacity: 1 !important;\n background: none !important;\n border-color: transparent !important;\n}\nhtml[data-ff-outline-${token}=\"off\"] ${selIs}[${nsAttr}*=\" \"] {\n outline: none !important;\n outline-offset: 0 !important;\n}\nhtml[data-ff-outline-${token}=\"off\"] ${selIs}[${nsAttr}*=\" \"]::after {\n display: none !important;\n}\n`);\n }\n\n chunks.push(`\n.aff-route-frame {\n position: fixed;\n inset: 0.6rem;\n pointer-events: none;\n z-index: 10040;\n box-sizing: border-box;\n border-radius: 0.5rem;\n overflow: hidden;\n}\n.aff-route-frame::before {\n content: \"\";\n position: absolute;\n inset: 0;\n border-radius: inherit;\n padding: 2px;\n background: var(--ff-route-outline-gradient, linear-gradient(90deg, #ef4444, #3b82f6, #22c55e));\n -webkit-mask:\n linear-gradient(#000 0 0) content-box,\n linear-gradient(#000 0 0);\n -webkit-mask-composite: xor;\n mask-composite: exclude;\n}\n.aff-route-badge {\n position: fixed;\n top: 0.75rem;\n right: 0.75rem;\n z-index: 10060;\n pointer-events: auto;\n box-sizing: border-box;\n display: inline-flex;\n align-items: center;\n max-width: min(34rem, calc(100vw - 1.5rem));\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n border: 1px solid var(--ff-route-badge-border, color-mix(in oklab, #94a3b8 55%, transparent));\n border-radius: 9999px;\n padding: 0.15rem 0.5rem;\n font-size: 0.625rem;\n line-height: 1.2;\n letter-spacing: 0.02em;\n font-weight: 600;\n color: var(--ff-route-text, #111827);\n background: var(--ff-route-badge-gradient, linear-gradient(90deg, #fff1f2));\n opacity: 1;\n transition: opacity 0.14s ease, border-color 0.14s ease, background 0.14s ease;\n}\n.aff-route-badge:hover {\n opacity: 0.2;\n border-color: color-mix(in oklab, #94a3b8 35%, transparent);\n}\n.aff-route-disabled-overlay {\n position: fixed;\n inset: 0;\n z-index: 10050;\n box-sizing: border-box;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: clamp(1rem, 4vw, 2.5rem);\n text-align: center;\n font: 600 0.95rem/1.45 system-ui, -apple-system, Segoe UI, sans-serif;\n color: #f8fafc;\n background: rgba(15, 23, 42, 0.52);\n backdrop-filter: blur(2px);\n pointer-events: auto;\n}\n.aff-route-disabled-overlay > p {\n margin: 0;\n max-width: min(44rem, calc(100vw - 2rem));\n white-space: pre-line;\n}\n`);\n\n for (const flag of Object.keys(runtime.flags)) {\n const token = toToken(flag);\n chunks.push(`\n[data-ff-route~=\"${token}\"][data-ff-outline-${token}=\"off\"] #aff-route-frame {\n display: none !important;\n}\n[data-ff-route~=\"${token}\"][data-ff-badge-${token}=\"off\"] #aff-route-badge {\n display: none !important;\n}\n[data-ff-route~=\"${token}\"][data-ff-enabled-${token}=\"off\"] #aff-route-frame {\n display: none !important;\n}\n[data-ff-route~=\"${token}\"][data-ff-enabled-${token}=\"off\"] #aff-route-badge {\n display: none !important;\n}\n`);\n }\n\n return \"\\n\" + chunks.join(\"\\n\") + \"\\n\";\n}\n\n/**\n * CSS-only hiding for disabled flags. The Astro integration no longer injects this into\n * production builds — static HTML is culled instead. Kept for custom tooling or tests.\n */\nexport function createProductionGateStyles(\n runtime: ResolvedFeatureRuntime,\n): string {\n const nsAttr = `data-${toToken(runtime.namespace) || \"ff\"}`;\n const chunks: string[] = [];\n for (const [flag, enabled] of Object.entries(runtime.flags)) {\n if (enabled) continue;\n const token = toToken(flag);\n const valueSelectors = [`[${nsAttr}~=\"${token}\"]`];\n if (flag !== token) valueSelectors.push(`[${nsAttr}~=\"${flag}\"]`);\n chunks.push(\n `${[...valueSelectors, `[${nsAttr}-${token}]`].join(\", \")} { display: none !important; }`,\n );\n }\n return chunks.length ? `\\n${chunks.join(\"\\n\")}\\n` : \"\";\n}\n\nexport function buildAffDevBootstrapScript(\n flagTokens: string[],\n colors: Record<string, string>,\n outlineDefaults: Record<string, boolean>,\n badgeDefaults: Record<string, boolean>,\n routeFlags: Record<string, string[]>,\n flagNameToToken: Record<string, string>,\n namespace: string,\n): string {\n return inlineInvoke(affDevBootstrapRuntime, {\n flagTokens,\n colors,\n outlineDefaults,\n badgeDefaults,\n routeFlags,\n flagNameToToken,\n namespace: toToken(namespace) || \"ff\",\n });\n}\n","import { readdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { parse, type HTMLElement } from \"node-html-parser\";\nimport type { ResolvedFeatureRuntime } from \"./runtime\";\nimport { toToken } from \"./runtime\";\n\nfunction walkHtmlFiles(dir: string): string[] {\n const out: string[] = [];\n const entries = readdirSync(dir, { withFileTypes: true });\n for (const ent of entries) {\n const p = join(dir, ent.name);\n if (ent.isDirectory()) out.push(...walkHtmlFiles(p));\n else if (ent.isFile() && ent.name.endsWith(\".html\")) out.push(p);\n }\n return out;\n}\n\nfunction namespaceAttrName(namespace: string): string {\n return `data-${toToken(namespace) || \"ff\"}`;\n}\n\nfunction elementFlagSlugs(\n el: HTMLElement,\n nsAttr: string,\n knownTokens: readonly string[],\n): string[] {\n const slugSet = new Set<string>();\n const combined = el.getAttribute(nsAttr)?.trim();\n if (combined) {\n for (const part of combined.split(/\\s+/).filter(Boolean)) {\n slugSet.add(part);\n }\n }\n for (const tk of knownTokens) {\n if (el.hasAttribute(`${nsAttr}-${tk}`)) slugSet.add(tk);\n }\n return [...slugSet];\n}\n\nfunction slugToFlagNames(slug: string, flagNames: readonly string[]): string[] {\n const out: string[] = [];\n for (const name of flagNames) {\n if (slug === name || slug === toToken(name)) out.push(name);\n }\n return out;\n}\n\nfunction shouldCullElement(\n el: HTMLElement,\n runtime: ResolvedFeatureRuntime,\n nsAttr: string,\n knownTokens: readonly string[],\n flagNames: readonly string[],\n): boolean {\n const slugs = elementFlagSlugs(el, nsAttr, knownTokens);\n if (slugs.length === 0) return false;\n for (const slug of slugs) {\n const names = slugToFlagNames(slug, flagNames);\n if (names.length === 0) return false;\n }\n for (const slug of slugs) {\n const names = slugToFlagNames(slug, flagNames);\n for (const name of names) {\n if (!runtime.flags[name]) return true;\n }\n }\n return false;\n}\n\nfunction elementDepth(el: HTMLElement): number {\n let d = 0;\n let n: HTMLElement | null = el.parentNode as HTMLElement | null;\n while (n) {\n d++;\n n = n.parentNode as HTMLElement | null;\n }\n return d;\n}\n\n/**\n * Production static HTML: remove nodes gated by disabled flags (same rules as dev\n * `data-*` / `data-*-<token>`), strip dev-only route attributes on `<html>`, and drop\n * empty `<style>` tags left after `featureFlagStyles` is empty.\n */\nexport function cullProductionHtml(\n html: string,\n runtime: ResolvedFeatureRuntime,\n): string {\n const root = parse(html) as HTMLElement;\n const nsAttr = namespaceAttrName(runtime.namespace);\n const flagNames = Object.keys(runtime.flags);\n const knownTokens = flagNames.map((n) => toToken(n));\n\n const candidates = root.querySelectorAll(\"*\") as unknown as HTMLElement[];\n const toRemove: HTMLElement[] = [];\n for (const el of candidates) {\n if (shouldCullElement(el, runtime, nsAttr, knownTokens, flagNames)) {\n toRemove.push(el);\n }\n }\n toRemove.sort((a, b) => elementDepth(b) - elementDepth(a));\n for (const el of toRemove) {\n el.remove();\n }\n\n const htmlEl = root.querySelector(\"html\") as HTMLElement | null;\n if (htmlEl) {\n htmlEl.removeAttribute(\"data-ff-route\");\n htmlEl.removeAttribute(\"data-ff-route-label\");\n }\n\n const styles = root.querySelectorAll(\"style\") as unknown as HTMLElement[];\n for (const st of styles) {\n const text = st.textContent ?? \"\";\n if (!text.trim()) st.remove();\n }\n\n return root.outerHTML;\n}\n\n/**\n * Walk `outDir` (e.g. Astro `dist/`) and apply {@link cullProductionHtml} to every `.html` file.\n */\nexport function applyProductionHtmlCullToDist(\n outDir: string,\n runtime: ResolvedFeatureRuntime,\n): void {\n for (const file of walkHtmlFiles(outDir)) {\n const before = readFileSync(file, \"utf8\");\n const after = cullProductionHtml(before, runtime);\n if (after !== before) writeFileSync(file, after, \"utf8\");\n }\n}\n","import type { ResolvedFeatureRuntime } from \"./runtime\";\nimport { toToken } from \"./runtime\";\nimport { inlineInvoke } from \"./inline-script\";\nimport { affHeadInlineRuntime } from \"./dev-inline-runtimes\";\n\n/**\n * Single `injectScript('head-inline', …)` payload for `astro dev`: dev-only outline CSS,\n * `data-ff-route` on `<html>` (pathname + {@link ResolvedFeatureRuntime.routeFlags}), then\n * the feature-flag dev bootstrap (toolbar state, combo badges, route chrome).\n */\nexport function buildAffDevHeadInline(args: {\n runtime: ResolvedFeatureRuntime;\n featureFlagStyles: string;\n affDevBootstrap: string;\n}): string {\n const { runtime, featureFlagStyles, affDevBootstrap } = args;\n const flagNameToToken = Object.fromEntries(\n Object.keys(runtime.flags).map((name) => [name, toToken(name)]),\n );\n const setup = inlineInvoke(affHeadInlineRuntime, {\n featureFlagStyles,\n routeFlags: runtime.routeFlags,\n flagNameToToken,\n });\n return `${setup}\\n${affDevBootstrap}`;\n}\n","/**\n * Shared JS helper source used by generated browser scripts.\n * Keep this in sync with runtime.routePatternToPrefix semantics.\n */\nexport function routePrefixJsHelper(functionName: string): string {\n return `function ${functionName}(pattern){var p=String(pattern||'').trim();if(p.endsWith('/**'))p=p.slice(0,-3);else if(p.endsWith('/*'))p=p.slice(0,-2);else if(p.length>1&&p.endsWith('*'))p=p.slice(0,-1);return p.endsWith('/')?p:p+'/';}`;\n}\n","import { rmSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport {\n elementBadgePositionBlock,\n normalizeElementBadgeLayout,\n} from \"./badge-layout\";\nimport { DEV_TOOLBAR_FLAG_ICON_SVG } from \"./dev-toolbar-flag-icon\";\nimport type {\n ElementBadgeHorizontalAlign,\n ElementBadgeLayoutOptions,\n ElementBadgeVerticalAnchor,\n NormalizedElementBadgeLayout,\n} from \"./badge-layout\";\nimport {\n type FeatureFlagMap,\n type ResolveFeatureRuntimeOptions,\n type ResolvedFeatureRuntime,\n resolveFeatureFlagsByEnvironment,\n resolveFeatureRuntime,\n routePathsToPrune,\n shouldIncludeRoute,\n toEnumKey,\n toToken,\n} from \"./runtime\";\n\nexport type {\n ElementBadgeHorizontalAlign,\n ElementBadgeLayoutOptions,\n ElementBadgeVerticalAnchor,\n NormalizedElementBadgeLayout,\n};\nexport { elementBadgePositionBlock, normalizeElementBadgeLayout };\n\nexport type {\n DevOutlineCssOptions,\n DevOutlineHiddenStrategy,\n} from \"./dev-outline-css\";\n\nimport type { DevOutlineCssOptions } from \"./dev-outline-css\";\nimport {\n buildAffDevBootstrapScript,\n createFeatureFlagStyles,\n createProductionGateStyles,\n} from \"./dev-outline-css\";\nimport { applyProductionHtmlCullToDist } from \"./production-html-cull\";\nimport { buildAffDevHeadInline } from \"./dev-head-inject\";\nimport { routePrefixJsHelper } from \"./route-prefix-js\";\n\nexport { createFeatureFlagStyles, createProductionGateStyles };\nexport {\n cullProductionHtml,\n applyProductionHtmlCullToDist,\n} from \"./production-html-cull\";\n\nexport interface AstroFeatureFlagsOptions extends ResolveFeatureRuntimeOptions {\n css?: DevOutlineCssOptions;\n /** When false, keeps static build output untouched (no route pruning / HTML cull). */\n staticMinify?: boolean;\n}\n\n/** Prefer `prod` if present; else first non-`dev` key (sorted); else `\"prod\"`. */\nexport function primaryNonDevEnvironmentKey(\n flagsByEnvironment: Record<string, FeatureFlagMap>,\n): string {\n const keys = Object.keys(flagsByEnvironment);\n if (keys.includes(\"prod\")) return \"prod\";\n const nonDev = keys.filter((k) => k !== \"dev\").sort();\n return nonDev[0] ?? \"prod\";\n}\n\nfunction withDefaultEnvironments(\n options: ResolveFeatureRuntimeOptions = {},\n): ResolveFeatureRuntimeOptions {\n const mode = options.mode ?? process.env.NODE_ENV ?? \"development\";\n const raw =\n options.environments &&\n typeof options.environments === \"object\" &&\n !Array.isArray(options.environments)\n ? { ...options.environments }\n : {};\n delete raw.dev;\n const environments: NonNullable<ResolveFeatureRuntimeOptions[\"environments\"]> =\n {\n dev: {\n when: mode !== \"production\",\n },\n ...raw,\n };\n if (Object.keys(environments).length < 2) {\n environments.prod = environments.prod ?? {\n when: mode === \"production\",\n flags: {},\n };\n }\n return { ...options, environments };\n}\n\nexport function createVirtualModuleSource(\n runtime: ResolvedFeatureRuntime,\n css?: DevOutlineCssOptions,\n flagsByEnvironment: Record<string, FeatureFlagMap> = {},\n devBootstrap?: string,\n): string {\n const flagNames = Object.keys(runtime.flags);\n const flagTokens = flagNames.map((name) => toToken(name));\n const enumEntries = flagNames\n .map((name) => ` ${toEnumKey(name)}: ${JSON.stringify(name)}`)\n .join(\",\\n\");\n const tokenEntries = flagNames\n .map((name) => ` ${toEnumKey(name)}: ${JSON.stringify(toToken(name))}`)\n .join(\",\\n\");\n const styles = createFeatureFlagStyles(runtime, css);\n const colorsJson = JSON.stringify(runtime.flagColorsByToken, null, 2);\n const flagNameToToken = JSON.stringify(\n Object.fromEntries(flagNames.map((name) => [name, toToken(name)])),\n null,\n 2,\n );\n const defaultNonDevEnvironment =\n primaryNonDevEnvironmentKey(flagsByEnvironment);\n const bootstrap =\n devBootstrap ??\n buildAffDevBootstrapScript(\n flagTokens,\n { ...runtime.flagColorsByToken },\n { ...runtime.flagOutlineDefaultsByToken },\n { ...runtime.flagBadgeDefaultsByToken },\n { ...runtime.routeFlags },\n Object.fromEntries(flagNames.map((name) => [name, toToken(name)])),\n runtime.namespace,\n );\n const affRoutePatternToPrefix = routePrefixJsHelper(\"affRoutePatternToPrefix\");\n\n return `${affRoutePatternToPrefix}\n\nexport const FeatureFlag = Object.freeze({\n${enumEntries}\n});\n\nexport const FeatureToken = Object.freeze({\n${tokenEntries}\n});\n\nexport const featureFlagTokens = Object.freeze(${JSON.stringify(flagTokens)});\nexport const featureFlagColors = Object.freeze(${colorsJson});\nexport const featureFlags = Object.freeze(${JSON.stringify(runtime.flags, null, 2)});\nexport const featureFlagsByEnvironment = Object.freeze(${JSON.stringify(flagsByEnvironment, null, 2)});\nexport const defaultNonDevEnvironment = ${JSON.stringify(defaultNonDevEnvironment)};\nexport const featureRouteFlags = Object.freeze(${JSON.stringify(runtime.routeFlags, null, 2)});\nexport const featureNamespace = ${JSON.stringify(runtime.namespace)};\n/** True when Astro is running the dev server or dev build (import.meta.env.DEV). Not the same as the reserved \\`dev\\` environment layer — see \\`activeEnvironmentKey\\`. */\nexport const isAstroDev = import.meta.env.DEV;\nexport const activeEnvironmentKey = ${JSON.stringify(runtime.activeEnvironment)};\n\nexport const affDevBootstrap = import.meta.env.DEV ? ${JSON.stringify(bootstrap)} : '';\n\nexport function isFeatureEnabled(flag) {\n return Boolean(featureFlags[flag]);\n}\n\nexport function shouldRenderFeature(flag) {\n return isFeatureEnabled(flag);\n}\n\nexport function isFeatureRoute(pathname) {\n const normalized = pathname.endsWith('/') ? pathname : pathname + '/';\n return Object.keys(featureRouteFlags).some((route) => {\n const candidate = affRoutePatternToPrefix(route);\n return normalized === candidate || normalized.startsWith(candidate);\n });\n}\n\nexport function shouldIncludePath(pathname) {\n if (import.meta.env.DEV) return true;\n const normalized = pathname.endsWith('/') ? pathname : pathname + '/';\n for (const [route, flags] of Object.entries(featureRouteFlags)) {\n const candidate = affRoutePatternToPrefix(route);\n if (normalized === candidate || normalized.startsWith(candidate)) {\n return flags.every((flag) => isFeatureEnabled(flag));\n }\n }\n return true;\n}\n\nexport function flagsForEnvironment(envName) {\n var F = featureFlagsByEnvironment[envName];\n return F != null ? F : null;\n}\n\nexport function isFeatureEnabledForEnvironment(flag, envName) {\n var F = featureFlagsByEnvironment[envName];\n return !!(F && F[flag]);\n}\n\nexport function shouldIncludePathForEnvironment(pathname, envName) {\n var F = featureFlagsByEnvironment[envName];\n if (!F) return true;\n const normalized = pathname.endsWith('/') ? pathname : pathname + '/';\n for (const [route, flagList] of Object.entries(featureRouteFlags)) {\n const candidate = affRoutePatternToPrefix(route);\n if (normalized === candidate || normalized.startsWith(candidate)) {\n return flagList.every((flag) => Boolean(F[flag]));\n }\n }\n return true;\n}\n\nconst flagNameToToken = Object.freeze(${flagNameToToken});\n\nexport function matchedFeatureRoutePrefix(pathname) {\n if (!pathname) return null;\n const normalized = pathname.endsWith('/') ? pathname : pathname + '/';\n let best = null;\n let bestLen = -1;\n for (const routePrefix of Object.keys(featureRouteFlags)) {\n const route = affRoutePatternToPrefix(routePrefix);\n if (normalized === route || normalized.startsWith(route)) {\n if (route.length > bestLen) {\n bestLen = route.length;\n best = routePrefix;\n }\n }\n }\n return best;\n}\n\nexport function routeFeatureTokenForPath(pathname) {\n const prefix = matchedFeatureRoutePrefix(pathname);\n if (!prefix) return undefined;\n const flagNames = featureRouteFlags[prefix] || [];\n const first = flagNames[0];\n return first != null ? flagNameToToken[first] : undefined;\n}\n\nexport function routeFeatureTokensForPath(pathname) {\n if (!pathname) return [];\n const normalized = pathname.endsWith('/') ? pathname : pathname + '/';\n const out = [];\n const seen = new Set();\n for (const [routePrefix, flagNames] of Object.entries(featureRouteFlags)) {\n const route = affRoutePatternToPrefix(routePrefix);\n if (!(normalized === route || normalized.startsWith(route))) continue;\n for (const name of flagNames || []) {\n const token = flagNameToToken[name];\n if (!token || seen.has(token)) continue;\n seen.add(token);\n out.push(token);\n }\n }\n return out;\n}\n\nexport function routeFeatureMatchesForPath(pathname) {\n if (!pathname) return [];\n const normalized = pathname.endsWith('/') ? pathname : pathname + '/';\n var byFlag = {};\n for (const [routePattern, flagNames] of Object.entries(featureRouteFlags)) {\n const candidate = affRoutePatternToPrefix(routePattern);\n if (!(normalized === candidate || normalized.startsWith(candidate))) continue;\n for (const flagName of flagNames || []) {\n const token = flagNameToToken[flagName];\n if (!token) continue;\n if (!byFlag[flagName]) byFlag[flagName] = { flag: flagName, token, routes: [] };\n byFlag[flagName].routes.push(routePattern);\n }\n }\n return Object.values(byFlag);\n}\n\nexport const featureFlagStyles = import.meta.env.DEV ? ${JSON.stringify(styles)} : \"\";\n`;\n}\n\nexport function getResolvedFeatures(\n options: ResolveFeatureRuntimeOptions = {},\n): ResolvedFeatureRuntime {\n return resolveFeatureRuntime(withDefaultEnvironments(options));\n}\n\nexport function featureRouteIncluded(\n pathname: string,\n runtime: ResolvedFeatureRuntime,\n): boolean {\n return shouldIncludeRoute({\n pathname,\n routeFlags: runtime.routeFlags,\n flags: runtime.flags,\n isDev: runtime.isDev,\n });\n}\n\nexport type { FeatureFlagMap } from \"./runtime\";\nexport {\n longestMatchingRoutePrefix,\n mergeFlagsWithProcessEnvOverrides,\n resolveFeatureFlagsByEnvironment,\n routePatternToPrefix,\n} from \"./runtime\";\n\nexport default function astroFeatureFlags(\n options: AstroFeatureFlagsOptions = {},\n): any {\n const { css, staticMinify = true, ...flagOpts } = options;\n const opts = withDefaultEnvironments(flagOpts);\n const mode = opts.mode ?? process.env.NODE_ENV ?? \"development\";\n\n const runtime = resolveFeatureRuntime({\n ...opts,\n mode,\n });\n\n return {\n name: \"astro-feature-flags\",\n hooks: {\n \"astro:config:setup\": ({\n updateConfig,\n addDevToolbarApp,\n command,\n injectScript,\n }: {\n updateConfig: (config: unknown) => void;\n addDevToolbarApp?: (opts: {\n id: string;\n name: string;\n icon: string;\n entrypoint: string;\n }) => void;\n command?: string;\n injectScript?: (stage: string, content: string) => void;\n }) => {\n const flagNames = Object.keys(runtime.flags);\n const flagTokens = flagNames.map((name) => toToken(name));\n const flagsByEnvironment = resolveFeatureFlagsByEnvironment(opts);\n const flagNameToToken = Object.fromEntries(\n flagNames.map((name) => [name, toToken(name)]),\n );\n const bootstrap = buildAffDevBootstrapScript(\n flagTokens,\n { ...runtime.flagColorsByToken },\n { ...runtime.flagOutlineDefaultsByToken },\n { ...runtime.flagBadgeDefaultsByToken },\n { ...runtime.routeFlags },\n flagNameToToken,\n runtime.namespace,\n );\n\n if (\n command === \"dev\" &&\n runtime.isDev &&\n typeof addDevToolbarApp === \"function\"\n ) {\n addDevToolbarApp({\n id: \"astro-feature-flags\",\n name: \"Feature flags\",\n icon: DEV_TOOLBAR_FLAG_ICON_SVG,\n entrypoint: fileURLToPath(\n new URL(\"./dev-toolbar-app.mjs\", import.meta.url),\n ),\n });\n }\n if (\n command === \"dev\" &&\n runtime.isDev &&\n typeof injectScript === \"function\"\n ) {\n const styles = createFeatureFlagStyles(runtime, css);\n injectScript(\n \"head-inline\",\n buildAffDevHeadInline({\n runtime,\n featureFlagStyles: styles,\n affDevBootstrap: bootstrap,\n }),\n );\n }\n updateConfig({\n vite: {\n plugins: [\n {\n name: \"astro-feature-flags:virtual-module\",\n resolveId(id: string) {\n if (id === \"virtual:astro-feature-flags\")\n return \"\\0virtual:astro-feature-flags\";\n return null;\n },\n load(id: string) {\n if (id === \"\\0virtual:astro-feature-flags\") {\n return createVirtualModuleSource(\n runtime,\n css,\n flagsByEnvironment,\n bootstrap,\n );\n }\n return null;\n },\n },\n ],\n },\n });\n },\n \"astro:build:done\": ({ dir }: { dir: URL }) => {\n if (runtime.isDev || !staticMinify) return;\n const outDir = fileURLToPath(dir);\n const prunePaths = routePathsToPrune({\n routeFlags: runtime.routeFlags,\n flags: runtime.flags,\n });\n for (const routePath of prunePaths) {\n rmSync(join(outDir, routePath), { recursive: true, force: true });\n }\n applyProductionHtmlCullToDist(outDir, runtime);\n },\n },\n };\n}\n"],"mappings":"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"}
|
|
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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"}
|
package/dist/runtime.d.mts
CHANGED
|
@@ -49,7 +49,8 @@ interface ResolveFeatureRuntimeOptions {
|
|
|
49
49
|
}>;
|
|
50
50
|
environments?: Record<string, {
|
|
51
51
|
when?: boolean;
|
|
52
|
-
flags?: Record<string, boolean>;
|
|
52
|
+
flags?: Record<string, boolean>;
|
|
53
|
+
/** Optional JSON merged after the root file when this environment is active. */
|
|
53
54
|
jsonConfigPath?: string;
|
|
54
55
|
}>;
|
|
55
56
|
}
|
|
@@ -88,21 +89,13 @@ interface ResolvedFeatureRuntime {
|
|
|
88
89
|
declare function resolveFeatureRuntime(options?: ResolveFeatureRuntimeOptions): ResolvedFeatureRuntime;
|
|
89
90
|
declare function isFlagEnabled(flags: FeatureFlagMap, flag: string): boolean;
|
|
90
91
|
declare function isRouteFlagged(pathname: string, routeFlags: FeatureRouteMap): boolean;
|
|
91
|
-
declare function shouldIncludeRoute({
|
|
92
|
-
pathname,
|
|
93
|
-
routeFlags,
|
|
94
|
-
flags,
|
|
95
|
-
isDev
|
|
96
|
-
}: {
|
|
92
|
+
declare function shouldIncludeRoute({ pathname, routeFlags, flags, isDev }: {
|
|
97
93
|
pathname: string;
|
|
98
94
|
routeFlags: FeatureRouteMap;
|
|
99
95
|
flags: FeatureFlagMap;
|
|
100
96
|
isDev: boolean;
|
|
101
97
|
}): boolean;
|
|
102
|
-
declare function routePathsToPrune({
|
|
103
|
-
routeFlags,
|
|
104
|
-
flags
|
|
105
|
-
}: {
|
|
98
|
+
declare function routePathsToPrune({ routeFlags, flags }: {
|
|
106
99
|
routeFlags: FeatureRouteMap;
|
|
107
100
|
flags: FeatureFlagMap;
|
|
108
101
|
}): string[];
|
package/dist/runtime.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.mts","names":[],"sources":["../src/runtime.ts"],"mappings":";KAGY,
|
|
1
|
+
{"version":3,"file":"runtime.d.mts","names":[],"sources":["../src/runtime.ts"],"mappings":";KAGY,iBAAiB;KACjB,kBAAkB;KAClB,kBAAkB;KAClB,wBAAwB;UAEnB;EACf;EACA,OAAO;EACP,YAAY;EACZ,QAAQ;EACR,wBAAwB;EACxB,sBAAsB;EACtB;;EAEA;;;;;;;;;;;UAYe;;;;;EAKf;;EAEA;;EAEA;;EAEA,MAAM;;;;EAIN;EACA;EACA,QAAQ;IAGJ;IACA;IACA;IACA;IACA;;EAGJ,eAAe;IAGX;IACA,QAAQ;;IAER;;;iBAmBU,cAAc;;;;;iBASd,qBAAqB;iBAYrB,UAAU;iBASV,QAAQ;iBAsPR,kBACd,UAAS,+BACR;;iBA2Ca,kCACd,OAAO,gBACP,MAAK,qCAIJ;;;;;iBAyBa,iCACd,UAAS,+BACR,eAAe;;iBAmBF,iBACd,QAAQ,kBACP;UAQc;EACf;EACA;;EAEA;EACA;EACA,OAAO;EACP,YAAY;;EAEZ,mBAAmB;;EAEnB,4BAA4B;EAC5B,0BAA0B;;iBAGZ,sBACd,UAAS,+BACR;iBAwBa,cAAc,OAAO,gBAAgB;iBAIrC,eACd,kBACA,YAAY;iBASE,qBACd,UACA,YACA,OACA;EAEA;EACA,YAAY;EACZ,OAAO;EACP;;iBAec,oBACd,YACA;EAEA,YAAY;EACZ,OAAO;;;;;iBAeO,2BACd,kBACA,YAAY"}
|
package/dist/runtime.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.mjs","names":[],"sources":["../src/runtime.ts"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { isAbsolute, join } from \"node:path\";\n\nexport type FeatureFlagMap = Record<string, boolean>;\nexport type FeatureRouteMap = Record<string, string[]>;\nexport type FeatureColorMap = Record<string, string>;\nexport type FeatureBoolByTokenMap = Record<string, boolean>;\n\nexport interface FeatureConfig {\n namespace: string;\n flags: FeatureFlagMap;\n routeFlags: FeatureRouteMap;\n colors: FeatureColorMap;\n outlineDefaultsByToken: FeatureBoolByTokenMap;\n badgeDefaultsByToken: FeatureBoolByTokenMap;\n mode: string;\n /** Which `environments` entry was used (reserved `dev` = all flags on, toolbar layer). */\n activeEnvironment: string;\n}\n\n/**\n * Declarative feature flags for Astro. Merge order: inline options → optional root\n * {@link ResolveFeatureRuntimeOptions.jsonConfigPath} → optional per-environment\n * `environments.<name>.jsonConfigPath` (merged when that layer is active).\n *\n * Exactly one `when: true` across environments unless `forceEnvironment` or `AFF_ENVIRONMENT`\n * selects the layer. A reserved **`dev`** environment is required (all flags on at resolve time;\n * toggles are dev-toolbar only) plus at least one other environment (e.g. `prod`).\n */\nexport interface ResolveFeatureRuntimeOptions {\n /**\n * Directory used to resolve relative {@link jsonConfigPath} values (root and per-environment).\n * Defaults to `process.cwd()`.\n */\n configRoot?: string;\n /** Optional main JSON file merged after inline `flags` / `environments`. */\n jsonConfigPath?: string;\n /** Passed through to `ResolvedFeatureRuntime.mode` (e.g. `process.env.NODE_ENV`). */\n mode?: string;\n /** Process env for `AFF_FEATURE_*` and `ASTRO_FEATURE_FLAGS` (skipped when active env is `dev`). */\n env?: Record<string, string | undefined>;\n /**\n * Use this `environments` entry regardless of `when` / `AFF_ENVIRONMENT` (e.g. sitemaps, tests).\n */\n forceEnvironment?: string;\n tokenNamespace?: string;\n flags?: Record<\n string,\n {\n color?: string;\n colour?: string;\n outline?: boolean;\n badge?: boolean;\n routes?: string[];\n }\n >;\n environments?: Record<\n string,\n {\n when?: boolean;\n flags?: Record<string, boolean>;\n /** Optional JSON merged after the root file when this environment is active. */\n jsonConfigPath?: string;\n }\n >;\n}\n\nfunction toBoolean(value: unknown, fallback = false): boolean {\n if (typeof value === \"boolean\") return value;\n if (typeof value !== \"string\") return fallback;\n const normalized = value.trim().toLowerCase();\n if ([\"1\", \"true\", \"yes\", \"on\"].includes(normalized)) return true;\n if ([\"0\", \"false\", \"no\", \"off\"].includes(normalized)) return false;\n return fallback;\n}\n\nfunction readJsonIfExists(pathname: string): Record<string, unknown> {\n if (!existsSync(pathname)) return {};\n return JSON.parse(readFileSync(pathname, \"utf8\")) as Record<string, unknown>;\n}\n\nexport function normalizePath(pathname: string): string {\n if (!pathname) return \"/\";\n return pathname.endsWith(\"/\") ? pathname : `${pathname}/`;\n}\n\n/**\n * Normalizes a route key from `ff.json` for prefix matching.\n * Supports wildcards: `/blog/*`, `/docs/**` → prefix `/blog/`, `/docs/`.\n */\nexport function routePatternToPrefix(pattern: string): string {\n let p = pattern.trim();\n if (p.endsWith(\"/**\")) {\n p = p.slice(0, -3);\n } else if (p.endsWith(\"/*\")) {\n p = p.slice(0, -2);\n } else if (p.endsWith(\"*\") && p.length > 1) {\n p = p.slice(0, -1);\n }\n return normalizePath(p);\n}\n\nexport function toEnumKey(flagName: string): string {\n return flagName\n .replace(/[^a-zA-Z0-9]+/g, \" \")\n .trim()\n .split(/\\s+/)\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join(\"\");\n}\n\nexport function toToken(flagName: string): string {\n return flagName\n .replace(/[^a-zA-Z0-9]+/g, \"-\")\n .replace(/([a-z0-9])([A-Z])/g, \"$1-$2\")\n .replace(/([a-zA-Z])([0-9])/g, \"$1-$2\")\n .replace(/([0-9])([a-zA-Z])/g, \"$1-$2\")\n .toLowerCase()\n .replace(/-+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n}\n\nfunction mergeRecords<T extends Record<string, unknown>>(\n base: T,\n ...layers: Partial<T>[]\n): T {\n const out = { ...base };\n for (const layer of layers) {\n for (const [k, v] of Object.entries(layer)) {\n if (v === undefined) continue;\n const cur = out[k as keyof T];\n if (\n v &&\n typeof v === \"object\" &&\n !Array.isArray(v) &&\n cur &&\n typeof cur === \"object\" &&\n !Array.isArray(cur)\n ) {\n (out as Record<string, unknown>)[k] = mergeRecords(\n cur as Record<string, unknown>,\n v as Record<string, unknown>,\n );\n } else {\n (out as Record<string, unknown>)[k] = v;\n }\n }\n }\n return out;\n}\n\nfunction resolvePath(relativeOrAbsolute: string, configRoot: string): string {\n return isAbsolute(relativeOrAbsolute)\n ? relativeOrAbsolute\n : join(configRoot, relativeOrAbsolute);\n}\n\ntype NormalizedFlagDef = {\n color?: string;\n outline: boolean;\n badge: boolean;\n routes: string[];\n};\n\ntype NormalizedEnvEntry = {\n when?: boolean;\n flags: Record<string, boolean>;\n /** Per-environment JSON merged after root `jsonConfigPath` when this layer is active. */\n jsonConfigPath?: string;\n};\n\ntype DeclarativeFeatureConfig = {\n tokenNamespace: string;\n flags: Record<string, NormalizedFlagDef>;\n environments: Record<string, NormalizedEnvEntry>;\n};\n\nfunction normalizeDeclarativeConfig(\n raw: Record<string, unknown>,\n mode: string = process.env.NODE_ENV ?? \"development\",\n): DeclarativeFeatureConfig {\n const tokenNamespaceRaw =\n (typeof raw.tokenNamespace === \"string\" ? raw.tokenNamespace : undefined) ??\n (typeof raw.namespace === \"string\" ? raw.namespace : undefined) ??\n \"ff\";\n\n const flagsRaw =\n raw.flags && typeof raw.flags === \"object\" && !Array.isArray(raw.flags)\n ? (raw.flags as Record<string, unknown>)\n : {};\n const flags: Record<string, NormalizedFlagDef> = {};\n for (const [flagName, maybeDef] of Object.entries(flagsRaw)) {\n if (!maybeDef || typeof maybeDef !== \"object\" || Array.isArray(maybeDef))\n continue;\n const def = maybeDef as Record<string, unknown>;\n const color =\n (typeof def.colour === \"string\" ? def.colour : undefined) ??\n (typeof def.color === \"string\" ? def.color : undefined);\n const outline = typeof def.outline === \"boolean\" ? def.outline : true;\n const badge = typeof def.badge === \"boolean\" ? def.badge : true;\n const routes = Array.isArray(def.routes)\n ? def.routes.filter((v): v is string => typeof v === \"string\")\n : [];\n flags[flagName] = { color, outline, badge, routes };\n }\n\n const envRaw =\n raw.environments &&\n typeof raw.environments === \"object\" &&\n !Array.isArray(raw.environments)\n ? (raw.environments as Record<string, unknown>)\n : {};\n const environments: Record<string, NormalizedEnvEntry> = {};\n for (const [envName, maybeEnv] of Object.entries(envRaw)) {\n if (envName === \"dev\") continue;\n if (!maybeEnv || typeof maybeEnv !== \"object\" || Array.isArray(maybeEnv))\n continue;\n const env = maybeEnv as Record<string, unknown>;\n const envFlagsRaw =\n env.flags && typeof env.flags === \"object\" && !Array.isArray(env.flags)\n ? (env.flags as Record<string, unknown>)\n : {};\n const envFlags: Record<string, boolean> = {};\n for (const [flagName, value] of Object.entries(envFlagsRaw)) {\n envFlags[flagName] = Boolean(value);\n }\n const when = typeof env.when === \"boolean\" ? env.when : undefined;\n const layerJson =\n typeof env.jsonConfigPath === \"string\" ? env.jsonConfigPath : undefined;\n environments[envName] = {\n ...(when !== undefined ? { when } : {}),\n ...(layerJson ? { jsonConfigPath: layerJson } : {}),\n flags: envFlags,\n };\n }\n\n environments.dev = {\n when: mode !== \"production\",\n flags: {},\n };\n\n return {\n tokenNamespace: tokenNamespaceRaw,\n flags,\n environments,\n };\n}\n\nfunction assertEnvironmentShape(environments: Record<string, NormalizedEnvEntry>) {\n const keys = Object.keys(environments);\n if (!environments.dev) {\n throw new Error(\n 'astro-feature-flags: environments must include a reserved \"dev\" entry.',\n );\n }\n if (keys.length < 2) {\n throw new Error(\n \"astro-feature-flags: environments must include `dev` plus at least one other layer (e.g. `prod`).\",\n );\n }\n}\n\nfunction countActiveWhen(environments: Record<string, NormalizedEnvEntry>): number {\n return Object.values(environments).filter((e) => e.when === true).length;\n}\n\nfunction resolveAffEnvironment(\n options: ResolveFeatureRuntimeOptions,\n): string | undefined {\n const proc =\n options.env ?? (process.env as Record<string, string | undefined>);\n return proc.AFF_ENVIRONMENT?.trim();\n}\n\nfunction resolveActiveEnvironmentName(\n normalized: DeclarativeFeatureConfig,\n options: ResolveFeatureRuntimeOptions,\n): string {\n const { environments } = normalized;\n assertEnvironmentShape(environments);\n\n const forced = options.forceEnvironment?.trim();\n if (forced && environments[forced]) return forced;\n\n const aff = resolveAffEnvironment(options);\n if (aff && environments[aff]) return aff;\n\n const n = countActiveWhen(environments);\n if (n !== 1) {\n const activeNames = Object.entries(environments)\n .filter(([, e]) => e.when === true)\n .map(([k]) => k);\n const devWhen = environments.dev?.when;\n const hint =\n n > 1 && environments.dev && devWhen === true\n ? \" The reserved `dev` environment is active for this `mode` while another layer also has `when: true`. Pin one layer with `forceEnvironment` or `AFF_ENVIRONMENT`, or adjust non-dev `when` predicates.\"\n : \"\";\n throw new Error(\n `astro-feature-flags: exactly one environment must have when: true (found ${n}${activeNames.length ? `: ${activeNames.join(\", \")}` : \"\"}).${hint} Set forceEnvironment or AFF_ENVIRONMENT to pick a layer explicitly.`,\n );\n }\n for (const [name, cfg] of Object.entries(environments)) {\n if (cfg.when === true) return name;\n }\n throw new Error(\"astro-feature-flags: internal error resolving environment.\");\n}\n\nfunction readBaseMergedRaw(options: ResolveFeatureRuntimeOptions): Record<string, unknown> {\n const configRoot = options.configRoot ?? process.cwd();\n const {\n tokenNamespace = \"ff\",\n flags = {},\n environments = {},\n } = options;\n const inlineRaw: Record<string, unknown> = {\n tokenNamespace,\n flags,\n environments,\n };\n let merged = { ...inlineRaw };\n if (options.jsonConfigPath) {\n const p = resolvePath(options.jsonConfigPath, configRoot);\n merged = mergeRecords(merged as Record<string, unknown>, readJsonIfExists(p));\n }\n return merged;\n}\n\nfunction mergePerEnvironmentJson(\n baseRaw: Record<string, unknown>,\n envName: string,\n configRoot: string,\n mode: string,\n): Record<string, unknown> {\n if (envName === \"dev\") return baseRaw;\n const normalized = normalizeDeclarativeConfig(baseRaw, mode);\n const entry = normalized.environments[envName];\n const rel = entry?.jsonConfigPath;\n if (!rel) return baseRaw;\n const p = resolvePath(rel, configRoot);\n return mergeRecords(baseRaw, readJsonIfExists(p));\n}\n\nfunction readMergedRawForActiveEnv(\n options: ResolveFeatureRuntimeOptions,\n envName: string,\n): Record<string, unknown> {\n const configRoot = options.configRoot ?? process.cwd();\n const mode = options.mode ?? process.env.NODE_ENV ?? \"development\";\n const base = readBaseMergedRaw(options);\n return mergePerEnvironmentJson(base, envName, configRoot, mode);\n}\n\nexport function loadFeatureConfig(\n options: ResolveFeatureRuntimeOptions = {},\n): FeatureConfig {\n const mode = options.mode ?? process.env.NODE_ENV ?? \"development\";\n const baseRaw = readBaseMergedRaw(options);\n const baseNorm = normalizeDeclarativeConfig(baseRaw, mode);\n const envName = resolveActiveEnvironmentName(baseNorm, options);\n const fullRaw = readMergedRawForActiveEnv(options, envName);\n const normalized = normalizeDeclarativeConfig(fullRaw, mode);\n\n const envFlags = normalized.environments[envName]?.flags ?? {};\n const routeFlags: FeatureRouteMap = {};\n const colors: FeatureColorMap = {};\n const outlineDefaultsByToken: FeatureBoolByTokenMap = {};\n const badgeDefaultsByToken: FeatureBoolByTokenMap = {};\n const resolvedFlags: FeatureFlagMap = {};\n\n for (const [flagName, flagDef] of Object.entries(normalized.flags)) {\n if (envName === \"dev\") {\n resolvedFlags[flagName] = true;\n } else {\n resolvedFlags[flagName] = envFlags[flagName] === true;\n }\n if (flagDef.color) colors[flagName] = flagDef.color;\n const token = toToken(flagName);\n outlineDefaultsByToken[token] = flagDef.outline;\n badgeDefaultsByToken[token] = flagDef.badge;\n for (const route of flagDef.routes) {\n routeFlags[route] = [...(routeFlags[route] ?? []), flagName];\n }\n }\n\n return {\n namespace: normalized.tokenNamespace || \"ff\",\n flags: resolvedFlags,\n routeFlags,\n colors,\n outlineDefaultsByToken,\n badgeDefaultsByToken,\n mode,\n activeEnvironment: envName,\n };\n}\n\n/** Applies `AFF_FEATURE_*` and `ASTRO_FEATURE_FLAGS` on top of a resolved flag map. */\nexport function mergeFlagsWithProcessEnvOverrides(\n flags: FeatureFlagMap,\n env: Record<string, string | undefined> = process.env as Record<\n string,\n string | undefined\n >,\n): FeatureFlagMap {\n const resolvedFlags: FeatureFlagMap = { ...flags };\n for (const [key, value] of Object.entries(resolvedFlags)) {\n const slug = toToken(key).replace(/-/g, \"_\").toUpperCase();\n const envKey = `AFF_FEATURE_${slug}`;\n resolvedFlags[key] = toBoolean(env[envKey], Boolean(value));\n }\n const envMapRaw = env.ASTRO_FEATURE_FLAGS;\n if (envMapRaw) {\n try {\n const envMap = JSON.parse(envMapRaw) as Record<string, unknown>;\n for (const [flag, value] of Object.entries(envMap)) {\n resolvedFlags[flag] = Boolean(value);\n }\n } catch {\n // ignore invalid JSON override\n }\n }\n return resolvedFlags;\n}\n\n/**\n * Resolved flag booleans for each `environments` key, after the same process-env overrides\n * as {@link resolveFeatureRuntime} (overrides skipped for the reserved `dev` layer).\n */\nexport function resolveFeatureFlagsByEnvironment(\n options: ResolveFeatureRuntimeOptions = {},\n): Record<string, FeatureFlagMap> {\n const baseRaw = readBaseMergedRaw(options);\n const mode = options.mode ?? process.env.NODE_ENV ?? \"development\";\n const normalized = normalizeDeclarativeConfig(baseRaw, mode);\n const envKeys = Object.keys(normalized.environments);\n const proc = options.env ?? (process.env as Record<string, string | undefined>);\n const out: Record<string, FeatureFlagMap> = {};\n for (const envName of envKeys) {\n const cfg = loadFeatureConfig({ ...options, forceEnvironment: envName });\n out[envName] =\n envName === \"dev\"\n ? { ...cfg.flags }\n : mergeFlagsWithProcessEnvOverrides({ ...cfg.flags }, proc);\n }\n return out;\n}\n\n/** Normalize colour map keys (flag names or tokens) to CSS tokens. */\nexport function colorsToTokenMap(\n colors: FeatureColorMap,\n): Record<string, string> {\n const out: Record<string, string> = {};\n for (const [key, value] of Object.entries(colors)) {\n out[toToken(key)] = value;\n }\n return out;\n}\n\nexport interface ResolvedFeatureRuntime {\n namespace: string;\n mode: string;\n /** True when the active environment is the reserved `dev` layer (toolbar + all flags on). */\n isDev: boolean;\n activeEnvironment: string;\n flags: FeatureFlagMap;\n routeFlags: FeatureRouteMap;\n /** Outline/badge/route-frame colour per flag token (CSS colour strings). */\n flagColorsByToken: Record<string, string>;\n /** Default dev-toolbar outline/badge state per flag token. */\n flagOutlineDefaultsByToken: Record<string, boolean>;\n flagBadgeDefaultsByToken: Record<string, boolean>;\n}\n\nexport function resolveFeatureRuntime(\n options: ResolveFeatureRuntimeOptions = {},\n): ResolvedFeatureRuntime {\n const env = options.env ?? (process.env as Record<string, string | undefined>);\n const config = loadFeatureConfig(options);\n const isDevLayer = config.activeEnvironment === \"dev\";\n const resolvedFlags = isDevLayer\n ? { ...config.flags }\n : mergeFlagsWithProcessEnvOverrides(config.flags, env);\n\n const colorByToken = colorsToTokenMap(config.colors);\n\n return {\n namespace: config.namespace,\n mode: config.mode,\n isDev: isDevLayer,\n activeEnvironment: config.activeEnvironment,\n flags: resolvedFlags,\n routeFlags: config.routeFlags,\n flagColorsByToken: colorByToken,\n flagOutlineDefaultsByToken: config.outlineDefaultsByToken,\n flagBadgeDefaultsByToken: config.badgeDefaultsByToken,\n };\n}\n\nexport function isFlagEnabled(flags: FeatureFlagMap, flag: string): boolean {\n return Boolean(flags?.[flag]);\n}\n\nexport function isRouteFlagged(\n pathname: string,\n routeFlags: FeatureRouteMap,\n): boolean {\n const normalized = normalizePath(pathname);\n return Object.keys(routeFlags || {}).some((routePrefix) => {\n const route = routePatternToPrefix(routePrefix);\n return normalized === route || normalized.startsWith(route);\n });\n}\n\nexport function shouldIncludeRoute({\n pathname,\n routeFlags,\n flags,\n isDev,\n}: {\n pathname: string;\n routeFlags: FeatureRouteMap;\n flags: FeatureFlagMap;\n isDev: boolean;\n}): boolean {\n if (isDev) return true;\n const normalized = normalizePath(pathname);\n for (const [routePrefix, flagNames] of Object.entries(routeFlags || {})) {\n const route = routePatternToPrefix(routePrefix);\n if (normalized === route || normalized.startsWith(route)) {\n return (flagNames || []).every((flagName) =>\n isFlagEnabled(flags, flagName),\n );\n }\n }\n return true;\n}\n\nexport function routePathsToPrune({\n routeFlags,\n flags,\n}: {\n routeFlags: FeatureRouteMap;\n flags: FeatureFlagMap;\n}): string[] {\n return Object.entries(routeFlags || {})\n .filter(([, flagNames]) =>\n (flagNames || []).some((flagName) => !isFlagEnabled(flags, flagName)),\n )\n .map(([routePath]) =>\n routePatternToPrefix(routePath).replace(/^\\/+|\\/+$/g, \"\"),\n )\n .filter(Boolean);\n}\n\n/**\n * Longest `routeFlags` key that matches `pathname` (prefix match, trailing slashes normalized).\n */\nexport function longestMatchingRoutePrefix(\n pathname: string,\n routeFlags: FeatureRouteMap,\n): string | null {\n const normalized = normalizePath(pathname);\n let best: string | null = null;\n let bestLen = -1;\n for (const routePrefix of Object.keys(routeFlags || {})) {\n const route = routePatternToPrefix(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"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"runtime.mjs","names":[],"sources":["../src/runtime.ts"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { isAbsolute, join } from \"node:path\";\n\nexport type FeatureFlagMap = Record<string, boolean>;\nexport type FeatureRouteMap = Record<string, string[]>;\nexport type FeatureColorMap = Record<string, string>;\nexport type FeatureBoolByTokenMap = Record<string, boolean>;\n\nexport interface FeatureConfig {\n namespace: string;\n flags: FeatureFlagMap;\n routeFlags: FeatureRouteMap;\n colors: FeatureColorMap;\n outlineDefaultsByToken: FeatureBoolByTokenMap;\n badgeDefaultsByToken: FeatureBoolByTokenMap;\n mode: string;\n /** Which `environments` entry was used (reserved `dev` = all flags on, toolbar layer). */\n activeEnvironment: string;\n}\n\n/**\n * Declarative feature flags for Astro. Merge order: inline options → optional root\n * {@link ResolveFeatureRuntimeOptions.jsonConfigPath} → optional per-environment\n * `environments.<name>.jsonConfigPath` (merged when that layer is active).\n *\n * Exactly one `when: true` across environments unless `forceEnvironment` or `AFF_ENVIRONMENT`\n * selects the layer. A reserved **`dev`** environment is required (all flags on at resolve time;\n * toggles are dev-toolbar only) plus at least one other environment (e.g. `prod`).\n */\nexport interface ResolveFeatureRuntimeOptions {\n /**\n * Directory used to resolve relative {@link jsonConfigPath} values (root and per-environment).\n * Defaults to `process.cwd()`.\n */\n configRoot?: string;\n /** Optional main JSON file merged after inline `flags` / `environments`. */\n jsonConfigPath?: string;\n /** Passed through to `ResolvedFeatureRuntime.mode` (e.g. `process.env.NODE_ENV`). */\n mode?: string;\n /** Process env for `AFF_FEATURE_*` and `ASTRO_FEATURE_FLAGS` (skipped when active env is `dev`). */\n env?: Record<string, string | undefined>;\n /**\n * Use this `environments` entry regardless of `when` / `AFF_ENVIRONMENT` (e.g. sitemaps, tests).\n */\n forceEnvironment?: string;\n tokenNamespace?: string;\n flags?: Record<\n string,\n {\n color?: string;\n colour?: string;\n outline?: boolean;\n badge?: boolean;\n routes?: string[];\n }\n >;\n environments?: Record<\n string,\n {\n when?: boolean;\n flags?: Record<string, boolean>;\n /** Optional JSON merged after the root file when this environment is active. */\n jsonConfigPath?: string;\n }\n >;\n}\n\nfunction toBoolean(value: unknown, fallback = false): boolean {\n if (typeof value === \"boolean\") return value;\n if (typeof value !== \"string\") return fallback;\n const normalized = value.trim().toLowerCase();\n if ([\"1\", \"true\", \"yes\", \"on\"].includes(normalized)) return true;\n if ([\"0\", \"false\", \"no\", \"off\"].includes(normalized)) return false;\n return fallback;\n}\n\nfunction readJsonIfExists(pathname: string): Record<string, unknown> {\n if (!existsSync(pathname)) return {};\n return JSON.parse(readFileSync(pathname, \"utf8\")) as Record<string, unknown>;\n}\n\nexport function normalizePath(pathname: string): string {\n if (!pathname) return \"/\";\n return pathname.endsWith(\"/\") ? pathname : `${pathname}/`;\n}\n\n/**\n * Normalizes a route key from `ff.json` for prefix matching.\n * Supports wildcards: `/blog/*`, `/docs/**` → prefix `/blog/`, `/docs/`.\n */\nexport function routePatternToPrefix(pattern: string): string {\n let p = pattern.trim();\n if (p.endsWith(\"/**\")) {\n p = p.slice(0, -3);\n } else if (p.endsWith(\"/*\")) {\n p = p.slice(0, -2);\n } else if (p.endsWith(\"*\") && p.length > 1) {\n p = p.slice(0, -1);\n }\n return normalizePath(p);\n}\n\nexport function toEnumKey(flagName: string): string {\n return flagName\n .replace(/[^a-zA-Z0-9]+/g, \" \")\n .trim()\n .split(/\\s+/)\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join(\"\");\n}\n\nexport function toToken(flagName: string): string {\n return flagName\n .replace(/[^a-zA-Z0-9]+/g, \"-\")\n .replace(/([a-z0-9])([A-Z])/g, \"$1-$2\")\n .replace(/([a-zA-Z])([0-9])/g, \"$1-$2\")\n .replace(/([0-9])([a-zA-Z])/g, \"$1-$2\")\n .toLowerCase()\n .replace(/-+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n}\n\nfunction mergeRecords<T extends Record<string, unknown>>(\n base: T,\n ...layers: Partial<T>[]\n): T {\n const out = { ...base };\n for (const layer of layers) {\n for (const [k, v] of Object.entries(layer)) {\n if (v === undefined) continue;\n const cur = out[k as keyof T];\n if (\n v &&\n typeof v === \"object\" &&\n !Array.isArray(v) &&\n cur &&\n typeof cur === \"object\" &&\n !Array.isArray(cur)\n ) {\n (out as Record<string, unknown>)[k] = mergeRecords(\n cur as Record<string, unknown>,\n v as Record<string, unknown>,\n );\n } else {\n (out as Record<string, unknown>)[k] = v;\n }\n }\n }\n return out;\n}\n\nfunction resolvePath(relativeOrAbsolute: string, configRoot: string): string {\n return isAbsolute(relativeOrAbsolute)\n ? relativeOrAbsolute\n : join(configRoot, relativeOrAbsolute);\n}\n\ntype NormalizedFlagDef = {\n color?: string;\n outline: boolean;\n badge: boolean;\n routes: string[];\n};\n\ntype NormalizedEnvEntry = {\n when?: boolean;\n flags: Record<string, boolean>;\n /** Per-environment JSON merged after root `jsonConfigPath` when this layer is active. */\n jsonConfigPath?: string;\n};\n\ntype DeclarativeFeatureConfig = {\n tokenNamespace: string;\n flags: Record<string, NormalizedFlagDef>;\n environments: Record<string, NormalizedEnvEntry>;\n};\n\nfunction normalizeDeclarativeConfig(\n raw: Record<string, unknown>,\n mode: string = process.env.NODE_ENV ?? \"development\",\n): DeclarativeFeatureConfig {\n const tokenNamespaceRaw =\n (typeof raw.tokenNamespace === \"string\" ? raw.tokenNamespace : undefined) ??\n (typeof raw.namespace === \"string\" ? raw.namespace : undefined) ??\n \"ff\";\n\n const flagsRaw =\n raw.flags && typeof raw.flags === \"object\" && !Array.isArray(raw.flags)\n ? (raw.flags as Record<string, unknown>)\n : {};\n const flags: Record<string, NormalizedFlagDef> = {};\n for (const [flagName, maybeDef] of Object.entries(flagsRaw)) {\n if (!maybeDef || typeof maybeDef !== \"object\" || Array.isArray(maybeDef))\n continue;\n const def = maybeDef as Record<string, unknown>;\n const color =\n (typeof def.colour === \"string\" ? def.colour : undefined) ??\n (typeof def.color === \"string\" ? def.color : undefined);\n const outline = typeof def.outline === \"boolean\" ? def.outline : true;\n const badge = typeof def.badge === \"boolean\" ? def.badge : true;\n const routes = Array.isArray(def.routes)\n ? def.routes.filter((v): v is string => typeof v === \"string\")\n : [];\n flags[flagName] = { color, outline, badge, routes };\n }\n\n const envRaw =\n raw.environments &&\n typeof raw.environments === \"object\" &&\n !Array.isArray(raw.environments)\n ? (raw.environments as Record<string, unknown>)\n : {};\n const environments: Record<string, NormalizedEnvEntry> = {};\n for (const [envName, maybeEnv] of Object.entries(envRaw)) {\n if (envName === \"dev\") continue;\n if (!maybeEnv || typeof maybeEnv !== \"object\" || Array.isArray(maybeEnv))\n continue;\n const env = maybeEnv as Record<string, unknown>;\n const envFlagsRaw =\n env.flags && typeof env.flags === \"object\" && !Array.isArray(env.flags)\n ? (env.flags as Record<string, unknown>)\n : {};\n const envFlags: Record<string, boolean> = {};\n for (const [flagName, value] of Object.entries(envFlagsRaw)) {\n envFlags[flagName] = Boolean(value);\n }\n const when = typeof env.when === \"boolean\" ? env.when : undefined;\n const layerJson =\n typeof env.jsonConfigPath === \"string\" ? env.jsonConfigPath : undefined;\n environments[envName] = {\n ...(when !== undefined ? { when } : {}),\n ...(layerJson ? { jsonConfigPath: layerJson } : {}),\n flags: envFlags,\n };\n }\n\n environments.dev = {\n when: mode !== \"production\",\n flags: {},\n };\n\n return {\n tokenNamespace: tokenNamespaceRaw,\n flags,\n environments,\n };\n}\n\nfunction assertEnvironmentShape(\n environments: Record<string, NormalizedEnvEntry>,\n) {\n const keys = Object.keys(environments);\n if (!environments.dev) {\n throw new Error(\n 'astro-feature-flags: environments must include a reserved \"dev\" entry.',\n );\n }\n if (keys.length < 2) {\n throw new Error(\n \"astro-feature-flags: environments must include `dev` plus at least one other layer (e.g. `prod`).\",\n );\n }\n}\n\nfunction countActiveWhen(\n environments: Record<string, NormalizedEnvEntry>,\n): number {\n return Object.values(environments).filter((e) => e.when === true).length;\n}\n\nfunction resolveAffEnvironment(\n options: ResolveFeatureRuntimeOptions,\n): string | undefined {\n const proc =\n options.env ?? (process.env as Record<string, string | undefined>);\n return proc.AFF_ENVIRONMENT?.trim();\n}\n\nfunction resolveActiveEnvironmentName(\n normalized: DeclarativeFeatureConfig,\n options: ResolveFeatureRuntimeOptions,\n): string {\n const { environments } = normalized;\n assertEnvironmentShape(environments);\n\n const forced = options.forceEnvironment?.trim();\n if (forced && environments[forced]) return forced;\n\n const aff = resolveAffEnvironment(options);\n if (aff && environments[aff]) return aff;\n\n const n = countActiveWhen(environments);\n if (n !== 1) {\n const activeNames = Object.entries(environments)\n .filter(([, e]) => e.when === true)\n .map(([k]) => k);\n const devWhen = environments.dev?.when;\n const hint =\n n > 1 && environments.dev && devWhen === true\n ? \" The reserved `dev` environment is active for this `mode` while another layer also has `when: true`. Pin one layer with `forceEnvironment` or `AFF_ENVIRONMENT`, or adjust non-dev `when` predicates.\"\n : \"\";\n throw new Error(\n `astro-feature-flags: exactly one environment must have when: true (found ${n}${activeNames.length ? `: ${activeNames.join(\", \")}` : \"\"}).${hint} Set forceEnvironment or AFF_ENVIRONMENT to pick a layer explicitly.`,\n );\n }\n for (const [name, cfg] of Object.entries(environments)) {\n if (cfg.when === true) return name;\n }\n throw new Error(\"astro-feature-flags: internal error resolving environment.\");\n}\n\nfunction readBaseMergedRaw(\n options: ResolveFeatureRuntimeOptions,\n): Record<string, unknown> {\n const configRoot = options.configRoot ?? process.cwd();\n const { tokenNamespace = \"ff\", flags = {}, environments = {} } = options;\n const inlineRaw: Record<string, unknown> = {\n tokenNamespace,\n flags,\n environments,\n };\n let merged = { ...inlineRaw };\n if (options.jsonConfigPath) {\n const p = resolvePath(options.jsonConfigPath, configRoot);\n merged = mergeRecords(\n merged as Record<string, unknown>,\n readJsonIfExists(p),\n );\n }\n return merged;\n}\n\nfunction mergePerEnvironmentJson(\n baseRaw: Record<string, unknown>,\n envName: string,\n configRoot: string,\n mode: string,\n): Record<string, unknown> {\n if (envName === \"dev\") return baseRaw;\n const normalized = normalizeDeclarativeConfig(baseRaw, mode);\n const entry = normalized.environments[envName];\n const rel = entry?.jsonConfigPath;\n if (!rel) return baseRaw;\n const p = resolvePath(rel, configRoot);\n return mergeRecords(baseRaw, readJsonIfExists(p));\n}\n\nfunction readMergedRawForActiveEnv(\n options: ResolveFeatureRuntimeOptions,\n envName: string,\n): Record<string, unknown> {\n const configRoot = options.configRoot ?? process.cwd();\n const mode = options.mode ?? process.env.NODE_ENV ?? \"development\";\n const base = readBaseMergedRaw(options);\n return mergePerEnvironmentJson(base, envName, configRoot, mode);\n}\n\nexport function loadFeatureConfig(\n options: ResolveFeatureRuntimeOptions = {},\n): FeatureConfig {\n const mode = options.mode ?? process.env.NODE_ENV ?? \"development\";\n const baseRaw = readBaseMergedRaw(options);\n const baseNorm = normalizeDeclarativeConfig(baseRaw, mode);\n const envName = resolveActiveEnvironmentName(baseNorm, options);\n const fullRaw = readMergedRawForActiveEnv(options, envName);\n const normalized = normalizeDeclarativeConfig(fullRaw, mode);\n\n const envFlags = normalized.environments[envName]?.flags ?? {};\n const routeFlags: FeatureRouteMap = {};\n const colors: FeatureColorMap = {};\n const outlineDefaultsByToken: FeatureBoolByTokenMap = {};\n const badgeDefaultsByToken: FeatureBoolByTokenMap = {};\n const resolvedFlags: FeatureFlagMap = {};\n\n for (const [flagName, flagDef] of Object.entries(normalized.flags)) {\n if (envName === \"dev\") {\n resolvedFlags[flagName] = true;\n } else {\n resolvedFlags[flagName] = envFlags[flagName] === true;\n }\n if (flagDef.color) colors[flagName] = flagDef.color;\n const token = toToken(flagName);\n outlineDefaultsByToken[token] = flagDef.outline;\n badgeDefaultsByToken[token] = flagDef.badge;\n for (const route of flagDef.routes) {\n routeFlags[route] = [...(routeFlags[route] ?? []), flagName];\n }\n }\n\n return {\n namespace: normalized.tokenNamespace || \"ff\",\n flags: resolvedFlags,\n routeFlags,\n colors,\n outlineDefaultsByToken,\n badgeDefaultsByToken,\n mode,\n activeEnvironment: envName,\n };\n}\n\n/** Applies `AFF_FEATURE_*` and `ASTRO_FEATURE_FLAGS` on top of a resolved flag map. */\nexport function mergeFlagsWithProcessEnvOverrides(\n flags: FeatureFlagMap,\n env: Record<string, string | undefined> = process.env as Record<\n string,\n string | undefined\n >,\n): FeatureFlagMap {\n const resolvedFlags: FeatureFlagMap = { ...flags };\n for (const [key, value] of Object.entries(resolvedFlags)) {\n const slug = toToken(key).replace(/-/g, \"_\").toUpperCase();\n const envKey = `AFF_FEATURE_${slug}`;\n resolvedFlags[key] = toBoolean(env[envKey], Boolean(value));\n }\n const envMapRaw = env.ASTRO_FEATURE_FLAGS;\n if (envMapRaw) {\n try {\n const envMap = JSON.parse(envMapRaw) as Record<string, unknown>;\n for (const [flag, value] of Object.entries(envMap)) {\n resolvedFlags[flag] = Boolean(value);\n }\n } catch {\n // ignore invalid JSON override\n }\n }\n return resolvedFlags;\n}\n\n/**\n * Resolved flag booleans for each `environments` key, after the same process-env overrides\n * as {@link resolveFeatureRuntime} (overrides skipped for the reserved `dev` layer).\n */\nexport function resolveFeatureFlagsByEnvironment(\n options: ResolveFeatureRuntimeOptions = {},\n): Record<string, FeatureFlagMap> {\n const baseRaw = readBaseMergedRaw(options);\n const mode = options.mode ?? process.env.NODE_ENV ?? \"development\";\n const normalized = normalizeDeclarativeConfig(baseRaw, mode);\n const envKeys = Object.keys(normalized.environments);\n const proc =\n options.env ?? (process.env as Record<string, string | undefined>);\n const out: Record<string, FeatureFlagMap> = {};\n for (const envName of envKeys) {\n const cfg = loadFeatureConfig({ ...options, forceEnvironment: envName });\n out[envName] =\n envName === \"dev\"\n ? { ...cfg.flags }\n : mergeFlagsWithProcessEnvOverrides({ ...cfg.flags }, proc);\n }\n return out;\n}\n\n/** Normalize colour map keys (flag names or tokens) to CSS tokens. */\nexport function colorsToTokenMap(\n colors: FeatureColorMap,\n): Record<string, string> {\n const out: Record<string, string> = {};\n for (const [key, value] of Object.entries(colors)) {\n out[toToken(key)] = value;\n }\n return out;\n}\n\nexport interface ResolvedFeatureRuntime {\n namespace: string;\n mode: string;\n /** True when the active environment is the reserved `dev` layer (toolbar + all flags on). */\n isDev: boolean;\n activeEnvironment: string;\n flags: FeatureFlagMap;\n routeFlags: FeatureRouteMap;\n /** Outline/badge/route-frame colour per flag token (CSS colour strings). */\n flagColorsByToken: Record<string, string>;\n /** Default dev-toolbar outline/badge state per flag token. */\n flagOutlineDefaultsByToken: Record<string, boolean>;\n flagBadgeDefaultsByToken: Record<string, boolean>;\n}\n\nexport function resolveFeatureRuntime(\n options: ResolveFeatureRuntimeOptions = {},\n): ResolvedFeatureRuntime {\n const env =\n options.env ?? (process.env as Record<string, string | undefined>);\n const config = loadFeatureConfig(options);\n const isDevLayer = config.activeEnvironment === \"dev\";\n const resolvedFlags = isDevLayer\n ? { ...config.flags }\n : mergeFlagsWithProcessEnvOverrides(config.flags, env);\n\n const colorByToken = colorsToTokenMap(config.colors);\n\n return {\n namespace: config.namespace,\n mode: config.mode,\n isDev: isDevLayer,\n activeEnvironment: config.activeEnvironment,\n flags: resolvedFlags,\n routeFlags: config.routeFlags,\n flagColorsByToken: colorByToken,\n flagOutlineDefaultsByToken: config.outlineDefaultsByToken,\n flagBadgeDefaultsByToken: config.badgeDefaultsByToken,\n };\n}\n\nexport function isFlagEnabled(flags: FeatureFlagMap, flag: string): boolean {\n return Boolean(flags?.[flag]);\n}\n\nexport function isRouteFlagged(\n pathname: string,\n routeFlags: FeatureRouteMap,\n): boolean {\n const normalized = normalizePath(pathname);\n return Object.keys(routeFlags || {}).some((routePrefix) => {\n const route = routePatternToPrefix(routePrefix);\n return normalized === route || normalized.startsWith(route);\n });\n}\n\nexport function shouldIncludeRoute({\n pathname,\n routeFlags,\n flags,\n isDev,\n}: {\n pathname: string;\n routeFlags: FeatureRouteMap;\n flags: FeatureFlagMap;\n isDev: boolean;\n}): boolean {\n if (isDev) return true;\n const normalized = normalizePath(pathname);\n for (const [routePrefix, flagNames] of Object.entries(routeFlags || {})) {\n const route = routePatternToPrefix(routePrefix);\n if (normalized === route || normalized.startsWith(route)) {\n return (flagNames || []).every((flagName) =>\n isFlagEnabled(flags, flagName),\n );\n }\n }\n return true;\n}\n\nexport function routePathsToPrune({\n routeFlags,\n flags,\n}: {\n routeFlags: FeatureRouteMap;\n flags: FeatureFlagMap;\n}): string[] {\n return Object.entries(routeFlags || {})\n .filter(([, flagNames]) =>\n (flagNames || []).some((flagName) => !isFlagEnabled(flags, flagName)),\n )\n .map(([routePath]) =>\n routePatternToPrefix(routePath).replace(/^\\/+|\\/+$/g, \"\"),\n )\n .filter(Boolean);\n}\n\n/**\n * Longest `routeFlags` key that matches `pathname` (prefix match, trailing slashes normalized).\n */\nexport function longestMatchingRoutePrefix(\n pathname: string,\n routeFlags: FeatureRouteMap,\n): string | null {\n const normalized = normalizePath(pathname);\n let best: string | null = null;\n let bestLen = -1;\n for (const routePrefix of Object.keys(routeFlags || {})) {\n const route = routePatternToPrefix(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"],"mappings":"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"}
|
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.3",
|
|
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>",
|
|
@@ -35,14 +35,14 @@
|
|
|
35
35
|
}
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
|
-
"astro": "
|
|
38
|
+
"astro": ">=5.0.0"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@types/node": "24.7.0",
|
|
42
|
-
"astro": "
|
|
43
|
-
"tsdown": "0.
|
|
44
|
-
"typescript": "
|
|
45
|
-
"vitest": "
|
|
42
|
+
"astro": "7.1.3",
|
|
43
|
+
"tsdown": "0.22.13",
|
|
44
|
+
"typescript": "7.0.2",
|
|
45
|
+
"vitest": "4.1.10"
|
|
46
46
|
},
|
|
47
47
|
"repository": {
|
|
48
48
|
"type": "git",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"access": "public"
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"node-html-parser": "
|
|
60
|
+
"node-html-parser": "9.0.0"
|
|
61
61
|
},
|
|
62
62
|
"scripts": {
|
|
63
63
|
"build": "tsdown",
|
|
@@ -43,7 +43,10 @@ export function affHeadInlineRuntime(payload: HeadInlinePayload): void {
|
|
|
43
43
|
ensureFeatureFlagStyles();
|
|
44
44
|
|
|
45
45
|
const affPath = () => {
|
|
46
|
-
let p =
|
|
46
|
+
let p =
|
|
47
|
+
typeof location !== "undefined" && location.pathname
|
|
48
|
+
? location.pathname
|
|
49
|
+
: "/";
|
|
47
50
|
if (!p.endsWith("/")) p += "/";
|
|
48
51
|
return p;
|
|
49
52
|
};
|
|
@@ -82,7 +85,8 @@ export function affHeadInlineRuntime(payload: HeadInlinePayload): void {
|
|
|
82
85
|
for (let i = 0; i < names.length; i++) {
|
|
83
86
|
const tk = M[names[i]];
|
|
84
87
|
if (!tk) continue;
|
|
85
|
-
if (root.getAttribute(`data-ff-enabled-${tk}`) === "off")
|
|
88
|
+
if (root.getAttribute(`data-ff-enabled-${tk}`) === "off")
|
|
89
|
+
disabled.push(tk);
|
|
86
90
|
}
|
|
87
91
|
const id = "aff-route-disabled-overlay";
|
|
88
92
|
let el = document.getElementById(id);
|
|
@@ -113,7 +117,10 @@ export function affHeadInlineRuntime(payload: HeadInlinePayload): void {
|
|
|
113
117
|
|
|
114
118
|
const affApply = () => {
|
|
115
119
|
ensureFeatureFlagStyles();
|
|
116
|
-
document.documentElement.setAttribute(
|
|
120
|
+
document.documentElement.setAttribute(
|
|
121
|
+
"data-ff-route",
|
|
122
|
+
affTokens(affPath()).join(" "),
|
|
123
|
+
);
|
|
117
124
|
affSyncDisabledOverlay();
|
|
118
125
|
};
|
|
119
126
|
|
|
@@ -122,7 +129,10 @@ export function affHeadInlineRuntime(payload: HeadInlinePayload): void {
|
|
|
122
129
|
const obs = new MutationObserver((muts) => {
|
|
123
130
|
for (let i = 0; i < muts.length; i++) {
|
|
124
131
|
const a = muts[i] && muts[i].attributeName;
|
|
125
|
-
if (
|
|
132
|
+
if (
|
|
133
|
+
a === "data-ff-route" ||
|
|
134
|
+
(a && a.indexOf("data-ff-enabled-") === 0)
|
|
135
|
+
) {
|
|
126
136
|
affSyncDisabledOverlay();
|
|
127
137
|
break;
|
|
128
138
|
}
|
|
@@ -155,7 +165,8 @@ export function affDevBootstrapRuntime(payload: BootstrapPayload): void {
|
|
|
155
165
|
const K = `${N}.dev.v1`;
|
|
156
166
|
|
|
157
167
|
const root = document.documentElement;
|
|
158
|
-
const isEnabled = (tk: string) =>
|
|
168
|
+
const isEnabled = (tk: string) =>
|
|
169
|
+
root.getAttribute(`data-ff-enabled-${tk}`) !== "off";
|
|
159
170
|
const currentTokenColor = (
|
|
160
171
|
tk: string,
|
|
161
172
|
cols: Record<string, string>,
|
|
@@ -194,7 +205,8 @@ export function affDevBootstrapRuntime(payload: BootstrapPayload): void {
|
|
|
194
205
|
const applyCombo = (el: HTMLElement, cols: Record<string, string>) => {
|
|
195
206
|
const allTokens = comboTokens(el);
|
|
196
207
|
const hasCombo = allTokens.length >= 2;
|
|
197
|
-
const hasDisabledInCombo =
|
|
208
|
+
const hasDisabledInCombo =
|
|
209
|
+
hasCombo && allTokens.some((tk) => !isEnabled(tk));
|
|
198
210
|
const visual = allTokens.filter((tk) => isEnabled(tk));
|
|
199
211
|
if (hasDisabledInCombo) {
|
|
200
212
|
// Fail-closed for combos: if any member token is disabled, hide the whole element.
|
|
@@ -225,15 +237,24 @@ export function affDevBootstrapRuntime(payload: BootstrapPayload): void {
|
|
|
225
237
|
el.setAttribute("data-ff-label", visual.join(" | "));
|
|
226
238
|
const strong = visual.map((tk) => currentTokenColor(tk, cols));
|
|
227
239
|
const soft = strong.map((c) => `color-mix(in oklab, white 86%, ${c} 14%)`);
|
|
228
|
-
el.style.setProperty(
|
|
229
|
-
|
|
240
|
+
el.style.setProperty(
|
|
241
|
+
"--ff-combo-gradient",
|
|
242
|
+
`linear-gradient(90deg, ${strong.join(", ")})`,
|
|
243
|
+
);
|
|
244
|
+
el.style.setProperty(
|
|
245
|
+
"--ff-combo-gradient-soft",
|
|
246
|
+
`linear-gradient(90deg, ${soft.join(", ")})`,
|
|
247
|
+
);
|
|
230
248
|
el.style.setProperty("--ff-combo-outline", strong[0] || "#64748b");
|
|
231
249
|
el.style.setProperty("--ff-combo-text", strong[0] || "#111827");
|
|
232
250
|
el.style.setProperty(
|
|
233
251
|
"--ff-combo-badge-border",
|
|
234
252
|
`color-mix(in oklab, ${strong[0] || "#64748b"} 35%, transparent)`,
|
|
235
253
|
);
|
|
236
|
-
el.style.setProperty(
|
|
254
|
+
el.style.setProperty(
|
|
255
|
+
"--ff-combo-badge-gradient",
|
|
256
|
+
`linear-gradient(90deg, ${soft.join(", ")})`,
|
|
257
|
+
);
|
|
237
258
|
};
|
|
238
259
|
|
|
239
260
|
const routeTokensForPath = (path: string): string[] => {
|
|
@@ -359,10 +380,21 @@ export function affDevBootstrapRuntime(payload: BootstrapPayload): void {
|
|
|
359
380
|
const toks = routeTokensForPath(p).filter((tk) => isEnabled(tk));
|
|
360
381
|
if (toks.length) {
|
|
361
382
|
const strong = toks.map((tk) => currentTokenColor(tk, cols));
|
|
362
|
-
const soft = strong.map(
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
root.style.setProperty(
|
|
383
|
+
const soft = strong.map(
|
|
384
|
+
(c) => `color-mix(in oklab, white 86%, ${c} 14%)`,
|
|
385
|
+
);
|
|
386
|
+
root.style.setProperty(
|
|
387
|
+
`--${N}-route-gradient`,
|
|
388
|
+
`linear-gradient(90deg, ${soft.join(", ")})`,
|
|
389
|
+
);
|
|
390
|
+
root.style.setProperty(
|
|
391
|
+
"--ff-route-outline-gradient",
|
|
392
|
+
`linear-gradient(90deg, ${strong.join(", ")})`,
|
|
393
|
+
);
|
|
394
|
+
root.style.setProperty(
|
|
395
|
+
"--ff-route-badge-gradient",
|
|
396
|
+
`linear-gradient(90deg, ${soft.join(", ")})`,
|
|
397
|
+
);
|
|
366
398
|
root.style.setProperty(
|
|
367
399
|
"--ff-route-badge-border",
|
|
368
400
|
`color-mix(in oklab, ${strong[0] || "#64748b"} 35%, transparent)`,
|
|
@@ -383,7 +415,9 @@ export function affDevBootstrapRuntime(payload: BootstrapPayload): void {
|
|
|
383
415
|
|
|
384
416
|
affAfterNav();
|
|
385
417
|
if (document.readyState === "loading") {
|
|
386
|
-
document.addEventListener("DOMContentLoaded", affAfterNav, {
|
|
418
|
+
document.addEventListener("DOMContentLoaded", affAfterNav, {
|
|
419
|
+
once: true,
|
|
420
|
+
});
|
|
387
421
|
}
|
|
388
422
|
document.addEventListener("astro:page-load", affAfterNav);
|
|
389
423
|
document.addEventListener("astro:after-swap", affAfterNav);
|
package/src/dev-outline-css.ts
CHANGED
|
@@ -67,7 +67,9 @@ function normalizeCssOptions(
|
|
|
67
67
|
...(css?.outlineColorByToken ?? {}),
|
|
68
68
|
};
|
|
69
69
|
const badgeLabelWip =
|
|
70
|
-
css?.badgeLabelWip ??
|
|
70
|
+
css?.badgeLabelWip ??
|
|
71
|
+
css?.badgeLabelDev ??
|
|
72
|
+
defaultDevOutlineCssOptions.badgeLabelWip;
|
|
71
73
|
return {
|
|
72
74
|
...defaultDevOutlineCssOptions,
|
|
73
75
|
...(css ?? {}),
|
|
@@ -111,7 +113,9 @@ export function createFeatureFlagStyles(
|
|
|
111
113
|
return {
|
|
112
114
|
token,
|
|
113
115
|
baseSelectors,
|
|
114
|
-
selOutline: baseSelectors
|
|
116
|
+
selOutline: baseSelectors
|
|
117
|
+
.map((s) => `${s}:not([${nsAttr}*=" "])`)
|
|
118
|
+
.join(", "),
|
|
115
119
|
selIs: `:is(${baseSelectors.join(", ")})`,
|
|
116
120
|
};
|
|
117
121
|
};
|
package/src/dev-toolbar-app.ts
CHANGED
|
@@ -79,11 +79,9 @@ function loadState(tokens: string[], namespace: string): AffState {
|
|
|
79
79
|
if (!raw) return next;
|
|
80
80
|
const parsed = JSON.parse(raw) as Record<string, unknown>;
|
|
81
81
|
const outline = (parsed.outline ?? parsed.chrome) as
|
|
82
|
-
|
|
83
|
-
| undefined;
|
|
82
|
+
Record<string, boolean> | undefined;
|
|
84
83
|
const enabled = (parsed.enabled ?? parsed.render) as
|
|
85
|
-
|
|
86
|
-
| undefined;
|
|
84
|
+
Record<string, boolean> | undefined;
|
|
87
85
|
const badge = parsed.badge as Record<string, boolean> | undefined;
|
|
88
86
|
const colors = parsed.colors as Record<string, string> | undefined;
|
|
89
87
|
if (outline) Object.assign(next.outline, outline);
|
package/src/index.ts
CHANGED
|
@@ -80,13 +80,14 @@ function withDefaultEnvironments(
|
|
|
80
80
|
? { ...options.environments }
|
|
81
81
|
: {};
|
|
82
82
|
delete raw.dev;
|
|
83
|
-
const environments: NonNullable<
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
83
|
+
const environments: NonNullable<
|
|
84
|
+
ResolveFeatureRuntimeOptions["environments"]
|
|
85
|
+
> = {
|
|
86
|
+
dev: {
|
|
87
|
+
when: mode !== "production",
|
|
88
|
+
},
|
|
89
|
+
...raw,
|
|
90
|
+
};
|
|
90
91
|
if (Object.keys(environments).length < 2) {
|
|
91
92
|
environments.prod = environments.prod ?? {
|
|
92
93
|
when: mode === "production",
|
|
@@ -130,7 +131,9 @@ export function createVirtualModuleSource(
|
|
|
130
131
|
Object.fromEntries(flagNames.map((name) => [name, toToken(name)])),
|
|
131
132
|
runtime.namespace,
|
|
132
133
|
);
|
|
133
|
-
const affRoutePatternToPrefix = routePrefixJsHelper(
|
|
134
|
+
const affRoutePatternToPrefix = routePrefixJsHelper(
|
|
135
|
+
"affRoutePatternToPrefix",
|
|
136
|
+
);
|
|
134
137
|
|
|
135
138
|
return `${affRoutePatternToPrefix}
|
|
136
139
|
|
package/src/inline-script.ts
CHANGED
|
@@ -2,7 +2,9 @@ export function compactInlineScript(source: string): string {
|
|
|
2
2
|
const lines = source
|
|
3
3
|
.split("\n")
|
|
4
4
|
.map((line) => line.trim())
|
|
5
|
-
.filter(
|
|
5
|
+
.filter(
|
|
6
|
+
(line, idx, arr) => line.length > 0 || (idx > 0 && idx < arr.length - 1),
|
|
7
|
+
);
|
|
6
8
|
return lines.join("\n").trim();
|
|
7
9
|
}
|
|
8
10
|
|
package/src/runtime.ts
CHANGED
|
@@ -246,7 +246,9 @@ function normalizeDeclarativeConfig(
|
|
|
246
246
|
};
|
|
247
247
|
}
|
|
248
248
|
|
|
249
|
-
function assertEnvironmentShape(
|
|
249
|
+
function assertEnvironmentShape(
|
|
250
|
+
environments: Record<string, NormalizedEnvEntry>,
|
|
251
|
+
) {
|
|
250
252
|
const keys = Object.keys(environments);
|
|
251
253
|
if (!environments.dev) {
|
|
252
254
|
throw new Error(
|
|
@@ -260,7 +262,9 @@ function assertEnvironmentShape(environments: Record<string, NormalizedEnvEntry>
|
|
|
260
262
|
}
|
|
261
263
|
}
|
|
262
264
|
|
|
263
|
-
function countActiveWhen(
|
|
265
|
+
function countActiveWhen(
|
|
266
|
+
environments: Record<string, NormalizedEnvEntry>,
|
|
267
|
+
): number {
|
|
264
268
|
return Object.values(environments).filter((e) => e.when === true).length;
|
|
265
269
|
}
|
|
266
270
|
|
|
@@ -305,13 +309,11 @@ function resolveActiveEnvironmentName(
|
|
|
305
309
|
throw new Error("astro-feature-flags: internal error resolving environment.");
|
|
306
310
|
}
|
|
307
311
|
|
|
308
|
-
function readBaseMergedRaw(
|
|
312
|
+
function readBaseMergedRaw(
|
|
313
|
+
options: ResolveFeatureRuntimeOptions,
|
|
314
|
+
): Record<string, unknown> {
|
|
309
315
|
const configRoot = options.configRoot ?? process.cwd();
|
|
310
|
-
const {
|
|
311
|
-
tokenNamespace = "ff",
|
|
312
|
-
flags = {},
|
|
313
|
-
environments = {},
|
|
314
|
-
} = options;
|
|
316
|
+
const { tokenNamespace = "ff", flags = {}, environments = {} } = options;
|
|
315
317
|
const inlineRaw: Record<string, unknown> = {
|
|
316
318
|
tokenNamespace,
|
|
317
319
|
flags,
|
|
@@ -320,7 +322,10 @@ function readBaseMergedRaw(options: ResolveFeatureRuntimeOptions): Record<string
|
|
|
320
322
|
let merged = { ...inlineRaw };
|
|
321
323
|
if (options.jsonConfigPath) {
|
|
322
324
|
const p = resolvePath(options.jsonConfigPath, configRoot);
|
|
323
|
-
merged = mergeRecords(
|
|
325
|
+
merged = mergeRecords(
|
|
326
|
+
merged as Record<string, unknown>,
|
|
327
|
+
readJsonIfExists(p),
|
|
328
|
+
);
|
|
324
329
|
}
|
|
325
330
|
return merged;
|
|
326
331
|
}
|
|
@@ -433,7 +438,8 @@ export function resolveFeatureFlagsByEnvironment(
|
|
|
433
438
|
const mode = options.mode ?? process.env.NODE_ENV ?? "development";
|
|
434
439
|
const normalized = normalizeDeclarativeConfig(baseRaw, mode);
|
|
435
440
|
const envKeys = Object.keys(normalized.environments);
|
|
436
|
-
const proc =
|
|
441
|
+
const proc =
|
|
442
|
+
options.env ?? (process.env as Record<string, string | undefined>);
|
|
437
443
|
const out: Record<string, FeatureFlagMap> = {};
|
|
438
444
|
for (const envName of envKeys) {
|
|
439
445
|
const cfg = loadFeatureConfig({ ...options, forceEnvironment: envName });
|
|
@@ -474,7 +480,8 @@ export interface ResolvedFeatureRuntime {
|
|
|
474
480
|
export function resolveFeatureRuntime(
|
|
475
481
|
options: ResolveFeatureRuntimeOptions = {},
|
|
476
482
|
): ResolvedFeatureRuntime {
|
|
477
|
-
const env =
|
|
483
|
+
const env =
|
|
484
|
+
options.env ?? (process.env as Record<string, string | undefined>);
|
|
478
485
|
const config = loadFeatureConfig(options);
|
|
479
486
|
const isDevLayer = config.activeEnvironment === "dev";
|
|
480
487
|
const resolvedFlags = isDevLayer
|