diff --git a/README.md b/README.md
index 489893e..c227b14 100644
--- a/README.md
+++ b/README.md
@@ -15,17 +15,17 @@ Lightweight, environment-based feature flag system for Nuxt - made for developer
- 🎯 Roll out features to internal QA teams without branching or releases
- 📆 Schedule feature launches for specific environments or timeframes
- 🕵️♀️ Detect undeclared feature flags at build time with configurable validation and precise file context
+- 🌳 Group flags with hierarchical names and enable bundles via wildcard (`*`) patterns
## Planned Features
+- 📊 A/B testing support for feature flags
+- 💡 Flag descriptions / metadata for better documentation, DevTools tooltips, or internal usage notes
- 🧩 Nuxt DevTools integration with a Feature Flag Explorer and Environment Switcher
- 🔄 Dynamic feature flag updates without server restarts through a remote config service
-- 📊 A/B testing support for feature flags
-- 📈 Analytics for feature flag usage
- 🧍♂️ Show features only for specific users (e.g., staff-only UIs, admin panels etc.)
-- 🧬 Environment inheritance which lets environments inherit feature flags from others
-- 💡 Flag descriptions / metadata for better documentation, DevTools tooltips, or internal usage notes
- 🛠 Programmatic overrides to toggle or override feature flags dynamically at runtime (e.g., per user or session)
+- 📈 Analytics for feature flag usage and user feedback collection
## Quick Setup
@@ -53,6 +53,49 @@ export default defineNuxtConfig({
})
```
+### Hierarchical & Wildcard Flags
+
+Feature flags can be organized with `/`-separated paths and enabled in bulk using `*`.
+
+```ts
+export default defineNuxtConfig({
+ modules: ['nuxt-feature-flags-module'],
+ featureFlags: {
+ environment: process.env.FEATURE_ENV || 'development',
+ flagSets: {
+ development: [
+ 'solutions/*',
+ 'staging/*',
+ 'internal/experimental/ui'
+ ],
+ staging: [
+ 'solutions/company-portal/addons/sales',
+ 'solutions/company-portal/addons/marketing',
+ 'internal/experimental/ui'
+ ],
+ production: [
+ 'solutions/company-portal/addons/sales'
+ ]
+ }
+ }
+})
+```
+
+```ts
+const { isEnabled } = useFeatureFlag()
+
+if (isEnabled('solutions/company-portal/addons/sales')) {
+ // sales addon enabled
+}
+
+if (isEnabled('solutions/*')) {
+ // any solution-related flag is active
+}
+```
+
+> [!CAUTION]
+> Using `*` enables every flag and the validator will emit a warning. Reserve it for debugging scenarios.
+
Use in your app:
```html
diff --git a/playground/nuxt.config.ts b/playground/nuxt.config.ts
index 9750cc4..f8df5c9 100644
--- a/playground/nuxt.config.ts
+++ b/playground/nuxt.config.ts
@@ -33,9 +33,16 @@ export default defineNuxtConfig({
activeFrom: '2000-01-01T00:00:00Z',
activeUntil: '2001-01-01T00:00:00Z',
},
+ 'solutions/*',
+ 'internal/experimental/ui',
],
- staging: ['newSystem'],
- production: [],
+ staging: [
+ 'newSystem',
+ 'solutions/company-portal/addons/sales',
+ 'solutions/company-portal/addons/marketing',
+ 'internal/experimental/ui',
+ ],
+ production: ['solutions/company-portal/addons/sales'],
},
validation: {
mode: 'warn',
diff --git a/playground/pages/hierarchical.vue b/playground/pages/hierarchical.vue
new file mode 100644
index 0000000..9b97b8a
--- /dev/null
+++ b/playground/pages/hierarchical.vue
@@ -0,0 +1,45 @@
+
+
+
+ 🌳 Hierarchical Flags Demo
+
+
+ The solutions/* wildcard enables all nested solution flags.
+
+
+
+ Sales addon is enabled
+
+
+ Marketing addon is enabled
+
+
+ Internal experimental UI is enabled
+
+
+
+ Group check for solutions/* returned true.
+
+
+
+
+
diff --git a/playground/pages/index.vue b/playground/pages/index.vue
index 4c3a516..628c29e 100644
--- a/playground/pages/index.vue
+++ b/playground/pages/index.vue
@@ -75,6 +75,22 @@
Redirects to /404 unless 'newSystem' is active.
+
+
+
+
+ 🌳 Hierarchical Flags
+
+
+ View Demo
+
+
+ Demonstrates grouped flags enabled via wildcard patterns.
+
+
@@ -129,6 +145,10 @@ function navigateToProtected() {
}
}
+function navigateToHierarchical() {
+ navigateTo('/hierarchical')
+}
+
useSeoMeta({
title: 'Nuxt Feature Flags Playground',
description: 'A playground for the Nuxt Feature Flags Module. Test and explore feature flags in a Nuxt application.',
diff --git a/playground/server/api/solutions.ts b/playground/server/api/solutions.ts
new file mode 100644
index 0000000..e3844c1
--- /dev/null
+++ b/playground/server/api/solutions.ts
@@ -0,0 +1,9 @@
+import { isFeatureEnabled } from '../../../src/runtime/server/isFeatureEnabled'
+
+export default defineEventHandler((event) => {
+ if (!isFeatureEnabled('solutions/company-portal/addons/sales', event)) {
+ return sendError(event, createError({ statusCode: 403, message: 'Sales addon disabled' }))
+ }
+
+ return { message: 'Sales addon feature unlocked 🎉' }
+})
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 95f28d2..8968e56 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -17,10 +17,10 @@ importers:
version: 2.6.3(vite@7.1.3(@types/node@24.3.0)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))(vue@3.5.19(typescript@5.9.2))
'@nuxt/eslint':
specifier: 1.9.0
- version: 1.9.0(@typescript-eslint/utils@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(@vue/compiler-sfc@3.5.19)(eslint@9.33.0(jiti@2.5.1))(magicast@0.3.5)(typescript@5.9.2)(vite@7.1.3(@types/node@24.3.0)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))
+ version: 1.9.0(@typescript-eslint/utils@8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(@vue/compiler-sfc@3.5.19)(eslint@9.34.0(jiti@2.5.1))(magicast@0.3.5)(typescript@5.9.2)(vite@7.1.3(@types/node@24.3.0)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))
'@nuxt/eslint-config':
specifier: ^1.3.1
- version: 1.9.0(@typescript-eslint/utils@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(@vue/compiler-sfc@3.5.19)(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)
+ version: 1.9.0(@typescript-eslint/utils@8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(@vue/compiler-sfc@3.5.19)(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2)
'@nuxt/module-builder':
specifier: ^1.0.1
version: 1.0.2(@nuxt/cli@3.28.0(magicast@0.3.5))(@vue/compiler-core@3.5.19)(esbuild@0.25.9)(typescript@5.9.2)(vue-tsc@2.2.12(typescript@5.9.2))(vue@3.5.19(typescript@5.9.2))
@@ -38,13 +38,13 @@ importers:
version: 0.6.2(magicast@0.3.5)
eslint:
specifier: ^9.26.0
- version: 9.33.0(jiti@2.5.1)
+ version: 9.34.0(jiti@2.5.1)
globby:
specifier: ^14.1.0
version: 14.1.0
nuxt:
specifier: ^4.0.3
- version: 4.0.3(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@24.3.0)(@vue/compiler-sfc@3.5.19)(db0@0.3.2)(eslint@9.33.0(jiti@2.5.1))(ioredis@5.7.0)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.47.1)(terser@5.43.1)(typescript@5.9.2)(vite@7.1.3(@types/node@24.3.0)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))(vue-tsc@2.2.12(typescript@5.9.2))(yaml@2.8.1)
+ version: 4.0.3(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@24.3.0)(@vue/compiler-sfc@3.5.19)(db0@0.3.2)(eslint@9.34.0(jiti@2.5.1))(ioredis@5.7.0)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.47.1)(terser@5.43.1)(typescript@5.9.2)(vite@7.1.3(@types/node@24.3.0)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))(vue-tsc@2.2.12(typescript@5.9.2))(yaml@2.8.1)
typescript:
specifier: ~5.9.2
version: 5.9.2
@@ -572,8 +572,8 @@ packages:
resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- '@eslint/js@9.33.0':
- resolution: {integrity: sha512-5K1/mKhWaMfreBGJTwval43JJmkip0RmM+3+IuqupeSKNC/Th2Kc7ucaq5ovTSra/OOKB9c58CGSz3QMVbWt0A==}
+ '@eslint/js@9.34.0':
+ resolution: {integrity: sha512-EoyvqQnBNsV1CWaEJ559rxXL4c8V92gxirbawSmVUOWXlsRxxQXl6LmCpdUblgxgSkDIqKnhzba2SjRTI/A5Rw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/object-schema@2.1.6':
@@ -1974,8 +1974,8 @@ packages:
caniuse-lite@1.0.30001737:
resolution: {integrity: sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw==}
- chai@5.3.2:
- resolution: {integrity: sha512-kx7GHSOBiiIQ+DDgMP6YMtYkb/3Usm2nUYblNEM9P+/OfkuP7OjfoDlq/DCe1OU0GsREUa0rNAxZmzxgO6+jWg==}
+ chai@5.3.3:
+ resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==}
engines: {node: '>=18'}
chalk@4.1.2:
@@ -2174,8 +2174,8 @@ packages:
engines: {node: '>=4'}
hasBin: true
- cssnano-preset-default@7.0.8:
- resolution: {integrity: sha512-d+3R2qwrUV3g4LEMOjnndognKirBZISylDZAF/TPeCWVjEwlXS2e4eN4ICkoobRe7pD3H6lltinKVyS1AJhdjQ==}
+ cssnano-preset-default@7.0.9:
+ resolution: {integrity: sha512-tCD6AAFgYBOVpMBX41KjbvRh9c2uUjLXRyV7KHSIrwHiq5Z9o0TFfUCoM3TwVrRsRteN3sVXGNvjVNxYzkpTsA==}
engines: {node: ^18.12.0 || ^20.9.0 || >=22.0}
peerDependencies:
postcss: ^8.4.32
@@ -2186,8 +2186,8 @@ packages:
peerDependencies:
postcss: ^8.4.32
- cssnano@7.1.0:
- resolution: {integrity: sha512-Pu3rlKkd0ZtlCUzBrKL1Z4YmhKppjC1H9jo7u1o4qaKqyhvixFgu5qLyNIAOjSTg9DjVPtUqdROq2EfpVMEe+w==}
+ cssnano@7.1.1:
+ resolution: {integrity: sha512-fm4D8ti0dQmFPeF8DXSAA//btEmqCOgAc/9Oa3C1LW94h5usNrJEfrON7b4FkPZgnDEn6OUs5NdxiJZmAtGOpQ==}
engines: {node: ^18.12.0 || ^20.9.0 || >=22.0}
peerDependencies:
postcss: ^8.4.32
@@ -2558,8 +2558,8 @@ packages:
resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- eslint@9.33.0:
- resolution: {integrity: sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA==}
+ eslint@9.34.0:
+ resolution: {integrity: sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
hasBin: true
peerDependencies:
@@ -3318,8 +3318,8 @@ packages:
vue-tsc:
optional: true
- mlly@1.7.4:
- resolution: {integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==}
+ mlly@1.8.0:
+ resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==}
mocked-exports@0.1.1:
resolution: {integrity: sha512-aF7yRQr/Q0O2/4pIXm6PZ5G+jAd7QS4Yu8m+WEeEHGnbo+7mE36CbLSDQiXYV8bVL3NfmdeqPJct0tUlnjVSnA==}
@@ -3673,8 +3673,8 @@ packages:
peerDependencies:
postcss: ^8.4.32
- postcss-convert-values@7.0.6:
- resolution: {integrity: sha512-MD/eb39Mr60hvgrqpXsgbiqluawYg/8K4nKsqRsuDX9f+xN1j6awZCUv/5tLH8ak3vYp/EMXwdcnXvfZYiejCQ==}
+ postcss-convert-values@7.0.7:
+ resolution: {integrity: sha512-HR9DZLN04Xbe6xugRH6lS4ZQH2zm/bFh/ZyRkpedZozhvh+awAfbA0P36InO4fZfDhvYfNJeNvlTf1sjwGbw/A==}
engines: {node: ^18.12.0 || ^20.9.0 || >=22.0}
peerDependencies:
postcss: ^8.4.32
@@ -5239,16 +5239,16 @@ snapshots:
'@esbuild/win32-x64@0.25.9':
optional: true
- '@eslint-community/eslint-utils@4.7.0(eslint@9.33.0(jiti@2.5.1))':
+ '@eslint-community/eslint-utils@4.7.0(eslint@9.34.0(jiti@2.5.1))':
dependencies:
- eslint: 9.33.0(jiti@2.5.1)
+ eslint: 9.34.0(jiti@2.5.1)
eslint-visitor-keys: 3.4.3
'@eslint-community/regexpp@4.12.1': {}
- '@eslint/compat@1.3.2(eslint@9.33.0(jiti@2.5.1))':
+ '@eslint/compat@1.3.2(eslint@9.34.0(jiti@2.5.1))':
optionalDependencies:
- eslint: 9.33.0(jiti@2.5.1)
+ eslint: 9.34.0(jiti@2.5.1)
'@eslint/config-array@0.21.0':
dependencies:
@@ -5260,7 +5260,7 @@ snapshots:
'@eslint/config-helpers@0.3.1': {}
- '@eslint/config-inspector@1.2.0(eslint@9.33.0(jiti@2.5.1))':
+ '@eslint/config-inspector@1.2.0(eslint@9.34.0(jiti@2.5.1))':
dependencies:
'@nodelib/fs.walk': 3.0.1
ansis: 4.1.0
@@ -5269,11 +5269,11 @@ snapshots:
chokidar: 4.0.3
debug: 4.4.1
esbuild: 0.25.9
- eslint: 9.33.0(jiti@2.5.1)
+ eslint: 9.34.0(jiti@2.5.1)
find-up: 7.0.0
get-port-please: 3.2.0
h3: 1.15.4
- mlly: 1.7.4
+ mlly: 1.8.0
mrmime: 2.0.1
open: 10.2.0
tinyglobby: 0.2.14
@@ -5301,7 +5301,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@eslint/js@9.33.0': {}
+ '@eslint/js@9.34.0': {}
'@eslint/object-schema@2.1.6': {}
@@ -5610,30 +5610,30 @@ snapshots:
- utf-8-validate
- vue
- '@nuxt/eslint-config@1.9.0(@typescript-eslint/utils@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(@vue/compiler-sfc@3.5.19)(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)':
+ '@nuxt/eslint-config@1.9.0(@typescript-eslint/utils@8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(@vue/compiler-sfc@3.5.19)(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2)':
dependencies:
'@antfu/install-pkg': 1.1.0
'@clack/prompts': 0.11.0
- '@eslint/js': 9.33.0
- '@nuxt/eslint-plugin': 1.9.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)
- '@stylistic/eslint-plugin': 5.2.3(eslint@9.33.0(jiti@2.5.1))
- '@typescript-eslint/eslint-plugin': 8.40.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)
- '@typescript-eslint/parser': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)
- eslint: 9.33.0(jiti@2.5.1)
- eslint-config-flat-gitignore: 2.1.0(eslint@9.33.0(jiti@2.5.1))
+ '@eslint/js': 9.34.0
+ '@nuxt/eslint-plugin': 1.9.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2)
+ '@stylistic/eslint-plugin': 5.2.3(eslint@9.34.0(jiti@2.5.1))
+ '@typescript-eslint/eslint-plugin': 8.40.0(@typescript-eslint/parser@8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2)
+ '@typescript-eslint/parser': 8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2)
+ eslint: 9.34.0(jiti@2.5.1)
+ eslint-config-flat-gitignore: 2.1.0(eslint@9.34.0(jiti@2.5.1))
eslint-flat-config-utils: 2.1.1
- eslint-merge-processors: 2.0.0(eslint@9.33.0(jiti@2.5.1))
- eslint-plugin-import-lite: 0.3.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)
- eslint-plugin-import-x: 4.16.1(@typescript-eslint/utils@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1))
- eslint-plugin-jsdoc: 54.1.1(eslint@9.33.0(jiti@2.5.1))
- eslint-plugin-regexp: 2.10.0(eslint@9.33.0(jiti@2.5.1))
- eslint-plugin-unicorn: 60.0.0(eslint@9.33.0(jiti@2.5.1))
- eslint-plugin-vue: 10.4.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1))(vue-eslint-parser@10.2.0(eslint@9.33.0(jiti@2.5.1)))
- eslint-processor-vue-blocks: 2.0.0(@vue/compiler-sfc@3.5.19)(eslint@9.33.0(jiti@2.5.1))
+ eslint-merge-processors: 2.0.0(eslint@9.34.0(jiti@2.5.1))
+ eslint-plugin-import-lite: 0.3.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2)
+ eslint-plugin-import-x: 4.16.1(@typescript-eslint/utils@8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.34.0(jiti@2.5.1))
+ eslint-plugin-jsdoc: 54.1.1(eslint@9.34.0(jiti@2.5.1))
+ eslint-plugin-regexp: 2.10.0(eslint@9.34.0(jiti@2.5.1))
+ eslint-plugin-unicorn: 60.0.0(eslint@9.34.0(jiti@2.5.1))
+ eslint-plugin-vue: 10.4.0(@typescript-eslint/parser@8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.34.0(jiti@2.5.1))(vue-eslint-parser@10.2.0(eslint@9.34.0(jiti@2.5.1)))
+ eslint-processor-vue-blocks: 2.0.0(@vue/compiler-sfc@3.5.19)(eslint@9.34.0(jiti@2.5.1))
globals: 16.3.0
local-pkg: 1.1.2
pathe: 2.0.3
- vue-eslint-parser: 10.2.0(eslint@9.33.0(jiti@2.5.1))
+ vue-eslint-parser: 10.2.0(eslint@9.34.0(jiti@2.5.1))
transitivePeerDependencies:
- '@typescript-eslint/utils'
- '@vue/compiler-sfc'
@@ -5641,29 +5641,29 @@ snapshots:
- supports-color
- typescript
- '@nuxt/eslint-plugin@1.9.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)':
+ '@nuxt/eslint-plugin@1.9.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2)':
dependencies:
'@typescript-eslint/types': 8.40.0
- '@typescript-eslint/utils': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)
- eslint: 9.33.0(jiti@2.5.1)
+ '@typescript-eslint/utils': 8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2)
+ eslint: 9.34.0(jiti@2.5.1)
transitivePeerDependencies:
- supports-color
- typescript
- '@nuxt/eslint@1.9.0(@typescript-eslint/utils@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(@vue/compiler-sfc@3.5.19)(eslint@9.33.0(jiti@2.5.1))(magicast@0.3.5)(typescript@5.9.2)(vite@7.1.3(@types/node@24.3.0)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))':
+ '@nuxt/eslint@1.9.0(@typescript-eslint/utils@8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(@vue/compiler-sfc@3.5.19)(eslint@9.34.0(jiti@2.5.1))(magicast@0.3.5)(typescript@5.9.2)(vite@7.1.3(@types/node@24.3.0)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))':
dependencies:
- '@eslint/config-inspector': 1.2.0(eslint@9.33.0(jiti@2.5.1))
+ '@eslint/config-inspector': 1.2.0(eslint@9.34.0(jiti@2.5.1))
'@nuxt/devtools-kit': 2.6.3(magicast@0.3.5)(vite@7.1.3(@types/node@24.3.0)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))
- '@nuxt/eslint-config': 1.9.0(@typescript-eslint/utils@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(@vue/compiler-sfc@3.5.19)(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)
- '@nuxt/eslint-plugin': 1.9.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)
+ '@nuxt/eslint-config': 1.9.0(@typescript-eslint/utils@8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(@vue/compiler-sfc@3.5.19)(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2)
+ '@nuxt/eslint-plugin': 1.9.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2)
'@nuxt/kit': 4.0.3(magicast@0.3.5)
chokidar: 4.0.3
- eslint: 9.33.0(jiti@2.5.1)
+ eslint: 9.34.0(jiti@2.5.1)
eslint-flat-config-utils: 2.1.1
- eslint-typegen: 2.3.0(eslint@9.33.0(jiti@2.5.1))
+ eslint-typegen: 2.3.0(eslint@9.34.0(jiti@2.5.1))
find-up: 7.0.0
get-port-please: 3.2.0
- mlly: 1.7.4
+ mlly: 1.8.0
pathe: 2.0.3
unimport: 5.2.0
transitivePeerDependencies:
@@ -5690,7 +5690,7 @@ snapshots:
jiti: 2.5.1
klona: 2.0.6
knitwork: 1.2.0
- mlly: 1.7.4
+ mlly: 1.8.0
ohash: 2.0.11
pathe: 2.0.3
pkg-types: 2.3.0
@@ -5716,7 +5716,7 @@ snapshots:
ignore: 7.0.5
jiti: 2.5.1
klona: 2.0.6
- mlly: 1.7.4
+ mlly: 1.8.0
ohash: 2.0.11
pathe: 2.0.3
pkg-types: 2.3.0
@@ -5740,7 +5740,7 @@ snapshots:
jiti: 2.5.1
magic-regexp: 0.10.0
mkdist: 2.3.0(typescript@5.9.2)(vue-sfc-transformer@0.1.16(@vue/compiler-core@3.5.19)(esbuild@0.25.9)(vue@3.5.19(typescript@5.9.2)))(vue-tsc@2.2.12(typescript@5.9.2))(vue@3.5.19(typescript@5.9.2))
- mlly: 1.7.4
+ mlly: 1.8.0
pathe: 2.0.3
pkg-types: 2.3.0
tsconfck: 3.1.6(typescript@5.9.2)
@@ -5812,7 +5812,7 @@ snapshots:
- magicast
- typescript
- '@nuxt/vite-builder@4.0.3(@types/node@24.3.0)(eslint@9.33.0(jiti@2.5.1))(magicast@0.3.5)(optionator@0.9.4)(rollup@4.47.1)(terser@5.43.1)(typescript@5.9.2)(vue-tsc@2.2.12(typescript@5.9.2))(vue@3.5.19(typescript@5.9.2))(yaml@2.8.1)':
+ '@nuxt/vite-builder@4.0.3(@types/node@24.3.0)(eslint@9.34.0(jiti@2.5.1))(magicast@0.3.5)(optionator@0.9.4)(rollup@4.47.1)(terser@5.43.1)(typescript@5.9.2)(vue-tsc@2.2.12(typescript@5.9.2))(vue@3.5.19(typescript@5.9.2))(yaml@2.8.1)':
dependencies:
'@nuxt/kit': 4.0.3(magicast@0.3.5)
'@rollup/plugin-replace': 6.0.2(rollup@4.47.1)
@@ -5820,7 +5820,7 @@ snapshots:
'@vitejs/plugin-vue-jsx': 5.0.1(vite@7.1.3(@types/node@24.3.0)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))(vue@3.5.19(typescript@5.9.2))
autoprefixer: 10.4.21(postcss@8.5.6)
consola: 3.4.2
- cssnano: 7.1.0(postcss@8.5.6)
+ cssnano: 7.1.1(postcss@8.5.6)
defu: 6.1.4
esbuild: 0.25.9
escape-string-regexp: 5.0.0
@@ -5830,7 +5830,7 @@ snapshots:
jiti: 2.5.1
knitwork: 1.2.0
magic-string: 0.30.18
- mlly: 1.7.4
+ mlly: 1.8.0
mocked-exports: 0.1.1
pathe: 2.0.3
pkg-types: 2.3.0
@@ -5841,7 +5841,7 @@ snapshots:
unenv: 2.0.0-rc.19
vite: 7.1.3(@types/node@24.3.0)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)
vite-node: 3.2.4(@types/node@24.3.0)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)
- vite-plugin-checker: 0.10.2(eslint@9.33.0(jiti@2.5.1))(optionator@0.9.4)(typescript@5.9.2)(vite@7.1.3(@types/node@24.3.0)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))(vue-tsc@2.2.12(typescript@5.9.2))
+ vite-plugin-checker: 0.10.2(eslint@9.34.0(jiti@2.5.1))(optionator@0.9.4)(typescript@5.9.2)(vite@7.1.3(@types/node@24.3.0)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))(vue-tsc@2.2.12(typescript@5.9.2))
vue: 3.5.19(typescript@5.9.2)
vue-bundle-renderer: 2.1.2
transitivePeerDependencies:
@@ -6227,11 +6227,11 @@ snapshots:
'@speed-highlight/core@1.2.7': {}
- '@stylistic/eslint-plugin@5.2.3(eslint@9.33.0(jiti@2.5.1))':
+ '@stylistic/eslint-plugin@5.2.3(eslint@9.34.0(jiti@2.5.1))':
dependencies:
- '@eslint-community/eslint-utils': 4.7.0(eslint@9.33.0(jiti@2.5.1))
+ '@eslint-community/eslint-utils': 4.7.0(eslint@9.34.0(jiti@2.5.1))
'@typescript-eslint/types': 8.40.0
- eslint: 9.33.0(jiti@2.5.1)
+ eslint: 9.34.0(jiti@2.5.1)
eslint-visitor-keys: 4.2.1
espree: 10.4.0
estraverse: 5.3.0
@@ -6271,15 +6271,15 @@ snapshots:
'@types/node': 24.3.0
optional: true
- '@typescript-eslint/eslint-plugin@8.40.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)':
+ '@typescript-eslint/eslint-plugin@8.40.0(@typescript-eslint/parser@8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2)':
dependencies:
'@eslint-community/regexpp': 4.12.1
- '@typescript-eslint/parser': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)
+ '@typescript-eslint/parser': 8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2)
'@typescript-eslint/scope-manager': 8.40.0
- '@typescript-eslint/type-utils': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)
- '@typescript-eslint/utils': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)
+ '@typescript-eslint/type-utils': 8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2)
+ '@typescript-eslint/utils': 8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2)
'@typescript-eslint/visitor-keys': 8.40.0
- eslint: 9.33.0(jiti@2.5.1)
+ eslint: 9.34.0(jiti@2.5.1)
graphemer: 1.4.0
ignore: 7.0.5
natural-compare: 1.4.0
@@ -6288,14 +6288,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)':
+ '@typescript-eslint/parser@8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2)':
dependencies:
'@typescript-eslint/scope-manager': 8.40.0
'@typescript-eslint/types': 8.40.0
'@typescript-eslint/typescript-estree': 8.40.0(typescript@5.9.2)
'@typescript-eslint/visitor-keys': 8.40.0
debug: 4.4.1
- eslint: 9.33.0(jiti@2.5.1)
+ eslint: 9.34.0(jiti@2.5.1)
typescript: 5.9.2
transitivePeerDependencies:
- supports-color
@@ -6318,13 +6318,13 @@ snapshots:
dependencies:
typescript: 5.9.2
- '@typescript-eslint/type-utils@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)':
+ '@typescript-eslint/type-utils@8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2)':
dependencies:
'@typescript-eslint/types': 8.40.0
'@typescript-eslint/typescript-estree': 8.40.0(typescript@5.9.2)
- '@typescript-eslint/utils': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)
+ '@typescript-eslint/utils': 8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2)
debug: 4.4.1
- eslint: 9.33.0(jiti@2.5.1)
+ eslint: 9.34.0(jiti@2.5.1)
ts-api-utils: 2.1.0(typescript@5.9.2)
typescript: 5.9.2
transitivePeerDependencies:
@@ -6348,13 +6348,13 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/utils@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)':
+ '@typescript-eslint/utils@8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2)':
dependencies:
- '@eslint-community/eslint-utils': 4.7.0(eslint@9.33.0(jiti@2.5.1))
+ '@eslint-community/eslint-utils': 4.7.0(eslint@9.34.0(jiti@2.5.1))
'@typescript-eslint/scope-manager': 8.40.0
'@typescript-eslint/types': 8.40.0
'@typescript-eslint/typescript-estree': 8.40.0(typescript@5.9.2)
- eslint: 9.33.0(jiti@2.5.1)
+ eslint: 9.34.0(jiti@2.5.1)
typescript: 5.9.2
transitivePeerDependencies:
- supports-color
@@ -6470,7 +6470,7 @@ snapshots:
'@types/chai': 5.2.2
'@vitest/spy': 3.2.4
'@vitest/utils': 3.2.4
- chai: 5.3.2
+ chai: 5.3.3
tinyrainbow: 2.0.0
'@vitest/mocker@3.2.4(vite@7.1.3(@types/node@24.3.0)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))':
@@ -6906,7 +6906,7 @@ snapshots:
caniuse-lite@1.0.30001737: {}
- chai@5.3.2:
+ chai@5.3.3:
dependencies:
assertion-error: 2.0.1
check-error: 2.1.1
@@ -7106,7 +7106,7 @@ snapshots:
cssesc@3.0.0: {}
- cssnano-preset-default@7.0.8(postcss@8.5.6):
+ cssnano-preset-default@7.0.9(postcss@8.5.6):
dependencies:
browserslist: 4.25.3
css-declaration-sorter: 7.2.0(postcss@8.5.6)
@@ -7114,7 +7114,7 @@ snapshots:
postcss: 8.5.6
postcss-calc: 10.1.1(postcss@8.5.6)
postcss-colormin: 7.0.4(postcss@8.5.6)
- postcss-convert-values: 7.0.6(postcss@8.5.6)
+ postcss-convert-values: 7.0.7(postcss@8.5.6)
postcss-discard-comments: 7.0.4(postcss@8.5.6)
postcss-discard-duplicates: 7.0.2(postcss@8.5.6)
postcss-discard-empty: 7.0.1(postcss@8.5.6)
@@ -7144,9 +7144,9 @@ snapshots:
dependencies:
postcss: 8.5.6
- cssnano@7.1.0(postcss@8.5.6):
+ cssnano@7.1.1(postcss@8.5.6):
dependencies:
- cssnano-preset-default: 7.0.8(postcss@8.5.6)
+ cssnano-preset-default: 7.0.9(postcss@8.5.6)
lilconfig: 3.1.3
postcss: 8.5.6
@@ -7404,10 +7404,10 @@ snapshots:
optionalDependencies:
source-map: 0.6.1
- eslint-config-flat-gitignore@2.1.0(eslint@9.33.0(jiti@2.5.1)):
+ eslint-config-flat-gitignore@2.1.0(eslint@9.34.0(jiti@2.5.1)):
dependencies:
- '@eslint/compat': 1.3.2(eslint@9.33.0(jiti@2.5.1))
- eslint: 9.33.0(jiti@2.5.1)
+ '@eslint/compat': 1.3.2(eslint@9.34.0(jiti@2.5.1))
+ eslint: 9.34.0(jiti@2.5.1)
eslint-flat-config-utils@2.1.1:
dependencies:
@@ -7420,24 +7420,24 @@ snapshots:
optionalDependencies:
unrs-resolver: 1.11.1
- eslint-merge-processors@2.0.0(eslint@9.33.0(jiti@2.5.1)):
+ eslint-merge-processors@2.0.0(eslint@9.34.0(jiti@2.5.1)):
dependencies:
- eslint: 9.33.0(jiti@2.5.1)
+ eslint: 9.34.0(jiti@2.5.1)
- eslint-plugin-import-lite@0.3.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2):
+ eslint-plugin-import-lite@0.3.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2):
dependencies:
- '@eslint-community/eslint-utils': 4.7.0(eslint@9.33.0(jiti@2.5.1))
+ '@eslint-community/eslint-utils': 4.7.0(eslint@9.34.0(jiti@2.5.1))
'@typescript-eslint/types': 8.40.0
- eslint: 9.33.0(jiti@2.5.1)
+ eslint: 9.34.0(jiti@2.5.1)
optionalDependencies:
typescript: 5.9.2
- eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1)):
+ eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.34.0(jiti@2.5.1)):
dependencies:
'@typescript-eslint/types': 8.40.0
comment-parser: 1.4.1
debug: 4.4.1
- eslint: 9.33.0(jiti@2.5.1)
+ eslint: 9.34.0(jiti@2.5.1)
eslint-import-context: 0.1.9(unrs-resolver@1.11.1)
is-glob: 4.0.3
minimatch: 10.0.3
@@ -7445,18 +7445,18 @@ snapshots:
stable-hash-x: 0.2.0
unrs-resolver: 1.11.1
optionalDependencies:
- '@typescript-eslint/utils': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)
+ '@typescript-eslint/utils': 8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2)
transitivePeerDependencies:
- supports-color
- eslint-plugin-jsdoc@54.1.1(eslint@9.33.0(jiti@2.5.1)):
+ eslint-plugin-jsdoc@54.1.1(eslint@9.34.0(jiti@2.5.1)):
dependencies:
'@es-joy/jsdoccomment': 0.53.0
are-docs-informative: 0.0.2
comment-parser: 1.4.1
debug: 4.4.1
escape-string-regexp: 4.0.0
- eslint: 9.33.0(jiti@2.5.1)
+ eslint: 9.34.0(jiti@2.5.1)
espree: 10.4.0
esquery: 1.6.0
parse-imports-exports: 0.2.4
@@ -7465,27 +7465,27 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-plugin-regexp@2.10.0(eslint@9.33.0(jiti@2.5.1)):
+ eslint-plugin-regexp@2.10.0(eslint@9.34.0(jiti@2.5.1)):
dependencies:
- '@eslint-community/eslint-utils': 4.7.0(eslint@9.33.0(jiti@2.5.1))
+ '@eslint-community/eslint-utils': 4.7.0(eslint@9.34.0(jiti@2.5.1))
'@eslint-community/regexpp': 4.12.1
comment-parser: 1.4.1
- eslint: 9.33.0(jiti@2.5.1)
+ eslint: 9.34.0(jiti@2.5.1)
jsdoc-type-pratt-parser: 4.8.0
refa: 0.12.1
regexp-ast-analysis: 0.7.1
scslre: 0.3.0
- eslint-plugin-unicorn@60.0.0(eslint@9.33.0(jiti@2.5.1)):
+ eslint-plugin-unicorn@60.0.0(eslint@9.34.0(jiti@2.5.1)):
dependencies:
'@babel/helper-validator-identifier': 7.27.1
- '@eslint-community/eslint-utils': 4.7.0(eslint@9.33.0(jiti@2.5.1))
+ '@eslint-community/eslint-utils': 4.7.0(eslint@9.34.0(jiti@2.5.1))
'@eslint/plugin-kit': 0.3.5
change-case: 5.4.4
ci-info: 4.3.0
clean-regexp: 1.0.0
core-js-compat: 3.45.1
- eslint: 9.33.0(jiti@2.5.1)
+ eslint: 9.34.0(jiti@2.5.1)
esquery: 1.6.0
find-up-simple: 1.0.1
globals: 16.3.0
@@ -7498,32 +7498,32 @@ snapshots:
semver: 7.7.2
strip-indent: 4.0.0
- eslint-plugin-vue@10.4.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1))(vue-eslint-parser@10.2.0(eslint@9.33.0(jiti@2.5.1))):
+ eslint-plugin-vue@10.4.0(@typescript-eslint/parser@8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.34.0(jiti@2.5.1))(vue-eslint-parser@10.2.0(eslint@9.34.0(jiti@2.5.1))):
dependencies:
- '@eslint-community/eslint-utils': 4.7.0(eslint@9.33.0(jiti@2.5.1))
- eslint: 9.33.0(jiti@2.5.1)
+ '@eslint-community/eslint-utils': 4.7.0(eslint@9.34.0(jiti@2.5.1))
+ eslint: 9.34.0(jiti@2.5.1)
natural-compare: 1.4.0
nth-check: 2.1.1
postcss-selector-parser: 6.1.2
semver: 7.7.2
- vue-eslint-parser: 10.2.0(eslint@9.33.0(jiti@2.5.1))
+ vue-eslint-parser: 10.2.0(eslint@9.34.0(jiti@2.5.1))
xml-name-validator: 4.0.0
optionalDependencies:
- '@typescript-eslint/parser': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)
+ '@typescript-eslint/parser': 8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2)
- eslint-processor-vue-blocks@2.0.0(@vue/compiler-sfc@3.5.19)(eslint@9.33.0(jiti@2.5.1)):
+ eslint-processor-vue-blocks@2.0.0(@vue/compiler-sfc@3.5.19)(eslint@9.34.0(jiti@2.5.1)):
dependencies:
'@vue/compiler-sfc': 3.5.19
- eslint: 9.33.0(jiti@2.5.1)
+ eslint: 9.34.0(jiti@2.5.1)
eslint-scope@8.4.0:
dependencies:
esrecurse: 4.3.0
estraverse: 5.3.0
- eslint-typegen@2.3.0(eslint@9.33.0(jiti@2.5.1)):
+ eslint-typegen@2.3.0(eslint@9.34.0(jiti@2.5.1)):
dependencies:
- eslint: 9.33.0(jiti@2.5.1)
+ eslint: 9.34.0(jiti@2.5.1)
json-schema-to-typescript-lite: 15.0.0
ohash: 2.0.11
@@ -7531,15 +7531,15 @@ snapshots:
eslint-visitor-keys@4.2.1: {}
- eslint@9.33.0(jiti@2.5.1):
+ eslint@9.34.0(jiti@2.5.1):
dependencies:
- '@eslint-community/eslint-utils': 4.7.0(eslint@9.33.0(jiti@2.5.1))
+ '@eslint-community/eslint-utils': 4.7.0(eslint@9.34.0(jiti@2.5.1))
'@eslint-community/regexpp': 4.12.1
'@eslint/config-array': 0.21.0
'@eslint/config-helpers': 0.3.1
'@eslint/core': 0.15.2
'@eslint/eslintrc': 3.3.1
- '@eslint/js': 9.33.0
+ '@eslint/js': 9.34.0
'@eslint/plugin-kit': 0.3.5
'@humanfs/node': 0.16.6
'@humanwhocodes/module-importer': 1.0.1
@@ -7698,7 +7698,7 @@ snapshots:
fix-dts-default-cjs-exports@1.0.1:
dependencies:
magic-string: 0.30.18
- mlly: 1.7.4
+ mlly: 1.8.0
rollup: 4.47.1
flat-cache@4.0.1:
@@ -8102,7 +8102,7 @@ snapshots:
h3: 1.15.4
http-shutdown: 1.2.2
jiti: 2.5.1
- mlly: 1.7.4
+ mlly: 1.8.0
node-forge: 1.3.1
pathe: 1.1.2
std-env: 3.9.0
@@ -8114,7 +8114,7 @@ snapshots:
local-pkg@1.1.2:
dependencies:
- mlly: 1.7.4
+ mlly: 1.8.0
pkg-types: 2.3.0
quansync: 0.2.11
@@ -8165,7 +8165,7 @@ snapshots:
dependencies:
estree-walker: 3.0.3
magic-string: 0.30.18
- mlly: 1.7.4
+ mlly: 1.8.0
regexp-tree: 0.1.27
type-level-regexp: 0.1.17
ufo: 1.6.1
@@ -8252,11 +8252,11 @@ snapshots:
dependencies:
autoprefixer: 10.4.21(postcss@8.5.6)
citty: 0.1.6
- cssnano: 7.1.0(postcss@8.5.6)
+ cssnano: 7.1.1(postcss@8.5.6)
defu: 6.1.4
esbuild: 0.25.9
jiti: 1.21.7
- mlly: 1.7.4
+ mlly: 1.8.0
pathe: 2.0.3
pkg-types: 2.3.0
postcss: 8.5.6
@@ -8269,7 +8269,7 @@ snapshots:
vue-sfc-transformer: 0.1.16(@vue/compiler-core@3.5.19)(esbuild@0.25.9)(vue@3.5.19(typescript@5.9.2))
vue-tsc: 2.2.12(typescript@5.9.2)
- mlly@1.7.4:
+ mlly@1.8.0:
dependencies:
acorn: 8.15.0
pathe: 2.0.3
@@ -8353,7 +8353,7 @@ snapshots:
magic-string: 0.30.18
magicast: 0.3.5
mime: 4.0.7
- mlly: 1.7.4
+ mlly: 1.8.0
node-fetch-native: 1.6.7
node-mock-http: 1.0.2
ofetch: 1.4.1
@@ -8469,7 +8469,7 @@ snapshots:
dependencies:
boolbase: 1.0.0
- nuxt@4.0.3(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@24.3.0)(@vue/compiler-sfc@3.5.19)(db0@0.3.2)(eslint@9.33.0(jiti@2.5.1))(ioredis@5.7.0)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.47.1)(terser@5.43.1)(typescript@5.9.2)(vite@7.1.3(@types/node@24.3.0)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))(vue-tsc@2.2.12(typescript@5.9.2))(yaml@2.8.1):
+ nuxt@4.0.3(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@24.3.0)(@vue/compiler-sfc@3.5.19)(db0@0.3.2)(eslint@9.34.0(jiti@2.5.1))(ioredis@5.7.0)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.47.1)(terser@5.43.1)(typescript@5.9.2)(vite@7.1.3(@types/node@24.3.0)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))(vue-tsc@2.2.12(typescript@5.9.2))(yaml@2.8.1):
dependencies:
'@nuxt/cli': 3.28.0(magicast@0.3.5)
'@nuxt/devalue': 2.0.2
@@ -8477,7 +8477,7 @@ snapshots:
'@nuxt/kit': 4.0.3(magicast@0.3.5)
'@nuxt/schema': 4.0.3
'@nuxt/telemetry': 2.6.6(magicast@0.3.5)
- '@nuxt/vite-builder': 4.0.3(@types/node@24.3.0)(eslint@9.33.0(jiti@2.5.1))(magicast@0.3.5)(optionator@0.9.4)(rollup@4.47.1)(terser@5.43.1)(typescript@5.9.2)(vue-tsc@2.2.12(typescript@5.9.2))(vue@3.5.19(typescript@5.9.2))(yaml@2.8.1)
+ '@nuxt/vite-builder': 4.0.3(@types/node@24.3.0)(eslint@9.34.0(jiti@2.5.1))(magicast@0.3.5)(optionator@0.9.4)(rollup@4.47.1)(terser@5.43.1)(typescript@5.9.2)(vue-tsc@2.2.12(typescript@5.9.2))(vue@3.5.19(typescript@5.9.2))(yaml@2.8.1)
'@unhead/vue': 2.0.14(vue@3.5.19(typescript@5.9.2))
'@vue/shared': 3.5.19
c12: 3.2.0(magicast@0.3.5)
@@ -8501,7 +8501,7 @@ snapshots:
klona: 2.0.6
knitwork: 1.2.0
magic-string: 0.30.18
- mlly: 1.7.4
+ mlly: 1.8.0
mocked-exports: 0.1.1
nanotar: 0.2.0
nitropack: 2.12.4(@netlify/blobs@9.1.2)
@@ -8811,7 +8811,7 @@ snapshots:
pkg-types@1.3.1:
dependencies:
confbox: 0.1.8
- mlly: 1.7.4
+ mlly: 1.8.0
pathe: 2.0.3
pkg-types@2.3.0:
@@ -8836,7 +8836,7 @@ snapshots:
postcss: 8.5.6
postcss-value-parser: 4.2.0
- postcss-convert-values@7.0.6(postcss@8.5.6):
+ postcss-convert-values@7.0.7(postcss@8.5.6):
dependencies:
browserslist: 4.25.3
postcss: 8.5.6
@@ -9562,7 +9562,7 @@ snapshots:
jiti: 2.5.1
magic-string: 0.30.18
mkdist: 2.3.0(typescript@5.9.2)(vue-sfc-transformer@0.1.16(@vue/compiler-core@3.5.19)(esbuild@0.25.9)(vue@3.5.19(typescript@5.9.2)))(vue-tsc@2.2.12(typescript@5.9.2))(vue@3.5.19(typescript@5.9.2))
- mlly: 1.7.4
+ mlly: 1.8.0
pathe: 2.0.3
pkg-types: 2.3.0
pretty-bytes: 7.0.1
@@ -9613,7 +9613,7 @@ snapshots:
estree-walker: 3.0.3
local-pkg: 1.1.2
magic-string: 0.30.18
- mlly: 1.7.4
+ mlly: 1.8.0
pathe: 2.0.3
picomatch: 4.0.3
pkg-types: 2.3.0
@@ -9642,7 +9642,7 @@ snapshots:
json5: 2.2.3
local-pkg: 1.1.2
magic-string: 0.30.18
- mlly: 1.7.4
+ mlly: 1.8.0
muggle-string: 0.4.1
pathe: 2.0.3
picomatch: 4.0.3
@@ -9721,7 +9721,7 @@ snapshots:
dependencies:
knitwork: 1.2.0
magic-string: 0.30.18
- mlly: 1.7.4
+ mlly: 1.8.0
pathe: 2.0.3
pkg-types: 2.3.0
unplugin: 2.3.8
@@ -9782,7 +9782,7 @@ snapshots:
- tsx
- yaml
- vite-plugin-checker@0.10.2(eslint@9.33.0(jiti@2.5.1))(optionator@0.9.4)(typescript@5.9.2)(vite@7.1.3(@types/node@24.3.0)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))(vue-tsc@2.2.12(typescript@5.9.2)):
+ vite-plugin-checker@0.10.2(eslint@9.34.0(jiti@2.5.1))(optionator@0.9.4)(typescript@5.9.2)(vite@7.1.3(@types/node@24.3.0)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))(vue-tsc@2.2.12(typescript@5.9.2)):
dependencies:
'@babel/code-frame': 7.27.1
chokidar: 4.0.3
@@ -9795,7 +9795,7 @@ snapshots:
vite: 7.1.3(@types/node@24.3.0)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)
vscode-uri: 3.1.0
optionalDependencies:
- eslint: 9.33.0(jiti@2.5.1)
+ eslint: 9.34.0(jiti@2.5.1)
optionator: 0.9.4
typescript: 5.9.2
vue-tsc: 2.2.12(typescript@5.9.2)
@@ -9869,7 +9869,7 @@ snapshots:
'@vitest/snapshot': 3.2.4
'@vitest/spy': 3.2.4
'@vitest/utils': 3.2.4
- chai: 5.3.2
+ chai: 5.3.3
debug: 4.4.1
expect-type: 1.2.2
magic-string: 0.30.18
@@ -9908,10 +9908,10 @@ snapshots:
vue-devtools-stub@0.1.0: {}
- vue-eslint-parser@10.2.0(eslint@9.33.0(jiti@2.5.1)):
+ vue-eslint-parser@10.2.0(eslint@9.34.0(jiti@2.5.1)):
dependencies:
debug: 4.4.1
- eslint: 9.33.0(jiti@2.5.1)
+ eslint: 9.34.0(jiti@2.5.1)
eslint-scope: 8.4.0
eslint-visitor-keys: 4.2.1
espree: 10.4.0
diff --git a/src/runtime/composables/useFeatureFlag.ts b/src/runtime/composables/useFeatureFlag.ts
index e500d40..d5065e9 100644
--- a/src/runtime/composables/useFeatureFlag.ts
+++ b/src/runtime/composables/useFeatureFlag.ts
@@ -1,10 +1,11 @@
import type { FeatureFlagsConfig, FeatureFlag, FeatureFlagInput } from '../../../types/feature-flags'
import { isFlagActiveNow } from '../utils/isFlagActiveNow'
+import { matchFlag } from '../utils/matchFlag'
import { useRuntimeConfig } from '#imports'
/**
* Provides runtime access to feature flag utilities within the client app.
- * Supports both static and scheduled flags.
+ * Supports static flags, scheduled flags, and hierarchical paths with `*` wildcards for grouped checks.
*
* @returns An object with utilities:
* - `isEnabled(flagName)` — checks if a feature is currently active
@@ -12,7 +13,7 @@ import { useRuntimeConfig } from '#imports'
*/
export const useFeatureFlag = () => {
const config: FeatureFlagsConfig = useRuntimeConfig().public.featureFlags
- const env = config.environment
+ const env: string = config.environment
const flags: FeatureFlagInput[] = config.flagSets?.[env] || []
/**
@@ -20,14 +21,25 @@ export const useFeatureFlag = () => {
* Supports:
* - Static string flags
* - Scheduled flags (with `activeFrom` and/or `activeUntil`)
+ * - Hierarchical paths and `*` wildcards declared in the flag set
+ *
+ * Wildcard queries only return `true` if the wildcard itself is enabled
+ * (e.g. checking `solutions/*` requires that `solutions/*` exists in the
+ * current flag set).
*
* @param flagName - The name of the feature flag to check.
* @returns `true` if the feature is currently enabled, otherwise `false`.
*/
const isEnabled = (flagName: string): boolean => {
for (const flag of flags) {
- if (typeof flag === 'string' && flag === flagName) return true
- if (typeof flag === 'object' && flag.name === flagName && isFlagActiveNow(flag)) return true
+ const name: string = typeof flag === 'string' ? flag : flag.name
+ if (!matchFlag(name, flagName)) continue
+ if (typeof flag === 'object') {
+ if (isFlagActiveNow(flag)) return true
+ }
+ else {
+ return true
+ }
}
return false
}
diff --git a/src/runtime/server/isFeatureEnabled.ts b/src/runtime/server/isFeatureEnabled.ts
index 901022e..ac899ee 100644
--- a/src/runtime/server/isFeatureEnabled.ts
+++ b/src/runtime/server/isFeatureEnabled.ts
@@ -1,14 +1,19 @@
import type { H3Event } from 'h3'
import { isFlagActiveNow } from '../utils/isFlagActiveNow'
+import { matchFlag } from '../utils/matchFlag'
import { useRuntimeConfig } from '#imports'
import type { FeatureFlagInput, FeatureFlagsConfig } from '~/types/feature-flags'
/**
* Server-side utility to check if a feature flag is currently enabled.
- * Supports both string and scheduled flags.
+ * Supports string flags, scheduled flags, and wildcard groups declared in the
+ * flag set (e.g. `solutions/*`).
*
* Intended for use in server routes (`server/api/**`) or middleware.
*
+ * Wildcard queries only return `true` if the wildcard itself is enabled in the
+ * active flag set.
+ *
* @param feature - The name of the feature flag to check.
* @param event - Optional H3 event context (used to access runtime config).
* @returns `true` if the feature is currently enabled in the active environment.
@@ -26,8 +31,15 @@ export const isFeatureEnabled = (feature: string, event?: H3Event): boolean => {
const flags: FeatureFlagInput[] = config.flagSets?.[env] || []
for (const flag of flags) {
- if (typeof flag === 'string' && flag === feature) return true
- if (typeof flag === 'object' && flag.name === feature && isFlagActiveNow(flag)) return true
+ const name: string = typeof flag === 'string' ? flag : flag.name
+ if (!matchFlag(name, feature)) continue
+
+ if (typeof flag === 'object') {
+ if (isFlagActiveNow(flag)) return true
+ }
+ else {
+ return true
+ }
}
return false
diff --git a/src/runtime/utils/flagValidator.ts b/src/runtime/utils/flagValidator.ts
index 5d267b9..1be6726 100644
--- a/src/runtime/utils/flagValidator.ts
+++ b/src/runtime/utils/flagValidator.ts
@@ -2,10 +2,12 @@ import { readFile } from 'node:fs/promises'
import { globby } from 'globby'
import { resolve as resolvePath } from 'pathe'
import type { FeatureFlagsConfig } from '../../../types/feature-flags'
+import { matchFlag } from './matchFlag'
/**
* Validates that all feature flags used in the source code are declared
* in at least one environment of the FeatureFlagsConfig.
+ * Handles hierarchical flag paths and `*` wildcards, allowing patterns such as `solutions/*` in both declarations and usage.
*
* Supports customizing:
* - `validation.mode` (disabled | warn | error)
@@ -56,20 +58,20 @@ export async function validateFeatureFlags(
// 4. Prepare regex patterns (allow letters, digits, underscores, hyphens, dots)
const regexes: RegExp[] = [
// Matches: v-feature="flagName", v-feature='flagName', v-feature="'flagName'"
- /v-feature\s*=\s*["']\s*'?([\w.-]+)'?\s*["']/g,
+ /v-feature\s*=\s*["']\s*'?([\w./*-]+)'?\s*["']/g,
// Matches: isEnabled('flagName') or isEnabled("flagName")
- /\bisEnabled\(\s*['"]([\w.-]+)['"]\s*\)/g,
+ /\bisEnabled\(\s*['"]([\w./*-]+)['"]\s*\)/g,
// Matches: defineFeatureFlagMiddleware('flagName') or defineFeatureFlagMiddleware("flagName")
- /\bdefineFeatureFlagMiddleware\(\s*['"]([\w.-]+)['"]\s*\)/g,
+ /\bdefineFeatureFlagMiddleware\(\s*['"]([\w./*-]+)['"]\s*\)/g,
]
// 5. Read & scan each file for literal flag usage
interface FileContext { path: string, content: string }
const contexts: FileContext[] = []
- await Promise.all(allFiles.map(async (relativePath) => {
+ await Promise.all(allFiles.map(async (relativePath): Promise => {
const absolutePath: string = resolvePath(rootDir, relativePath)
try {
const content: string = await readFile(absolutePath, 'utf-8')
@@ -91,7 +93,11 @@ export async function validateFeatureFlags(
for (const regex of regexes) {
let match: RegExpExecArray | null
while ((match = regex.exec(content)) !== null) {
- const flagName: string = match[1] // capture group 1 is always the flag
+ const captured: string | undefined = match[1]
+ if (typeof captured !== 'string' || captured.length === 0) {
+ continue
+ }
+ const flagName: string = captured // capture group 1 is always the flag
// Calculate line/column from match.index
const beforeMatch: string = content.slice(0, match.index)
const lineNumber: number = beforeMatch.split('\n').length
@@ -111,17 +117,21 @@ export async function validateFeatureFlags(
}
// 7. Build Set of all declared flags (across all flags in all flagSets)
- const declaredFlags: Set = new Set()
- for (const envFlags of Object.values(options.flagSets)) {
+ const declaredFlags: string[] = []
+ for (const [env, envFlags] of Object.entries(options.flagSets)) {
for (const item of envFlags || []) {
const name: string = typeof item === 'string' ? item : item.name
- declaredFlags.add(name)
+ declaredFlags.push(name)
+ if (name === '*') {
+ console.warn(`[nuxt-feature-flags] Environment "${env}" uses "*" which enables all flags and should only be used for debugging.`)
+ }
}
}
// 8. Compare used vs. declared, emit warnings/errors with context
for (const info of usedFlagsInfo) {
- if (!declaredFlags.has(info.name)) {
+ const declared: boolean = declaredFlags.some(pattern => matchFlag(pattern, info.name))
+ if (!declared) {
const location: string = `${info.file}:${info.line}:${info.column}`
const message: string
= `[nuxt-feature-flags] ${location} → Flag "${info.name}" `
diff --git a/src/runtime/utils/matchFlag.ts b/src/runtime/utils/matchFlag.ts
new file mode 100644
index 0000000..49dc50e
--- /dev/null
+++ b/src/runtime/utils/matchFlag.ts
@@ -0,0 +1,24 @@
+/**
+ * Checks whether a feature flag name matches a given pattern.
+ *
+ * Both `pattern` and `value` support hierarchical segments separated by `/`.
+ * Only the `pattern` may end in a trailing `*` to match any descendant nodes
+ * (e.g. `solutions/*`). The `value` is always treated as a concrete flag name.
+ *
+ * @param pattern - The flag or wildcard pattern to test against.
+ * @param value - The concrete flag name being evaluated.
+ * @returns `true` if the pattern matches the value, otherwise `false`.
+ */
+export const matchFlag = (pattern: string, value: string): boolean => {
+ if (pattern === '*' || value === '*') return true
+
+ const patternIsWildcard: boolean = pattern.endsWith('/*')
+
+ const patternBase: string = patternIsWildcard ? pattern.slice(0, -1) : pattern
+
+ if (patternIsWildcard) {
+ return value.startsWith(patternBase)
+ }
+
+ return pattern === value
+}
diff --git a/test/unit/flagValidator.test.ts b/test/unit/flagValidator.test.ts
index 1660833..b515d8f 100644
--- a/test/unit/flagValidator.test.ts
+++ b/test/unit/flagValidator.test.ts
@@ -6,17 +6,17 @@ import { validateFeatureFlags } from '../../src/runtime/utils/flagValidator'
let dir: string
-beforeEach(async () => {
+beforeEach(async (): Promise => {
dir = await mkdtemp(join(tmpdir(), 'ff-'))
})
-afterEach(async () => {
+afterEach(async (): Promise => {
// cleanup automatically by tmpdir removal on container end
})
-describe('flagValidator', () => {
- it('warns on missing flags in warn mode', async () => {
- const file = join(dir, 'test.ts')
+describe('flagValidator', (): void => {
+ it('warns on missing flags in warn mode', async (): Promise => {
+ const file: string = join(dir, 'test.ts')
await writeFile(file, 'isEnabled(\'missing\')')
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
await validateFeatureFlags({
@@ -28,8 +28,8 @@ describe('flagValidator', () => {
warnSpy.mockRestore()
})
- it('throws on missing flags in error mode', async () => {
- const file = join(dir, 'test2.ts')
+ it('throws on missing flags in error mode', async (): Promise => {
+ const file: string = join(dir, 'test2.ts')
await writeFile(file, 'isEnabled(\'missing\')')
await expect(validateFeatureFlags({
environment: 'prod',
@@ -37,4 +37,28 @@ describe('flagValidator', () => {
validation: { mode: 'error', includeGlobs: ['**/*.ts'], excludeGlobs: [] },
}, dir)).rejects.toThrow()
})
+
+ it('supports wildcard declarations', async (): Promise => {
+ const file: string = join(dir, 'wild.ts')
+ await writeFile(file, 'isEnabled(\'solutions/company-portal/addons/sales\')')
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation((): void => {})
+ await validateFeatureFlags({
+ environment: 'prod',
+ flagSets: { prod: ['solutions/*'] },
+ validation: { mode: 'warn', includeGlobs: ['**/*.ts'], excludeGlobs: [] },
+ }, dir)
+ expect(warnSpy).not.toHaveBeenCalled()
+ warnSpy.mockRestore()
+ })
+
+ it('warns when root wildcard is used', async (): Promise => {
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation((): void => {})
+ await validateFeatureFlags({
+ environment: 'prod',
+ flagSets: { prod: ['*'] },
+ validation: { mode: 'warn', includeGlobs: ['**/*.ts'], excludeGlobs: [] },
+ }, dir)
+ expect(warnSpy).toHaveBeenCalled()
+ warnSpy.mockRestore()
+ })
})
diff --git a/test/unit/isFeatureEnabled.test.ts b/test/unit/isFeatureEnabled.test.ts
index 443be0c..89f7bd3 100644
--- a/test/unit/isFeatureEnabled.test.ts
+++ b/test/unit/isFeatureEnabled.test.ts
@@ -12,7 +12,7 @@ vi.mock('#imports', () => ({
useRuntimeConfig: () => runtimeConfig,
}))
-beforeEach(() => {
+beforeEach((): void => {
runtimeConfig = {
featureFlags: {
environment: 'prod',
@@ -23,15 +23,15 @@ beforeEach(() => {
}
})
-describe('isFeatureEnabled', () => {
- it('checks simple string flags', () => {
+describe('isFeatureEnabled', (): void => {
+ it('checks simple string flags', (): void => {
runtimeConfig.featureFlags.flagSets.prod = ['flagA']
expect(isFeatureEnabled('flagA')).toBe(true)
expect(isFeatureEnabled('unknown')).toBe(false)
})
- it('evaluates scheduled flags', () => {
- const now = new Date('2024-06-01T12:00:00Z')
+ it('evaluates scheduled flags', (): void => {
+ const now: Date = new Date('2024-06-01T12:00:00Z')
vi.useFakeTimers()
vi.setSystemTime(now)
@@ -43,8 +43,8 @@ describe('isFeatureEnabled', () => {
vi.useRealTimers()
})
- it('returns false when flag is inactive', () => {
- const now = new Date('2024-08-01T12:00:00Z')
+ it('returns false when flag is inactive', (): void => {
+ const now: Date = new Date('2024-08-01T12:00:00Z')
vi.useFakeTimers()
vi.setSystemTime(now)
@@ -55,4 +55,16 @@ describe('isFeatureEnabled', () => {
expect(isFeatureEnabled('scheduled')).toBe(false)
vi.useRealTimers()
})
+
+ it('supports hierarchical wildcard patterns', (): void => {
+ runtimeConfig.featureFlags.flagSets.prod = ['solutions/*']
+ expect(isFeatureEnabled('solutions/company-portal/addons/sales')).toBe(true)
+ expect(isFeatureEnabled('solutions/*')).toBe(true)
+
+ runtimeConfig.featureFlags.flagSets.prod = ['solutions/company-portal/addons/sales']
+ expect(isFeatureEnabled('solutions/*')).toBe(false)
+
+ runtimeConfig.featureFlags.flagSets.prod = ['*']
+ expect(isFeatureEnabled('any/flag')).toBe(true)
+ })
})
diff --git a/test/unit/useFeatureFlag.test.ts b/test/unit/useFeatureFlag.test.ts
index a8d04e0..2214ac1 100644
--- a/test/unit/useFeatureFlag.test.ts
+++ b/test/unit/useFeatureFlag.test.ts
@@ -14,7 +14,7 @@ vi.mock('#imports', () => ({
useRuntimeConfig: () => runtimeConfig,
}))
-beforeEach(() => {
+beforeEach((): void => {
runtimeConfig = {
public: {
featureFlags: {
@@ -27,16 +27,16 @@ beforeEach(() => {
}
})
-describe('useFeatureFlag', () => {
- it('detects static flags', () => {
+describe('useFeatureFlag', (): void => {
+ it('detects static flags', (): void => {
runtimeConfig.public.featureFlags.flagSets.prod = ['flagA']
const { isEnabled } = useFeatureFlag()
expect(isEnabled('flagA')).toBe(true)
expect(isEnabled('unknown')).toBe(false)
})
- it('handles scheduled flags', () => {
- const now = new Date('2024-06-01T12:00:00Z')
+ it('handles scheduled flags', (): void => {
+ const now: Date = new Date('2024-06-01T12:00:00Z')
vi.useFakeTimers()
vi.setSystemTime(now)
@@ -51,8 +51,8 @@ describe('useFeatureFlag', () => {
vi.useRealTimers()
})
- it('filters out inactive scheduled flags', () => {
- const now = new Date('2024-04-01T12:00:00Z')
+ it('filters out inactive scheduled flags', (): void => {
+ const now: Date = new Date('2024-04-01T12:00:00Z')
vi.useFakeTimers()
vi.setSystemTime(now)
@@ -66,4 +66,19 @@ describe('useFeatureFlag', () => {
vi.useRealTimers()
})
+
+ it('supports hierarchical wildcard patterns', (): void => {
+ runtimeConfig.public.featureFlags.flagSets.prod = ['solutions/*']
+ let utils = useFeatureFlag()
+ expect(utils.isEnabled('solutions/company-portal/addons/sales')).toBe(true)
+ expect(utils.isEnabled('solutions/*')).toBe(true)
+
+ runtimeConfig.public.featureFlags.flagSets.prod = ['solutions/company-portal/addons/sales']
+ utils = useFeatureFlag()
+ expect(utils.isEnabled('solutions/*')).toBe(false)
+
+ runtimeConfig.public.featureFlags.flagSets.prod = ['*']
+ utils = useFeatureFlag()
+ expect(utils.isEnabled('whatever')).toBe(true)
+ })
})
diff --git a/types/feature-flags.d.ts b/types/feature-flags.d.ts
index f6b6e5d..bf5107c 100644
--- a/types/feature-flags.d.ts
+++ b/types/feature-flags.d.ts
@@ -1,6 +1,15 @@
+/**
+ * A feature flag definition which may be a simple string or an object with
+ * scheduling metadata. Flag names support hierarchical segments separated by
+ * `/` and may include `*` wildcards for pattern matching (e.g. `section/*`).
+ */
export type FeatureFlagInput = string | FeatureFlag
export interface FeatureFlag {
+ /**
+ * Unique name of the feature flag. May include `/` segments and end with
+ * `/*` to represent a wildcard group.
+ */
name: string
activeFrom?: string
activeUntil?: string
@@ -28,7 +37,8 @@ export interface FeatureFlagsConfig {
environment: string
/**
- * A record mapping each environment to an array of flags (string or { name, activeFrom, activeUntil }).
+ * A record mapping each environment to an array of flags (string or `{ name, activeFrom, activeUntil }`).
+ * Flag names may leverage hierarchical paths and `*` wildcards for grouped enablement.
*/
flagSets: Record