@storybook/react-native 10.3.1 → 10.4.0-canary-20260407095432

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.
@@ -0,0 +1,15 @@
1
+ import { W as WebsocketsOptions } from './index-6iAzVvXp.js';
2
+
3
+ interface WithStorybookOptions {
4
+ configPath?: string;
5
+ websockets?: WebsocketsOptions | 'auto';
6
+ useJs?: boolean;
7
+ enabled?: boolean;
8
+ docTools?: boolean;
9
+ liteMode?: boolean;
10
+ experimental_mcp?: boolean;
11
+ }
12
+
13
+ declare function withStorybook<T>(config: T, options?: WithStorybookOptions): T;
14
+
15
+ export { withStorybook };
@@ -0,0 +1,1304 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __esm = (fn, res) => function __init() {
8
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
9
+ };
10
+ var __commonJS = (cb, mod) => function __require() {
11
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
12
+ };
13
+ var __export = (target, all) => {
14
+ for (var name in all)
15
+ __defProp(target, name, { get: all[name], enumerable: true });
16
+ };
17
+ var __copyProps = (to, from, except, desc) => {
18
+ if (from && typeof from === "object" || typeof from === "function") {
19
+ for (let key of __getOwnPropNames(from))
20
+ if (!__hasOwnProp.call(to, key) && key !== except)
21
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
22
+ }
23
+ return to;
24
+ };
25
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
26
+ // If the importer is in node compatibility mode or this is not an ESM
27
+ // file that has been converted to a CommonJS file using a Babel-
28
+ // compatible transform (i.e. "__esModule" has not been set), then set
29
+ // "default" to the CommonJS "module.exports" for node compatibility.
30
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
31
+ mod
32
+ ));
33
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
34
+
35
+ // scripts/common.js
36
+ var require_common = __commonJS({
37
+ "scripts/common.js"(exports2, module2) {
38
+ var { globToRegexp } = require("storybook/internal/common");
39
+ var path5 = require("path");
40
+ var fs2 = require("fs");
41
+ var cwd2 = process.cwd();
42
+ var toRequireContext = (specifier) => {
43
+ const { directory, files } = specifier;
44
+ const match = globToRegexp(`./${files}`);
45
+ return {
46
+ path: directory,
47
+ recursive: files.includes("**") || files.split("/").length > 1,
48
+ match
49
+ };
50
+ };
51
+ var supportedExtensions = ["js", "jsx", "ts", "tsx", "cjs", "mjs"];
52
+ function getFilePathExtension({ configPath }, fileName) {
53
+ for (const ext of supportedExtensions) {
54
+ const filePath = path5.resolve(cwd2, configPath, `${fileName}.${ext}`);
55
+ if (fs2.existsSync(filePath)) {
56
+ return ext;
57
+ }
58
+ }
59
+ return null;
60
+ }
61
+ function getFilePathWithExtension2({ configPath }, fileName) {
62
+ for (const ext of supportedExtensions) {
63
+ const filePath = path5.resolve(cwd2, configPath, `${fileName}.${ext}`);
64
+ if (fs2.existsSync(filePath)) {
65
+ return filePath;
66
+ }
67
+ }
68
+ return null;
69
+ }
70
+ function ensureRelativePathHasDot2(relativePath) {
71
+ return relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
72
+ }
73
+ function getPreviewExists({ configPath }) {
74
+ return !!getFilePathExtension({ configPath }, "preview");
75
+ }
76
+ function resolveAddonFile(addon, file, extensions = ["js", "mjs", "ts"], configPath) {
77
+ if (!addon || typeof addon !== "string") return null;
78
+ const resolvePaths = { paths: [cwd2] };
79
+ try {
80
+ const basePath = `${addon}/${file}`;
81
+ require.resolve(basePath, resolvePaths);
82
+ return basePath;
83
+ } catch (_error) {
84
+ }
85
+ for (const ext of extensions) {
86
+ try {
87
+ const filePath = `${addon}/${file}.${ext}`;
88
+ require.resolve(filePath, resolvePaths);
89
+ return filePath;
90
+ } catch (_error) {
91
+ }
92
+ }
93
+ if (addon.startsWith("./") || addon.startsWith("../")) {
94
+ try {
95
+ const extension = getFilePathExtension({ configPath }, `${addon}/${file}`);
96
+ if (extension) {
97
+ return `${addon}/${file}`;
98
+ }
99
+ } catch (_error) {
100
+ }
101
+ }
102
+ return null;
103
+ }
104
+ function getAddonName(addon) {
105
+ if (typeof addon === "string") return addon;
106
+ if (typeof addon === "object" && addon.name && typeof addon.name === "string") return addon.name;
107
+ console.error("Invalid addon configuration", addon);
108
+ return null;
109
+ }
110
+ module2.exports = {
111
+ toRequireContext,
112
+ getFilePathExtension,
113
+ ensureRelativePathHasDot: ensureRelativePathHasDot2,
114
+ getPreviewExists,
115
+ resolveAddonFile,
116
+ getAddonName,
117
+ getFilePathWithExtension: getFilePathWithExtension2
118
+ };
119
+ }
120
+ });
121
+
122
+ // scripts/require-interop.js
123
+ var require_require_interop = __commonJS({
124
+ "scripts/require-interop.js"(exports2, module2) {
125
+ var registered = false;
126
+ function interopRequireDefault(filePath) {
127
+ const hasEsbuildBeenRegistered = !!require("module")._extensions[".ts"];
128
+ if (registered === false && !hasEsbuildBeenRegistered) {
129
+ const { register } = require("esbuild-register/dist/node");
130
+ registered = true;
131
+ register({
132
+ target: `node${process.version.slice(1)}`,
133
+ format: "cjs",
134
+ hookIgnoreNodeModules: true,
135
+ // Some frameworks, like Stylus, rely on the 'name' property of classes or functions
136
+ // https://github.com/storybookjs/storybook/issues/19049
137
+ keepNames: true,
138
+ tsconfigRaw: `{
139
+ "compilerOptions": {
140
+ "strict": false,
141
+ "skipLibCheck": true,
142
+ },
143
+ }`
144
+ });
145
+ }
146
+ const result = require(filePath);
147
+ const isES6DefaultExported = typeof result === "object" && result !== null && typeof result.default !== "undefined";
148
+ return isES6DefaultExported ? result.default : result;
149
+ }
150
+ module2.exports = { interopRequireDefault };
151
+ }
152
+ });
153
+
154
+ // scripts/generate.js
155
+ var require_generate = __commonJS({
156
+ "scripts/generate.js"(exports2, module2) {
157
+ var {
158
+ toRequireContext,
159
+ ensureRelativePathHasDot: ensureRelativePathHasDot2,
160
+ getPreviewExists,
161
+ resolveAddonFile,
162
+ getAddonName
163
+ } = require_common();
164
+ var { normalizeStories: normalizeStories2, globToRegexp, loadMainConfig: loadMainConfig2 } = require("storybook/internal/common");
165
+ var { interopRequireDefault } = require_require_interop();
166
+ var fs2 = require("fs");
167
+ var { networkInterfaces } = require("os");
168
+ var path5 = require("path");
169
+ var cwd2 = process.cwd();
170
+ var loadMain = async ({ configPath, cwd: cwd3 }) => {
171
+ try {
172
+ const main = await loadMainConfig2({ configDir: configPath, cwd: cwd3 });
173
+ return main;
174
+ } catch {
175
+ console.error("Error loading main config, trying fallback");
176
+ }
177
+ const mainPathTs = path5.resolve(cwd3, configPath, `main.ts`);
178
+ const mainPathJs = path5.resolve(cwd3, configPath, `main.js`);
179
+ if (fs2.existsSync(mainPathTs)) {
180
+ return interopRequireDefault(mainPathTs);
181
+ } else if (fs2.existsSync(mainPathJs)) {
182
+ return interopRequireDefault(mainPathJs);
183
+ } else {
184
+ throw new Error(`Main config file not found at ${mainPathTs} or ${mainPathJs}`);
185
+ }
186
+ };
187
+ function getLocalIPAddress() {
188
+ const nets = networkInterfaces();
189
+ for (const name of Object.keys(nets)) {
190
+ for (const net of nets[name]) {
191
+ const familyV4Value = typeof net.family === "string" ? "IPv4" : 4;
192
+ if (net.family === familyV4Value && !net.internal) {
193
+ return net.address;
194
+ }
195
+ }
196
+ }
197
+ return "0.0.0.0";
198
+ }
199
+ async function generate2({
200
+ configPath,
201
+ useJs = false,
202
+ docTools = true,
203
+ host = void 0,
204
+ port = void 0,
205
+ secured = false
206
+ }) {
207
+ const channelHost = host === "auto" ? getLocalIPAddress() : host;
208
+ const storybookRequiresLocation = path5.resolve(
209
+ cwd2,
210
+ configPath,
211
+ `storybook.requires.${useJs ? "js" : "ts"}`
212
+ );
213
+ const main = await loadMain({ configPath, cwd: cwd2 });
214
+ const storiesSpecifiers = normalizeStories2(main.stories, {
215
+ configDir: configPath,
216
+ workingDir: cwd2
217
+ });
218
+ const normalizedStories = storiesSpecifiers.map((specifier) => {
219
+ const reg = globToRegexp(`./${specifier.files}`);
220
+ const { path: p, recursive: r, match: m } = toRequireContext(specifier);
221
+ const pathToStory = ensureRelativePathHasDot2(path5.posix.relative(configPath, p));
222
+ return `{
223
+ titlePrefix: "${specifier.titlePrefix}",
224
+ directory: "${specifier.directory}",
225
+ files: "${specifier.files}",
226
+ importPathMatcher: /${reg.source}/,
227
+ req: require.context(
228
+ '${pathToStory}',
229
+ ${r},
230
+ ${m}
231
+ ),
232
+ }`;
233
+ });
234
+ const registeredAddons = [];
235
+ for (const addon of main.addons) {
236
+ const registerPath = resolveAddonFile(
237
+ getAddonName(addon),
238
+ "register",
239
+ ["js", "mjs", "jsx", "ts", "tsx"],
240
+ configPath
241
+ );
242
+ if (registerPath) {
243
+ registeredAddons.push(`import "${registerPath}";`);
244
+ }
245
+ }
246
+ const docToolsAnnotation = 'require("@storybook/react-native/preview")';
247
+ const enhancers = [];
248
+ if (docTools) {
249
+ enhancers.push(docToolsAnnotation);
250
+ }
251
+ for (const addon of main.addons) {
252
+ const previewPath = resolveAddonFile(
253
+ getAddonName(addon),
254
+ "preview",
255
+ ["js", "mjs", "jsx", "ts", "tsx"],
256
+ configPath
257
+ );
258
+ if (previewPath) {
259
+ enhancers.push(`require('${previewPath}')`);
260
+ continue;
261
+ }
262
+ }
263
+ let options = "";
264
+ let optionsVar = "";
265
+ const reactNativeOptions = main.reactNative;
266
+ if (reactNativeOptions && typeof reactNativeOptions === "object") {
267
+ optionsVar = `const options = ${JSON.stringify(reactNativeOptions, null, 2)}`;
268
+ options = "options";
269
+ }
270
+ let featuresAssignment = "";
271
+ const features = main.features;
272
+ if (features && typeof features === "object") {
273
+ const featureEntries = Object.entries(features).filter(
274
+ ([, value]) => typeof value === "boolean"
275
+ );
276
+ if (featureEntries.length > 0) {
277
+ const assignments = featureEntries.map(([key, value]) => `globalThis.FEATURES.${key} = ${value};`).join("\n");
278
+ featuresAssignment = assignments;
279
+ }
280
+ }
281
+ const previewExists = getPreviewExists({ configPath });
282
+ if (previewExists) {
283
+ enhancers.unshift("require('./preview')");
284
+ }
285
+ const annotations = `[
286
+ ${enhancers.join(",\n ")}
287
+ ]`;
288
+ const hasWebsocketConfig = host !== void 0 || port !== void 0 || secured;
289
+ const websocketAssignmentLines = [];
290
+ if (channelHost) {
291
+ websocketAssignmentLines.push(`host: '${channelHost}',`);
292
+ }
293
+ if (hasWebsocketConfig) {
294
+ websocketAssignmentLines.push(`port: ${port ?? 7007},`);
295
+ websocketAssignmentLines.push(`secured: ${Boolean(secured)},`);
296
+ }
297
+ const globalTypes = `
298
+ declare global {
299
+ var view: View;
300
+ var STORIES: typeof normalizedStories;
301
+ var STORYBOOK_WEBSOCKET:
302
+ | { host?: string; port?: number; secured?: boolean }
303
+ | undefined;
304
+ var FEATURES: Features;
305
+ }
306
+ `;
307
+ const fileContent = `/* do not change this file, it is auto generated by storybook. */
308
+ ${useJs ? "" : '/// <reference types="@storybook/react-native/metro-env" />\n'}import { start, updateView${useJs ? "" : ", View, type Features"} } from '@storybook/react-native';
309
+
310
+ ${registeredAddons.join("\n")}
311
+
312
+ const normalizedStories = [
313
+ ${normalizedStories.join(",\n ")}
314
+ ];
315
+
316
+ ${useJs ? "" : globalTypes}
317
+
318
+ const annotations = ${annotations};
319
+
320
+ globalThis.STORIES = normalizedStories;
321
+ ${hasWebsocketConfig ? `globalThis.STORYBOOK_WEBSOCKET = {
322
+ ${websocketAssignmentLines.join("\n ")}
323
+ };` : ""}
324
+
325
+ module?.hot?.accept?.();
326
+ ${featuresAssignment ? `
327
+ ${featuresAssignment}
328
+ ` : ""}
329
+ ${optionsVar}
330
+
331
+ if (!globalThis.view) {
332
+ globalThis.view = start({
333
+ annotations,
334
+ storyEntries: normalizedStories,
335
+ ${options ? ` ${options},` : ""}
336
+ });
337
+ } else {
338
+ updateView(globalThis.view, annotations, normalizedStories${options ? `, ${options}` : ""});
339
+ }
340
+
341
+ export const view${useJs ? "" : ": View"} = globalThis.view;
342
+ `;
343
+ fs2.writeFileSync(storybookRequiresLocation, fileContent, {
344
+ encoding: "utf8",
345
+ flag: "w"
346
+ });
347
+ }
348
+ module2.exports = {
349
+ generate: generate2
350
+ };
351
+ }
352
+ });
353
+
354
+ // src/metro/buildIndex.ts
355
+ var buildIndex_exports = {};
356
+ __export(buildIndex_exports, {
357
+ buildIndex: () => buildIndex
358
+ });
359
+ function ensureRelativePathHasDot(relativePath) {
360
+ return relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
361
+ }
362
+ async function buildIndex({ configPath }) {
363
+ const main = await (0, import_common.loadMainConfig)({ configDir: configPath, cwd });
364
+ if (!main.stories || !Array.isArray(main.stories)) {
365
+ throw new Error("No stories found");
366
+ }
367
+ const storiesSpecifiers = (0, import_common.normalizeStories)(main.stories, {
368
+ configDir: configPath,
369
+ workingDir: cwd
370
+ });
371
+ const specifierStoryPaths = storiesSpecifiers.map((specifier) => {
372
+ return (0, import_glob.sync)(specifier.files, {
373
+ cwd: import_path.default.resolve(process.cwd(), specifier.directory),
374
+ absolute: true,
375
+ // default to always ignore (exclude) anything in node_modules
376
+ ignore: ["**/node_modules"]
377
+ }).map((storyPath) => {
378
+ const normalizePathForWindows = (str) => import_path.default.sep === "\\" ? str.replace(/\\/g, "/") : str;
379
+ return normalizePathForWindows(storyPath);
380
+ });
381
+ });
382
+ const csfStories = specifierStoryPaths.reduce(
383
+ (acc, specifierStoryPathList, specifierIndex) => {
384
+ const paths = specifierStoryPathList.map((storyPath) => {
385
+ const code = (0, import_node_fs.readFileSync)(storyPath, { encoding: "utf-8" }).toString();
386
+ const relativePath = ensureRelativePathHasDot(import_path.default.posix.relative(cwd, storyPath));
387
+ return {
388
+ result: (0, import_csf_tools.loadCsf)(code, {
389
+ fileName: storyPath,
390
+ makeTitle: (userTitle) => makeTitle(relativePath, storiesSpecifiers[specifierIndex], userTitle)
391
+ }).parse(),
392
+ specifier: storiesSpecifiers[specifierIndex],
393
+ fileName: relativePath
394
+ };
395
+ });
396
+ return [...acc, ...paths];
397
+ },
398
+ new Array()
399
+ );
400
+ const index = {
401
+ v: 5,
402
+ entries: {}
403
+ };
404
+ for (const { result, specifier, fileName } of csfStories) {
405
+ const { meta, stories } = result;
406
+ if (stories && stories.length > 0) {
407
+ for (const story of stories) {
408
+ const id = story.id ?? (0, import_csf.toId)(meta.title, story.name);
409
+ if (!id) {
410
+ throw new Error(`Failed to generate id for story ${story.name} in file ${fileName}`);
411
+ }
412
+ index.entries[id] = {
413
+ type: "story",
414
+ subtype: "story",
415
+ id,
416
+ name: story.name,
417
+ title: meta.title,
418
+ importPath: `${specifier.directory}/${import_path.default.posix.relative(specifier.directory, fileName)}`,
419
+ tags: ["story"]
420
+ };
421
+ }
422
+ } else {
423
+ console.log(`No stories found for ${fileName}`);
424
+ }
425
+ }
426
+ try {
427
+ const previewPath = (0, import_common2.getFilePathWithExtension)({ configPath }, "preview");
428
+ const previewSourceCode = (0, import_node_fs.readFileSync)(previewPath, { encoding: "utf-8" }).toString();
429
+ const storySort = (0, import_csf_tools.getStorySortParameter)(previewSourceCode);
430
+ const sortableStories = Object.values(index.entries);
431
+ (0, import_preview_api.sortStoriesV7)(
432
+ sortableStories,
433
+ storySort,
434
+ sortableStories.map((entry) => entry.importPath)
435
+ );
436
+ const sorted = sortableStories.reduce(
437
+ (acc, item) => {
438
+ acc[item.id] = item;
439
+ return acc;
440
+ },
441
+ {}
442
+ );
443
+ return { v: 5, entries: sorted };
444
+ } catch {
445
+ console.warn("Failed to sort stories, using unordered index");
446
+ return index;
447
+ }
448
+ }
449
+ var import_common, import_node_fs, import_glob, import_path, import_csf_tools, import_csf, import_preview_api, import_common2, cwd, makeTitle;
450
+ var init_buildIndex = __esm({
451
+ "src/metro/buildIndex.ts"() {
452
+ import_common = require("storybook/internal/common");
453
+ import_node_fs = require("fs");
454
+ import_glob = require("glob");
455
+ import_path = __toESM(require("path"));
456
+ import_csf_tools = require("storybook/internal/csf-tools");
457
+ import_csf = require("storybook/internal/csf");
458
+ import_preview_api = require("storybook/internal/preview-api");
459
+ import_common2 = __toESM(require_common());
460
+ cwd = process.cwd();
461
+ makeTitle = (fileName, specifier, userTitle) => {
462
+ const title = (0, import_preview_api.userOrAutoTitleFromSpecifier)(fileName, specifier, userTitle);
463
+ if (title) {
464
+ return title.replace("./", "");
465
+ } else if (userTitle) {
466
+ return userTitle.replace("./", "");
467
+ } else {
468
+ console.error("Could not generate title!!");
469
+ process.exit(1);
470
+ }
471
+ };
472
+ }
473
+ });
474
+
475
+ // src/metro/manifest/storyInstructions.ts
476
+ var storyInstructions_exports = {};
477
+ __export(storyInstructions_exports, {
478
+ storyInstructions: () => storyInstructions
479
+ });
480
+ var storyInstructions;
481
+ var init_storyInstructions = __esm({
482
+ "src/metro/manifest/storyInstructions.ts"() {
483
+ storyInstructions = `# Writing React Native UI Components
484
+
485
+ When writing UI, prefer breaking larger components up into smaller parts.
486
+
487
+ ALWAYS write a Storybook story for any component written. If editing a component, ensure appropriate changes have been made to stories for that component.
488
+
489
+ ## How to write good stories
490
+
491
+ Goal: Cover every distinct piece of business logic and state the component can reach (happy paths, error/edge states, loading, permissions/roles, empty states, variations from props/context). Avoid redundant stories that show the same logic.
492
+
493
+ Interactivity: For interactive components, create separate stories that demonstrate each interaction state. Use \`fn()\` from \`storybook/test\` to mock callback props so you can verify they are wired up correctly.
494
+
495
+ Data/setup: Provide realistic props, state, and mocked data. Include meaningful labels/text to make behaviors observable. Stub network/services with deterministic fixtures; keep stories reliable.
496
+
497
+ Variants to consider (pick only those that change behavior): default vs. alternate themes; loading vs. loaded vs. empty vs. error; validated vs. invalid input; permissions/roles/capabilities; feature flags; size/density/layout variants that alter logic.
498
+
499
+ Accessibility: Use semantic roles/labels where applicable.
500
+
501
+ Naming/structure: Use clear story names that describe the scenario ("Error state after failed submit"). Group related variants logically; don't duplicate.
502
+
503
+ Imports/format: Import Meta/StoryObj from the framework package. Keep stories minimal\u2014only what's needed to demonstrate behavior.
504
+
505
+ ## React Native Storybook Essentials
506
+
507
+ ### Framework and Renderer
508
+
509
+ React Native Storybook uses \`@storybook/react-native\` as the framework. Stories use the same CSF (Component Story Format) as web Storybook.
510
+
511
+ ### Meta and StoryObj imports
512
+
513
+ \`\`\`ts
514
+ import type { Meta, StoryObj } from '@storybook/react-native';
515
+ \`\`\`
516
+
517
+ ### Story file structure
518
+
519
+ \`\`\`tsx
520
+ import type { Meta, StoryObj } from '@storybook/react-native';
521
+ import { MyComponent } from './MyComponent';
522
+
523
+ const meta: Meta<typeof MyComponent> = {
524
+ title: 'Components/MyComponent',
525
+ component: MyComponent,
526
+ args: {
527
+ // default args
528
+ },
529
+ };
530
+
531
+ export default meta;
532
+
533
+ type Story = StoryObj<typeof meta>;
534
+
535
+ export const Default: Story = {};
536
+
537
+ export const WithCustomProps: Story = {
538
+ args: {
539
+ label: 'Custom Label',
540
+ variant: 'secondary',
541
+ },
542
+ };
543
+ \`\`\`
544
+
545
+ ### Global State Changes
546
+
547
+ The \`globals\` annotation has been renamed to \`initialGlobals\`:
548
+
549
+ \`\`\`diff
550
+ // .rnstorybook/preview.js
551
+ export default {
552
+ - globals: { theme: 'light' }
553
+ + initialGlobals: { theme: 'light' }
554
+ };
555
+ \`\`\`
556
+
557
+ ### React Native Specific Considerations
558
+
559
+ - The config directory is \`.rnstorybook\` (not \`.storybook\`)
560
+ - Stories run on-device (iOS/Android), not in a browser
561
+ - Use React Native components (\`View\`, \`Text\`, \`Pressable\`, etc.), not HTML elements
562
+ - \`StyleSheet\` or inline styles instead of CSS
563
+ - No DOM APIs \u2014 use React Native's layout system (Flexbox)
564
+ - For navigation-dependent components, mock the navigation context
565
+ - For platform-specific stories, use \`Platform.OS\` checks or separate story files
566
+ - Test on both iOS and Android when possible
567
+
568
+ ### Key Requirements
569
+
570
+ - **Node.js 20+**, **TypeScript 4.9+**
571
+ - React Native 0.72+
572
+ - Storybook 10+
573
+ `;
574
+ }
575
+ });
576
+
577
+ // src/withStorybook.ts
578
+ var withStorybook_exports = {};
579
+ __export(withStorybook_exports, {
580
+ withStorybook: () => withStorybook
581
+ });
582
+ module.exports = __toCommonJS(withStorybook_exports);
583
+ var path4 = __toESM(require("path"));
584
+
585
+ // src/enhanceMetroConfig.ts
586
+ var path = __toESM(require("path"));
587
+ function enhanceMetroConfig(config, options = {}) {
588
+ const { liteMode = false, swap } = options;
589
+ return {
590
+ ...config,
591
+ transformer: {
592
+ ...config.transformer,
593
+ unstable_allowRequireContext: true
594
+ },
595
+ resolver: {
596
+ ...config.resolver,
597
+ resolveRequest: (context, moduleName, platform) => {
598
+ const resolveFunction = config?.resolver?.resolveRequest ? config.resolver.resolveRequest : context.resolveRequest;
599
+ const shouldUseCustomResolveConfig = moduleName.startsWith("storybook") || moduleName.startsWith("@storybook") || moduleName.startsWith("uuid");
600
+ const theContext = shouldUseCustomResolveConfig ? {
601
+ ...context,
602
+ unstable_enablePackageExports: true,
603
+ unstable_conditionNames: ["import"]
604
+ } : context;
605
+ const resolveResult = resolveFunction(theContext, moduleName, platform);
606
+ if (resolveResult?.filePath?.includes?.("@storybook/react/template/cli")) {
607
+ return { type: "empty" };
608
+ }
609
+ if (moduleName === "tty" || moduleName === "os") {
610
+ return { type: "empty" };
611
+ }
612
+ if (liteMode && resolveResult?.filePath?.includes?.("@storybook/react-native-ui") && !resolveResult?.filePath?.includes?.("@storybook/react-native-ui-lite") && !resolveResult?.filePath?.includes?.("@storybook/react-native-ui-common")) {
613
+ return { type: "empty" };
614
+ }
615
+ if (swap && resolveResult?.filePath && path.resolve(resolveResult.filePath) === swap.appEntryPoint) {
616
+ return {
617
+ filePath: swap.storybookEntryPoint,
618
+ type: "sourceFile"
619
+ };
620
+ }
621
+ return resolveResult;
622
+ }
623
+ }
624
+ };
625
+ }
626
+
627
+ // src/enhanceRepackConfig.ts
628
+ function enhanceRepackConfig(config, options = {}) {
629
+ const { swap, liteMode = false } = options;
630
+ if (!swap) {
631
+ return config;
632
+ }
633
+ const result = {
634
+ ...config,
635
+ entry: swap.storybookEntryPoint
636
+ };
637
+ if (liteMode) {
638
+ const resolve4 = result.resolve ?? {};
639
+ const alias = resolve4.alias ?? {};
640
+ alias["@storybook/react-native-ui$"] = false;
641
+ result.resolve = { ...resolve4, alias };
642
+ }
643
+ return result;
644
+ }
645
+
646
+ // src/metro/utils.ts
647
+ var path2 = __toESM(require("path"));
648
+ var fs = __toESM(require("fs"));
649
+ var ENTRY_EXTENSIONS = ["js", "jsx", "ts", "tsx"];
650
+ function resolveEntryPoint(projectRoot = process.cwd()) {
651
+ const pkgJsonPath = path2.resolve(projectRoot, "package.json");
652
+ let mainField;
653
+ try {
654
+ const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
655
+ mainField = pkgJson.main;
656
+ if (mainField === "expo-router/entry") {
657
+ const expoRouterEntry = resolveFileWithExtensions(
658
+ path2.resolve(projectRoot, "node_modules", "expo-router", "entry"),
659
+ ENTRY_EXTENSIONS
660
+ );
661
+ if (expoRouterEntry) {
662
+ return expoRouterEntry;
663
+ }
664
+ }
665
+ } catch {
666
+ }
667
+ if (mainField && mainField !== "expo-router/entry") {
668
+ const resolved = resolveFileWithExtensions(
669
+ path2.resolve(projectRoot, mainField),
670
+ ENTRY_EXTENSIONS
671
+ );
672
+ if (resolved) {
673
+ return resolved;
674
+ }
675
+ }
676
+ const fallback = resolveFileWithExtensions(path2.resolve(projectRoot, "index"), ENTRY_EXTENSIONS);
677
+ return fallback;
678
+ }
679
+ function resolveFileWithExtensions(basePath, extensions) {
680
+ try {
681
+ if (fs.statSync(basePath).isFile()) {
682
+ return basePath;
683
+ }
684
+ } catch {
685
+ }
686
+ for (const ext of extensions) {
687
+ const candidate = `${basePath}.${ext}`;
688
+ if (fs.existsSync(candidate)) {
689
+ return candidate;
690
+ }
691
+ }
692
+ return void 0;
693
+ }
694
+ function resolveStorybookEntry(configPath) {
695
+ return resolveFileWithExtensions(path2.resolve(configPath, "index"), ENTRY_EXTENSIONS);
696
+ }
697
+
698
+ // src/withStorybook.ts
699
+ var import_generate = __toESM(require_generate());
700
+
701
+ // src/metro/channelServer.ts
702
+ var import_ws2 = require("ws");
703
+ var import_node_http = require("http");
704
+ var import_node_https = require("https");
705
+ init_buildIndex();
706
+
707
+ // src/metro/mcpServer.ts
708
+ var import_consumers = require("stream/consumers");
709
+ function toHeaderEntries(nodeHeaders) {
710
+ const entries = [];
711
+ for (const [key, value] of Object.entries(nodeHeaders)) {
712
+ if (value === void 0) continue;
713
+ entries.push([key, Array.isArray(value) ? value.join(", ") : value]);
714
+ }
715
+ return entries;
716
+ }
717
+ async function incomingMessageToWebRequest(req) {
718
+ const host = req.headers.host || "localhost";
719
+ const isTLS = "encrypted" in req.socket && req.socket.encrypted;
720
+ const protocol = isTLS ? "https" : "http";
721
+ const url = new URL(req.url || "/", `${protocol}://${host}`);
722
+ const bodyBuffer = await (0, import_consumers.buffer)(req);
723
+ return new Request(url, {
724
+ method: req.method,
725
+ headers: toHeaderEntries(req.headers),
726
+ body: bodyBuffer.length > 0 ? new Uint8Array(bodyBuffer) : void 0
727
+ });
728
+ }
729
+ async function webResponseToServerResponse(webResponse, nodeResponse) {
730
+ nodeResponse.statusCode = webResponse.status;
731
+ webResponse.headers.forEach((value, key) => {
732
+ nodeResponse.setHeader(key, value);
733
+ });
734
+ if (webResponse.body) {
735
+ const reader = webResponse.body.getReader();
736
+ try {
737
+ while (true) {
738
+ const { done, value } = await reader.read();
739
+ if (done) break;
740
+ nodeResponse.write(value);
741
+ }
742
+ } finally {
743
+ reader.releaseLock();
744
+ }
745
+ }
746
+ nodeResponse.end();
747
+ }
748
+ function createMcpHandler(configPath, wss) {
749
+ let handler = null;
750
+ let initPromise = null;
751
+ async function init() {
752
+ if (handler) return;
753
+ if (initPromise) {
754
+ await initPromise;
755
+ return;
756
+ }
757
+ initPromise = (async () => {
758
+ try {
759
+ const [
760
+ { McpServer },
761
+ { ValibotJsonSchemaAdapter },
762
+ { HttpTransport },
763
+ { addListAllDocumentationTool, addGetDocumentationTool, addGetStoryDocumentationTool },
764
+ { storyInstructions: storyInstructions2 },
765
+ { buildIndex: buildIndex2 },
766
+ valibot,
767
+ { experimental_manifests }
768
+ ] = await Promise.all([
769
+ import("tmcp"),
770
+ import("@tmcp/adapter-valibot"),
771
+ import("@tmcp/transport-http"),
772
+ import("@storybook/mcp"),
773
+ Promise.resolve().then(() => (init_storyInstructions(), storyInstructions_exports)),
774
+ Promise.resolve().then(() => (init_buildIndex(), buildIndex_exports)),
775
+ import("valibot"),
776
+ import("@storybook/react/preset")
777
+ ]);
778
+ const manifestProvider = async (_request, manifestPath) => {
779
+ if (manifestPath.includes("docs.json")) {
780
+ throw new Error("Docs manifest not available in React Native Storybook");
781
+ }
782
+ const index = await buildIndex2({ configPath });
783
+ const entries = Object.values(index.entries);
784
+ const manifest = await experimental_manifests({}, { manifestEntries: entries });
785
+ return JSON.stringify(manifest.components);
786
+ };
787
+ const server = new McpServer(
788
+ {
789
+ name: "@storybook/react-native",
790
+ version: "1.0.0",
791
+ description: "Storybook React Native MCP server"
792
+ },
793
+ {
794
+ adapter: new ValibotJsonSchemaAdapter(),
795
+ capabilities: {
796
+ tools: { listChanged: true }
797
+ }
798
+ }
799
+ ).withContext();
800
+ addListAllDocumentationTool(server);
801
+ addGetDocumentationTool(server);
802
+ addGetStoryDocumentationTool(server);
803
+ server.tool(
804
+ {
805
+ name: "get-storybook-story-instructions",
806
+ title: "React Native Storybook Story Instructions",
807
+ description: "Get instructions for writing React Native Storybook stories. Call this before creating or modifying story files (.stories.tsx, .stories.ts)."
808
+ },
809
+ async () => ({
810
+ content: [{ type: "text", text: storyInstructions2 }]
811
+ })
812
+ );
813
+ if (wss) {
814
+ const broadcastEvent = (event) => {
815
+ const message = JSON.stringify(event);
816
+ wss.clients.forEach((client) => {
817
+ if (client.readyState === 1) {
818
+ client.send(message);
819
+ }
820
+ });
821
+ };
822
+ server.tool(
823
+ {
824
+ name: "select-story",
825
+ title: "Select Story",
826
+ description: 'Select and display a story on the connected device. Use the story ID in the format "title--name" (e.g. "button--primary"). Use the list-all-documentation tool to discover available components and stories.',
827
+ schema: valibot.object({ storyId: valibot.string() })
828
+ },
829
+ async ({ storyId }) => {
830
+ try {
831
+ const index = await buildIndex2({ configPath });
832
+ if (!index.entries[storyId]) {
833
+ const availableIds = Object.keys(index.entries).slice(0, 10);
834
+ return {
835
+ content: [
836
+ {
837
+ type: "text",
838
+ text: `Story "${storyId}" not found. Available stories include: ${availableIds.join(", ")}` + (Object.keys(index.entries).length > 10 ? ", ..." : "")
839
+ }
840
+ ],
841
+ isError: true
842
+ };
843
+ }
844
+ broadcastEvent({
845
+ type: "setCurrentStory",
846
+ args: [{ storyId, viewMode: "story" }]
847
+ });
848
+ const entry = index.entries[storyId];
849
+ return {
850
+ content: [
851
+ {
852
+ type: "text",
853
+ text: `Selected story "${entry.name}" (${entry.title}) on connected devices.`
854
+ }
855
+ ]
856
+ };
857
+ } catch (error) {
858
+ return {
859
+ content: [
860
+ {
861
+ type: "text",
862
+ text: `Failed to select story: ${error instanceof Error ? error.message : String(error)}`
863
+ }
864
+ ],
865
+ isError: true
866
+ };
867
+ }
868
+ }
869
+ );
870
+ }
871
+ const transport = new HttpTransport(server, { path: null });
872
+ handler = (req) => transport.respond(req, {
873
+ request: req,
874
+ manifestProvider
875
+ });
876
+ console.log("[Storybook] MCP server initialized");
877
+ } catch (error) {
878
+ initPromise = null;
879
+ console.error("[Storybook] Failed to initialize MCP server:", error);
880
+ throw error;
881
+ }
882
+ })();
883
+ await initPromise;
884
+ }
885
+ async function handleMcpRequest(req, res) {
886
+ try {
887
+ await init();
888
+ if (!handler) {
889
+ res.writeHead(500, { "Content-Type": "application/json" });
890
+ res.end(JSON.stringify({ error: "MCP handler not initialized" }));
891
+ return;
892
+ }
893
+ const webRequest = await incomingMessageToWebRequest(req);
894
+ const webResponse = await handler(webRequest);
895
+ await webResponseToServerResponse(webResponse, res);
896
+ } catch (error) {
897
+ console.error("[Storybook] MCP request failed:", error);
898
+ res.writeHead(500, { "Content-Type": "application/json" });
899
+ res.end(JSON.stringify({ error: "MCP request failed" }));
900
+ }
901
+ }
902
+ function preInit() {
903
+ init().catch(
904
+ (e) => console.warn("[Storybook] MCP pre-initialization failed (will retry on first request):", e)
905
+ );
906
+ }
907
+ return { handleMcpRequest, preInit };
908
+ }
909
+
910
+ // src/metro/selectStorySyncEndpoint.ts
911
+ var import_ws = require("ws");
912
+ var SELECT_STORY_SYNC_ROUTE = "/select-story-sync/";
913
+ var SELECT_STORY_SYNC_TIMEOUT_MS = 1e3;
914
+ var LAST_RENDERED_STORY_TIMEOUT_MS = 500;
915
+ function getRenderedStoryId(event) {
916
+ if (!event || typeof event !== "object") {
917
+ return null;
918
+ }
919
+ const { type, args } = event;
920
+ if (type !== "storyRendered" || !Array.isArray(args) || args.length === 0) {
921
+ return null;
922
+ }
923
+ const [firstArg] = args;
924
+ if (typeof firstArg === "string") {
925
+ return firstArg;
926
+ }
927
+ if (firstArg && typeof firstArg === "object" && "storyId" in firstArg) {
928
+ const { storyId } = firstArg;
929
+ return typeof storyId === "string" ? storyId : null;
930
+ }
931
+ return null;
932
+ }
933
+ function parseStoryIdFromPath(pathname) {
934
+ const match = pathname.match(/^\/select-story-sync\/([^/]+)$/);
935
+ if (!match) {
936
+ return null;
937
+ }
938
+ try {
939
+ const storyId = decodeURIComponent(match[1]);
940
+ return storyId || null;
941
+ } catch {
942
+ return null;
943
+ }
944
+ }
945
+ function createSelectStorySyncEndpoint(wss) {
946
+ const pendingStorySelections = /* @__PURE__ */ new Map();
947
+ const lastRenderedStoryIdByClient = /* @__PURE__ */ new Map();
948
+ const waitForStoryRender = (storyId, timeoutMs) => {
949
+ let cancelSelection = () => {
950
+ };
951
+ let resolveWait = () => {
952
+ };
953
+ const promise = new Promise((resolve4, reject) => {
954
+ resolveWait = resolve4;
955
+ let selections = pendingStorySelections.get(storyId);
956
+ if (!selections) {
957
+ selections = /* @__PURE__ */ new Set();
958
+ pendingStorySelections.set(storyId, selections);
959
+ }
960
+ const cleanup = () => {
961
+ clearTimeout(selection.timeout);
962
+ selections.delete(selection);
963
+ if (selections.size === 0) {
964
+ pendingStorySelections.delete(storyId);
965
+ }
966
+ };
967
+ const selection = {
968
+ resolve: () => {
969
+ if (selection.settled) {
970
+ return;
971
+ }
972
+ selection.settled = true;
973
+ cleanup();
974
+ resolve4();
975
+ },
976
+ timeout: setTimeout(() => {
977
+ if (selection.settled) {
978
+ return;
979
+ }
980
+ selection.settled = true;
981
+ cleanup();
982
+ reject(new Error(`Story "${storyId}" did not render in time`));
983
+ }, timeoutMs),
984
+ settled: false
985
+ };
986
+ cancelSelection = () => {
987
+ if (selection.settled) {
988
+ return;
989
+ }
990
+ selection.settled = true;
991
+ cleanup();
992
+ resolveWait();
993
+ };
994
+ selections.add(selection);
995
+ });
996
+ return {
997
+ promise,
998
+ cancel: cancelSelection
999
+ };
1000
+ };
1001
+ const resolveStorySelection = (storyId) => {
1002
+ const selections = pendingStorySelections.get(storyId);
1003
+ if (!selections) {
1004
+ return;
1005
+ }
1006
+ [...selections].forEach((selection) => selection.resolve());
1007
+ };
1008
+ const handleRequest = async (pathname, res) => {
1009
+ const storyId = parseStoryIdFromPath(pathname);
1010
+ if (!storyId) {
1011
+ res.writeHead(400, { "Content-Type": "application/json" });
1012
+ res.end(JSON.stringify({ success: false, error: "Invalid story id" }));
1013
+ return;
1014
+ }
1015
+ const waitForRender = waitForStoryRender(storyId, SELECT_STORY_SYNC_TIMEOUT_MS);
1016
+ const message = JSON.stringify({
1017
+ type: "setCurrentStory",
1018
+ args: [{ viewMode: "story", storyId }]
1019
+ });
1020
+ wss.clients.forEach((wsClient) => {
1021
+ if (wsClient.readyState === import_ws.WebSocket.OPEN) {
1022
+ wsClient.send(message);
1023
+ }
1024
+ });
1025
+ try {
1026
+ const hasConnectedClientWithRenderedStory = [...wss.clients].some(
1027
+ (client) => client.readyState === import_ws.WebSocket.OPEN && lastRenderedStoryIdByClient.get(client) === storyId
1028
+ );
1029
+ if (hasConnectedClientWithRenderedStory) {
1030
+ const raceResult = await Promise.race([
1031
+ waitForRender.promise.then(() => "rendered"),
1032
+ new Promise((resolve4) => {
1033
+ setTimeout(() => resolve4("alreadyRendered"), LAST_RENDERED_STORY_TIMEOUT_MS);
1034
+ })
1035
+ ]);
1036
+ if (raceResult === "alreadyRendered") {
1037
+ waitForRender.cancel();
1038
+ }
1039
+ } else {
1040
+ await waitForRender.promise;
1041
+ }
1042
+ res.writeHead(200, { "Content-Type": "application/json" });
1043
+ res.end(JSON.stringify({ success: true, storyId }));
1044
+ } catch (error) {
1045
+ res.writeHead(408, { "Content-Type": "application/json" });
1046
+ res.end(
1047
+ JSON.stringify({
1048
+ success: false,
1049
+ storyId,
1050
+ error: error instanceof Error ? error.message : String(error)
1051
+ })
1052
+ );
1053
+ }
1054
+ };
1055
+ const onSocketMessage = (event, ws) => {
1056
+ const renderedStoryId = getRenderedStoryId(event);
1057
+ if (renderedStoryId) {
1058
+ lastRenderedStoryIdByClient.set(ws, renderedStoryId);
1059
+ resolveStorySelection(renderedStoryId);
1060
+ }
1061
+ };
1062
+ const onSocketClose = (ws) => {
1063
+ lastRenderedStoryIdByClient.delete(ws);
1064
+ };
1065
+ return {
1066
+ handleRequest,
1067
+ onSocketMessage,
1068
+ onSocketClose
1069
+ };
1070
+ }
1071
+
1072
+ // src/metro/channelServer.ts
1073
+ function createChannelServer({
1074
+ port = 7007,
1075
+ host = void 0,
1076
+ configPath,
1077
+ experimental_mcp = false,
1078
+ websockets = true,
1079
+ secured = false,
1080
+ ssl
1081
+ }) {
1082
+ if (secured && (!ssl?.key || !ssl?.cert)) {
1083
+ throw new Error("[Storybook] Secure channel server requires both `ssl.key` and `ssl.cert`.");
1084
+ }
1085
+ const httpServer = secured ? (0, import_node_https.createServer)(ssl) : (0, import_node_http.createServer)();
1086
+ const wss = websockets ? new import_ws2.WebSocketServer({ server: httpServer }) : null;
1087
+ const mcpServer = experimental_mcp ? createMcpHandler(configPath, wss ?? void 0) : null;
1088
+ const selectStorySyncEndpoint = wss ? createSelectStorySyncEndpoint(wss) : null;
1089
+ httpServer.on("request", async (req, res) => {
1090
+ const protocol = "encrypted" in req.socket && req.socket.encrypted ? "https" : "http";
1091
+ const requestUrl = new URL(req.url ?? "/", `${protocol}://${req.headers.host ?? "localhost"}`);
1092
+ if (req.method === "OPTIONS") {
1093
+ res.writeHead(204);
1094
+ res.end();
1095
+ return;
1096
+ }
1097
+ if (req.method === "GET" && requestUrl.pathname === "/index.json") {
1098
+ try {
1099
+ const index = await buildIndex({ configPath });
1100
+ res.writeHead(200, { "Content-Type": "application/json" });
1101
+ res.end(JSON.stringify(index));
1102
+ } catch (error) {
1103
+ console.error("Failed to build index:", error);
1104
+ res.writeHead(500, { "Content-Type": "application/json" });
1105
+ res.end(JSON.stringify({ error: "Failed to build story index" }));
1106
+ }
1107
+ return;
1108
+ }
1109
+ if (req.method === "POST" && requestUrl.pathname === "/send-event") {
1110
+ if (!wss) {
1111
+ res.writeHead(503, { "Content-Type": "application/json" });
1112
+ res.end(JSON.stringify({ success: false, error: "WebSockets are disabled" }));
1113
+ return;
1114
+ }
1115
+ let body = "";
1116
+ req.on("data", (chunk) => {
1117
+ body += chunk.toString();
1118
+ });
1119
+ req.on("end", () => {
1120
+ try {
1121
+ const json = JSON.parse(body);
1122
+ wss.clients.forEach((wsClient) => wsClient.send(JSON.stringify(json)));
1123
+ res.writeHead(200, { "Content-Type": "application/json" });
1124
+ res.end(JSON.stringify({ success: true }));
1125
+ } catch (error) {
1126
+ console.error("Failed to parse event:", error);
1127
+ res.writeHead(400, { "Content-Type": "application/json" });
1128
+ res.end(JSON.stringify({ success: false, error: "Invalid JSON" }));
1129
+ }
1130
+ });
1131
+ return;
1132
+ }
1133
+ if (req.method === "POST" && requestUrl.pathname.startsWith(SELECT_STORY_SYNC_ROUTE)) {
1134
+ if (!selectStorySyncEndpoint) {
1135
+ res.writeHead(503, { "Content-Type": "application/json" });
1136
+ res.end(JSON.stringify({ success: false, error: "WebSockets are disabled" }));
1137
+ return;
1138
+ }
1139
+ await selectStorySyncEndpoint.handleRequest(requestUrl.pathname, res);
1140
+ return;
1141
+ }
1142
+ if (mcpServer && requestUrl.pathname === "/mcp" && (req.method === "POST" || req.method === "GET")) {
1143
+ await mcpServer.handleMcpRequest(req, res);
1144
+ return;
1145
+ }
1146
+ res.writeHead(404, { "Content-Type": "application/json" });
1147
+ res.end(JSON.stringify({ error: "Not found" }));
1148
+ });
1149
+ if (wss) {
1150
+ wss.on("error", () => {
1151
+ });
1152
+ const pingInterval = setInterval(function ping() {
1153
+ wss.clients.forEach(function each(client) {
1154
+ if (client.readyState === import_ws2.WebSocket.OPEN) {
1155
+ client.send(JSON.stringify({ type: "ping", args: [] }));
1156
+ }
1157
+ });
1158
+ }, 1e4);
1159
+ pingInterval.unref?.();
1160
+ wss.on("connection", function connection(ws) {
1161
+ console.log("WebSocket connection established");
1162
+ ws.on("error", console.error);
1163
+ ws.on("message", function message(data) {
1164
+ try {
1165
+ const json = JSON.parse(data.toString());
1166
+ selectStorySyncEndpoint?.onSocketMessage(json, ws);
1167
+ const msg = JSON.stringify(json);
1168
+ wss.clients.forEach((wsClient) => {
1169
+ if (wsClient !== ws && wsClient.readyState === import_ws2.WebSocket.OPEN) {
1170
+ wsClient.send(msg);
1171
+ }
1172
+ });
1173
+ } catch (error) {
1174
+ console.error(error);
1175
+ }
1176
+ });
1177
+ ws.on("close", () => {
1178
+ selectStorySyncEndpoint?.onSocketClose(ws);
1179
+ });
1180
+ });
1181
+ }
1182
+ httpServer.on("error", (error) => {
1183
+ if (error.code === "EADDRINUSE") {
1184
+ console.warn(
1185
+ `[Storybook] Port ${port} is already in use. The channel server will not start. Another instance may already be running.`
1186
+ );
1187
+ } else {
1188
+ console.error(`[Storybook] Channel server error:`, error);
1189
+ }
1190
+ });
1191
+ httpServer.listen(port, host, () => {
1192
+ const protocol = wss ? secured ? "WSS" : "WebSocket" : secured ? "HTTPS" : "HTTP";
1193
+ console.log(`${protocol} server listening on ${host ?? "localhost"}:${port}`);
1194
+ });
1195
+ mcpServer?.preInit();
1196
+ return wss;
1197
+ }
1198
+
1199
+ // src/withStorybook.ts
1200
+ function envVariableToBoolean(value, defaultValue = false) {
1201
+ switch (value) {
1202
+ case "true":
1203
+ return true;
1204
+ case "false":
1205
+ return false;
1206
+ default:
1207
+ return !!defaultValue;
1208
+ }
1209
+ }
1210
+ function envVariableToString(value, defaultValue) {
1211
+ return value ?? defaultValue;
1212
+ }
1213
+ function envVariableToNumber(value, defaultValue) {
1214
+ const parsed = parseInt(value ?? "", 10);
1215
+ if (!isNaN(parsed)) {
1216
+ return parsed;
1217
+ }
1218
+ return defaultValue;
1219
+ }
1220
+ function isMetroConfig(config) {
1221
+ return config != null && typeof config === "object" && "transformer" in config;
1222
+ }
1223
+ function loadWebsocketEnvOverrides(websockets) {
1224
+ const envHost = envVariableToString(
1225
+ process.env.STORYBOOK_WS_HOST,
1226
+ websockets === "auto" ? void 0 : websockets?.host ?? void 0
1227
+ );
1228
+ const envPort = envVariableToNumber(
1229
+ process.env.STORYBOOK_WS_PORT,
1230
+ websockets === "auto" ? 7007 : websockets?.port ?? 7007
1231
+ );
1232
+ const envSecured = envVariableToBoolean(process.env.STORYBOOK_WS_SECURED);
1233
+ if (websockets === void 0 && !envHost) {
1234
+ return {
1235
+ host: void 0,
1236
+ port: void 0,
1237
+ secured: false
1238
+ };
1239
+ }
1240
+ const config = websockets === "auto" || websockets === void 0 ? {} : { ...websockets };
1241
+ if (envHost) {
1242
+ config.host = envHost;
1243
+ }
1244
+ if (envPort) {
1245
+ config.port = envPort;
1246
+ }
1247
+ if (envSecured) {
1248
+ config.secured = true;
1249
+ }
1250
+ return config;
1251
+ }
1252
+ function withStorybook(config, options = {}) {
1253
+ const enabled = envVariableToBoolean(process.env.STORYBOOK_ENABLED, false);
1254
+ if (!enabled) {
1255
+ return config;
1256
+ }
1257
+ const server = envVariableToBoolean(process.env.STORYBOOK_SERVER, true);
1258
+ const liteMode = envVariableToBoolean(process.env.STORYBOOK_LITE_MODE, options.liteMode ?? false);
1259
+ const settings = { ...options };
1260
+ if (server) {
1261
+ settings.experimental_mcp = false;
1262
+ }
1263
+ if (liteMode) {
1264
+ settings.docTools = false;
1265
+ }
1266
+ const defaultConfigPath = path4.resolve(process.cwd(), "./.rnstorybook");
1267
+ const configPath = options.configPath || defaultConfigPath;
1268
+ const websockets = loadWebsocketEnvOverrides(options.websockets);
1269
+ const appEntryPoint = resolveEntryPoint();
1270
+ const storybookEntryPoint = resolveStorybookEntry(configPath);
1271
+ const swap = appEntryPoint && storybookEntryPoint ? { appEntryPoint, storybookEntryPoint } : void 0;
1272
+ const { useJs = false, docTools = true, experimental_mcp = false } = settings;
1273
+ if (server || experimental_mcp) {
1274
+ createChannelServer({
1275
+ port: websockets.port,
1276
+ host: websockets.host,
1277
+ configPath,
1278
+ experimental_mcp,
1279
+ websockets: Boolean(websockets.host),
1280
+ secured: websockets.secured,
1281
+ ssl: websockets.secured ? {
1282
+ key: websockets.key,
1283
+ cert: websockets.cert,
1284
+ ca: websockets.ca,
1285
+ passphrase: websockets.passphrase
1286
+ } : void 0
1287
+ });
1288
+ }
1289
+ const host = websockets.host;
1290
+ (0, import_generate.generate)({
1291
+ configPath,
1292
+ useJs,
1293
+ docTools,
1294
+ ...!!host ? { host, port: websockets.port, secured: !websockets.secured } : {}
1295
+ });
1296
+ if (isMetroConfig(config)) {
1297
+ return enhanceMetroConfig(config, { liteMode, swap });
1298
+ }
1299
+ return enhanceRepackConfig(config, { liteMode, swap });
1300
+ }
1301
+ // Annotate the CommonJS export names for ESM import in node:
1302
+ 0 && (module.exports = {
1303
+ withStorybook
1304
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@storybook/react-native",
3
- "version": "10.3.1",
3
+ "version": "10.4.0-canary-20260407095432",
4
4
  "description": "A better way to develop React Native Components for your app",
5
5
  "keywords": [
6
6
  "react",
@@ -23,6 +23,7 @@
23
23
  },
24
24
  "exports": {
25
25
  ".": "./dist/index.js",
26
+ "./withStorybook": "./dist/withStorybook.js",
26
27
  "./metro/withStorybook": "./dist/metro/withStorybook.js",
27
28
  "./repack/withStorybook": "./dist/repack/withStorybook.js",
28
29
  "./metro-env": "./metro-env.d.ts",
@@ -45,9 +46,9 @@
45
46
  "dependencies": {
46
47
  "@storybook/mcp": "^0.6.1",
47
48
  "@storybook/react": "^10.3.1",
48
- "@storybook/react-native-theming": "^10.3.1",
49
- "@storybook/react-native-ui": "^10.3.1",
50
- "@storybook/react-native-ui-common": "^10.3.1",
49
+ "@storybook/react-native-theming": "10.4.0-canary-20260407095432",
50
+ "@storybook/react-native-ui": "10.4.0-canary-20260407095432",
51
+ "@storybook/react-native-ui-common": "10.4.0-canary-20260407095432",
51
52
  "@tmcp/adapter-valibot": "^0.1.4",
52
53
  "@tmcp/transport-http": "^0.8.5",
53
54
  "commander": "^14.0.2",