@ops-ai/astro-feature-flags-toggly 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +23 -0
- package/README.md +462 -0
- package/dist/chunk-354E3C57.js +173 -0
- package/dist/chunk-354E3C57.js.map +1 -0
- package/dist/chunk-4UKIT2NP.js +44 -0
- package/dist/chunk-4UKIT2NP.js.map +1 -0
- package/dist/chunk-FK633ULF.js +200 -0
- package/dist/chunk-FK633ULF.js.map +1 -0
- package/dist/chunk-XCJSQJHR.js +74 -0
- package/dist/chunk-XCJSQJHR.js.map +1 -0
- package/dist/chunk-XQGKGTBK.js +161 -0
- package/dist/chunk-XQGKGTBK.js.map +1 -0
- package/dist/client/setup.d.ts +3 -0
- package/dist/client/setup.js +21 -0
- package/dist/client/setup.js.map +1 -0
- package/dist/client/store.d.ts +59 -0
- package/dist/client/store.js +25 -0
- package/dist/client/store.js.map +1 -0
- package/dist/components/Feature.astro +79 -0
- package/dist/components/FeatureClient.astro +144 -0
- package/dist/frameworks/react/Feature.d.ts +86 -0
- package/dist/frameworks/react/Feature.js +14 -0
- package/dist/frameworks/react/Feature.js.map +1 -0
- package/dist/frameworks/react/index.d.ts +3 -0
- package/dist/frameworks/react/index.js +12 -0
- package/dist/frameworks/react/index.js.map +1 -0
- package/dist/frameworks/svelte/Feature.svelte +75 -0
- package/dist/frameworks/svelte/stores.d.ts +54 -0
- package/dist/frameworks/svelte/stores.js +34 -0
- package/dist/frameworks/svelte/stores.js.map +1 -0
- package/dist/frameworks/vue/Feature.vue +93 -0
- package/dist/frameworks/vue/composables.d.ts +56 -0
- package/dist/frameworks/vue/composables.js +39 -0
- package/dist/frameworks/vue/composables.js.map +1 -0
- package/dist/index-S3g0i0FH.d.ts +102 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +47 -0
- package/dist/index.js.map +1 -0
- package/dist/integration/index.d.ts +24 -0
- package/dist/integration/index.js +10 -0
- package/dist/integration/index.js.map +1 -0
- package/dist/server/toggly-server.d.ts +53 -0
- package/dist/server/toggly-server.js +9 -0
- package/dist/server/toggly-server.js.map +1 -0
- package/dist/server/utils.d.ts +43 -0
- package/dist/server/utils.js +13 -0
- package/dist/server/utils.js.map +1 -0
- package/package.json +105 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Ops.AI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
# Toggly Astro SDK
|
|
2
|
+
|
|
3
|
+
Feature flag management for Astro applications with support for SSR, SSG, and client-side rendering.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🚀 **Native Astro Components** - Server-rendered `.astro` components for optimal performance
|
|
8
|
+
- 🏝️ **Island Architecture** - Client-side hydration with Astro islands
|
|
9
|
+
- ⚛️ **Framework Support** - React, Vue, and Svelte component wrappers
|
|
10
|
+
- 📄 **Page-Level Gating** - Control entire pages via frontmatter
|
|
11
|
+
- 🔄 **SSR & SSG Support** - Works seamlessly with both rendering modes
|
|
12
|
+
- 🎯 **User Targeting** - Identity-based feature rollouts
|
|
13
|
+
- ⚡ **Lightweight** - Minimal client bundle using nanostores (~300 bytes)
|
|
14
|
+
- 🔌 **Edge Ready** - Optional Cloudflare Worker integration
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @ops-ai/astro-feature-flags-toggly
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
### 1. Add the Integration
|
|
25
|
+
|
|
26
|
+
In your `astro.config.mjs`:
|
|
27
|
+
|
|
28
|
+
```javascript
|
|
29
|
+
import { defineConfig } from 'astro/config';
|
|
30
|
+
import togglyIntegration from '@ops-ai/astro-feature-flags-toggly/integration';
|
|
31
|
+
|
|
32
|
+
export default defineConfig({
|
|
33
|
+
integrations: [
|
|
34
|
+
togglyIntegration({
|
|
35
|
+
appKey: process.env.TOGGLY_APP_KEY,
|
|
36
|
+
environment: process.env.TOGGLY_ENVIRONMENT || 'Production',
|
|
37
|
+
baseURI: 'https://client.toggly.io',
|
|
38
|
+
flagDefaults: {
|
|
39
|
+
// Fallback values when API is unavailable
|
|
40
|
+
'example-feature': false,
|
|
41
|
+
},
|
|
42
|
+
isDebug: process.env.NODE_ENV === 'development',
|
|
43
|
+
}),
|
|
44
|
+
],
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 2. Configure Middleware
|
|
49
|
+
|
|
50
|
+
Create or update `src/middleware.ts`:
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { sequence } from 'astro:middleware';
|
|
54
|
+
import { createTogglyMiddleware } from '@ops-ai/astro-feature-flags-toggly';
|
|
55
|
+
|
|
56
|
+
const toggly = createTogglyMiddleware({
|
|
57
|
+
appKey: import.meta.env.TOGGLY_APP_KEY,
|
|
58
|
+
environment: import.meta.env.TOGGLY_ENVIRONMENT || 'Production',
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
export const onRequest = sequence(toggly);
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 3. Use the Feature Component
|
|
65
|
+
|
|
66
|
+
In your `.astro` files:
|
|
67
|
+
|
|
68
|
+
```astro
|
|
69
|
+
---
|
|
70
|
+
import Feature from '@ops-ai/astro-feature-flags-toggly/components/Feature.astro';
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
<Feature flag="new-dashboard">
|
|
74
|
+
<h1>New Dashboard</h1>
|
|
75
|
+
<p>This content is only visible when the feature is enabled</p>
|
|
76
|
+
</Feature>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Usage
|
|
80
|
+
|
|
81
|
+
### Server-Side Components (Recommended)
|
|
82
|
+
|
|
83
|
+
Use the `Feature.astro` component for server-side evaluation (SSR/SSG):
|
|
84
|
+
|
|
85
|
+
```astro
|
|
86
|
+
---
|
|
87
|
+
import Feature from '@ops-ai/astro-feature-flags-toggly/components/Feature.astro';
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
<!-- Single flag -->
|
|
91
|
+
<Feature flag="beta-feature">
|
|
92
|
+
<p>Beta content</p>
|
|
93
|
+
</Feature>
|
|
94
|
+
|
|
95
|
+
<!-- Multiple flags with 'all' requirement -->
|
|
96
|
+
<Feature flags={['feature1', 'feature2']}>
|
|
97
|
+
<p>Both features must be enabled</p>
|
|
98
|
+
</Feature>
|
|
99
|
+
|
|
100
|
+
<!-- Multiple flags with 'any' requirement -->
|
|
101
|
+
<Feature flags={['feature1', 'feature2']} requirement="any">
|
|
102
|
+
<p>At least one feature must be enabled</p>
|
|
103
|
+
</Feature>
|
|
104
|
+
|
|
105
|
+
<!-- With fallback content -->
|
|
106
|
+
<Feature flag="premium-feature">
|
|
107
|
+
<p>Premium content</p>
|
|
108
|
+
<div slot="fallback">
|
|
109
|
+
<p>Upgrade to unlock this feature</p>
|
|
110
|
+
</div>
|
|
111
|
+
</Feature>
|
|
112
|
+
|
|
113
|
+
<!-- Negated (show when disabled) -->
|
|
114
|
+
<Feature flag="old-feature" negate={true}>
|
|
115
|
+
<p>This shows when the feature is OFF</p>
|
|
116
|
+
</Feature>
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Client-Side Components (Islands)
|
|
120
|
+
|
|
121
|
+
Use `FeatureClient.astro` for client-side evaluation with hydration:
|
|
122
|
+
|
|
123
|
+
```astro
|
|
124
|
+
---
|
|
125
|
+
import FeatureClient from '@ops-ai/astro-feature-flags-toggly/components/FeatureClient.astro';
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
<!-- Hydrate on page load -->
|
|
129
|
+
<FeatureClient flag="interactive-widget" client="load">
|
|
130
|
+
<InteractiveWidget />
|
|
131
|
+
</FeatureClient>
|
|
132
|
+
|
|
133
|
+
<!-- Lazy hydration when visible -->
|
|
134
|
+
<FeatureClient flag="below-fold-content" client="visible">
|
|
135
|
+
<HeavyComponent />
|
|
136
|
+
</FeatureClient>
|
|
137
|
+
|
|
138
|
+
<!-- Hydrate when browser is idle -->
|
|
139
|
+
<FeatureClient flag="non-critical-feature" client="idle">
|
|
140
|
+
<NonCriticalContent />
|
|
141
|
+
</FeatureClient>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Page-Level Gating
|
|
145
|
+
|
|
146
|
+
Control entire pages using frontmatter:
|
|
147
|
+
|
|
148
|
+
```astro
|
|
149
|
+
---
|
|
150
|
+
// src/pages/beta-feature.astro
|
|
151
|
+
x-feature: beta-feature
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
<html>
|
|
155
|
+
<body>
|
|
156
|
+
<h1>Beta Feature Page</h1>
|
|
157
|
+
<p>This entire page is gated by the 'beta-feature' flag</p>
|
|
158
|
+
</body>
|
|
159
|
+
</html>
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
During build, the integration generates a `toggly-page-features.json` manifest that can be used with a Cloudflare Worker for true edge enforcement (404s for disabled pages).
|
|
163
|
+
|
|
164
|
+
### React Integration
|
|
165
|
+
|
|
166
|
+
Use in React islands:
|
|
167
|
+
|
|
168
|
+
```tsx
|
|
169
|
+
// Component.tsx
|
|
170
|
+
import { Feature, useFeatureFlag } from '@ops-ai/astro-feature-flags-toggly/react';
|
|
171
|
+
|
|
172
|
+
// With component
|
|
173
|
+
export function Dashboard() {
|
|
174
|
+
return (
|
|
175
|
+
<Feature flag="new-dashboard">
|
|
176
|
+
<NewDashboard />
|
|
177
|
+
</Feature>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// With hook
|
|
182
|
+
export function ConditionalContent() {
|
|
183
|
+
const { enabled, isReady } = useFeatureFlag('premium-feature');
|
|
184
|
+
|
|
185
|
+
if (!isReady) return <Loading />;
|
|
186
|
+
if (!enabled) return <FreeTier />;
|
|
187
|
+
return <PremiumTier />;
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
In your Astro file:
|
|
192
|
+
|
|
193
|
+
```astro
|
|
194
|
+
---
|
|
195
|
+
import Dashboard from '../components/Dashboard.tsx';
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
<Dashboard client:load />
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Vue Integration
|
|
202
|
+
|
|
203
|
+
```vue
|
|
204
|
+
<!-- Component.vue -->
|
|
205
|
+
<script setup>
|
|
206
|
+
import Feature from '@ops-ai/astro-feature-flags-toggly/vue/Feature.vue';
|
|
207
|
+
import { useFeatureFlag } from '@ops-ai/astro-feature-flags-toggly/vue';
|
|
208
|
+
|
|
209
|
+
const { enabled } = useFeatureFlag('new-feature');
|
|
210
|
+
</script>
|
|
211
|
+
|
|
212
|
+
<template>
|
|
213
|
+
<Feature flag="beta-widget">
|
|
214
|
+
<BetaWidget />
|
|
215
|
+
<template #fallback>
|
|
216
|
+
<ComingSoon />
|
|
217
|
+
</template>
|
|
218
|
+
</Feature>
|
|
219
|
+
|
|
220
|
+
<div v-if="enabled">
|
|
221
|
+
<p>Feature-controlled content</p>
|
|
222
|
+
</div>
|
|
223
|
+
</template>
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Svelte Integration
|
|
227
|
+
|
|
228
|
+
```svelte
|
|
229
|
+
<!-- Component.svelte -->
|
|
230
|
+
<script>
|
|
231
|
+
import Feature from '@ops-ai/astro-feature-flags-toggly/svelte/Feature.svelte';
|
|
232
|
+
import { featureFlag } from '@ops-ai/astro-feature-flags-toggly/svelte';
|
|
233
|
+
|
|
234
|
+
const newDashboard = featureFlag('new-dashboard');
|
|
235
|
+
</script>
|
|
236
|
+
|
|
237
|
+
<Feature flag="beta-feature">
|
|
238
|
+
<BetaContent />
|
|
239
|
+
<svelte:fragment slot="fallback">
|
|
240
|
+
<RegularContent />
|
|
241
|
+
</svelte:fragment>
|
|
242
|
+
</Feature>
|
|
243
|
+
|
|
244
|
+
{#if $newDashboard}
|
|
245
|
+
<NewDashboard />
|
|
246
|
+
{:else}
|
|
247
|
+
<OldDashboard />
|
|
248
|
+
{/if}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Configuration Options
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
interface TogglyConfig {
|
|
255
|
+
/** Base URI for the Toggly API (default: 'https://client.toggly.io') */
|
|
256
|
+
baseURI?: string;
|
|
257
|
+
|
|
258
|
+
/** Application key from Toggly */
|
|
259
|
+
appKey?: string;
|
|
260
|
+
|
|
261
|
+
/** Environment name (default: 'Production') */
|
|
262
|
+
environment?: string;
|
|
263
|
+
|
|
264
|
+
/** Default flag values to use when API is unavailable */
|
|
265
|
+
flagDefaults?: Record<string, boolean>;
|
|
266
|
+
|
|
267
|
+
/** Feature flags refresh interval in milliseconds (default: 180000 = 3 minutes) */
|
|
268
|
+
featureFlagsRefreshInterval?: number;
|
|
269
|
+
|
|
270
|
+
/** Enable debug logging (default: false) */
|
|
271
|
+
isDebug?: boolean;
|
|
272
|
+
|
|
273
|
+
/** Connection timeout in milliseconds (default: 5000) */
|
|
274
|
+
connectTimeout?: number;
|
|
275
|
+
|
|
276
|
+
/** User identity for targeting (optional) */
|
|
277
|
+
identity?: string;
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## SSR vs SSG Considerations
|
|
282
|
+
|
|
283
|
+
### SSR (Server-Side Rendering)
|
|
284
|
+
|
|
285
|
+
- Flags are fetched on each request
|
|
286
|
+
- Fresh flag values for every visitor
|
|
287
|
+
- Slightly slower initial page load
|
|
288
|
+
- Use `output: 'server'` in `astro.config.mjs`
|
|
289
|
+
|
|
290
|
+
```javascript
|
|
291
|
+
export default defineConfig({
|
|
292
|
+
output: 'server',
|
|
293
|
+
// ...
|
|
294
|
+
});
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### SSG (Static Site Generation)
|
|
298
|
+
|
|
299
|
+
- Flags are fetched at build time
|
|
300
|
+
- Same flag values for all visitors
|
|
301
|
+
- Fastest possible page loads
|
|
302
|
+
- Requires rebuild to update flags
|
|
303
|
+
- Use client-side components for dynamic updates
|
|
304
|
+
|
|
305
|
+
```javascript
|
|
306
|
+
export default defineConfig({
|
|
307
|
+
output: 'static',
|
|
308
|
+
// ...
|
|
309
|
+
});
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### Hybrid Approach
|
|
313
|
+
|
|
314
|
+
Use server components for critical gating and client components for non-critical features:
|
|
315
|
+
|
|
316
|
+
```astro
|
|
317
|
+
---
|
|
318
|
+
import Feature from '@ops-ai/astro-feature-flags-toggly/components/Feature.astro';
|
|
319
|
+
import FeatureClient from '@ops-ai/astro-feature-flags-toggly/components/FeatureClient.astro';
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
<!-- Critical feature - evaluated at build/request time -->
|
|
323
|
+
<Feature flag="access-control">
|
|
324
|
+
<SecureContent />
|
|
325
|
+
</Feature>
|
|
326
|
+
|
|
327
|
+
<!-- Non-critical feature - evaluated on client -->
|
|
328
|
+
<FeatureClient flag="ui-enhancement" client="idle">
|
|
329
|
+
<EnhancedUI />
|
|
330
|
+
</FeatureClient>
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
## Advanced Usage
|
|
334
|
+
|
|
335
|
+
### User Identity for Targeting
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
// In middleware or component
|
|
339
|
+
import { setIdentity } from '@ops-ai/astro-feature-flags-toggly';
|
|
340
|
+
|
|
341
|
+
// Set user identity for targeting
|
|
342
|
+
setIdentity('user-123');
|
|
343
|
+
|
|
344
|
+
// Clear identity (e.g., on logout)
|
|
345
|
+
import { clearIdentity } from '@ops-ai/astro-feature-flags-toggly';
|
|
346
|
+
clearIdentity();
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Manual Flag Refresh
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
import { refreshFlags } from '@ops-ai/astro-feature-flags-toggly';
|
|
353
|
+
|
|
354
|
+
// Manually refresh flags
|
|
355
|
+
await refreshFlags();
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### Programmatic Flag Evaluation
|
|
359
|
+
|
|
360
|
+
In server-side code:
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
const toggly = Astro.locals.toggly;
|
|
364
|
+
|
|
365
|
+
const isEnabled = await toggly.getFlag('feature-key');
|
|
366
|
+
const allFlags = await toggly.getFlags();
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
## Edge Enforcement (Optional)
|
|
370
|
+
|
|
371
|
+
For true enforcement at the edge (prevents page access even if client-side JS is disabled):
|
|
372
|
+
|
|
373
|
+
1. The integration generates `toggly-page-features.json` during build
|
|
374
|
+
2. Deploy a Cloudflare Worker that reads this manifest
|
|
375
|
+
3. The worker intercepts requests and returns 404 for disabled pages
|
|
376
|
+
|
|
377
|
+
See the [Cloudflare Worker integration guide](https://github.com/ops-ai/Toggly.CloudflareWorker) for setup instructions.
|
|
378
|
+
|
|
379
|
+
## TypeScript Support
|
|
380
|
+
|
|
381
|
+
Full TypeScript support is included:
|
|
382
|
+
|
|
383
|
+
```typescript
|
|
384
|
+
import type {
|
|
385
|
+
TogglyConfig,
|
|
386
|
+
Flags,
|
|
387
|
+
TogglyClient,
|
|
388
|
+
FeatureProps,
|
|
389
|
+
} from '@ops-ai/astro-feature-flags-toggly';
|
|
390
|
+
|
|
391
|
+
// Augmented Astro global
|
|
392
|
+
Astro.locals.toggly; // Typed as TogglyClient
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
## Best Practices
|
|
396
|
+
|
|
397
|
+
1. **Use Server Components When Possible** - Better performance, no client-side JavaScript
|
|
398
|
+
2. **Set Flag Defaults** - Always provide fallback values for offline scenarios
|
|
399
|
+
3. **Use Environment Variables** - Never hardcode credentials
|
|
400
|
+
4. **Enable Debug Mode in Development** - Helps troubleshoot issues
|
|
401
|
+
5. **Cache Appropriately** - Adjust `featureFlagsRefreshInterval` based on your needs
|
|
402
|
+
6. **Provide User Identity** - Required for targeting and consistent rollouts
|
|
403
|
+
7. **Consider SSR vs SSG** - Choose based on how dynamic your flags need to be
|
|
404
|
+
8. **Use Page-Level Gating** - For entire pages, use frontmatter instead of wrapping all content
|
|
405
|
+
|
|
406
|
+
## Troubleshooting
|
|
407
|
+
|
|
408
|
+
### Flags not loading
|
|
409
|
+
|
|
410
|
+
1. Check that the integration is properly configured in `astro.config.mjs`
|
|
411
|
+
2. Verify `appKey` and `environment` are correct
|
|
412
|
+
3. Enable debug mode: `isDebug: true`
|
|
413
|
+
4. Check browser console and server logs
|
|
414
|
+
|
|
415
|
+
### TypeScript errors
|
|
416
|
+
|
|
417
|
+
Make sure you have the necessary dependencies:
|
|
418
|
+
|
|
419
|
+
```bash
|
|
420
|
+
npm install -D @types/node astro
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
### Client components not hydrating
|
|
424
|
+
|
|
425
|
+
Ensure you're using the correct hydration directive:
|
|
426
|
+
|
|
427
|
+
```astro
|
|
428
|
+
<FeatureClient flag="test" client="load">
|
|
429
|
+
<!-- content -->
|
|
430
|
+
</FeatureClient>
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### Middleware not working
|
|
434
|
+
|
|
435
|
+
Verify middleware is properly configured in `src/middleware.ts` and exported as `onRequest`.
|
|
436
|
+
|
|
437
|
+
## Examples
|
|
438
|
+
|
|
439
|
+
Check the `examples/` directory for complete working examples:
|
|
440
|
+
|
|
441
|
+
- Basic Astro + Toggly setup
|
|
442
|
+
- SSR with feature flags
|
|
443
|
+
- SSG with client-side updates
|
|
444
|
+
- React/Vue/Svelte islands
|
|
445
|
+
|
|
446
|
+
## License
|
|
447
|
+
|
|
448
|
+
MIT
|
|
449
|
+
|
|
450
|
+
## Support
|
|
451
|
+
|
|
452
|
+
- [Documentation](https://docs.toggly.io)
|
|
453
|
+
- [GitHub Issues](https://github.com/ops-ai/Toggly.FeatureManagement/issues)
|
|
454
|
+
- [Discord Community](https://discord.gg/toggly)
|
|
455
|
+
|
|
456
|
+
## Related
|
|
457
|
+
|
|
458
|
+
- [@ops-ai/toggly-docusaurus-plugin](https://www.npmjs.com/package/@ops-ai/toggly-docusaurus-plugin) - Docusaurus integration
|
|
459
|
+
- [@ops-ai/react-feature-flags-toggly](https://www.npmjs.com/package/@ops-ai/react-feature-flags-toggly) - React SDK
|
|
460
|
+
- [@ops-ai/feature-flags-toggly](https://www.npmjs.com/package/@ops-ai/feature-flags-toggly) - Vanilla JavaScript SDK
|
|
461
|
+
|
|
462
|
+
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createTogglyServerClient
|
|
3
|
+
} from "./chunk-XQGKGTBK.js";
|
|
4
|
+
|
|
5
|
+
// src/integration/index.ts
|
|
6
|
+
import * as fs from "fs";
|
|
7
|
+
import * as path from "path";
|
|
8
|
+
import { glob } from "glob";
|
|
9
|
+
function togglyIntegration(options = {}) {
|
|
10
|
+
const config = {
|
|
11
|
+
baseURI: "https://client.toggly.io",
|
|
12
|
+
environment: "Production",
|
|
13
|
+
flagDefaults: {},
|
|
14
|
+
featureFlagsRefreshInterval: 3 * 60 * 1e3,
|
|
15
|
+
isDebug: false,
|
|
16
|
+
connectTimeout: 5 * 1e3,
|
|
17
|
+
...options
|
|
18
|
+
};
|
|
19
|
+
let pageFeatureMapping = {};
|
|
20
|
+
let astroConfig;
|
|
21
|
+
return {
|
|
22
|
+
name: "@ops-ai/astro-feature-flags-toggly",
|
|
23
|
+
hooks: {
|
|
24
|
+
"astro:config:setup": async ({ config: cfg, injectScript, updateConfig }) => {
|
|
25
|
+
astroConfig = cfg;
|
|
26
|
+
if (config.isDebug) {
|
|
27
|
+
console.log("[Toggly Integration] Setting up integration...");
|
|
28
|
+
}
|
|
29
|
+
injectScript(
|
|
30
|
+
"page",
|
|
31
|
+
`
|
|
32
|
+
window.__TOGGLY_CONFIG__ = ${JSON.stringify(config)};
|
|
33
|
+
import('@ops-ai/astro-feature-flags-toggly/client/setup');
|
|
34
|
+
`
|
|
35
|
+
);
|
|
36
|
+
updateConfig({
|
|
37
|
+
vite: {
|
|
38
|
+
ssr: {
|
|
39
|
+
noExternal: ["@ops-ai/astro-feature-flags-toggly"]
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
},
|
|
44
|
+
"astro:server:setup": async ({ server }) => {
|
|
45
|
+
if (config.isDebug) {
|
|
46
|
+
console.log("[Toggly Integration] Server setup...");
|
|
47
|
+
}
|
|
48
|
+
const togglyClient = createTogglyServerClient(config);
|
|
49
|
+
server.middlewares.use((req, res, next) => {
|
|
50
|
+
req.togglyClient = togglyClient;
|
|
51
|
+
next();
|
|
52
|
+
});
|
|
53
|
+
},
|
|
54
|
+
"astro:build:start": async () => {
|
|
55
|
+
if (config.isDebug) {
|
|
56
|
+
console.log("[Toggly Integration] Build started, extracting frontmatter...");
|
|
57
|
+
}
|
|
58
|
+
pageFeatureMapping = await extractPageFeatures(astroConfig, config.isDebug);
|
|
59
|
+
if (config.isDebug) {
|
|
60
|
+
console.log(
|
|
61
|
+
`[Toggly Integration] Found ${Object.keys(pageFeatureMapping).length} pages with x-feature`
|
|
62
|
+
);
|
|
63
|
+
Object.entries(pageFeatureMapping).forEach(([route, feature]) => {
|
|
64
|
+
console.log(` ${route} -> ${feature}`);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"astro:build:done": async ({ dir }) => {
|
|
69
|
+
if (config.isDebug) {
|
|
70
|
+
console.log("[Toggly Integration] Build done, writing manifest...");
|
|
71
|
+
}
|
|
72
|
+
const manifestPath = path.join(dir.pathname, "toggly-page-features.json");
|
|
73
|
+
fs.writeFileSync(manifestPath, JSON.stringify(pageFeatureMapping, null, 2), "utf-8");
|
|
74
|
+
if (config.isDebug) {
|
|
75
|
+
console.log(`[Toggly Integration] Manifest written to: ${manifestPath}`);
|
|
76
|
+
}
|
|
77
|
+
const configPath = path.join(dir.pathname, "toggly-config.json");
|
|
78
|
+
fs.writeFileSync(
|
|
79
|
+
configPath,
|
|
80
|
+
JSON.stringify(
|
|
81
|
+
{
|
|
82
|
+
...config,
|
|
83
|
+
// Don't expose appKey in public build output
|
|
84
|
+
appKey: config.appKey ? "***" : void 0
|
|
85
|
+
},
|
|
86
|
+
null,
|
|
87
|
+
2
|
|
88
|
+
),
|
|
89
|
+
"utf-8"
|
|
90
|
+
);
|
|
91
|
+
},
|
|
92
|
+
"astro:config:done": ({ config: cfg, setAdapter }) => {
|
|
93
|
+
astroConfig = cfg;
|
|
94
|
+
if (config.isDebug) {
|
|
95
|
+
console.log("[Toggly Integration] Configuration finalized");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
async function extractPageFeatures(astroConfig, isDebug) {
|
|
102
|
+
const mapping = {};
|
|
103
|
+
const srcDir = astroConfig.srcDir?.pathname || path.join(process.cwd(), "src");
|
|
104
|
+
const pagesDir = path.join(srcDir, "pages");
|
|
105
|
+
const contentDir = path.join(srcDir, "content");
|
|
106
|
+
const dirsToScan = [];
|
|
107
|
+
if (fs.existsSync(pagesDir)) {
|
|
108
|
+
dirsToScan.push(pagesDir);
|
|
109
|
+
}
|
|
110
|
+
if (fs.existsSync(contentDir)) {
|
|
111
|
+
dirsToScan.push(contentDir);
|
|
112
|
+
}
|
|
113
|
+
if (dirsToScan.length === 0) {
|
|
114
|
+
if (isDebug) {
|
|
115
|
+
console.warn("[Toggly Integration] No pages or content directories found");
|
|
116
|
+
}
|
|
117
|
+
return mapping;
|
|
118
|
+
}
|
|
119
|
+
for (const dir of dirsToScan) {
|
|
120
|
+
const files = await glob("**/*.{astro,md,mdx}", {
|
|
121
|
+
cwd: dir,
|
|
122
|
+
absolute: false,
|
|
123
|
+
ignore: ["node_modules/**", "**/node_modules/**"]
|
|
124
|
+
});
|
|
125
|
+
for (const file of files) {
|
|
126
|
+
const filePath = path.join(dir, file);
|
|
127
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
128
|
+
const frontmatterMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
129
|
+
if (!frontmatterMatch) {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
const frontmatter = frontmatterMatch[1];
|
|
133
|
+
const xFeatureMatch = frontmatter.match(/^x-feature:\s*(.+)$/m);
|
|
134
|
+
if (!xFeatureMatch) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
let featureKey = xFeatureMatch[1].trim();
|
|
138
|
+
featureKey = featureKey.replace(/^["']|["']$/g, "");
|
|
139
|
+
let route = convertFilePathToRoute(file, dir === pagesDir);
|
|
140
|
+
const base = astroConfig.base || "/";
|
|
141
|
+
if (base !== "/") {
|
|
142
|
+
route = path.join(base, route).replace(/\\/g, "/");
|
|
143
|
+
}
|
|
144
|
+
mapping[route] = featureKey;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return mapping;
|
|
148
|
+
}
|
|
149
|
+
function convertFilePathToRoute(filePath, isPages) {
|
|
150
|
+
let route = filePath.replace(/\.(astro|md|mdx)$/, "");
|
|
151
|
+
route = route.split("/").map((segment) => segment.replace(/^\d+-/, "")).join("/");
|
|
152
|
+
if (route.endsWith("/index") || route === "index") {
|
|
153
|
+
route = route.replace(/\/index$/, "") || "/";
|
|
154
|
+
}
|
|
155
|
+
if (!route.startsWith("/")) {
|
|
156
|
+
route = "/" + route;
|
|
157
|
+
}
|
|
158
|
+
return route;
|
|
159
|
+
}
|
|
160
|
+
function createTogglyMiddleware(config) {
|
|
161
|
+
return async function togglyMiddleware({ locals }, next) {
|
|
162
|
+
if (!locals.toggly) {
|
|
163
|
+
locals.toggly = createTogglyServerClient(config);
|
|
164
|
+
}
|
|
165
|
+
return next();
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export {
|
|
170
|
+
togglyIntegration,
|
|
171
|
+
createTogglyMiddleware
|
|
172
|
+
};
|
|
173
|
+
//# sourceMappingURL=chunk-354E3C57.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/integration/index.ts"],"sourcesContent":["/**\n * Toggly Astro Integration\n * \n * Provides build-time configuration, frontmatter extraction, and runtime injection\n */\n\nimport type { AstroIntegration, AstroConfig } from 'astro';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { glob } from 'glob';\nimport type { TogglyConfig, PageFeatureMapping } from '../types/index.js';\nimport { createTogglyServerClient } from '../server/toggly-server.js';\n\nexport interface TogglyIntegrationOptions extends TogglyConfig {}\n\n/**\n * Toggly Astro Integration\n */\nexport default function togglyIntegration(\n options: TogglyIntegrationOptions = {}\n): AstroIntegration {\n const config: TogglyConfig = {\n baseURI: 'https://client.toggly.io',\n environment: 'Production',\n flagDefaults: {},\n featureFlagsRefreshInterval: 3 * 60 * 1000,\n isDebug: false,\n connectTimeout: 5 * 1000,\n ...options,\n };\n\n let pageFeatureMapping: PageFeatureMapping = {};\n let astroConfig: AstroConfig;\n\n return {\n name: '@ops-ai/astro-feature-flags-toggly',\n hooks: {\n 'astro:config:setup': async ({ config: cfg, injectScript, updateConfig }) => {\n astroConfig = cfg;\n\n if (config.isDebug) {\n console.log('[Toggly Integration] Setting up integration...');\n }\n\n // Inject client setup script\n injectScript(\n 'page',\n `\n window.__TOGGLY_CONFIG__ = ${JSON.stringify(config)};\n import('@ops-ai/astro-feature-flags-toggly/client/setup');\n `\n );\n\n // Add middleware to inject Toggly client into Astro.locals\n updateConfig({\n vite: {\n ssr: {\n noExternal: ['@ops-ai/astro-feature-flags-toggly'],\n },\n },\n });\n },\n\n 'astro:server:setup': async ({ server }) => {\n if (config.isDebug) {\n console.log('[Toggly Integration] Server setup...');\n }\n\n // Create server client for SSR\n const togglyClient = createTogglyServerClient(config);\n\n // Inject into server context (this will be available in SSR)\n server.middlewares.use((req, res, next) => {\n // @ts-ignore - Adding toggly to request\n req.togglyClient = togglyClient;\n next();\n });\n },\n\n 'astro:build:start': async () => {\n if (config.isDebug) {\n console.log('[Toggly Integration] Build started, extracting frontmatter...');\n }\n\n // Extract page feature mapping from frontmatter\n pageFeatureMapping = await extractPageFeatures(astroConfig, config.isDebug);\n\n if (config.isDebug) {\n console.log(\n `[Toggly Integration] Found ${Object.keys(pageFeatureMapping).length} pages with x-feature`\n );\n Object.entries(pageFeatureMapping).forEach(([route, feature]) => {\n console.log(` ${route} -> ${feature}`);\n });\n }\n },\n\n 'astro:build:done': async ({ dir }) => {\n if (config.isDebug) {\n console.log('[Toggly Integration] Build done, writing manifest...');\n }\n\n // Write page feature manifest for edge workers\n const manifestPath = path.join(dir.pathname, 'toggly-page-features.json');\n fs.writeFileSync(manifestPath, JSON.stringify(pageFeatureMapping, null, 2), 'utf-8');\n\n if (config.isDebug) {\n console.log(`[Toggly Integration] Manifest written to: ${manifestPath}`);\n }\n\n // Also write config for reference\n const configPath = path.join(dir.pathname, 'toggly-config.json');\n fs.writeFileSync(\n configPath,\n JSON.stringify(\n {\n ...config,\n // Don't expose appKey in public build output\n appKey: config.appKey ? '***' : undefined,\n },\n null,\n 2\n ),\n 'utf-8'\n );\n },\n\n 'astro:config:done': ({ config: cfg, setAdapter }) => {\n // Store final config\n astroConfig = cfg;\n\n if (config.isDebug) {\n console.log('[Toggly Integration] Configuration finalized');\n }\n },\n },\n };\n}\n\n/**\n * Extract x-feature frontmatter from pages\n */\nasync function extractPageFeatures(\n astroConfig: AstroConfig,\n isDebug?: boolean\n): Promise<PageFeatureMapping> {\n const mapping: PageFeatureMapping = {};\n\n // Determine source directory\n const srcDir = astroConfig.srcDir?.pathname || path.join(process.cwd(), 'src');\n const pagesDir = path.join(srcDir, 'pages');\n const contentDir = path.join(srcDir, 'content');\n\n // Check if directories exist\n const dirsToScan: string[] = [];\n if (fs.existsSync(pagesDir)) {\n dirsToScan.push(pagesDir);\n }\n if (fs.existsSync(contentDir)) {\n dirsToScan.push(contentDir);\n }\n\n if (dirsToScan.length === 0) {\n if (isDebug) {\n console.warn('[Toggly Integration] No pages or content directories found');\n }\n return mapping;\n }\n\n for (const dir of dirsToScan) {\n // Find all .astro, .md, .mdx files\n const files = await glob('**/*.{astro,md,mdx}', {\n cwd: dir,\n absolute: false,\n ignore: ['node_modules/**', '**/node_modules/**'],\n });\n\n for (const file of files) {\n const filePath = path.join(dir, file);\n const content = fs.readFileSync(filePath, 'utf-8');\n\n // Extract frontmatter\n const frontmatterMatch = content.match(/^---\\s*\\n([\\s\\S]*?)\\n---/);\n if (!frontmatterMatch) {\n continue;\n }\n\n const frontmatter = frontmatterMatch[1];\n\n // Look for x-feature in frontmatter\n const xFeatureMatch = frontmatter.match(/^x-feature:\\s*(.+)$/m);\n if (!xFeatureMatch) {\n continue;\n }\n\n let featureKey = xFeatureMatch[1].trim();\n // Remove quotes if present\n featureKey = featureKey.replace(/^[\"']|[\"']$/g, '');\n\n // Convert file path to route\n let route = convertFilePathToRoute(file, dir === pagesDir);\n\n // Prepend base if configured\n const base = astroConfig.base || '/';\n if (base !== '/') {\n route = path.join(base, route).replace(/\\\\/g, '/');\n }\n\n mapping[route] = featureKey;\n }\n }\n\n return mapping;\n}\n\n/**\n * Convert file path to Astro route\n */\nfunction convertFilePathToRoute(filePath: string, isPages: boolean): string {\n // Remove file extension\n let route = filePath.replace(/\\.(astro|md|mdx)$/, '');\n\n // Remove numeric prefixes (e.g., 01-intro.md -> intro.md)\n route = route\n .split('/')\n .map((segment) => segment.replace(/^\\d+-/, ''))\n .join('/');\n\n // Handle index files\n if (route.endsWith('/index') || route === 'index') {\n route = route.replace(/\\/index$/, '') || '/';\n }\n\n // Ensure leading slash\n if (!route.startsWith('/')) {\n route = '/' + route;\n }\n\n // For content collections, prepend with collection name if not pages\n // This is a simplification - Astro content collections have more complex routing\n\n return route;\n}\n\n/**\n * Astro middleware to inject Toggly into locals\n * This should be added to src/middleware.ts in the user's project\n */\nexport function createTogglyMiddleware(config: TogglyConfig) {\n return async function togglyMiddleware(\n { locals }: { locals: Record<string, any> },\n next: () => Promise<Response>\n ): Promise<Response> {\n // Create or reuse Toggly client\n if (!locals.toggly) {\n locals.toggly = createTogglyServerClient(config);\n }\n\n return next();\n };\n}\n\n\n"],"mappings":"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","names":[]}
|