@squide/firefly-rsbuild-storybook 0.0.1 → 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/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @squide/firefly-rsbuild-storybook
2
2
 
3
+ ## 1.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - [#352](https://github.com/workleap/wl-squide/pull/352) [`7bd7af5`](https://github.com/workleap/wl-squide/commit/7bd7af5a757912309704b9e93bf8ecf1388e6787) Thanks [@patricklafrance](https://github.com/patricklafrance)! - Added the `withFeatureFlagOverrideDecorator` hook and first major release.
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [[`7bd7af5`](https://github.com/workleap/wl-squide/commit/7bd7af5a757912309704b9e93bf8ecf1388e6787), [`7bd7af5`](https://github.com/workleap/wl-squide/commit/7bd7af5a757912309704b9e93bf8ecf1388e6787), [`7bd7af5`](https://github.com/workleap/wl-squide/commit/7bd7af5a757912309704b9e93bf8ecf1388e6787)]:
12
+ - @squide/env-vars@1.4.10
13
+ - @squide/msw@4.0.8
14
+ - @squide/launch-darkly@1.0.0
15
+ - @squide/firefly@16.1.0
16
+
3
17
  ## 0.0.1
4
18
 
5
19
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export { initializeFireflyForStorybook, type InitializeFireflyForStorybookOptions } from "./initializeFireflyForStorybook.ts";
2
2
  export { StorybookRuntime, StorybookRuntimeScope } from "./StorybookRuntime.ts";
3
+ export { withFeatureFlagsOverrideDecorator } from "./withFeatureFlagsOverrideDecorator.tsx";
3
4
  export { FireflyDecorator, withFireflyDecorator, type FireflyDecoratorProps } from "./withFireflyDecorator.tsx";
package/dist/index.js CHANGED
@@ -1,11 +1,14 @@
1
1
  import { initializeFireflyForStorybook } from "./initializeFireflyForStorybook.js";
2
2
  import { StorybookRuntime, StorybookRuntimeScope } from "./StorybookRuntime.js";
3
+ import { withFeatureFlagsOverrideDecorator } from "./withFeatureFlagsOverrideDecorator.js";
3
4
  import { FireflyDecorator, withFireflyDecorator } from "./withFireflyDecorator.js";
4
5
 
5
6
  ;// CONCATENATED MODULE: external "./initializeFireflyForStorybook.js"
6
7
 
7
8
  ;// CONCATENATED MODULE: external "./StorybookRuntime.js"
8
9
 
10
+ ;// CONCATENATED MODULE: external "./withFeatureFlagsOverrideDecorator.js"
11
+
9
12
  ;// CONCATENATED MODULE: external "./withFireflyDecorator.js"
10
13
 
11
14
  ;// CONCATENATED MODULE: ./src/index.ts
@@ -13,6 +16,7 @@ import { FireflyDecorator, withFireflyDecorator } from "./withFireflyDecorator.j
13
16
 
14
17
 
15
18
 
16
- export { FireflyDecorator, StorybookRuntime, StorybookRuntimeScope, initializeFireflyForStorybook, withFireflyDecorator };
19
+
20
+ export { FireflyDecorator, StorybookRuntime, StorybookRuntimeScope, initializeFireflyForStorybook, withFeatureFlagsOverrideDecorator, withFireflyDecorator };
17
21
 
18
22
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["export { initializeFireflyForStorybook, type InitializeFireflyForStorybookOptions } from \"./initializeFireflyForStorybook.ts\";\nexport { StorybookRuntime, StorybookRuntimeScope } from \"./StorybookRuntime.ts\";\nexport { FireflyDecorator, withFireflyDecorator, type FireflyDecoratorProps } from \"./withFireflyDecorator.tsx\";\n\n"],"names":["initializeFireflyForStorybook","StorybookRuntime","StorybookRuntimeScope","FireflyDecorator","withFireflyDecorator"],"mappings":";;;;;;;;;;;AAA8H;AAC9C;AACgC"}
1
+ {"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["export { initializeFireflyForStorybook, type InitializeFireflyForStorybookOptions } from \"./initializeFireflyForStorybook.ts\";\nexport { StorybookRuntime, StorybookRuntimeScope } from \"./StorybookRuntime.ts\";\nexport { withFeatureFlagsOverrideDecorator } from \"./withFeatureFlagsOverrideDecorator.tsx\";\nexport { FireflyDecorator, withFireflyDecorator, type FireflyDecoratorProps } from \"./withFireflyDecorator.tsx\";\n\n"],"names":["initializeFireflyForStorybook","StorybookRuntime","StorybookRuntimeScope","withFeatureFlagsOverrideDecorator","FireflyDecorator","withFireflyDecorator"],"mappings":";;;;;;;;;;;;;;AAA8H;AAC9C;AACY;AACoB"}
@@ -1,8 +1,11 @@
1
1
  import { EnvironmentVariables } from "@squide/env-vars";
