@storybook/react-native 10.1.11 → 10.2.0-beta.1

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/dist/index.d.ts CHANGED
@@ -76,6 +76,8 @@ declare class View {
76
76
  storySpecifier: string;
77
77
  viewMode: string;
78
78
  }>;
79
+ _getHost: (params?: Partial<Params>) => any;
80
+ __getPort: (params?: Partial<Params>) => any;
79
81
  _getServerChannel: (params?: Partial<Params>) => Channel;
80
82
  createPreparedStoryMapping: () => Promise<void>;
81
83
  getStorybookUI: (params?: Partial<Params>) => () => react_jsx_runtime.JSX.Element;
@@ -113,6 +115,13 @@ declare function updateView(viewInstance: View, annotations: any[], normalizedSt
113
115
  req: any;
114
116
  })[], options?: ReactNativeOptions): void;
115
117
 
118
+ declare const RN_STORYBOOK_STORAGE_KEY = "lastOpenedStory";
119
+ declare const RN_STORYBOOK_EVENTS: {
120
+ RN_GET_INDEX: string;
121
+ RN_GET_INDEX_RESPONSE: string;
122
+ };
123
+ declare const STORYBOOK_STORY_ID_PARAM = "STORYBOOK_STORY_ID";
124
+
116
125
  interface StorybookConfig {
117
126
  stories: StorybookConfig$1['stories'];
118
127
  addons: Array<string | {
@@ -123,4 +132,4 @@ interface StorybookConfig {
123
132
  framework?: '@storybook/react-native';
124
133
  }
125
134
 
126
- export { type InitialSelection, type Params, type Storage, type StorybookConfig, type ThemePartial, View, getProjectAnnotations, prepareStories, start, updateView };
135
+ export { type InitialSelection, type Params, RN_STORYBOOK_EVENTS, RN_STORYBOOK_STORAGE_KEY, STORYBOOK_STORY_ID_PARAM, type Storage, type StorybookConfig, type ThemePartial, View, getProjectAnnotations, prepareStories, start, updateView };
package/dist/index.js CHANGED
@@ -29,6 +29,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
29
29
  // src/index.ts
30
30
  var index_exports = {};
31
31
  __export(index_exports, {
32
+ RN_STORYBOOK_EVENTS: () => RN_STORYBOOK_EVENTS,
33
+ RN_STORYBOOK_STORAGE_KEY: () => RN_STORYBOOK_STORAGE_KEY,
34
+ STORYBOOK_STORY_ID_PARAM: () => STORYBOOK_STORY_ID_PARAM,
32
35
  darkTheme: () => import_react_native_theming3.darkTheme,
33
36
  getProjectAnnotations: () => getProjectAnnotations,
34
37
  prepareStories: () => prepareStories,
@@ -857,54 +860,16 @@ var StoryView = ({ useWrapper = true }) => {
857
860
  };
858
861
  var StoryView_default = import_react3.default.memo(StoryView);
859
862
 
860
- // src/rn-host-detect.js
861
- function getByRemoteConfig(hostname) {
862
- var remoteModuleConfig = window?.__fbBatchedBridgeConfig?.remoteModuleConfig;
863
- if (!Array.isArray(remoteModuleConfig) || hostname !== "localhost" && hostname !== "127.0.0.1") {
864
- return { hostname, passed: false };
865
- }
866
- var constants = (remoteModuleConfig.find(getConstants) || [])[1];
867
- if (constants) {
868
- var serverHost = constants.ServerHost || hostname;
869
- return { hostname: serverHost.split(":")[0], passed: true };
870
- }
871
- return { hostname, passed: false };
872
- }
873
- function getConstants(config) {
874
- return config && (config[0] === "AndroidConstants" || config[0] === "PlatformConstants");
875
- }
876
- function getByRNRequirePolyfill(hostname) {
877
- var NativeModules;
878
- var PlatformConstants;
879
- var AndroidConstants;
880
- if (typeof window === "undefined" || !window.__DEV__ || typeof window.require !== "function" || // RN >= 0.56
881
- // TODO: Get NativeModules for RN >= 0.56
882
- window.require.name === "metroRequire") {
883
- return hostname;
884
- }
885
- NativeModules = window.require("NativeModules");
886
- if (!NativeModules || !NativeModules.PlatformConstants && !NativeModules.AndroidConstants) {
887
- return hostname;
888
- }
889
- PlatformConstants = NativeModules.PlatformConstants;
890
- AndroidConstants = NativeModules.AndroidConstants;
891
- var serverHost = (PlatformConstants ? PlatformConstants.ServerHost : AndroidConstants.ServerHost) || hostname;
892
- return serverHost.split(":")[0];
893
- }
894
- function getHost(hostname) {
895
- if (typeof __fbBatchedBridge !== "object" || hostname !== "localhost" && hostname !== "127.0.0.1") {
896
- return hostname;
897
- }
898
- var result = getByRemoteConfig(hostname);
899
- if (result.passed) {
900
- return result.hostname;
901
- }
902
- return getByRNRequirePolyfill(hostname);
903
- }
863
+ // src/constants.ts
864
+ var RN_STORYBOOK_STORAGE_KEY = "lastOpenedStory";
865
+ var RN_STORYBOOK_EVENTS = {
866
+ RN_GET_INDEX: "RN_GET_INDEX",
867
+ RN_GET_INDEX_RESPONSE: "RN_GET_INDEX_RESPONSE"
868
+ };
869
+ var STORYBOOK_STORY_ID_PARAM = "STORYBOOK_STORY_ID";
904
870
 
905
871
  // src/View.tsx
906
872
  var import_jsx_runtime3 = require("react/jsx-runtime");
907
- var STORAGE_KEY = "lastOpenedStory";
908
873
  var View3 = class {
909
874
  _storyIndex;
910
875
  _setStory = () => {
@@ -943,7 +908,7 @@ var View3 = class {
943
908
  try {
944
909
  let value = this._asyncStorageStoryId;
945
910
  if (!value && this._storage != null) {
946
- value = await this._storage.getItem(STORAGE_KEY);
911
+ value = await this._storage.getItem(RN_STORYBOOK_STORAGE_KEY);
947
912
  this._asyncStorageStoryId = value;
948
913
  }
949
914
  const exists = value && this._storyIdExists(value);
@@ -955,9 +920,27 @@ var View3 = class {
955
920
  }
956
921
  return { storySpecifier: "*", viewMode: "story" };
957
922
  };
923
+ _getHost = (params = {}) => {
924
+ if (params.host) {
925
+ return params.host;
926
+ }
927
+ if (globalThis.STORYBOOK_WEBSOCKET?.host) {
928
+ return globalThis.STORYBOOK_WEBSOCKET.host;
929
+ }
930
+ return import_react_native3.Platform.OS === "android" ? "10.0.2.2" : "localhost";
931
+ };
932
+ __getPort = (params = {}) => {
933
+ if (params.port) {
934
+ return params.port;
935
+ }
936
+ if (globalThis.STORYBOOK_WEBSOCKET?.port) {
937
+ return globalThis.STORYBOOK_WEBSOCKET.port;
938
+ }
939
+ return 7007;
940
+ };
958
941
  _getServerChannel = (params = {}) => {
959
- const host = getHost(params.host || "localhost");
960
- const port = `:${params.port || 7007}`;
942
+ const host = this._getHost(params);
943
+ const port = `:${this.__getPort(params)}`;
961
944
  const query = params.query || "";
962
945
  const websocketType = params.secured ? "wss" : "ws";
963
946
  const url = `${websocketType}://${host}${port}/${query}`;
@@ -1015,6 +998,9 @@ var View3 = class {
1015
998
  channel.emit(import_core_events.CHANNEL_CREATED);
1016
999
  this._preview.ready().then(() => this._preview.onStoryIndexChanged());
1017
1000
  }
1001
+ this._channel.on(RN_STORYBOOK_EVENTS.RN_GET_INDEX, () => {
1002
+ this._channel.emit(RN_STORYBOOK_EVENTS.RN_GET_INDEX_RESPONSE, { index: this._storyIndex });
1003
+ });
1018
1004
  import_manager_api.addons.loadAddons({
1019
1005
  store: () => ({
1020
1006
  fromId: (id) => {
@@ -1044,7 +1030,7 @@ var View3 = class {
1044
1030
  const listener = import_react_native3.Linking.addEventListener("url", ({ url }) => {
1045
1031
  if (typeof url === "string") {
1046
1032
  const urlObj = new URL(url);
1047
- const storyId = urlObj.searchParams.get("STORYBOOK_STORY_ID");
1033
+ const storyId = urlObj.searchParams.get(STORYBOOK_STORY_ID_PARAM);
1048
1034
  const hasStoryId = storyId && typeof storyId === "string";
1049
1035
  const storyExists = hasStoryId && self._storyIdExists(storyId);
1050
1036
  if (storyExists && self._ready) {
@@ -1069,7 +1055,7 @@ var View3 = class {
1069
1055
  return import_react_native3.Linking.getInitialURL().then((url) => {
1070
1056
  if (url && typeof url === "string") {
1071
1057
  const urlObj = new URL(url);
1072
- const storyId = urlObj.searchParams.get("STORYBOOK_STORY_ID");
1058
+ const storyId = urlObj.searchParams.get(STORYBOOK_STORY_ID_PARAM);
1073
1059
  const hasStoryId = storyId && typeof storyId === "string";
1074
1060
  const storyExists = hasStoryId && self._storyIdExists(storyId);
1075
1061
  if (hasStoryId && !storyExists) {
@@ -1112,7 +1098,7 @@ var View3 = class {
1112
1098
  `);
1113
1099
  }
1114
1100
  if (shouldPersistSelection && !!self._storage) {
1115
- self._storage.setItem(STORAGE_KEY, newStory.id).catch((e) => {
1101
+ self._storage.setItem(RN_STORYBOOK_STORAGE_KEY, newStory.id).catch((e) => {
1116
1102
  console.warn("storybook-log: error writing to async storage", e);
1117
1103
  });
1118
1104
  }
@@ -1412,6 +1398,9 @@ function updateView(viewInstance, annotations, normalizedStories, options) {
1412
1398
  }
1413
1399
  // Annotate the CommonJS export names for ESM import in node:
1414
1400
  0 && (module.exports = {
1401
+ RN_STORYBOOK_EVENTS,
1402
+ RN_STORYBOOK_STORAGE_KEY,
1403
+ STORYBOOK_STORY_ID_PARAM,
1415
1404
  darkTheme,
1416
1405
  getProjectAnnotations,
1417
1406
  prepareStories,
@@ -24,7 +24,7 @@ interface WithStorybookOptions {
24
24
  /**
25
25
  * WebSocket configuration for syncing storybook instances or sending events to storybook.
26
26
  */
27
- websockets?: WebsocketsOptions;
27
+ websockets?: WebsocketsOptions | 'auto';
28
28
  /**
29
29
  * Whether to use JavaScript files for Storybook configuration instead of TypeScript. Defaults to false.
30
30
  */
@@ -33,9 +33,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
33
33
  var require_common = __commonJS({
34
34
  "scripts/common.js"(exports2, module2) {
35
35
  var { globToRegexp } = require("storybook/internal/common");
36
- var path2 = require("path");
36
+ var path3 = require("path");
37
37
  var fs = require("fs");
38
- var cwd = process.cwd();
38
+ var cwd2 = process.cwd();
39
39
  var toRequireContext = (specifier) => {
40
40
  const { directory, files } = specifier;
41
41
  const match = globToRegexp(`./${files}`);
@@ -48,14 +48,23 @@ var require_common = __commonJS({
48
48
  var supportedExtensions = ["js", "jsx", "ts", "tsx", "cjs", "mjs"];
49
49
  function getFilePathExtension({ configPath }, fileName) {
50
50
  for (const ext of supportedExtensions) {
51
- const filePath = path2.resolve(cwd, configPath, `${fileName}.${ext}`);
51
+ const filePath = path3.resolve(cwd2, configPath, `${fileName}.${ext}`);
52
52
  if (fs.existsSync(filePath)) {
53
53
  return ext;
54
54
  }
55
55
  }
56
56
  return null;
57
57
  }
58
- function ensureRelativePathHasDot(relativePath) {
58
+ function getFilePathWithExtension2({ configPath }, fileName) {
59
+ for (const ext of supportedExtensions) {
60
+ const filePath = path3.resolve(cwd2, configPath, `${fileName}.${ext}`);
61
+ if (fs.existsSync(filePath)) {
62
+ return filePath;
63
+ }
64
+ }
65
+ return null;
66
+ }
67
+ function ensureRelativePathHasDot2(relativePath) {
59
68
  return relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
60
69
  }
61
70
  function getPreviewExists({ configPath }) {
@@ -97,10 +106,11 @@ var require_common = __commonJS({
97
106
  module2.exports = {
98
107
  toRequireContext,
99
108
  getFilePathExtension,
100
- ensureRelativePathHasDot,
109
+ ensureRelativePathHasDot: ensureRelativePathHasDot2,
101
110
  getPreviewExists,
102
111
  resolveAddonFile,
103
- getAddonName
112
+ getAddonName,
113
+ getFilePathWithExtension: getFilePathWithExtension2
104
114
  };
105
115
  }
106
116
  });
@@ -142,25 +152,26 @@ var require_generate = __commonJS({
142
152
  "scripts/generate.js"(exports2, module2) {
143
153
  var {
144
154
  toRequireContext,
145
- ensureRelativePathHasDot,
155
+ ensureRelativePathHasDot: ensureRelativePathHasDot2,
146
156
  getPreviewExists,
147
157
  resolveAddonFile,
148
158
  getAddonName
149
159
  } = require_common();
150
- var { normalizeStories, globToRegexp, loadMainConfig } = require("storybook/internal/common");
160
+ var { normalizeStories: normalizeStories2, globToRegexp, loadMainConfig: loadMainConfig2 } = require("storybook/internal/common");
151
161
  var { interopRequireDefault } = require_require_interop();
152
162
  var fs = require("fs");
153
- var path2 = require("path");
154
- var cwd = process.cwd();
155
- var loadMain = async ({ configPath, cwd: cwd2 }) => {
163
+ var { networkInterfaces } = require("os");
164
+ var path3 = require("path");
165
+ var cwd2 = process.cwd();
166
+ var loadMain = async ({ configPath, cwd: cwd3 }) => {
156
167
  try {
157
- const main = await loadMainConfig({ configDir: configPath, cwd: cwd2 });
168
+ const main = await loadMainConfig2({ configDir: configPath, cwd: cwd3 });
158
169
  return main;
159
170
  } catch {
160
171
  console.error("Error loading main config, trying fallback");
161
172
  }
162
- const mainPathTs = path2.resolve(cwd2, configPath, `main.ts`);
163
- const mainPathJs = path2.resolve(cwd2, configPath, `main.js`);
173
+ const mainPathTs = path3.resolve(cwd3, configPath, `main.ts`);
174
+ const mainPathJs = path3.resolve(cwd3, configPath, `main.js`);
164
175
  if (fs.existsSync(mainPathTs)) {
165
176
  return interopRequireDefault(mainPathTs);
166
177
  } else if (fs.existsSync(mainPathJs)) {
@@ -169,26 +180,40 @@ var require_generate = __commonJS({
169
180
  throw new Error(`Main config file not found at ${mainPathTs} or ${mainPathJs}`);
170
181
  }
171
182
  };
183
+ function getLocalIPAddress() {
184
+ const nets = networkInterfaces();
185
+ for (const name of Object.keys(nets)) {
186
+ for (const net of nets[name]) {
187
+ const familyV4Value = typeof net.family === "string" ? "IPv4" : 4;
188
+ if (net.family === familyV4Value && !net.internal) {
189
+ return net.address;
190
+ }
191
+ }
192
+ }
193
+ return "0.0.0.0";
194
+ }
172
195
  async function generate2({
173
196
  configPath,
174
- /* absolute = false, */
175
197
  useJs = false,
176
- docTools = true
198
+ docTools = true,
199
+ host = void 0,
200
+ port = 7007
177
201
  }) {
178
- const storybookRequiresLocation = path2.resolve(
179
- cwd,
202
+ const channelHost = host === "auto" ? getLocalIPAddress() : host;
203
+ const storybookRequiresLocation = path3.resolve(
204
+ cwd2,
180
205
  configPath,
181
206
  `storybook.requires.${useJs ? "js" : "ts"}`
182
207
  );
183
- const main = await loadMain({ configPath, cwd });
184
- const storiesSpecifiers = normalizeStories(main.stories, {
208
+ const main = await loadMain({ configPath, cwd: cwd2 });
209
+ const storiesSpecifiers = normalizeStories2(main.stories, {
185
210
  configDir: configPath,
186
- workingDir: cwd
211
+ workingDir: cwd2
187
212
  });
188
213
  const normalizedStories = storiesSpecifiers.map((specifier) => {
189
214
  const reg = globToRegexp(`./${specifier.files}`);
190
215
  const { path: p, recursive: r, match: m } = toRequireContext(specifier);
191
- const pathToStory = ensureRelativePathHasDot(path2.posix.relative(configPath, p));
216
+ const pathToStory = ensureRelativePathHasDot2(path3.posix.relative(configPath, p));
192
217
  return `{
193
218
  titlePrefix: "${specifier.titlePrefix}",
194
219
  directory: "${specifier.directory}",
@@ -249,6 +274,7 @@ var require_generate = __commonJS({
249
274
  declare global {
250
275
  var view: View;
251
276
  var STORIES: typeof normalizedStories;
277
+ var STORYBOOK_WEBSOCKET: { host: string; port: number } | undefined;
252
278
  }
253
279
  `;
254
280
  const fileContent = `/* do not change this file, it is auto generated by storybook. */
@@ -264,24 +290,25 @@ ${useJs ? "" : globalTypes}
264
290
 
265
291
  const annotations = ${annotations};
266
292
 
267
- global.STORIES = normalizedStories;
293
+ globalThis.STORIES = normalizedStories;
294
+ ${channelHost ? `globalThis.STORYBOOK_WEBSOCKET = { host: '${channelHost}', port: ${port ?? 7007} };` : ""}
268
295
 
269
296
  ${useJs ? "" : "// @ts-ignore"}
270
297
  module?.hot?.accept?.();
271
298
 
272
299
  ${optionsVar}
273
300
 
274
- if (!global.view) {
275
- global.view = start({
301
+ if (!globalThis.view) {
302
+ globalThis.view = start({
276
303
  annotations,
277
304
  storyEntries: normalizedStories,
278
305
  ${options ? ` ${options},` : ""}
279
306
  });
280
307
  } else {
281
- updateView(global.view, annotations, normalizedStories${options ? `, ${options}` : ""});
308
+ updateView(globalThis.view, annotations, normalizedStories${options ? `, ${options}` : ""});
282
309
  }
283
310
 
284
- export const view${useJs ? "" : ": View"} = global.view;
311
+ export const view${useJs ? "" : ": View"} = globalThis.view;
285
312
  `;
286
313
  fs.writeFileSync(storybookRequiresLocation, fileContent, {
287
314
  encoding: "utf8",
@@ -300,27 +327,213 @@ __export(withStorybook_exports, {
300
327
  withStorybook: () => withStorybook
301
328
  });
302
329
  module.exports = __toCommonJS(withStorybook_exports);
303
- var path = __toESM(require("path"));
330
+ var path2 = __toESM(require("path"));
304
331
  var import_generate = __toESM(require_generate());
332
+ var import_common3 = require("storybook/internal/common");
333
+ var import_telemetry = require("storybook/internal/telemetry");
334
+
335
+ // src/metro/channelServer.ts
305
336
  var import_ws = require("ws");
337
+ var import_node_http = require("http");
338
+
339
+ // src/metro/buildIndex.ts
306
340
  var import_common = require("storybook/internal/common");
307
- var import_telemetry = require("storybook/internal/telemetry");
341
+ var import_node_fs = require("fs");
342
+ var import_glob = require("glob");
343
+ var import_path = __toESM(require("path"));
344
+ var import_csf_tools = require("storybook/internal/csf-tools");
345
+ var import_csf = require("storybook/internal/csf");
346
+ var import_preview_api = require("storybook/internal/preview-api");
347
+ var import_common2 = __toESM(require_common());
348
+ var cwd = process.cwd();
349
+ var makeTitle = (fileName, specifier, userTitle) => {
350
+ const title = (0, import_preview_api.userOrAutoTitleFromSpecifier)(fileName, specifier, userTitle);
351
+ if (title) {
352
+ return title.replace("./", "");
353
+ } else if (userTitle) {
354
+ return userTitle.replace("./", "");
355
+ } else {
356
+ console.error("Could not generate title!!");
357
+ process.exit(1);
358
+ }
359
+ };
360
+ function ensureRelativePathHasDot(relativePath) {
361
+ return relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
362
+ }
363
+ async function buildIndex({ configPath }) {
364
+ const main = await (0, import_common.loadMainConfig)({ configDir: configPath, cwd });
365
+ if (!main.stories || !Array.isArray(main.stories)) {
366
+ throw new Error("No stories found");
367
+ }
368
+ const storiesSpecifiers = (0, import_common.normalizeStories)(main.stories, {
369
+ configDir: configPath,
370
+ workingDir: cwd
371
+ });
372
+ const specifierStoryPaths = storiesSpecifiers.map((specifier) => {
373
+ return (0, import_glob.sync)(specifier.files, {
374
+ cwd: import_path.default.resolve(process.cwd(), specifier.directory),
375
+ absolute: true,
376
+ // default to always ignore (exclude) anything in node_modules
377
+ ignore: ["**/node_modules"]
378
+ }).map((storyPath) => {
379
+ const normalizePathForWindows = (str) => import_path.default.sep === "\\" ? str.replace(/\\/g, "/") : str;
380
+ return normalizePathForWindows(storyPath);
381
+ });
382
+ });
383
+ const csfStories = specifierStoryPaths.reduce(
384
+ (acc, specifierStoryPathList, specifierIndex) => {
385
+ const paths = specifierStoryPathList.map((storyPath) => {
386
+ const code = (0, import_node_fs.readFileSync)(storyPath, { encoding: "utf-8" }).toString();
387
+ const relativePath = ensureRelativePathHasDot(import_path.default.posix.relative(cwd, storyPath));
388
+ return {
389
+ result: (0, import_csf_tools.loadCsf)(code, {
390
+ fileName: storyPath,
391
+ makeTitle: (userTitle) => makeTitle(relativePath, storiesSpecifiers[specifierIndex], userTitle)
392
+ }).parse(),
393
+ specifier: storiesSpecifiers[specifierIndex],
394
+ fileName: relativePath
395
+ };
396
+ });
397
+ return [...acc, ...paths];
398
+ },
399
+ new Array()
400
+ );
401
+ const index = {
402
+ v: 5,
403
+ entries: {}
404
+ };
405
+ for (const { result, specifier, fileName } of csfStories) {
406
+ const { meta, stories } = result;
407
+ if (stories && stories.length > 0) {
408
+ for (const story of stories) {
409
+ const id = (0, import_csf.toId)(meta.title, story.name);
410
+ index.entries[id] = {
411
+ type: "story",
412
+ subtype: "story",
413
+ id,
414
+ name: story.name,
415
+ title: meta.title,
416
+ importPath: `${specifier.directory}/${import_path.default.posix.relative(specifier.directory, fileName)}`,
417
+ tags: ["story"]
418
+ };
419
+ }
420
+ } else {
421
+ console.log(`No stories found for ${fileName}`);
422
+ }
423
+ }
424
+ try {
425
+ const previewPath = (0, import_common2.getFilePathWithExtension)({ configPath }, "preview");
426
+ const previewSourceCode = (0, import_node_fs.readFileSync)(previewPath, { encoding: "utf-8" }).toString();
427
+ const storySort = (0, import_csf_tools.getStorySortParameter)(previewSourceCode);
428
+ const sortableStories = Object.values(index.entries);
429
+ (0, import_preview_api.sortStoriesV7)(
430
+ sortableStories,
431
+ storySort,
432
+ sortableStories.map((entry) => entry.importPath)
433
+ );
434
+ const sorted = sortableStories.reduce(
435
+ (acc, item) => {
436
+ acc[item.id] = item;
437
+ return acc;
438
+ },
439
+ {}
440
+ );
441
+ return { v: 5, entries: sorted };
442
+ } catch {
443
+ console.warn("Failed to sort stories, using unordered index");
444
+ return index;
445
+ }
446
+ }
447
+
448
+ // src/metro/channelServer.ts
449
+ function createChannelServer({
450
+ port = 7007,
451
+ host = void 0,
452
+ configPath
453
+ }) {
454
+ const httpServer = (0, import_node_http.createServer)(async (req, res) => {
455
+ if (req.method === "OPTIONS") {
456
+ res.writeHead(204);
457
+ res.end();
458
+ return;
459
+ }
460
+ if (req.method === "GET" && req.url === "/index.json") {
461
+ try {
462
+ const index = await buildIndex({ configPath });
463
+ res.writeHead(200, { "Content-Type": "application/json" });
464
+ res.end(JSON.stringify(index));
465
+ } catch (error) {
466
+ console.error("Failed to build index:", error);
467
+ res.writeHead(500, { "Content-Type": "application/json" });
468
+ res.end(JSON.stringify({ error: "Failed to build story index" }));
469
+ }
470
+ return;
471
+ }
472
+ if (req.method === "POST" && req.url === "/send-event") {
473
+ let body = "";
474
+ req.on("data", (chunk) => {
475
+ body += chunk.toString();
476
+ });
477
+ req.on("end", () => {
478
+ try {
479
+ const json = JSON.parse(body);
480
+ wss.clients.forEach((wsClient) => wsClient.send(JSON.stringify(json)));
481
+ res.writeHead(200, { "Content-Type": "application/json" });
482
+ res.end(JSON.stringify({ success: true }));
483
+ } catch (error) {
484
+ console.error("Failed to parse event:", error);
485
+ res.writeHead(400, { "Content-Type": "application/json" });
486
+ res.end(JSON.stringify({ success: false, error: "Invalid JSON" }));
487
+ }
488
+ });
489
+ return;
490
+ }
491
+ res.writeHead(404, { "Content-Type": "application/json" });
492
+ res.end(JSON.stringify({ error: "Not found" }));
493
+ });
494
+ const wss = new import_ws.WebSocketServer({ server: httpServer });
495
+ setInterval(function ping() {
496
+ wss.clients.forEach(function each(client) {
497
+ if (client.readyState === import_ws.WebSocket.OPEN) {
498
+ client.send(JSON.stringify({ type: "ping", args: [] }));
499
+ }
500
+ });
501
+ }, 1e4);
502
+ wss.on("connection", function connection(ws) {
503
+ console.log("WebSocket connection established");
504
+ ws.on("error", console.error);
505
+ ws.on("message", function message(data) {
506
+ try {
507
+ const json = JSON.parse(data.toString());
508
+ wss.clients.forEach((wsClient) => wsClient.send(JSON.stringify(json)));
509
+ } catch (error) {
510
+ console.error(error);
511
+ }
512
+ });
513
+ });
514
+ httpServer.listen(port, host, () => {
515
+ console.log(`WebSocket server listening on ${host ?? "localhost"}:${port}`);
516
+ });
517
+ return wss;
518
+ }
519
+
520
+ // src/metro/withStorybook.ts
308
521
  function withStorybook(config, options = {
309
522
  useJs: false,
310
523
  enabled: true,
311
524
  docTools: true,
312
525
  liteMode: false,
313
- configPath: path.resolve(process.cwd(), "./.rnstorybook")
526
+ configPath: path2.resolve(process.cwd(), "./.rnstorybook")
314
527
  }) {
315
528
  const {
316
- configPath = path.resolve(process.cwd(), "./.rnstorybook"),
529
+ configPath = path2.resolve(process.cwd(), "./.rnstorybook"),
317
530
  websockets,
318
531
  useJs = false,
319
532
  enabled = true,
320
533
  docTools = true,
321
534
  liteMode = false
322
535
  } = options;
323
- const disableTelemetry = (0, import_common.optionalEnvToBoolean)(process.env.STORYBOOK_DISABLE_TELEMETRY);
536
+ const disableTelemetry = (0, import_common3.optionalEnvToBoolean)(process.env.STORYBOOK_DISABLE_TELEMETRY);
324
537
  if (!disableTelemetry && enabled) {
325
538
  const event = process.env.NODE_ENV === "production" ? "build" : "dev";
326
539
  (0, import_telemetry.telemetry)(event, {}).catch((e) => {
@@ -346,7 +559,7 @@ function withStorybook(config, options = {
346
559
  const resolved = resolveFunction(context, moduleName, platform);
347
560
  if (resolved.filePath?.includes?.(`${configPath}/index.tsx`)) {
348
561
  return {
349
- filePath: path.resolve(__dirname, "../stub.js"),
562
+ filePath: path2.resolve(__dirname, "../stub.js"),
350
563
  type: "sourceFile"
351
564
  };
352
565
  }
@@ -359,27 +572,23 @@ function withStorybook(config, options = {
359
572
  };
360
573
  }
361
574
  if (websockets) {
362
- const port = websockets.port ?? 7007;
363
- const host = websockets.host ?? "localhost";
364
- const wss = new import_ws.WebSocketServer({ port, host });
365
- wss.on("connection", function connection(ws) {
366
- console.log("WebSocket connection established");
367
- ws.on("error", console.error);
368
- ws.on("message", function message(data) {
369
- try {
370
- const json = JSON.parse(data.toString());
371
- wss.clients.forEach((wsClient) => wsClient.send(JSON.stringify(json)));
372
- } catch (error) {
373
- console.error(error);
374
- }
375
- });
575
+ const port = websockets === "auto" ? 7007 : websockets.port ?? 7007;
576
+ const host = websockets === "auto" ? "auto" : websockets.host;
577
+ createChannelServer({ port, host: host === "auto" ? void 0 : host, configPath });
578
+ (0, import_generate.generate)({
579
+ configPath,
580
+ useJs,
581
+ docTools,
582
+ host,
583
+ port
584
+ });
585
+ } else {
586
+ (0, import_generate.generate)({
587
+ configPath,
588
+ useJs,
589
+ docTools
376
590
  });
377
591
  }
378
- (0, import_generate.generate)({
379
- configPath,
380
- useJs,
381
- docTools
382
- });
383
592
  return {
384
593
  ...config,
385
594
  transformer: {
package/dist/node.d.ts ADDED
@@ -0,0 +1,40 @@
1
+ import { WebSocketServer } from 'ws';
2
+ import { StoryIndex } from 'storybook/internal/types';
3
+
4
+ /**
5
+ * Options for creating a channel server.
6
+ */
7
+ interface ChannelServerOptions {
8
+ /**
9
+ * The port the server will listen on.
10
+ */
11
+ port?: number;
12
+ /**
13
+ * The host the server will bind to.
14
+ */
15
+ host?: string;
16
+ /**
17
+ * The path to the Storybook config folder.
18
+ */
19
+ configPath: string;
20
+ }
21
+ /**
22
+ * Creates a channel server for syncing storybook instances and sending events.
23
+ * The server provides both WebSocket and REST endpoints:
24
+ * - WebSocket: broadcasts all received messages to all connected clients
25
+ * - POST /send-event: sends an event to all WebSocket clients
26
+ * - GET /index.json: returns the story index built from story files
27
+ *
28
+ * @param options - Configuration options for the channel server.
29
+ * @param options.port - The port to listen on.
30
+ * @param options.host - The host to bind to.
31
+ * @param options.configPath - The path to the Storybook config folder.
32
+ * @returns The created WebSocketServer instance.
33
+ */
34
+ declare function createChannelServer({ port, host, configPath, }: ChannelServerOptions): WebSocketServer;
35
+
36
+ declare function buildIndex({ configPath }: {
37
+ configPath: string;
38
+ }): Promise<StoryIndex>;
39
+
40
+ export { buildIndex, createChannelServer };