@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.
Files changed (48) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +462 -0
  3. package/dist/chunk-354E3C57.js +173 -0
  4. package/dist/chunk-354E3C57.js.map +1 -0
  5. package/dist/chunk-4UKIT2NP.js +44 -0
  6. package/dist/chunk-4UKIT2NP.js.map +1 -0
  7. package/dist/chunk-FK633ULF.js +200 -0
  8. package/dist/chunk-FK633ULF.js.map +1 -0
  9. package/dist/chunk-XCJSQJHR.js +74 -0
  10. package/dist/chunk-XCJSQJHR.js.map +1 -0
  11. package/dist/chunk-XQGKGTBK.js +161 -0
  12. package/dist/chunk-XQGKGTBK.js.map +1 -0
  13. package/dist/client/setup.d.ts +3 -0
  14. package/dist/client/setup.js +21 -0
  15. package/dist/client/setup.js.map +1 -0
  16. package/dist/client/store.d.ts +59 -0
  17. package/dist/client/store.js +25 -0
  18. package/dist/client/store.js.map +1 -0
  19. package/dist/components/Feature.astro +79 -0
  20. package/dist/components/FeatureClient.astro +144 -0
  21. package/dist/frameworks/react/Feature.d.ts +86 -0
  22. package/dist/frameworks/react/Feature.js +14 -0
  23. package/dist/frameworks/react/Feature.js.map +1 -0
  24. package/dist/frameworks/react/index.d.ts +3 -0
  25. package/dist/frameworks/react/index.js +12 -0
  26. package/dist/frameworks/react/index.js.map +1 -0
  27. package/dist/frameworks/svelte/Feature.svelte +75 -0
  28. package/dist/frameworks/svelte/stores.d.ts +54 -0
  29. package/dist/frameworks/svelte/stores.js +34 -0
  30. package/dist/frameworks/svelte/stores.js.map +1 -0
  31. package/dist/frameworks/vue/Feature.vue +93 -0
  32. package/dist/frameworks/vue/composables.d.ts +56 -0
  33. package/dist/frameworks/vue/composables.js +39 -0
  34. package/dist/frameworks/vue/composables.js.map +1 -0
  35. package/dist/index-S3g0i0FH.d.ts +102 -0
  36. package/dist/index.d.ts +7 -0
  37. package/dist/index.js +47 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/integration/index.d.ts +24 -0
  40. package/dist/integration/index.js +10 -0
  41. package/dist/integration/index.js.map +1 -0
  42. package/dist/server/toggly-server.d.ts +53 -0
  43. package/dist/server/toggly-server.js +9 -0
  44. package/dist/server/toggly-server.js.map +1 -0
  45. package/dist/server/utils.d.ts +43 -0
  46. package/dist/server/utils.js +13 -0
  47. package/dist/server/utils.js.map +1 -0
  48. 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":[]}