2
2
  import { FireflyRuntime, ModuleRegisterFunction } from "@squide/firefly";
3
+ import { LDClient, LDFlagValue } from "launchdarkly-js-client-sdk";
3
4
  import { StorybookRuntime } from "./StorybookRuntime.ts";
4
5
  export interface InitializeFireflyForStorybookOptions {
5
6
  localModules?: ModuleRegisterFunction<FireflyRuntime>[];
6
7
  environmentVariables?: EnvironmentVariables;
8
+ featureFlags?: Map<string, LDFlagValue> | Record<string, LDFlagValue>;
9
+ launchDarklyClient?: LDClient;
7
10
  }
8
11
  export declare function initializeFireflyForStorybook(options?: InitializeFireflyForStorybookOptions): Promise<StorybookRuntime>;
@@ -1,5 +1,5 @@
1
1
  import { EnvironmentVariablesPlugin } from "@squide/env-vars";
2
- import { toLocalModuleDefinitions } from "@squide/firefly";
2
+ import { InMemoryLaunchDarklyClient, LaunchDarklyPlugin, toLocalModuleDefinitions } from "@squide/firefly";
3
3
  import { MswPlugin } from "@squide/msw";
4
4
  import { StorybookRuntime } from "./StorybookRuntime.js";
5
5
 
@@ -17,19 +17,25 @@ import { StorybookRuntime } from "./StorybookRuntime.js";
17
17
 
18
18
 
19
19
  async function initializeFireflyForStorybook(options = {}) {
20
- const { localModules = [], environmentVariables = {} } = options;
20
+ const { localModules, environmentVariables, featureFlags = {}, launchDarklyClient } = options;
21
21
  const runtime = new StorybookRuntime({
22
22
  mode: "development",
23
23
  plugins: [
24
24
  (x)=>new MswPlugin(x),
25
25
  (x)=>new EnvironmentVariablesPlugin(x, {
26
26
  variables: environmentVariables
27
- })
27
+ }),
28
+ (x)=>new LaunchDarklyPlugin(x, // 1- If no client is provided create it.
29
+ // 2- If feature flags are provided and it's an instance of Map, use the argument.
30
+ // 3- If feature flags are provided (or the default value) and it's an object literal, convert the object to a Map instance.
31
+ launchDarklyClient ?? new InMemoryLaunchDarklyClient(featureFlags instanceof Map ? featureFlags : new Map(Object.entries(featureFlags))))
28
32
  ]
29
33
  });
30
- await runtime.moduleManager.registerModules([
31
- ...toLocalModuleDefinitions(localModules)
32
- ]);
34
+ if (localModules && localModules.length > 0) {
35
+ await runtime.moduleManager.registerModules([
36
+ ...toLocalModuleDefinitions(localModules)
37
+ ]);
38
+ }
33
39
  return runtime;
34
40
  }
35
41
 
