@ops-ai/astro-feature-flags-toggly 1.0.6 → 1.2.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/README.md CHANGED
@@ -275,9 +275,54 @@ interface TogglyConfig {
275
275
 
276
276
  /** User identity for targeting (optional) */
277
277
  identity?: string;
278
+
279
+ /**
280
+ * When true, all features are enabled during build time (SSG).
281
+ * This is useful when you have an edge worker (like Cloudflare Worker) that
282
+ * filters content based on feature flags at runtime.
283
+ * During dev server, actual feature flags from the API are still used.
284
+ * (default: false)
285
+ */
286
+ allFeaturesEnabledDuringBuild?: boolean;
278
287
  }
279
288
  ```
280
289
 
290
+ ## Build Mode for Edge Filtering
291
+
292
+ If you're using an edge worker (Cloudflare Worker, Vercel Edge, etc.) to filter content based on feature flags at runtime, you can enable all features during the static build:
293
+
294
+ ```javascript
295
+ // astro.config.mjs
296
+ const isDev = process.env.NODE_ENV === 'development';
297
+ const isBuild = process.argv.includes('build');
298
+
299
+ export default defineConfig({
300
+ integrations: [
301
+ togglyIntegration({
302
+ appKey: process.env.TOGGLY_APP_KEY,
303
+ environment: process.env.TOGGLY_ENVIRONMENT || 'Production',
304
+ baseURI: 'https://client.toggly.io',
305
+ // Enable all features during production builds
306
+ // Use actual feature flags during development
307
+ allFeaturesEnabledDuringBuild: isBuild && !isDev,
308
+ isDebug: isDev,
309
+ }),
310
+ ],
311
+ });
312
+ ```
313
+
314
+ **Benefits:**
315
+ - **SEO**: All feature-flagged content is present in the static build for search engines
316
+ - **No broken links**: Features disabled during build won't cause broken internal links
317
+ - **Edge performance**: Static build generated once, edge worker does lightweight filtering
318
+ - **Dynamic control**: Toggle features at the edge without rebuilding
319
+ - **Dev experience**: See actual feature states during local development
320
+
321
+ **How it works:**
322
+ 1. **Development** (`npm run dev`): Fetches actual feature flags from Toggly API
323
+ 2. **Build** (`npm run build`): Builds static site with all features enabled
324
+ 3. **Runtime** (Edge/CDN): Edge worker filters content based on current feature flag states
325
+
281
326
  ## SSR vs SSG Considerations
282
327
 
283
328
  ### SSR (Server-Side Rendering)
@@ -3,7 +3,8 @@ var TogglyServer = class {
3
3
  config;
4
4
  cache = null;
5
5
  fetchPromise = null;
6
- constructor(config) {
6
+ isBuildTime = false;
7
+ constructor(config, isBuildTime = false) {
7
8
  this.config = {
8
9
  baseURI: "https://client.toggly.io",
9
10
  environment: "Production",
@@ -13,8 +14,10 @@ var TogglyServer = class {
13
14
  isDebug: false,
14
15
  connectTimeout: 5 * 1e3,
15
16
  // 5 seconds
17
+ allFeaturesEnabledDuringBuild: false,
16
18
  ...config
17
19
  };
20
+ this.isBuildTime = isBuildTime;
18
21
  }
19
22
  /**
20
23
  * Get API URL for fetching flags
@@ -66,7 +69,16 @@ var TogglyServer = class {
66
69
  `Failed to fetch flags from Toggly API: ${response.status} ${response.statusText}`
67
70
  );
68
71
  }
69
- const flags = await response.json();
72
+ let flags = await response.json();
73
+ if (this.config.allFeaturesEnabledDuringBuild && this.isBuildTime) {
74
+ if (this.config.isDebug) {
75
+ console.log("[Toggly Server] Build mode: Enabling all features");
76
+ }
77
+ flags = Object.keys(flags).reduce((acc, key) => {
78
+ acc[key] = true;
79
+ return acc;
80
+ }, {});
81
+ }
70
82
  if (this.config.isDebug) {
71
83
  console.log("[Toggly Server] Fetched flags:", flags);
72
84
  }
@@ -150,12 +162,12 @@ var TogglyServer = class {
150
162
  return negate ? !isEnabled : isEnabled;
151
163
  }
152
164
  };
153
- function createTogglyServerClient(config) {
154
- return new TogglyServer(config);
165
+ function createTogglyServerClient(config, isBuildTime = false) {
166
+ return new TogglyServer(config, isBuildTime);
155
167
  }
156
168
 
157
169
  export {
158
170
  TogglyServer,
159
171
  createTogglyServerClient
160
172
  };
161
- //# sourceMappingURL=chunk-XQGKGTBK.js.map
173
+ //# sourceMappingURL=chunk-GEESG7MX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server/toggly-server.ts"],"sourcesContent":["/**\n * Toggly Server-Side Client for Astro SSR/SSG\n * \n * This module provides server-side feature flag evaluation for Astro applications.\n * It caches flags for SSG builds and fetches fresh flags for SSR requests.\n * This is a complete, embedded Toggly client implementation.\n */\n\nimport type { TogglyConfig, Flags, TogglyClient } from '../types/index.js';\n\ninterface CachedFlags {\n flags: Flags;\n timestamp: number;\n}\n\n/**\n * Server-side Toggly client implementation\n */\nexport class TogglyServer implements TogglyClient {\n private config: TogglyConfig;\n private cache: CachedFlags | null = null;\n private fetchPromise: Promise<Flags> | null = null;\n private isBuildTime: boolean = false;\n\n constructor(config: TogglyConfig, isBuildTime: boolean = false) {\n this.config = {\n baseURI: 'https://client.toggly.io',\n environment: 'Production',\n flagDefaults: {},\n featureFlagsRefreshInterval: 3 * 60 * 1000, // 3 minutes\n isDebug: false,\n connectTimeout: 5 * 1000, // 5 seconds\n allFeaturesEnabledDuringBuild: false,\n ...config,\n };\n this.isBuildTime = isBuildTime;\n }\n\n /**\n * Get API URL for fetching flags\n */\n private getApiUrl(): string {\n const { baseURI, appKey, environment, identity } = this.config;\n\n if (!appKey) {\n return '';\n }\n\n const baseUrl = baseURI!.replace(/\\/$/, '');\n let url = `${baseUrl}/${appKey}-${environment}/defs`;\n\n if (identity) {\n url += `?u=${encodeURIComponent(identity)}`;\n }\n\n return url;\n }\n\n /**\n * Check if cache is valid\n */\n private isCacheValid(): boolean {\n if (!this.cache) return false;\n const age = Date.now() - this.cache.timestamp;\n return age < this.config.featureFlagsRefreshInterval!;\n }\n\n /**\n * Fetch flags from Toggly API\n */\n private async fetchFlags(): Promise<Flags> {\n const url = this.getApiUrl();\n\n // If no appKey, return flagDefaults\n if (!url || !this.config.appKey) {\n if (this.config.isDebug) {\n console.log('[Toggly Server] Using flag defaults (no appKey):', this.config.flagDefaults);\n }\n return { ...this.config.flagDefaults! };\n }\n\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.config.connectTimeout);\n\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n Accept: 'application/json',\n },\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n throw new Error(\n `Failed to fetch flags from Toggly API: ${response.status} ${response.statusText}`\n );\n }\n\n let flags = (await response.json()) as Flags;\n\n // If allFeaturesEnabledDuringBuild is true and we're in build time,\n // override all flags to true\n if (this.config.allFeaturesEnabledDuringBuild && this.isBuildTime) {\n if (this.config.isDebug) {\n console.log('[Toggly Server] Build mode: Enabling all features');\n }\n flags = Object.keys(flags).reduce((acc, key) => {\n acc[key] = true;\n return acc;\n }, {} as Flags);\n }\n\n if (this.config.isDebug) {\n console.log('[Toggly Server] Fetched flags:', flags);\n }\n\n return flags;\n } catch (error) {\n if (this.config.isDebug) {\n console.error('[Toggly Server] Error fetching flags:', error);\n }\n\n // On error, try to use cached flags, otherwise use flagDefaults\n if (this.cache) {\n if (this.config.isDebug) {\n console.log('[Toggly Server] Using cached flags:', this.cache.flags);\n }\n return { ...this.cache.flags };\n }\n\n if (this.config.isDebug) {\n console.log('[Toggly Server] Using flag defaults:', this.config.flagDefaults);\n }\n\n return { ...this.config.flagDefaults! };\n }\n }\n\n /**\n * Refresh flags cache\n */\n async refreshFlags(): Promise<void> {\n if (this.config.isDebug) {\n console.log('[Toggly Server] Refreshing flags...');\n }\n\n // Prevent multiple concurrent fetches\n if (this.fetchPromise) {\n await this.fetchPromise;\n return;\n }\n\n this.fetchPromise = this.fetchFlags();\n\n try {\n const flags = await this.fetchPromise;\n this.cache = {\n flags,\n timestamp: Date.now(),\n };\n } finally {\n this.fetchPromise = null;\n }\n }\n\n /**\n * Get all feature flags\n */\n async getFlags(): Promise<Flags> {\n // If no appKey, return flagDefaults immediately\n if (!this.config.appKey) {\n return { ...this.config.flagDefaults! };\n }\n\n // If cache is valid, return it\n if (this.isCacheValid() && this.cache) {\n return { ...this.cache.flags };\n }\n\n // Otherwise, refresh and return\n await this.refreshFlags();\n return this.cache ? { ...this.cache.flags } : { ...this.config.flagDefaults! };\n }\n\n /**\n * Get a single feature flag value\n */\n async getFlag(key: string, defaultValue: boolean = false): Promise<boolean> {\n const flags = await this.getFlags();\n const value = flags[key];\n\n if (value !== undefined) {\n return value;\n }\n\n // Check flagDefaults first, then use provided defaultValue\n return this.config.flagDefaults?.[key] ?? defaultValue;\n }\n\n /**\n * Evaluate a feature gate with multiple flags\n */\n async evaluateGate(\n keys: string[],\n requirement: 'all' | 'any' = 'all',\n negate: boolean = false\n ): Promise<boolean> {\n if (keys.length === 0) {\n return !negate;\n }\n\n const flags = await this.getFlags();\n\n let isEnabled: boolean;\n\n if (requirement === 'any') {\n // At least one flag must be true\n isEnabled = keys.some((key) => flags[key] === true);\n } else {\n // All flags must be true\n isEnabled = keys.every((key) => flags[key] === true);\n }\n\n return negate ? !isEnabled : isEnabled;\n }\n}\n\n/**\n * Create a new Toggly server-side client instance\n */\nexport function createTogglyServerClient(config: TogglyConfig, isBuildTime: boolean = false): TogglyServer {\n return new TogglyServer(config, isBuildTime);\n}\n\n\n"],"mappings":"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","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createTogglyServerClient
3
- } from "./chunk-XQGKGTBK.js";
3
+ } from "./chunk-GEESG7MX.js";
4
4
 
5
5
  // src/integration/index.ts
6
6
  import * as fs from "fs";
@@ -14,10 +14,12 @@ function togglyIntegration(options = {}) {
14
14
  featureFlagsRefreshInterval: 3 * 60 * 1e3,
15
15
  isDebug: false,
16
16
  connectTimeout: 5 * 1e3,
17
+ allFeaturesEnabledDuringBuild: false,
17
18
  ...options
18
19
  };
19
20
  let pageFeatureMapping = {};
20
21
  let astroConfig;
22
+ let buildTimeClient = null;
21
23
  return {
22
24
  name: "@ops-ai/astro-feature-flags-toggly",
23
25
  hooks: {
@@ -26,10 +28,11 @@ function togglyIntegration(options = {}) {
26
28
  if (config.isDebug) {
27
29
  console.log("[Toggly Integration] Setting up integration...");
28
30
  }
31
+ const clientConfig = { ...config, allFeaturesEnabledDuringBuild: false };
29
32
  injectScript(
30
33
  "page",
31
34
  `
