{"_id":"@at-flux/astro-feature-flags","_rev":"4-a115b9fe35071b8eda6fca8a1ef7668c","name":"@at-flux/astro-feature-flags","dist-tags":{"latest":"1.0.4"},"versions":{"1.0.0":{"name":"@at-flux/astro-feature-flags","version":"1.0.0","keywords":["astro","astro-integration","feature-flags","typescript","devtools","tooling"],"author":{"name":"atflux","email":"dev@atflux.uk"},"license":"MIT","_id":"@at-flux/astro-feature-flags@1.0.0","maintainers":[{"name":"atflux","email":"magisterfluxart@gmail.com"}],"homepage":"https://github.com/at-flux/astroflare/tree/main/packages/astro-feature-flags","bugs":{"url":"https://github.com/at-flux/astroflare/issues"},"dist":{"shasum":"ab4e9598185bf74bc7e7bdd15e19a066e3db805c","tarball":"https://registry.npmjs.org/@at-flux/astro-feature-flags/-/astro-feature-flags-1.0.0.tgz","fileCount":48,"integrity":"sha512-5FUa2d1Cs57AL6FIm6eGnw6e8e66oIcmMWpue/0I47lNFWamu0uqc2mhzBPOBO6332f1Pbv9IgBXc86xHWKHiA==","signatures":[{"sig":"MEYCIQD2YBoRLlKMk2q1q+psxzka6mjL+oejGfzRUsEFlHxw9gIhAMwmmWuJeGR1o0Pz6u0y492gXqjC0ZsdaEe0Dzzb1z+q","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":765562},"main":"./dist/index.mjs","type":"module","_from":"file:at-flux-astro-feature-flags-1.0.0.tgz","exports":{".":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"./runtime":{"types":"./dist/runtime.d.mts","default":"./dist/runtime.mjs"},"./virtual-astro-feature-flags":{"types":"./virtual-astro-feature-flags.d.ts"}},"scripts":{"test":"vitest run","build":"tsdown","typecheck":"tsc --noEmit","test:watch":"vitest"},"_npmUser":{"name":"atflux","email":"magisterfluxart@gmail.com"},"_resolved":"/tmp/09935393730b9eed9d1a1c76777c8684/at-flux-astro-feature-flags-1.0.0.tgz","_integrity":"sha512-5FUa2d1Cs57AL6FIm6eGnw6e8e66oIcmMWpue/0I47lNFWamu0uqc2mhzBPOBO6332f1Pbv9IgBXc86xHWKHiA==","repository":{"url":"git+https://github.com/at-flux/astroflare.git","type":"git","directory":"packages/astro-feature-flags"},"_npmVersion":"11.5.1","description":"Typed Astro feature flags from JSON config. Includes environment specific rendering and pruning from elements to entire routes","directories":{},"_nodeVersion":"24.6.0","dependencies":{"node-html-parser":"^7.1.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"astro":"^5.18.0","tsdown":"^0.21.7","vitest":"^3.2.4","typescript":"^5.9.3","@types/node":"^24.7.0"},"peerDependencies":{"astro":"^4.7.0 || ^5.0.0"},"_npmOperationalInternal":{"tmp":"tmp/astro-feature-flags_1.0.0_1776190799538_0.1489160412543049","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"name":"@at-flux/astro-feature-flags","version":"1.0.2","keywords":["astro","astro-integration","feature-flags","typescript","devtools","tooling"],"author":{"name":"atflux","email":"dev@atflux.uk"},"license":"MIT","_id":"@at-flux/astro-feature-flags@1.0.2","maintainers":[{"name":"atflux","email":"magisterfluxart@gmail.com"}],"homepage":"https://github.com/at-flux/astroflare/tree/main/packages/astro-feature-flags","bugs":{"url":"https://github.com/at-flux/astroflare/issues"},"dist":{"shasum":"5ebee7ba469fd637c3654ddbaf9c338c06ec7745","tarball":"https://registry.npmjs.org/@at-flux/astro-feature-flags/-/astro-feature-flags-1.0.2.tgz","fileCount":48,"integrity":"sha512-XCV/FLI6QbcHJ2HFil4mYT7/X+3CFIdk8imNB2a0JGlhT/RvHw34bbnZuKzf/3dDN/c1DociqyuanBZJvIcJUw==","signatures":[{"sig":"MEUCIQCqVYRK1bZwRaVY7R2+IHC5ObfkNhY5zLlABntljJu+MwIgSULwIQ2cl3N552GyawUl1EsvSfD7AGOpkjnhwDa2qFk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@at-flux%2fastro-feature-flags@1.0.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":767592},"main":"./dist/index.mjs","type":"module","_from":"file:at-flux-astro-feature-flags-1.0.2.tgz","exports":{".":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"./runtime":{"types":"./dist/runtime.d.mts","default":"./dist/runtime.mjs"},"./virtual-astro-feature-flags":{"types":"./virtual-astro-feature-flags.d.ts"}},"scripts":{"test":"vitest run","build":"tsdown","typecheck":"tsc --noEmit","test:watch":"vitest"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:906933b2-1d14-4507-906e-d16050f4b6de"}},"_resolved":"/tmp/2e4e0cb38b6e28f03c032f3b316e0bac/at-flux-astro-feature-flags-1.0.2.tgz","_integrity":"sha512-XCV/FLI6QbcHJ2HFil4mYT7/X+3CFIdk8imNB2a0JGlhT/RvHw34bbnZuKzf/3dDN/c1DociqyuanBZJvIcJUw==","repository":{"url":"git+https://github.com/at-flux/astroflare.git","type":"git","directory":"packages/astro-feature-flags"},"_npmVersion":"11.18.0","description":"Typed Astro feature flags from JSON config. Includes environment specific rendering and pruning from elements to entire routes","directories":{},"_nodeVersion":"24.18.0","dependencies":{"node-html-parser":"7.1.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"astro":"5.18.1","tsdown":"0.21.8","vitest":"3.2.4","typescript":"5.9.3","@types/node":"24.7.0"},"peerDependencies":{"astro":"4.7.0 || 5.0.0"},"_npmOperationalInternal":{"tmp":"tmp/astro-feature-flags_1.0.2_1785182507332_0.33133701539890836","host":"s3://npm-registry-packages-npm-production"}},"1.0.3":{"name":"@at-flux/astro-feature-flags","version":"1.0.3","keywords":["astro","astro-integration","feature-flags","typescript","devtools","tooling"],"author":{"name":"atflux","email":"dev@atflux.uk"},"license":"MIT","_id":"@at-flux/astro-feature-flags@1.0.3","maintainers":[{"name":"atflux","email":"magisterfluxart@gmail.com"}],"homepage":"https://github.com/at-flux/astroflare/tree/main/packages/astro-feature-flags","bugs":{"url":"https://github.com/at-flux/astroflare/issues"},"dist":{"shasum":"19272614b26696e9ef9a67fbec9109ba24d2e4ad","tarball":"https://registry.npmjs.org/@at-flux/astro-feature-flags/-/astro-feature-flags-1.0.3.tgz","fileCount":48,"integrity":"sha512-adMgLFRHN34jNU7cmRudls66MelBQsYr6F2ClHMVqY1no0mraSLqSUeRhMWlro4Vm53uEYlt4W7dzkM+mUtGiw==","signatures":[{"sig":"MEYCIQCG6jeGVGhsGEozxUx+js3rcBloAq/A3csgQ4CYU/361gIhAIYkylXZUenH+3jEFJnNd00258BOMwayJjYW38twkuCU","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@at-flux%2fastro-feature-flags@1.0.3","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":768865},"main":"./dist/index.mjs","type":"module","_from":"file:at-flux-astro-feature-flags-1.0.3.tgz","exports":{".":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"./runtime":{"types":"./dist/runtime.d.mts","default":"./dist/runtime.mjs"},"./virtual-astro-feature-flags":{"types":"./virtual-astro-feature-flags.d.ts"}},"scripts":{"test":"vitest run","build":"tsdown","typecheck":"tsc --noEmit","test:watch":"vitest"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:906933b2-1d14-4507-906e-d16050f4b6de"}},"_resolved":"/tmp/e54d27e284835962464f058d2b40313a/at-flux-astro-feature-flags-1.0.3.tgz","_integrity":"sha512-adMgLFRHN34jNU7cmRudls66MelBQsYr6F2ClHMVqY1no0mraSLqSUeRhMWlro4Vm53uEYlt4W7dzkM+mUtGiw==","repository":{"url":"git+https://github.com/at-flux/astroflare.git","type":"git","directory":"packages/astro-feature-flags"},"_npmVersion":"11.19.0","description":"Typed Astro feature flags from JSON config. Includes environment specific rendering and pruning from elements to entire routes","directories":{},"_nodeVersion":"24.18.0","dependencies":{"node-html-parser":"9.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"astro":"7.1.3","tsdown":"0.22.13","vitest":"4.1.10","typescript":"7.0.2","@types/node":"24.7.0"},"peerDependencies":{"astro":">=5.0.0"},"_npmOperationalInternal":{"tmp":"tmp/astro-feature-flags_1.0.3_1785443752589_0.2157264171490314","host":"s3://npm-registry-packages-npm-production"}},"1.0.4":{"_id":"@at-flux/astro-feature-flags@1.0.4","bugs":{"url":"https://github.com/at-flux/astroflare/issues"},"dist":{"shasum":"0aab5362e62e7b496e41ec4b2b8ed452e468d370","tarball":"https://registry.npmjs.org/@at-flux/astro-feature-flags/-/astro-feature-flags-1.0.4.tgz","fileCount":49,"integrity":"sha512-adx1uGQyDr3aTHEbEZPPe6T4QiC3C8zCJk/JVYk7+VQUL7AwRlsqZPd4r/Zrlg4YvrZ8k3YKVHXcrcdZMKO0Nw==","signatures":[{"sig":"MEUCICTj+75a2dUXOjoVbBXBRbCI5h87OVy/t+SKgDyPtWTVAiEAieUlDVLtbDpotkfHfeIxKPuIqc3Zb/2/bvmuKwAsrVQ=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIB8LZv0RR91nZORNXoKIAknj2//Kbo4NArhK9QCaH7NGAiEA1FtrzthfCj1T0EgsUBny8XafWMJ53sQtOkCh7Ufvf0c="}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@at-flux%2fastro-feature-flags@1.0.4","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":805754},"main":"./dist/index.mjs","name":"@at-flux/astro-feature-flags","type":"module","_from":"file:at-flux-astro-feature-flags-1.0.4.tgz","author":{"name":"atflux","email":"dev@atflux.uk"},"exports":{".":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"./runtime":{"types":"./dist/runtime.d.mts","default":"./dist/runtime.mjs"},"./virtual-astro-feature-flags":{"types":"./virtual-astro-feature-flags.d.ts"}},"license":"MIT","scripts":{"test":"vitest run","build":"tsdown","typecheck":"tsc --noEmit","test:watch":"vitest"},"version":"1.0.4","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:906933b2-1d14-4507-906e-d16050f4b6de"}},"homepage":"https://github.com/at-flux/astroflare/tree/main/packages/astro-feature-flags","keywords":["astro","astro-integration","feature-flags","typescript","devtools","tooling"],"_resolved":"/tmp/d559ba45a846e491b75d2adad4439ac3/at-flux-astro-feature-flags-1.0.4.tgz","_integrity":"sha512-adx1uGQyDr3aTHEbEZPPe6T4QiC3C8zCJk/JVYk7+VQUL7AwRlsqZPd4r/Zrlg4YvrZ8k3YKVHXcrcdZMKO0Nw==","repository":{"url":"git+https://github.com/at-flux/astroflare.git","type":"git","directory":"packages/astro-feature-flags"},"_npmVersion":"11.19.1","description":"Typed Astro feature flags from JSON config. Includes environment specific rendering and pruning from elements to entire routes","directories":{},"maintainers":[{"name":"atflux","email":"magisterfluxart@gmail.com"}],"_nodeVersion":"24.20.0","dependencies":{"node-html-parser":"9.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"astro":"7.1.3","tsdown":"0.22.13","vitest":"4.1.10","typescript":"7.0.2","@types/node":"24.7.0"},"peerDependencies":{"astro":">=5.0.0"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/astro-feature-flags_1.0.4_1788980216299_0.4824554889840664"}}},"time":{"created":"2026-04-14T18:19:59.449Z","modified":"2026-09-09T18:56:56.949Z","1.0.0":"2026-04-14T18:19:59.782Z","1.0.2":"2026-07-27T20:01:47.530Z","1.0.3":"2026-07-30T20:35:52.776Z","1.0.4":"2026-09-09T18:56:56.472Z"},"bugs":{"url":"https://github.com/at-flux/astroflare/issues"},"author":{"name":"atflux","email":"dev@atflux.uk"},"license":"MIT","homepage":"https://github.com/at-flux/astroflare/tree/main/packages/astro-feature-flags","keywords":["astro","astro-integration","feature-flags","typescript","devtools","tooling"],"repository":{"url":"git+https://github.com/at-flux/astroflare.git","type":"git","directory":"packages/astro-feature-flags"},"description":"Typed Astro feature flags from JSON config. Includes environment specific rendering and pruning from elements to entire routes","maintainers":[{"name":"atflux","email":"magisterfluxart@gmail.com"}],"readme":"# @at-flux/astro-feature-flags\n\n[![npm version](https://img.shields.io/npm/v/@at-flux/astro-feature-flags.svg)](https://www.npmjs.com/package/@at-flux/astro-feature-flags)\n[![CI](https://github.com/at-flux/astroflare/actions/workflows/ci.yml/badge.svg)](https://github.com/at-flux/astroflare/actions/workflows/ci.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\n<p align=\"center\">\n  <img src=\"./docs/assets/toolbar-feature-flags.png\" alt=\"Feature Flags Toolbar\" width=\"50%\" style=\"max-width:500px;\" />\n  <img src=\"./docs/assets/element-gating.png\" alt=\"Feature Flags element gating\" width=\"40%\" style=\"max-width:400px;\" />\n  <br/>\n  <img src=\"./docs/assets/page-gating.png\" alt=\"Feature Flags route gating\" width=\"60%\" style=\"max-width:600px;\" />\n</p>\n\nFeature flags for Astro with a declarative config:\n\n- per-flag declaration (`colour`/`color` for the dev toolbar, optional `routes` for matching pages)\n- **Two environments:** **Astro dev** (`astro dev`) vs **non-dev builds** (production deploys, `astro build` in CI, staging, …). Locally, the integration uses a built-in **dev** environment layer (injected for you; all flags on at resolve time). For shipped output you declare layers such as **`prod`** / **`staging`** with `when` + `flags`. Exactly one `when: true` for the selected environment unless you override with **`forceEnvironment`** / **`AFF_ENVIRONMENT`**.\n- **Route gating (non-dev):** If a pathname matches a flag’s `routes` and that flag is **off** for the active layer, **static** `dist/` output under that prefix is **removed after build** (and you should filter those URLs from sitemaps—see how-to). Server/hybrid apps still need their own runtime routing if URLs can be requested without a matching static file. If the flag is **on**, routes emit like any other page.\n- **Route overlap:** `shouldIncludePath`, `shouldIncludeRoute`, and `shouldIncludePathForEnvironment` use the **first** matching `routes` entry from `Object.entries` order, not longest-prefix. `matchedFeatureRoutePrefix` / `routeFeatureTokensForPath` use **longest** match — avoid overlapping patterns unless order is intentional.\n- element gating via namespaced attributes (`data-ff` or `data-ff-<token>` by default)\n- production static HTML: gated `data-ff` nodes culled, dev-only CSS not shipped (`featureFlagStyles` is empty); dev route attributes (`data-ff-route*`) are stripped from `<html>`\n- route badge + production route pruning\n- dev toolbar for enabled/outline/badge/colour preview (when a URL would be pruned for a configured layer, the overlay names **environment keys**, not `NODE_ENV` text)\n\n## Terminology (short)\n\n| Term                              | Meaning                                                                                                                                                                                              |\n| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Astro dev**                     | Local `astro dev` — the built-in dev layer is active; all flags on at resolve time; dev toolbar only affects the browser.                                                                            |\n| **Non-dev build**                 | `astro build` / preview / deploy with a shipped layer (`prod`, `staging`, …): `environments.<key>.flags` drives SSR, HTML culling, and route pruning.                                                |\n| **`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. |\n\nYou cannot add an environment key named **`dev`** — that name is reserved and merged automatically (including its `when`; you do not set it in config).\n\n## Quickstart\n\n1. Configure in `astro.config.mjs`:\n\n   ```js\n   import astroFeatureFlags from \"@at-flux/astro-feature-flags\";\n\n   export default defineConfig({\n     integrations: [\n       astroFeatureFlags({\n         // optional: jsonConfigPath: \"./ff.json\",\n         // optional: configRoot: fileURLToPath(new URL(\".\", import.meta.url)),\n         flags: {\n           wip: {\n             colour: \"rgb(220 38 38)\",\n             routes: [\"/blog/*\"],\n           },\n           hotFeature1: {\n             colour: \"rgb(37 99 235)\",\n             routes: [\"/hot-feature-1/*\"],\n           },\n           hotFeature2: {\n             colour: \"rgb(34 197 94)\",\n             outline: false,\n             badge: true,\n             routes: [\"/hot/*\", \"/hot-dev/*\"],\n           },\n         },\n         environments: {\n           prod: {\n             when: process.env.NODE_ENV === \"production\",\n             flags: {\n               wip: false,\n               hotFeature1: true,\n               hotFeature2: false,\n             },\n           },\n         },\n       }),\n     ],\n   });\n   ```\n\n2. Optional JSON: **`jsonConfigPath`** on the integration root (merged after inline config). Optional **`jsonConfigPath`** on each **non-`dev`** environment merges when that layer is active (after the root file). Paths resolve relative to `configRoot` (`process.cwd()` by default).\n\n   ```json\n   {\n     \"environments\": {\n       \"prod\": {\n         \"flags\": { \"hotFeature1\": false }\n       }\n     }\n   }\n   ```\n\n3. Gate elements in markup:\n   - `data-ff={FeatureToken.HotFeature2}`\n   - `data-ff={[FeatureToken.Wip, FeatureToken.HotFeature2].join(' ')}`\n   - `data-ff=\"wip hot-feature-2\"` **(no import!)**\n   - `data-ff-wip` **(no import!)**\n   - `data-ff-hot-feature-2` **(no import!)**\n\n> [!NOTE]\n> Flags are combinatory. If an element has `data-ff=\"wip hot-feature-2\"`, both flags must be enabled for SSR outside the reserved `dev` layer.\n>\n> In the dev toolbar preview, combined flags are also combinatory for **Enabled**: if any token in the combo is set to Off, the whole combined element is hidden.\n>\n> In **Astro dev**, all declared flags are on at resolve time; the dev toolbar only changes client preview. In **non-dev** builds, nodes that fail the check are **removed from the HTML**. Route-mapped prefixes with a flag **off** are pruned from static `dist/` after build. Prefer **`shouldRenderFeature`** when you need compile-time omission with no trace in `dist/`.\n\n## Common Use Cases\n\n### 1) Per-element token (imported)\n\n```tsx\n---\nimport { FeatureToken } from 'virtual:astro-feature-flags';\n---\n\n<section data-ff={FeatureToken.Wip}>WIP section</section>\n<aside data-ff={FeatureToken.HotFeature2}>Hot section</aside>\n```\n\nYou can also use flag names in `data-ff`:\n\n```tsx\n---\nimport { FeatureFlag } from 'virtual:astro-feature-flags';\n---\n\n<aside data-ff={FeatureFlag.HotFeature2}>Hot section</aside>\n```\n\n### 2) Shorthand attribute (no import)\n\n```tsx\n<section data-ff-wip>WIP section</section>\n<aside data-ff-hot-feature-2>Hot section</aside>\n```\n\n`data-ff-wip` is equivalent to `data-ff={FeatureToken.Wip}` or `data-ff=\"wip\"`.\n\nIf `tokenNamespace` is `aff`, use `data-aff` / `data-aff-<token>` instead.\n\n### 2b) Combined flags (AND behavior)\n\nBoth flags must be enabled:\n\n```tsx\n<div data-ff-wip data-ff-hot-feature-2>\n  ...\n</div>\n```\n\nOr using `data-ff` with values:\n\n```tsx\n---\nimport { FeatureFlag } from 'virtual:astro-feature-flags';\n---\n\n<div data-ff={[FeatureFlag.Wip, FeatureFlag.HotFeature2].join(' ')}>...</div>\n// OR\n<div data-ff=\"wip hot-feature-2\">...</div>\n```\n\n`data-ff` expects a space-separated list of feature flags.\n\n### 3) Dev toolbar head injection (automatic)\n\nOn **`astro dev`**, when the built-in dev layer is active, the integration uses Astro’s **`injectScript('head-inline', …)`** to append dev-only CSS, set **`data-ff-route`** on `<html>` from the current URL (including after **`astro:page-load`** / **`astro:after-swap`**), and run the toolbar bootstrap script. You do **not** need to wire `affDevBootstrap`, `featureFlagStyles`, or `data-ff-route` in a root layout unless you intentionally want a second copy.\n\nThe virtual module still exports **`affDevBootstrap`**, **`featureFlagStyles`**, and **`routeFeatureTokensForPath`** for advanced layouts.\n\n### 4) Logic usage (`FeatureFlag`)\n\nUse this when you need explicit conditional logic in frontmatter (most UI cases can stay markup-only with `data-ff-*`):\n\n```ts\nimport { FeatureFlag, shouldRenderFeature } from \"virtual:astro-feature-flags\";\n```\n\n`shouldRenderFeature()` and `isFeatureEnabled()` follow config/env values.\\\nThe dev toolbar changes client-side preview state only.\n\n## Configuration Schema\n\n### Top-level options\n\n| Option             | Type                                  | Default         | Notes                                                                                                                                                                                              |\n| ------------------ | ------------------------------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `configRoot`       | `string`                              | `process.cwd()` | Resolves relative `jsonConfigPath` values (root + per-environment).                                                                                                                                |\n| `jsonConfigPath`   | `string`                              | unset           | Optional **root** JSON file merged after inline config (see merge order for per-environment files).                                                                                                |\n| `forceEnvironment` | `string`                              | unset           | Pin the active layer (skips `when` / `AFF_ENVIRONMENT` validation).                                                                                                                                |\n| `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`.                                                 |\n| `env`              | `Record<string, string \\| undefined>` | `process.env`   | `AFF_FEATURE_*` / `ASTRO_FEATURE_FLAGS` (not applied in `dev` layer).                                                                                                                              |\n| `tokenNamespace`   | `string`                              | `'ff'`          | CSS var namespace (`--ff-c-*`).                                                                                                                                                                    |\n| `flags`            | `Record<string, FlagConfig>`          | `{}`            | Flag declarations.                                                                                                                                                                                 |\n| `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.                                                                         |\n| `css`              | `DevOutlineCssOptions`                | defaults        | Global badge/outline layout and styling.                                                                                                                                                           |\n| `staticMinify`     | `boolean`                             | `true`          | For static builds: route-prune disabled prefixes + cull gated HTML in `dist/`. Set `false` to keep emitted files untouched.                                                                        |\n| `pruneSitemap`     | `boolean`                             | `true`          | Drop pruned routes from generated sitemaps in `dist/`. Needs this integration **after** `sitemap()` in `integrations`. See [Hide flagged routes from sitemaps](docs/how-to/hide-from-sitemaps.md). |\n\nIf 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.\n\n### Reserved name `dev`\n\nThe key **`dev`** is reserved: do not list it under `environments`. The integration injects it with `when: mode !== \"production\"` (mode defaults to `NODE_ENV`) so local **`astro dev`** uses the all-flags-on layer. Configure only shipped layers (`prod`, `staging`, …) yourself.\n\n### `FlagConfig`\n\n| Field              | Type       | Default            | Notes                                     |\n| ------------------ | ---------- | ------------------ | ----------------------------------------- |\n| `colour` / `color` | `string`   | inherited fallback | Outline/badge color.                      |\n| `routes`           | `string[]` | `[]`               | Route wildcard mapping (`/x/*`, `/x/**`). |\n| `outline`          | `boolean`  | `true`             | Default toolbar outline state in dev.     |\n| `badge`            | `boolean`  | `true`             | Default toolbar badge state in dev.       |\n\n### `EnvironmentConfig`\n\n| Field            | Type                      | Default | Notes                                                                                        |\n| ---------------- | ------------------------- | ------- | -------------------------------------------------------------------------------------------- |\n| `when`           | `boolean`                 | unset   | Exactly one environment must have `when: true` (unless forced).                              |\n| `flags`          | `Record<string, boolean>` | `{}`    | For non-`dev` layers: booleans per flag. Ignored for reserved `dev`.                         |\n| `jsonConfigPath` | `string`                  | unset   | Optional JSON merged when this environment is the active layer (not used on reserved `dev`). |\n\nMerge order:\n\n1. Inline config in `astro.config.mjs`\n2. Root `jsonConfigPath` (if set)\n3. `environments.<active>.jsonConfigPath` (if set; skipped for `dev`)\n4. Process overrides on non-`dev` layers: `AFF_FEATURE_*`, then `ASTRO_FEATURE_FLAGS`\n\nLayer select override: `AFF_ENVIRONMENT=prod`. **`forceEnvironment`** on the integration options wins over **`AFF_ENVIRONMENT`** when both are set, and skips the “exactly one `when: true`” check by pinning the layer directly. In static builds, changing `AFF_ENVIRONMENT` after build does nothing unless you rebuild (or disable `staticMinify` and use a server runtime that evaluates flags at request time).\n\n### DevOutlineCssOptions\n\n| Field                              | Default   | Meaning                                                                                         |\n| ---------------------------------- | --------- | ----------------------------------------------------------------------------------------------- |\n| `elementBadgeHorizontalAlign`      | `'end'`   | `'start'` \\| `'center'` \\| `'end'` — LTR: **`end`** = top-right.                                |\n| `elementBadgeHorizontalPercent`    | _(unset)_ | 0–100: horizontal anchor with pill centred (`translateX(-50%)`); overrides align.               |\n| `elementBadgeVerticalShiftPercent` | `80`      | Vertical shift as **% of the pill height** (default keeps most of the label above the element). |\n| `elementBadgeVerticalAnchor`       | `'top'`   | `'top'` or `'bottom'`.                                                                          |\n\nPer-element badge overrides (markup):\n\n- `data-ff-align=\"start|center|end\"`\n- `data-ff-horizontal=\"50\"`\n- `data-ff-vertical=\"100\"`\n- `data-ff-anchor=\"top|bottom\"`\n\nSee `example/src/pages/hot-feature-1/index.astro` for all three position controls in use.\n\n## TypeScript\n\nAstro projects typically pick this up automatically from package exports.\nIf your editor misses virtual module types, add one reference in `src/env.d.ts`.\n\n## Dev toolbar\n\n| Control     | Effect                                                                                                           |\n| ----------- | ---------------------------------------------------------------------------------------------------------------- |\n| **Enabled** | Off → hide nodes carrying that token in `data-ff` (including combined hosts/pages when any member token is Off). |\n| **Outline** | Toggle outlines only (element and route frame). For combined hosts this is a non-layout-shifting overlay ring.   |\n| **Badges**  | Toggle badges only (element pills + route pill).                                                                 |\n| **Colour**  | `--<namespace>-c-<token>` (persisted).                                                                           |\n\n## Virtual module\n\nExports include **`FeatureFlag`**, **`FeatureToken`**, **`isAstroDev`**, **`activeEnvironmentKey`**, **`defaultNonDevEnvironment`**, **`flagsForEnvironment`**, **`isFeatureEnabledForEnvironment`**, **`shouldIncludePathForEnvironment`**, **`affDevBootstrap`**, **`routeFeatureTokenForPath`**, **`routeFeatureTokensForPath`**, **`shouldRenderFeature`**, **`matchedFeatureRoutePrefix`**, **`featureFlagStyles`**, etc.\n\n**Programmatic resolution**: `getResolvedFeatures(config)` / `resolveFeatureRuntime(config)` use the same rules as the integration. Set **`forceEnvironment: \"prod\"`** (or any other key) to pin a layer (e.g. sitemaps generated while `astro` is in dev but routes should match a shipped layer).\n\n**`featureFlagStyles`**: dev-only (outlines, badges, route badges, route-prune overlay). In production it is always an **empty string** — static HTML is cleaned up after build instead. You can still import it so a shared layout keeps one code path; empty `<style>` tags are removed from emitted HTML.\n\n**`featureFlagsByEnvironment`**: frozen map of resolved booleans per `environments` key. The dev bootstrap compares the current URL against each layer and lists which keys would omit that route.\n\n**`defaultNonDevEnvironment`**: prefers `prod` if defined, otherwise the first non-`dev` key (sorted). Use with **`shouldIncludePathForEnvironment(path, defaultNonDevEnvironment)`** (or any explicit key) when you want “primary shipped layer” without hard-coding a name — your non-dev keys can be `staging`, `preview-123`, etc.\n\n## What this package is not\n\nRemote percentage rollouts, per-user experiment assignment, analytics, or a hosted flag service. This is **declarative Astro config** + build-time HTML cleanup + a **local dev toolbar**.\n\n## How-to\n\n- [Hide feature locked pages from sitemaps in production builds](docs/how-to/hide-from-sitemaps.md)\n\n## Example Pages\n\n`example/` — `pnpm install && pnpm dev`.\n\n- `/` integration overview + tagging options\n- `/hot-feature-1/` route mapped to `hotFeature1`\n- `/hot/` route mapped to `hotFeature2`\n- `/hot-dev/sub/` wildcard nested route + combined `wip` + `hotFeature2` element gating\n\n## Tests\n\n```bash\npnpm test\npnpm typecheck\nENABLE_SLOW=1 pnpm test\n```\n\nSlow checks that run a real `example` production build plus a small fixture (route pruning, `shouldRenderFeature`, and `data-ff` HTML culling) are documented in [docs/testing.md](./docs/testing.md). Enable them with `ENABLE_SLOW=1`.\n","readmeFilename":"README.md"}