@@ -1 +1 @@
1
- {"version":3,"file":"initializeFireflyForStorybook.js","sources":["../src/initializeFireflyForStorybook.ts"],"sourcesContent":["import { EnvironmentVariables, EnvironmentVariablesPlugin } from \"@squide/env-vars\";\nimport { FireflyRuntime, ModuleRegisterFunction, toLocalModuleDefinitions } from \"@squide/firefly\";\nimport { MswPlugin } from \"@squide/msw\";\nimport { StorybookRuntime } from \"./StorybookRuntime.ts\";\n\nexport interface InitializeFireflyForStorybookOptions {\n localModules?: ModuleRegisterFunction<FireflyRuntime>[];\n environmentVariables?: EnvironmentVariables;\n}\n\nexport async function initializeFireflyForStorybook(options: InitializeFireflyForStorybookOptions = {}) {\n const {\n localModules = [],\n environmentVariables = {}\n } = options;\n\n const runtime = new StorybookRuntime({\n mode: \"development\",\n plugins: [\n x => new MswPlugin(x),\n x => new EnvironmentVariablesPlugin(x, {\n variables: environmentVariables\n })\n ]\n });\n\n await runtime.moduleManager.registerModules([\n ...toLocalModuleDefinitions(localModules)\n ]);\n\n return runtime;\n}\n"],"names":["EnvironmentVariablesPlugin","toLocalModuleDefinitions","MswPlugin","StorybookRuntime","initializeFireflyForStorybook","options","localModules","environmentVariables","runtime","x"],"mappings":";;;;;;;;;;;;;;AAAoF;AACe;AAC3D;AACiB;AAOlD,eAAeI,8BAA8BC,UAAgD,CAAC,CAAC;IAClG,MAAM,EACFC,eAAe,EAAE,EACjBC,uBAAuB,CAAC,CAAC,EAC5B,GAAGF;IAEJ,MAAMG,UAAU,IAAIL,gBAAgBA,CAAC;QACjC,MAAM;QACN,SAAS;YACLM,CAAAA,IAAK,IAAIP,SAASA,CAACO;YACnBA,CAAAA,IAAK,IAAIT,0BAA0BA,CAACS,GAAG;oBACnC,WAAWF;gBACf;SACH;IACL;IAEA,MAAMC,QAAQ,aAAa,CAAC,eAAe,CAAC;WACrCP,wBAAwBA,CAACK;KAC/B;IAED,OAAOE;AACX"}
1
+ {"version":3,"file":"initializeFireflyForStorybook.js","sources":["../src/initializeFireflyForStorybook.ts"],"sourcesContent":["import { EnvironmentVariables, EnvironmentVariablesPlugin } from \"@squide/env-vars\";\nimport { FireflyRuntime, InMemoryLaunchDarklyClient, LaunchDarklyPlugin, ModuleRegisterFunction, toLocalModuleDefinitions } from \"@squide/firefly\";\nimport { MswPlugin } from \"@squide/msw\";\nimport { LDClient, LDFlagValue } from \"launchdarkly-js-client-sdk\";\nimport { StorybookRuntime } from \"./StorybookRuntime.ts\";\n\nexport interface InitializeFireflyForStorybookOptions {\n localModules?: ModuleRegisterFunction<FireflyRuntime>[];\n environmentVariables?: EnvironmentVariables;\n featureFlags?: Map<string, LDFlagValue> | Record<string, LDFlagValue>;\n launchDarklyClient?: LDClient;\n}\n\nexport async function initializeFireflyForStorybook(options: InitializeFireflyForStorybookOptions = {}) {\n const {\n localModules,\n environmentVariables,\n featureFlags = {},\n launchDarklyClient\n } = options;\n\n const runtime = new StorybookRuntime({\n mode: \"development\",\n plugins: [\n x => new MswPlugin(x),\n x => new EnvironmentVariablesPlugin(x, {\n variables: environmentVariables\n }),\n x => new LaunchDarklyPlugin(\n x,\n // 1- If no client is provided create it.\n // 2- If feature flags are provided and it's an instance of Map, use the argument.\n // 3- If feature flags are provided (or the default value) and it's an object literal, convert the object to a Map instance.\n launchDarklyClient ?? new InMemoryLaunchDarklyClient(featureFlags instanceof Map\n ? featureFlags\n : new Map<string, LDFlagValue>(Object.entries(featureFlags))\n )\n )\n ]\n });\n\n if (localModules && localModules.length > 0) {\n await runtime.moduleManager.registerModules([\n ...toLocalModuleDefinitions(localModules)\n ]);\n }\n\n return runtime;\n}\n"],"names":["EnvironmentVariablesPlugin","InMemoryLaunchDarklyClient","LaunchDarklyPlugin","toLocalModuleDefinitions","MswPlugin","StorybookRuntime","initializeFireflyForStorybook","options","localModules","environmentVariables","featureFlags","launchDarklyClient","runtime","x","Map","Object"],"mappings":";;;;;;;;;;;;;;AAAoF;AAC+D;AAC3G;AAEiB;AASlD,eAAeM,8BAA8BC,UAAgD,CAAC,CAAC;IAClG,MAAM,EACFC,YAAY,EACZC,oBAAoB,EACpBC,eAAe,CAAC,CAAC,EACjBC,kBAAkB,EACrB,GAAGJ;IAEJ,MAAMK,UAAU,IAAIP,gBAAgBA,CAAC;QACjC,MAAM;QACN,SAAS;YACLQ,CAAAA,IAAK,IAAIT,SAASA,CAACS;YACnBA,CAAAA,IAAK,IAAIb,0BAA0BA,CAACa,GAAG;oBACnC,WAAWJ;gBACf;YACAI,CAAAA,IAAK,IAAIX,kBAAkBA,CACvBW,GACA,yCAAyC;gBACzC,kFAAkF;gBAClF,4HAA4H;gBAC5HF,sBAAsB,IAAIV,0BAA0BA,CAACS,wBAAwBI,MACvEJ,eACA,IAAII,IAAyBC,OAAO,OAAO,CAACL;SAGzD;IACL;IAEA,IAAIF,gBAAgBA,aAAa,MAAM,GAAG,GAAG;QACzC,MAAMI,QAAQ,aAAa,CAAC,eAAe,CAAC;eACrCT,wBAAwBA,CAACK;SAC/B;IACL;IAEA,OAAOI;AACX"}
@@ -0,0 +1,3 @@
1
+ import { LDFlagValue } from "launchdarkly-js-client-sdk";
2
+ import { Decorator } from "storybook-react-rsbuild";
3
+ export declare function withFeatureFlagsOverrideDecorator<const T extends string>(featureFlags: Map<T, LDFlagValue>, overrides: Partial<Record<T, LDFlagValue>>): Decorator;
@@ -0,0 +1,47 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { useLayoutEffect } from "react";
3
+
4
+ ;// CONCATENATED MODULE: external "react/jsx-runtime"
5
+
6
+ ;// CONCATENATED MODULE: external "react"
7
+
8
+ ;// CONCATENATED MODULE: ./src/withFeatureFlagsOverrideDecorator.tsx
9
+
10
+
11
+ function OverrideFeatureFlags(props) {
12
+ const { featureFlags, overrides, children } = props;
13
+ // Must use a layout effect to override the feature flags before the story renders.
14
+ useLayoutEffect(()=>{
15
+ const originalValues = {};
16
+ Object.entries(overrides).forEach(([key, value])=>{
17
+ originalValues[key] = featureFlags.get(key);
18
+ featureFlags.set(key, value);
19
+ });
20
+ return ()=>{
21
+ // Reset the feature flags to the original values.
22
+ Object.entries(originalValues).forEach(([key, value])=>{
23
+ featureFlags.set(key, value);
24
+ });
25
+ };
26
+ }, [
27
+ featureFlags,
28
+ overrides
29
+ ]);
30
+ return children;
31
+ }
32
+ function withFeatureFlagsOverrideDecorator(featureFlags, overrides) {
33
+ if (!(featureFlags instanceof Map)) {
34
+ throw new TypeError("[squide] The \"featureFlags\" argument must be a Map instance.");
35
+ }
36
+ return (story)=>{
37
+ return /*#__PURE__*/ jsx(OverrideFeatureFlags, {
38
+ featureFlags: featureFlags,
39
+ overrides: overrides,
40
+ children: story()
41
+ });
42
+ };
43
+ }
44
+
45
+ export { withFeatureFlagsOverrideDecorator };
46
+
47
+ //# sourceMappingURL=withFeatureFlagsOverrideDecorator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"withFeatureFlagsOverrideDecorator.js","sources":["../src/withFeatureFlagsOverrideDecorator.tsx"],"sourcesContent":["import { LDFlagValue } from \"launchdarkly-js-client-sdk\";\nimport { PropsWithChildren, useLayoutEffect } from \"react\";\nimport { Decorator } from \"storybook-react-rsbuild\";\n\ninterface OverrideFeatureFlagsProps extends PropsWithChildren {\n featureFlags: Map<string, LDFlagValue>;\n overrides: Record<string, LDFlagValue>;\n}\n\nfunction OverrideFeatureFlags(props: OverrideFeatureFlagsProps) {\n const {\n featureFlags,\n overrides,\n children\n } = props;\n\n // Must use a layout effect to override the feature flags before the story renders.\n useLayoutEffect(() => {\n const originalValues: Record<string, LDFlagValue> = {};\n\n Object.entries(overrides).forEach(([key, value]) => {\n originalValues[key] = featureFlags.get(key);\n featureFlags.set(key, value);\n });\n\n return () => {\n // Reset the feature flags to the original values.\n Object.entries(originalValues).forEach(([key, value]) => {\n featureFlags.set(key, value);\n });\n };\n }, [featureFlags, overrides]);\n\n return children;\n}\n\nexport function withFeatureFlagsOverrideDecorator<const T extends string>(featureFlags: Map<T, LDFlagValue>, overrides: Partial<Record<T, LDFlagValue>>): Decorator {\n if (!(featureFlags instanceof Map)) {\n throw new TypeError(\"[squide] The \\\"featureFlags\\\" argument must be a Map instance.\");\n }\n\n return story => {\n return (\n <OverrideFeatureFlags featureFlags={featureFlags} overrides={overrides}>\n {story()}\n </OverrideFeatureFlags>\n );\n };\n}\n"],"names":["useLayoutEffect","OverrideFeatureFlags","props","featureFlags","overrides","children","originalValues","Object","key","value","withFeatureFlagsOverrideDecorator","Map","TypeError","story"],"mappings":";;;;;;;;;AAC2D;AAQ3D,SAASC,qBAAqBC,KAAgC;IAC1D,MAAM,EACFC,YAAY,EACZC,SAAS,EACTC,QAAQ,EACX,GAAGH;IAEJ,mFAAmF;IACnFF,eAAeA,CAAC;QACZ,MAAMM,iBAA8C,CAAC;QAErDC,OAAO,OAAO,CAACH,WAAW,OAAO,CAAC,CAAC,CAACI,KAAKC,MAAM;YAC3CH,cAAc,CAACE,IAAI,GAAGL,aAAa,GAAG,CAACK;YACvCL,aAAa,GAAG,CAACK,KAAKC;QAC1B;QAEA,OAAO;YACH,kDAAkD;YAClDF,OAAO,OAAO,CAACD,gBAAgB,OAAO,CAAC,CAAC,CAACE,KAAKC,MAAM;gBAChDN,aAAa,GAAG,CAACK,KAAKC;YAC1B;QACJ;IACJ,GAAG;QAACN;QAAcC;KAAU;IAE5B,OAAOC;AACX;AAEO,SAASK,kCAA0DP,YAAiC,EAAEC,SAA0C;IACnJ,IAAI,CAAED,CAAAA,wBAAwBQ,GAAE,GAAI;QAChC,MAAM,IAAIC,UAAU;IACxB;IAEA,OAAOC,CAAAA;QACH,qBACI,IAACZ;YAAqB,cAAcE;YAAc,WAAWC;sBACxDS;;IAGb;AACJ"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@squide/firefly-rsbuild-storybook",
3
3
  "author": "Workleap",