32
- window.__TOGGLY_CONFIG__ = ${JSON.stringify(config)};
35
+ window.__TOGGLY_CONFIG__ = ${JSON.stringify(clientConfig)};
33
36
  import('@ops-ai/astro-feature-flags-toggly/client/setup');
34
37
  `
35
38
  );
@@ -37,7 +40,29 @@ function togglyIntegration(options = {}) {
37
40
  vite: {
38
41
  ssr: {
39
42
  noExternal: ["@ops-ai/astro-feature-flags-toggly"]
40
- }
43
+ },
44
+ plugins: [
45
+ {
46
+ name: "toggly-x-feature-transform",
47
+ enforce: "pre",
48
+ load(id) {
49
+ if (!id.endsWith(".astro")) return null;
50
+ const code = fs.readFileSync(id, "utf-8");
51
+ const frontmatterMatch = code.match(/^(---\s*\n)([\s\S]*?)(\n---)/);
52
+ if (!frontmatterMatch) return null;
53
+ const frontmatter = frontmatterMatch[2];
54
+ if (!/^x-feature:\s*.+$/m.test(frontmatter)) return null;
55
+ const updatedFrontmatter = frontmatter.replace(
56
+ /^x-feature:\s*.+\n?/m,
57
+ ""
58
+ );
59
+ return code.replace(
60
+ frontmatterMatch[0],
61
+ frontmatterMatch[1] + updatedFrontmatter + frontmatterMatch[3]
62
+ );
63
+ }
64
+ }
65
+ ]
41
66
  }
42
67
  });
43
68
  },
@@ -45,7 +70,7 @@ function togglyIntegration(options = {}) {
45
70
  if (config.isDebug) {
46
71
  console.log("[Toggly Integration] Server setup...");
47
72
  }
48
- const togglyClient = createTogglyServerClient(config);
73
+ const togglyClient = createTogglyServerClient(config, false);
49
74
  server.middlewares.use((req, res, next) => {
50
75
  req.togglyClient = togglyClient;
51
76
  next();
@@ -55,6 +80,12 @@ function togglyIntegration(options = {}) {
55
80
  if (config.isDebug) {
56
81
  console.log("[Toggly Integration] Build started, extracting frontmatter...");
57
82
  }
83
+ if (config.allFeaturesEnabledDuringBuild) {
84
+ if (config.isDebug) {
85
+ console.log("[Toggly Integration] Build mode: All features will be enabled");
86
+ }
87
+ buildTimeClient = createTogglyServerClient(config, true);
88
+ }
58
89
  pageFeatureMapping = await extractPageFeatures(astroConfig, config.isDebug);
59
90
  if (config.isDebug) {
60
91
  console.log(
@@ -160,7 +191,7 @@ function convertFilePathToRoute(filePath, isPages) {
160
191
  function createTogglyMiddleware(config) {
161
192
  return async function togglyMiddleware({ locals }, next) {
162
193
  if (!locals.toggly) {
163
- locals.toggly = createTogglyServerClient(config);
194
+ locals.toggly = createTogglyServerClient(config, false);
164
195
  }
165
196
  return next();
166
197
  };
@@ -170,4 +201,4 @@ export {
170
201
  togglyIntegration,
171
202
  createTogglyMiddleware
172
203
  };
173
- //# sourceMappingURL=chunk-354E3C57.js.map
204
+ //# sourceMappingURL=chunk-IMMLLGGH.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 allFeaturesEnabledDuringBuild: false,\n ...options,\n };\n\n let pageFeatureMapping: PageFeatureMapping = {};\n let astroConfig: AstroConfig;\n let buildTimeClient: any = null;\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 // For client-side, we never want allFeaturesEnabledDuringBuild since that's only for SSG\n const clientConfig = { ...config, allFeaturesEnabledDuringBuild: false };\n injectScript(\n 'page',\n `\n window.__TOGGLY_CONFIG__ = ${JSON.stringify(clientConfig)};\n import('@ops-ai/astro-feature-flags-toggly/client/setup');\n `\n );\n\n // Add Vite plugin to strip x-feature directives before Astro's compiler\n updateConfig({\n vite: {\n ssr: {\n noExternal: ['@ops-ai/astro-feature-flags-toggly'],\n },\n plugins: [\n {\n name: 'toggly-x-feature-transform',\n enforce: 'pre' as const,\n load(id: string) {\n // Only process .astro files\n if (!id.endsWith('.astro')) return null;\n\n const code = fs.readFileSync(id, 'utf-8');\n\n // Check if frontmatter contains x-feature:\n const frontmatterMatch = code.match(/^(---\\s*\\n)([\\s\\S]*?)(\\n---)/);\n if (!frontmatterMatch) return null;\n\n const frontmatter = frontmatterMatch[2];\n if (!/^x-feature:\\s*.+$/m.test(frontmatter)) return null;\n\n // Strip the x-feature line entirely so esbuild doesn't choke on it\n const updatedFrontmatter = frontmatter.replace(\n /^x-feature:\\s*.+\\n?/m,\n ''\n );\n\n return code.replace(\n frontmatterMatch[0],\n frontmatterMatch[1] + updatedFrontmatter + frontmatterMatch[3]\n );\n },\n },\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/dev server\n // In dev mode, we don't enable all features - we use actual flags\n const togglyClient = createTogglyServerClient(config, false);\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 // If allFeaturesEnabledDuringBuild is true, create a build-time client\n // that will override all flags to true\n if (config.allFeaturesEnabledDuringBuild) {\n if (config.isDebug) {\n console.log('[Toggly Integration] Build mode: All features will be enabled');\n }\n // Create a build-time client that enables all features\n buildTimeClient = createTogglyServerClient(config, true);\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 // In middleware (runtime), we never enable all features - we use actual flags\n if (!locals.toggly) {\n locals.toggly = createTogglyServerClient(config, false);\n }\n\n return next();\n };\n}\n\n\n"],"mappings":"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","names":[]}
@@ -1,3 +1,3 @@
1
1
  export { initTogglyClient } from './store.js';
2
2
  import 'nanostores';
3
- import '../index-S3g0i0FH.js';
3
+ import '../index-CNGi0JrL.js';
@@ -1,6 +1,6 @@
1
1
  import * as nanostores from 'nanostores';
2
2
  import { ReadableAtom } from 'nanostores';
3
- import { T as TogglyConfig, F as Flags } from '../index-S3g0i0FH.js';
3
+ import { T as TogglyConfig, F as Flags } from '../index-CNGi0JrL.js';
4
4
 
5
5
  /**
6
6
  * Atom containing all feature flags
@@ -1,7 +1,7 @@
1
1
  import * as svelte_store from 'svelte/store';
2
2
  export { $flags as flags, $isReady as isReady } from '../../client/store.js';
3
3
  import 'nanostores';
4
- import '../../index-S3g0i0FH.js';
4
+ import '../../index-CNGi0JrL.js';
5
5
 
6
6
  /**
7
7
  * Create a derived store for a specific feature flag
@@ -21,6 +21,14 @@ interface TogglyConfig {
21
21
  connectTimeout?: number;
22
22
  /** User identity for targeting (optional) */
23
23
  identity?: string;
24
+ /**
25
+ * When true, all features are enabled during build time (SSG).
26
+ * This is useful when you have an edge worker (like Cloudflare Worker) that
27
+ * filters content based on feature flags at runtime.
28
+ * During dev server, actual feature flags from the API are still used.
29
+ * (default: false)
30
+ */
31
+ allFeaturesEnabledDuringBuild?: boolean;
24
32
  }
25
33
  /**
26
34
  * Map of feature flag keys to their boolean values
package/dist/index.d.ts CHANGED
@@ -2,6 +2,6 @@ export { TogglyIntegrationOptions, createTogglyMiddleware, default as togglyInte
2
2
  export { TogglyServer, createTogglyServerClient } from './server/toggly-server.js';
3
3
  export { allFeaturesEnabled, anyFeatureEnabled, getTogglyFromAstroGlobal, withFeatureFlag } from './server/utils.js';
4
4
  export { $error, $flag, $flags, $gate, $isReady, clearIdentity, initTogglyClient, refreshFlags, setIdentity, stopRefreshInterval } from './client/store.js';
5
- export { c as FeatureClientProps, b as FeatureProps, F as Flags, P as PageFeatureMapping, a as TogglyClient, T as TogglyConfig } from './index-S3g0i0FH.js';
5
+ export { c as FeatureClientProps, b as FeatureProps, F as Flags, P as PageFeatureMapping, a as TogglyClient, T as TogglyConfig } from './index-CNGi0JrL.js';
6
6
  import 'astro';
7
7
  import 'nanostores';
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  createTogglyMiddleware,
3
3
  togglyIntegration
4
- } from "./chunk-354E3C57.js";
4
+ } from "./chunk-IMMLLGGH.js";
5
5
  import {
6
6
  TogglyServer,
7
7
  createTogglyServerClient
8
- } from "./chunk-XQGKGTBK.js";
8
+ } from "./chunk-GEESG7MX.js";
9
9
  import {
10
10
  allFeaturesEnabled,
11
11
  anyFeatureEnabled,
@@ -1,5 +1,5 @@
1
1
  import { AstroIntegration } from 'astro';
2
- import { T as TogglyConfig } from '../index-S3g0i0FH.js';
2
+ import { T as TogglyConfig } from '../index-CNGi0JrL.js';
3
3
 
4
4
  /**
5
5
  * Toggly Astro Integration
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  createTogglyMiddleware,
3
3
  togglyIntegration
4
- } from "../chunk-354E3C57.js";
5
- import "../chunk-XQGKGTBK.js";
4
+ } from "../chunk-IMMLLGGH.js";
5
+ import "../chunk-GEESG7MX.js";
6
6
  export {
7
7
  createTogglyMiddleware,
8
8
  togglyIntegration as default
@@ -1,4 +1,4 @@
1
- import { T as TogglyConfig, a as TogglyClient, F as Flags } from '../index-S3g0i0FH.js';
1
+ import { T as TogglyConfig, a as TogglyClient, F as Flags } from '../index-CNGi0JrL.js';
2
2
 
3
3
  /**
4
4
  * Toggly Server-Side Client for Astro SSR/SSG
@@ -15,7 +15,8 @@ declare class TogglyServer implements TogglyClient {
15
15
  private config;
16
16
  private cache;
17
17
  private fetchPromise;
18
- constructor(config: TogglyConfig);
18
+ private isBuildTime;
19
+ constructor(config: TogglyConfig, isBuildTime?: boolean);
19
20
  /**
20
21
  * Get API URL for fetching flags
21
22
  */
@@ -48,6 +49,6 @@ declare class TogglyServer implements TogglyClient {
48
49
  /**
49
50
  * Create a new Toggly server-side client instance
50
51
  */
51
- declare function createTogglyServerClient(config: TogglyConfig): TogglyServer;
52
+ declare function createTogglyServerClient(config: TogglyConfig, isBuildTime?: boolean): TogglyServer;
52
53
 
53
54
  export { TogglyServer, createTogglyServerClient };
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  TogglyServer,
3
3
  createTogglyServerClient
4
- } from "../chunk-XQGKGTBK.js";
4
+ } from "../chunk-GEESG7MX.js";
5
5
  export {
6
6
  TogglyServer,
7
7
  createTogglyServerClient
@@ -1,5 +1,5 @@
1
1
  import { AstroGlobal } from 'astro';
2
- import { a as TogglyClient } from '../index-S3g0i0FH.js';
2
+ import { a as TogglyClient } from '../index-CNGi0JrL.js';
3
3
 
4
4
  /**
5
5
  * Toggly Server Utilities for Astro
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ops-ai/astro-feature-flags-toggly",
3
- "version": "1.0.6",
3
+ "version": "1.2.0",
4
4
  "description": "Toggly feature flags SDK for Astro with SSR, SSG, and framework support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -48,7 +48,10 @@
48
48
  "copy-components": "mkdir -p dist/components dist/frameworks/vue dist/frameworks/svelte && cp src/components/*.astro dist/components/ && cp src/frameworks/vue/*.vue dist/frameworks/vue/ && cp src/frameworks/svelte/*.svelte dist/frameworks/svelte/",
49
49
  "dev": "tsup --watch",
50
50
  "typecheck": "tsc --noEmit",
51
- "clean": "rm -rf dist"
51
+ "clean": "rm -rf dist",
52
+ "test": "vitest run",
53
+ "test:watch": "vitest",
54
+ "test:coverage": "vitest run --coverage"
52
55
  },
53
56
  "keywords": [
54
57
  "astro",
@@ -73,11 +76,14 @@
73
76
  "@nanostores/vue": "^1.0.1",
74
77
  "@types/node": "^20.11.0",
75
78
  "@types/react": "^19.2.7",
79
+ "@vitest/coverage-v8": "^1.0.0",
76
80
  "astro": "^5.16.0",
81
+ "jsdom": "^23.0.0",
77
82
  "react": "^19.2.3",
78
83
  "svelte": "^5.46.1",
79
84
  "tsup": "^8.0.0",
80
85
  "typescript": "^5.3.3",
86
+ "vitest": "^1.0.0",
81
87
  "vue": "^3.5.26"
82
88
  },
83
89
  "peerDependencies": {
@@ -1 +0,0 @@
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":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/server/toggly-server.ts"],"sourcesContent":["/**\n * Toggly Server-Side Client for Astro SSR/SSG\n * \n * This module provides server-side feature flag evaluation for Astro applications.\n * It caches flags for SSG builds and fetches fresh flags for SSR requests.\n * This is a complete, embedded Toggly client implementation.\n */\n\nimport type { TogglyConfig, Flags, TogglyClient } from '../types/index.js';\n\ninterface CachedFlags {\n flags: Flags;\n timestamp: number;\n}\n\n/**\n * Server-side Toggly client implementation\n */\nexport class TogglyServer implements TogglyClient {\n private config: TogglyConfig;\n private cache: CachedFlags | null = null;\n private fetchPromise: Promise<Flags> | null = null;\n\n constructor(config: TogglyConfig) {\n this.config = {\n baseURI: 'https://client.toggly.io',\n environment: 'Production',\n flagDefaults: {},\n featureFlagsRefreshInterval: 3 * 60 * 1000, // 3 minutes\n isDebug: false,\n connectTimeout: 5 * 1000, // 5 seconds\n ...config,\n };\n }\n\n /**\n * Get API URL for fetching flags\n */\n private getApiUrl(): string {\n const { baseURI, appKey, environment, identity } = this.config;\n\n if (!appKey) {\n return '';\n }\n\n const baseUrl = baseURI!.replace(/\\/$/, '');\n let url = `${baseUrl}/${appKey}-${environment}/defs`;\n\n if (identity) {\n url += `?u=${encodeURIComponent(identity)}`;\n }\n\n return url;\n }\n\n /**\n * Check if cache is valid\n */\n private isCacheValid(): boolean {\n if (!this.cache) return false;\n const age = Date.now() - this.cache.timestamp;\n return age < this.config.featureFlagsRefreshInterval!;\n }\n\n /**\n * Fetch flags from Toggly API\n */\n private async fetchFlags(): Promise<Flags> {\n const url = this.getApiUrl();\n\n // If no appKey, return flagDefaults\n if (!url || !this.config.appKey) {\n if (this.config.isDebug) {\n console.log('[Toggly Server] Using flag defaults (no appKey):', this.config.flagDefaults);\n }\n return { ...this.config.flagDefaults! };\n }\n\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.config.connectTimeout);\n\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n Accept: 'application/json',\n },\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n throw new Error(\n `Failed to fetch flags from Toggly API: ${response.status} ${response.statusText}`\n );\n }\n\n const flags = (await response.json()) as Flags;\n\n if (this.config.isDebug) {\n console.log('[Toggly Server] Fetched flags:', flags);\n }\n\n return flags;\n } catch (error) {\n if (this.config.isDebug) {\n console.error('[Toggly Server] Error fetching flags:', error);\n }\n\n // On error, try to use cached flags, otherwise use flagDefaults\n if (this.cache) {\n if (this.config.isDebug) {\n console.log('[Toggly Server] Using cached flags:', this.cache.flags);\n }\n return { ...this.cache.flags };\n }\n\n if (this.config.isDebug) {\n console.log('[Toggly Server] Using flag defaults:', this.config.flagDefaults);\n }\n\n return { ...this.config.flagDefaults! };\n }\n }\n\n /**\n * Refresh flags cache\n */\n async refreshFlags(): Promise<void> {\n if (this.config.isDebug) {\n console.log('[Toggly Server] Refreshing flags...');\n }\n\n // Prevent multiple concurrent fetches\n if (this.fetchPromise) {\n await this.fetchPromise;\n return;\n }\n\n this.fetchPromise = this.fetchFlags();\n\n try {\n const flags = await this.fetchPromise;\n this.cache = {\n flags,\n timestamp: Date.now(),\n };\n } finally {\n this.fetchPromise = null;\n }\n }\n\n /**\n * Get all feature flags\n */\n async getFlags(): Promise<Flags> {\n // If no appKey, return flagDefaults immediately\n if (!this.config.appKey) {\n return { ...this.config.flagDefaults! };\n }\n\n // If cache is valid, return it\n if (this.isCacheValid() && this.cache) {\n return { ...this.cache.flags };\n }\n\n // Otherwise, refresh and return\n await this.refreshFlags();\n return this.cache ? { ...this.cache.flags } : { ...this.config.flagDefaults! };\n }\n\n /**\n * Get a single feature flag value\n */\n async getFlag(key: string, defaultValue: boolean = false): Promise<boolean> {\n const flags = await this.getFlags();\n const value = flags[key];\n\n if (value !== undefined) {\n return value;\n }\n\n // Check flagDefaults first, then use provided defaultValue\n return this.config.flagDefaults?.[key] ?? defaultValue;\n }\n\n /**\n * Evaluate a feature gate with multiple flags\n */\n async evaluateGate(\n keys: string[],\n requirement: 'all' | 'any' = 'all',\n negate: boolean = false\n ): Promise<boolean> {\n if (keys.length === 0) {\n return !negate;\n }\n\n const flags = await this.getFlags();\n\n let isEnabled: boolean;\n\n if (requirement === 'any') {\n // At least one flag must be true\n isEnabled = keys.some((key) => flags[key] === true);\n } else {\n // All flags must be true\n isEnabled = keys.every((key) => flags[key] === true);\n }\n\n return negate ? !isEnabled : isEnabled;\n }\n}\n\n/**\n * Create a new Toggly server-side client instance\n */\nexport function createTogglyServerClient(config: TogglyConfig): TogglyServer {\n return new TogglyServer(config);\n}\n\n\n"],"mappings":"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","names":[]}