4
- "version": "0.0.1",
4
+ "version": "1.0.0",
5
5
  "description": "Squide firefly helpers for Storybook and Rsbuild.",
6
6
  "license": "Apache-2.0",
7
7
  "repository": {
@@ -28,14 +28,15 @@
28
28
  ],
29
29
  "peerDependencies": {
30
30
  "@opentelemetry/api": "^1.9.0",
31
- "@rsbuild/core": "^1.6.9",
32
- "@tanstack/react-query": "^5.90.11",
33
- "msw": "^2.12.3",
31
+ "@rsbuild/core": "^1.6.14",
32
+ "@tanstack/react-query": "^5.90.12",
33
+ "launchdarkly-js-client-sdk": "^3.9.0",
34
+ "msw": "^2.12.4",
34
35
  "react": "^18.0.0 || ^19.0.0",
35
36
  "react-dom": "^18.0.0 || ^19.0.0",
36
- "react-router": "^7.9.6",
37
- "storybook": "^10.1.0",
38
- "storybook-react-rsbuild": "^3.0.0"
37
+ "react-router": "^7.10.1",
38
+ "storybook": "^10.1.6",
39
+ "storybook-react-rsbuild": "^3.1.0"
39
40
  },
40
41
  "peerDependenciesMeta": {
41
42
  "@opentelemetry/api": {
@@ -43,30 +44,35 @@
43
44
  }
44
45
  },
45
46
  "dependencies": {
46
- "@workleap-telemetry/core": "^1.0.4",
47
+ "@workleap-telemetry/core": "^1.0.5",
47
48
  "@workleap/logging": "^1.3.2",
48
- "@squide/env-vars": "1.4.9",
49
- "@squide/firefly": "16.0.3",
50
- "@squide/msw": "4.0.7"
49
+ "@squide/env-vars": "1.4.10",
50
+ "@squide/firefly": "16.1.0",
51
+ "@squide/launch-darkly": "1.0.0",
52
+ "@squide/msw": "4.0.8"
51
53
  },
52
54
  "devDependencies": {
53
55
  "@eslint/js": "9.39.1",
54
- "@rsbuild/core": "1.6.9",
55
- "@rslib/core": "0.18.2",
56
+ "@rsbuild/core": "1.6.14",
57
+ "@rslib/core": "0.18.4",
56
58
  "@types/react": "19.2.7",
57
59
  "@types/react-dom": "19.2.3",
58
- "@typescript-eslint/parser": "8.48.0",
59
- "@workleap/eslint-configs": "1.1.5",
60
+ "@typescript-eslint/parser": "8.49.0",
61
+ "@vitejs/plugin-react": "5.1.2",
62
+ "@workleap/eslint-configs": "1.1.6",
60
63
  "@workleap/rslib-configs": "1.1.3",
61
64
  "@workleap/typescript-configs": "3.0.7",
62
65
  "eslint": "9.39.1",
66
+ "happy-dom": "20.0.11",
63
67
  "typescript": "5.9.3",
64
- "typescript-eslint": "8.48.0"
68
+ "typescript-eslint": "8.49.0",
69
+ "vitest": "4.0.15"
65
70
  },
66
71
  "sideEffects": false,
67
72
  "scripts": {
68
73
  "build": "rslib build --config ./rslib.build.ts",
69
74
  "eslint": "eslint . --max-warnings=0 --cache --cache-location node_modules/.cache/eslint",
70
- "typecheck": "tsc"
75
+ "typecheck": "tsc",
76
+ "test": "vitest --config vitest.config.ts --no-watch"
71
77
  }
72
78
  }
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { initializeFireflyForStorybook, type InitializeFireflyForStorybookOptions } from "./initializeFireflyForStorybook.ts";
2
2
  export { StorybookRuntime, StorybookRuntimeScope } from "./StorybookRuntime.ts";
3
+ export { withFeatureFlagsOverrideDecorator } from "./withFeatureFlagsOverrideDecorator.tsx";
3
4
  export { FireflyDecorator, withFireflyDecorator, type FireflyDecoratorProps } from "./withFireflyDecorator.tsx";
4
5
 
@@ -1,17 +1,22 @@
1
1
  import { EnvironmentVariables, EnvironmentVariablesPlugin } from "@squide/env-vars";
2
- import { FireflyRuntime, ModuleRegisterFunction, toLocalModuleDefinitions } from "@squide/firefly";
2
+ import { FireflyRuntime, InMemoryLaunchDarklyClient, LaunchDarklyPlugin, ModuleRegisterFunction, toLocalModuleDefinitions } from "@squide/firefly";
3
3
  import { MswPlugin } from "@squide/msw";
4
+ import { LDClient, LDFlagValue } from "launchdarkly-js-client-sdk";
4
5
  import { StorybookRuntime } from "./StorybookRuntime.ts";
5
6
 
6
7
  export interface InitializeFireflyForStorybookOptions {
7
8
  localModules?: ModuleRegisterFunction<FireflyRuntime>[];
8
9
  environmentVariables?: EnvironmentVariables;
10
+ featureFlags?: Map<string, LDFlagValue> | Record<string, LDFlagValue>;
11
+ launchDarklyClient?: LDClient;
9
12
  }
10
13
 
11
14
  export async function initializeFireflyForStorybook(options: InitializeFireflyForStorybookOptions = {}) {
12
15
  const {
13
- localModules = [],
14
- environmentVariables = {}
16
+ localModules,
17
+ environmentVariables,
18
+ featureFlags = {},
19
+ launchDarklyClient
15
20
  } = options;
16
21
 
17
22
  const runtime = new StorybookRuntime({
@@ -20,13 +25,25 @@ export async function initializeFireflyForStorybook(options: InitializeFireflyFo
20
25
  x => new MswPlugin(x),
21
26
  x => new EnvironmentVariablesPlugin(x, {
22
27
  variables: environmentVariables
23
- })
28
+ }),
29
+ x => new LaunchDarklyPlugin(
30
+ x,
31
+ // 1- If no client is provided create it.
32
+ // 2- If feature flags are provided and it's an instance of Map, use the argument.
33
+ // 3- If feature flags are provided (or the default value) and it's an object literal, convert the object to a Map instance.
34
+ launchDarklyClient ?? new InMemoryLaunchDarklyClient(featureFlags instanceof Map
35
+ ? featureFlags
36
+ : new Map<string, LDFlagValue>(Object.entries(featureFlags))
37
+ )
38
+ )
24
39
  ]
25
40
  });
26
41
 
27
- await runtime.moduleManager.registerModules([
28
- ...toLocalModuleDefinitions(localModules)
29
- ]);
42
+ if (localModules && localModules.length > 0) {
43
+ await runtime.moduleManager.registerModules([
44
+ ...toLocalModuleDefinitions(localModules)
45
+ ]);
46
+ }
30
47
 
31
48
  return runtime;
32
49
  }
@@ -0,0 +1,49 @@
1
+ import { LDFlagValue } from "launchdarkly-js-client-sdk";
2
+ import { PropsWithChildren, useLayoutEffect } from "react";
3
+ import { Decorator } from "storybook-react-rsbuild";
4
+
5
+ interface OverrideFeatureFlagsProps extends PropsWithChildren {
6
+ featureFlags: Map<string, LDFlagValue>;
7
+ overrides: Record<string, LDFlagValue>;
8
+ }
9
+
10
+ function OverrideFeatureFlags(props: OverrideFeatureFlagsProps) {
11
+ const {
12
+ featureFlags,
13
+ overrides,
14
+ children
15
+ } = props;
16
+
17
+ // Must use a layout effect to override the feature flags before the story renders.
18
+ useLayoutEffect(() => {
19
+ const originalValues: Record<string, LDFlagValue> = {};
20
+
21
+ Object.entries(overrides).forEach(([key, value]) => {
22
+ originalValues[key] = featureFlags.get(key);
23
+ featureFlags.set(key, value);
24
+ });
25
+
26
+ return () => {
27
+ // Reset the feature flags to the original values.
28
+ Object.entries(originalValues).forEach(([key, value]) => {
29
+ featureFlags.set(key, value);
30
+ });
31
+ };
32
+ }, [featureFlags, overrides]);
33
+
34
+ return children;
35
+ }
36
+
37
+ export function withFeatureFlagsOverrideDecorator<const T extends string>(featureFlags: Map<T, LDFlagValue>, overrides: Partial<Record<T, LDFlagValue>>): Decorator {
38
+ if (!(featureFlags instanceof Map)) {
39
+ throw new TypeError("[squide] The \"featureFlags\" argument must be a Map instance.");
40
+ }
41
+
42
+ return story => {
43
+ return (
44
+ <OverrideFeatureFlags featureFlags={featureFlags} overrides={overrides}>
45
+ {story()}
46
+ </OverrideFeatureFlags>
47
+ );
48
+ };
49
+ }