@storybook/react-native 10.4.0 → 10.4.1-canary-20260510001247

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,197 @@
1
+ const require_chunk = require("./chunk-Ble4zEEl.js");
2
+ let storybook_internal_preview_api = require("storybook/internal/preview-api");
3
+ let storybook_internal_csf = require("storybook/internal/csf");
4
+ let path = require("path");
5
+ path = require_chunk.__toESM(path);
6
+ let storybook_internal_common = require("storybook/internal/common");
7
+ let node_fs = require("node:fs");
8
+ let glob = require("glob");
9
+ let storybook_internal_csf_tools = require("storybook/internal/csf-tools");
10
+ //#region scripts/common.js
11
+ var require_common = /* @__PURE__ */ require_chunk.__commonJSMin(((exports, module) => {
12
+ const { globToRegexp } = require("storybook/internal/common");
13
+ const path$2 = require("path");
14
+ const fs = require("fs");
15
+ const cwd = process.cwd();
16
+ const toRequireContext = (specifier) => {
17
+ const { directory, files } = specifier;
18
+ const match = globToRegexp(`./${files}`);
19
+ return {
20
+ path: directory,
21
+ recursive: files.includes("**") || files.split("/").length > 1,
22
+ match
23
+ };
24
+ };
25
+ const supportedExtensions = [
26
+ "js",
27
+ "jsx",
28
+ "ts",
29
+ "tsx",
30
+ "cjs",
31
+ "mjs"
32
+ ];
33
+ function getFilePathExtension({ configPath }, fileName) {
34
+ for (const ext of supportedExtensions) {
35
+ const filePath = path$2.resolve(cwd, configPath, `${fileName}.${ext}`);
36
+ if (fs.existsSync(filePath)) return ext;
37
+ }
38
+ return null;
39
+ }
40
+ function getFilePathWithExtension({ configPath }, fileName) {
41
+ for (const ext of supportedExtensions) {
42
+ const filePath = path$2.resolve(cwd, configPath, `${fileName}.${ext}`);
43
+ if (fs.existsSync(filePath)) return filePath;
44
+ }
45
+ return null;
46
+ }
47
+ function ensureRelativePathHasDot(relativePath) {
48
+ return relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
49
+ }
50
+ function getPreviewExists({ configPath }) {
51
+ return !!getFilePathExtension({ configPath }, "preview");
52
+ }
53
+ function resolveAddonFile(addon, file, extensions = [
54
+ "js",
55
+ "mjs",
56
+ "ts"
57
+ ], configPath) {
58
+ if (!addon || typeof addon !== "string") return null;
59
+ const resolvePaths = { paths: [cwd] };
60
+ try {
61
+ const basePath = `${addon}/${file}`;
62
+ require.resolve(basePath, resolvePaths);
63
+ return basePath;
64
+ } catch (_error) {}
65
+ for (const ext of extensions) try {
66
+ const filePath = `${addon}/${file}.${ext}`;
67
+ require.resolve(filePath, resolvePaths);
68
+ return filePath;
69
+ } catch (_error) {}
70
+ if (addon.startsWith("./") || addon.startsWith("../")) try {
71
+ if (getFilePathExtension({ configPath }, `${addon}/${file}`)) return `${addon}/${file}`;
72
+ } catch (_error) {}
73
+ return null;
74
+ }
75
+ function getAddonName(addon) {
76
+ if (typeof addon === "string") return addon;
77
+ if (typeof addon === "object" && addon.name && typeof addon.name === "string") return addon.name;
78
+ console.error("Invalid addon configuration", addon);
79
+ return null;
80
+ }
81
+ module.exports = {
82
+ toRequireContext,
83
+ getFilePathExtension,
84
+ ensureRelativePathHasDot,
85
+ getPreviewExists,
86
+ resolveAddonFile,
87
+ getAddonName,
88
+ getFilePathWithExtension
89
+ };
90
+ }));
91
+ //#endregion
92
+ //#region src/metro/buildIndex.ts
93
+ var buildIndex_exports = /* @__PURE__ */ require_chunk.__exportAll({ buildIndex: () => buildIndex });
94
+ var import_common = require_common();
95
+ const cwd = process.cwd();
96
+ const makeTitle = (fileName, specifier, userTitle) => {
97
+ const title = (0, storybook_internal_preview_api.userOrAutoTitleFromSpecifier)(fileName, specifier, userTitle);
98
+ if (title) return title.replace("./", "");
99
+ else if (userTitle) return userTitle.replace("./", "");
100
+ else {
101
+ console.error("Could not generate title!!");
102
+ process.exit(1);
103
+ }
104
+ };
105
+ function ensureRelativePathHasDot(relativePath) {
106
+ return relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
107
+ }
108
+ async function buildIndex({ configPath }) {
109
+ const main = await (0, storybook_internal_common.loadMainConfig)({
110
+ configDir: configPath,
111
+ cwd
112
+ });
113
+ if (!main.stories || !Array.isArray(main.stories)) throw new Error("No stories found");
114
+ const storiesSpecifiers = (0, storybook_internal_common.normalizeStories)(main.stories, {
115
+ configDir: configPath,
116
+ workingDir: cwd
117
+ });
118
+ const csfStories = storiesSpecifiers.map((specifier) => {
119
+ return (0, glob.sync)(specifier.files, {
120
+ cwd: path.default.resolve(process.cwd(), specifier.directory),
121
+ absolute: true,
122
+ ignore: ["**/node_modules"]
123
+ }).map((storyPath) => {
124
+ const normalizePathForWindows = (str) => path.default.sep === "\\" ? str.replace(/\\/g, "/") : str;
125
+ return normalizePathForWindows(storyPath);
126
+ });
127
+ }).reduce((acc, specifierStoryPathList, specifierIndex) => {
128
+ const paths = specifierStoryPathList.map((storyPath) => {
129
+ const code = (0, node_fs.readFileSync)(storyPath, { encoding: "utf-8" }).toString();
130
+ const relativePath = ensureRelativePathHasDot(path.default.posix.relative(cwd, storyPath));
131
+ return {
132
+ result: (0, storybook_internal_csf_tools.loadCsf)(code, {
133
+ fileName: storyPath,
134
+ makeTitle: (userTitle) => makeTitle(relativePath, storiesSpecifiers[specifierIndex], userTitle)
135
+ }).parse(),
136
+ specifier: storiesSpecifiers[specifierIndex],
137
+ fileName: relativePath
138
+ };
139
+ });
140
+ return [...acc, ...paths];
141
+ }, new Array());
142
+ const index = {
143
+ v: 5,
144
+ entries: {}
145
+ };
146
+ for (const { result, specifier, fileName } of csfStories) {
147
+ const { meta, stories } = result;
148
+ if (stories && stories.length > 0) for (const story of stories) {
149
+ const id = story.id ?? (0, storybook_internal_csf.toId)(meta.title, story.name);
150
+ if (!id) throw new Error(`Failed to generate id for story ${story.name} in file ${fileName}`);
151
+ index.entries[id] = {
152
+ type: "story",
153
+ subtype: "story",
154
+ id,
155
+ name: story.name,
156
+ title: meta.title,
157
+ importPath: `${specifier.directory}/${path.default.posix.relative(specifier.directory, fileName)}`,
158
+ tags: ["story"]
159
+ };
160
+ }
161
+ else console.log(`No stories found for ${fileName}`);
162
+ }
163
+ try {
164
+ const storySort = (0, storybook_internal_csf_tools.getStorySortParameter)((0, node_fs.readFileSync)((0, import_common.getFilePathWithExtension)({ configPath }, "preview"), { encoding: "utf-8" }).toString());
165
+ const sortableStories = Object.values(index.entries);
166
+ (0, storybook_internal_preview_api.sortStoriesV7)(sortableStories, storySort, sortableStories.map((entry) => entry.importPath));
167
+ return {
168
+ v: 5,
169
+ entries: sortableStories.reduce((acc, item) => {
170
+ acc[item.id] = item;
171
+ return acc;
172
+ }, {})
173
+ };
174
+ } catch {
175
+ console.warn("Failed to sort stories, using unordered index");
176
+ return index;
177
+ }
178
+ }
179
+ //#endregion
180
+ Object.defineProperty(exports, "buildIndex", {
181
+ enumerable: true,
182
+ get: function() {
183
+ return buildIndex;
184
+ }
185
+ });
186
+ Object.defineProperty(exports, "buildIndex_exports", {
187
+ enumerable: true,
188
+ get: function() {
189
+ return buildIndex_exports;
190
+ }
191
+ });
192
+ Object.defineProperty(exports, "require_common", {
193
+ enumerable: true,
194
+ get: function() {
195
+ return require_common;
196
+ }
197
+ });
@@ -0,0 +1,471 @@
1
+ require("./chunk-Ble4zEEl.js");
2
+ const require_buildIndex = require("./buildIndex-l9rzAl79.js");
3
+ let ws = require("ws");
4
+ let node_http = require("node:http");
5
+ let node_https = require("node:https");
6
+ let node_stream_consumers = require("node:stream/consumers");
7
+ //#region src/metro/mcpServer.ts
8
+ /**
9
+ * Converts Node.js IncomingHttpHeaders to a format compatible with the Web Headers API.
10
+ * Handles multi-value headers by joining them with commas per HTTP spec.
11
+ */
12
+ function toHeaderEntries(nodeHeaders) {
13
+ const entries = [];
14
+ for (const [key, value] of Object.entries(nodeHeaders)) {
15
+ if (value === void 0) continue;
16
+ entries.push([key, Array.isArray(value) ? value.join(", ") : value]);
17
+ }
18
+ return entries;
19
+ }
20
+ /**
21
+ * Converts a Node.js IncomingMessage to a Web Request object.
22
+ */
23
+ async function incomingMessageToWebRequest(req) {
24
+ const host = req.headers.host || "localhost";
25
+ const protocol = "encrypted" in req.socket && req.socket.encrypted ? "https" : "http";
26
+ const url = new URL(req.url || "/", `${protocol}://${host}`);
27
+ const bodyBuffer = await (0, node_stream_consumers.buffer)(req);
28
+ return new Request(url, {
29
+ method: req.method,
30
+ headers: toHeaderEntries(req.headers),
31
+ body: bodyBuffer.length > 0 ? new Uint8Array(bodyBuffer) : void 0
32
+ });
33
+ }
34
+ /**
35
+ * Converts a Web Response to a Node.js ServerResponse.
36
+ */
37
+ async function webResponseToServerResponse(webResponse, nodeResponse) {
38
+ nodeResponse.statusCode = webResponse.status;
39
+ webResponse.headers.forEach((value, key) => {
40
+ nodeResponse.setHeader(key, value);
41
+ });
42
+ if (webResponse.body) {
43
+ const reader = webResponse.body.getReader();
44
+ try {
45
+ while (true) {
46
+ const { done, value } = await reader.read();
47
+ if (done) break;
48
+ nodeResponse.write(value);
49
+ }
50
+ } finally {
51
+ reader.releaseLock();
52
+ }
53
+ }
54
+ nodeResponse.end();
55
+ }
56
+ /**
57
+ * Creates an MCP (Model Context Protocol) request handler for AI agent integration.
58
+ *
59
+ * Provides tools for querying component documentation, props, and story snippets,
60
+ * plus React Native-specific story writing instructions.
61
+ *
62
+ * @param configPath - Path to the Storybook config folder, used for building the component manifest.
63
+ */
64
+ function createMcpHandler(configPath, wss) {
65
+ let handler = null;
66
+ let initPromise = null;
67
+ async function init() {
68
+ if (handler) return;
69
+ if (initPromise) {
70
+ await initPromise;
71
+ return;
72
+ }
73
+ initPromise = (async () => {
74
+ try {
75
+ const [{ McpServer }, { ValibotJsonSchemaAdapter }, { HttpTransport }, { addListAllDocumentationTool, addGetDocumentationTool, addGetStoryDocumentationTool }, { storyInstructions }, { buildIndex }, valibot, { experimental_manifests }] = await Promise.all([
76
+ import("tmcp"),
77
+ import("@tmcp/adapter-valibot"),
78
+ import("@tmcp/transport-http"),
79
+ import("@storybook/mcp"),
80
+ Promise.resolve().then(() => require("./storyInstructions-FLR5G1Xm.js")),
81
+ Promise.resolve().then(() => require("./buildIndex-l9rzAl79.js")).then((n) => n.buildIndex_exports),
82
+ import("valibot"),
83
+ import("@storybook/react/preset")
84
+ ]);
85
+ const manifestProvider = async (_request, manifestPath) => {
86
+ if (manifestPath.includes("docs.json")) throw new Error("Docs manifest not available in React Native Storybook");
87
+ const index = await buildIndex({ configPath });
88
+ const manifest = await experimental_manifests({}, { manifestEntries: Object.values(index.entries) });
89
+ return JSON.stringify(manifest.components);
90
+ };
91
+ const server = new McpServer({
92
+ name: "@storybook/react-native",
93
+ version: "1.0.0",
94
+ description: "Storybook React Native MCP server"
95
+ }, {
96
+ adapter: new ValibotJsonSchemaAdapter(),
97
+ capabilities: { tools: { listChanged: true } }
98
+ }).withContext();
99
+ addListAllDocumentationTool(server);
100
+ addGetDocumentationTool(server);
101
+ addGetStoryDocumentationTool(server);
102
+ server.tool({
103
+ name: "get-storybook-story-instructions",
104
+ title: "React Native Storybook Story Instructions",
105
+ description: "Get instructions for writing React Native Storybook stories. Call this before creating or modifying story files (.stories.tsx, .stories.ts)."
106
+ }, async () => ({ content: [{
107
+ type: "text",
108
+ text: storyInstructions
109
+ }] }));
110
+ if (wss) {
111
+ const broadcastEvent = (event) => {
112
+ const message = JSON.stringify(event);
113
+ wss.clients.forEach((client) => {
114
+ if (client.readyState === 1) client.send(message);
115
+ });
116
+ };
117
+ server.tool({
118
+ name: "select-story",
119
+ title: "Select Story",
120
+ 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.",
121
+ schema: valibot.object({ storyId: valibot.string() })
122
+ }, async ({ storyId }) => {
123
+ try {
124
+ const index = await buildIndex({ configPath });
125
+ if (!index.entries[storyId]) return {
126
+ content: [{
127
+ type: "text",
128
+ text: `Story "${storyId}" not found. Available stories include: ${Object.keys(index.entries).slice(0, 10).join(", ")}` + (Object.keys(index.entries).length > 10 ? ", ..." : "")
129
+ }],
130
+ isError: true
131
+ };
132
+ broadcastEvent({
133
+ type: "setCurrentStory",
134
+ args: [{
135
+ storyId,
136
+ viewMode: "story"
137
+ }]
138
+ });
139
+ const entry = index.entries[storyId];
140
+ return { content: [{
141
+ type: "text",
142
+ text: `Selected story "${entry.name}" (${entry.title}) on connected devices.`
143
+ }] };
144
+ } catch (error) {
145
+ return {
146
+ content: [{
147
+ type: "text",
148
+ text: `Failed to select story: ${error instanceof Error ? error.message : String(error)}`
149
+ }],
150
+ isError: true
151
+ };
152
+ }
153
+ });
154
+ }
155
+ const transport = new HttpTransport(server, { path: null });
156
+ handler = (req) => transport.respond(req, {
157
+ request: req,
158
+ manifestProvider
159
+ });
160
+ console.log("[Storybook] MCP server initialized");
161
+ } catch (error) {
162
+ initPromise = null;
163
+ console.error("[Storybook] Failed to initialize MCP server:", error);
164
+ throw error;
165
+ }
166
+ })();
167
+ await initPromise;
168
+ }
169
+ /**
170
+ * Handles an incoming MCP HTTP request (POST /mcp or GET /mcp).
171
+ */
172
+ async function handleMcpRequest(req, res) {
173
+ try {
174
+ await init();
175
+ if (!handler) {
176
+ res.writeHead(500, { "Content-Type": "application/json" });
177
+ res.end(JSON.stringify({ error: "MCP handler not initialized" }));
178
+ return;
179
+ }
180
+ const webRequest = await incomingMessageToWebRequest(req);
181
+ await webResponseToServerResponse(await handler(webRequest), res);
182
+ } catch (error) {
183
+ console.error("[Storybook] MCP request failed:", error);
184
+ res.writeHead(500, { "Content-Type": "application/json" });
185
+ res.end(JSON.stringify({ error: "MCP request failed" }));
186
+ }
187
+ }
188
+ /**
189
+ * Pre-initializes the MCP server (non-blocking).
190
+ */
191
+ function preInit() {
192
+ init().catch((e) => console.warn("[Storybook] MCP pre-initialization failed (will retry on first request):", e));
193
+ }
194
+ return {
195
+ handleMcpRequest,
196
+ preInit
197
+ };
198
+ }
199
+ const SELECT_STORY_SYNC_TIMEOUT_MS = 1e3;
200
+ const LAST_RENDERED_STORY_TIMEOUT_MS = 500;
201
+ function getRenderedStoryId(event) {
202
+ if (!event || typeof event !== "object") return null;
203
+ const { type, args } = event;
204
+ if (type !== "storyRendered" || !Array.isArray(args) || args.length === 0) return null;
205
+ const [firstArg] = args;
206
+ if (typeof firstArg === "string") return firstArg;
207
+ if (firstArg && typeof firstArg === "object" && "storyId" in firstArg) {
208
+ const { storyId } = firstArg;
209
+ return typeof storyId === "string" ? storyId : null;
210
+ }
211
+ return null;
212
+ }
213
+ function parseStoryIdFromPath(pathname) {
214
+ const match = pathname.match(/^\/select-story-sync\/([^/]+)$/);
215
+ if (!match) return null;
216
+ try {
217
+ return decodeURIComponent(match[1]) || null;
218
+ } catch {
219
+ return null;
220
+ }
221
+ }
222
+ function createSelectStorySyncEndpoint(wss) {
223
+ const pendingStorySelections = /* @__PURE__ */ new Map();
224
+ const lastRenderedStoryIdByClient = /* @__PURE__ */ new Map();
225
+ const waitForStoryRender = (storyId, timeoutMs) => {
226
+ let cancelSelection = () => {};
227
+ let resolveWait = () => {};
228
+ return {
229
+ promise: new Promise((resolve, reject) => {
230
+ resolveWait = resolve;
231
+ let selections = pendingStorySelections.get(storyId);
232
+ if (!selections) {
233
+ selections = /* @__PURE__ */ new Set();
234
+ pendingStorySelections.set(storyId, selections);
235
+ }
236
+ const cleanup = () => {
237
+ clearTimeout(selection.timeout);
238
+ selections.delete(selection);
239
+ if (selections.size === 0) pendingStorySelections.delete(storyId);
240
+ };
241
+ const selection = {
242
+ resolve: () => {
243
+ if (selection.settled) return;
244
+ selection.settled = true;
245
+ cleanup();
246
+ resolve();
247
+ },
248
+ timeout: setTimeout(() => {
249
+ if (selection.settled) return;
250
+ selection.settled = true;
251
+ cleanup();
252
+ reject(/* @__PURE__ */ new Error(`Story "${storyId}" did not render in time`));
253
+ }, timeoutMs),
254
+ settled: false
255
+ };
256
+ cancelSelection = () => {
257
+ if (selection.settled) return;
258
+ selection.settled = true;
259
+ cleanup();
260
+ resolveWait();
261
+ };
262
+ selections.add(selection);
263
+ }),
264
+ cancel: cancelSelection
265
+ };
266
+ };
267
+ const resolveStorySelection = (storyId) => {
268
+ const selections = pendingStorySelections.get(storyId);
269
+ if (!selections) return;
270
+ [...selections].forEach((selection) => selection.resolve());
271
+ };
272
+ const handleRequest = async (pathname, res) => {
273
+ const storyId = parseStoryIdFromPath(pathname);
274
+ if (!storyId) {
275
+ res.writeHead(400, { "Content-Type": "application/json" });
276
+ res.end(JSON.stringify({
277
+ success: false,
278
+ error: "Invalid story id"
279
+ }));
280
+ return;
281
+ }
282
+ const waitForRender = waitForStoryRender(storyId, SELECT_STORY_SYNC_TIMEOUT_MS);
283
+ const message = JSON.stringify({
284
+ type: "setCurrentStory",
285
+ args: [{
286
+ viewMode: "story",
287
+ storyId
288
+ }]
289
+ });
290
+ wss.clients.forEach((wsClient) => {
291
+ if (wsClient.readyState === ws.WebSocket.OPEN) wsClient.send(message);
292
+ });
293
+ try {
294
+ if ([...wss.clients].some((client) => client.readyState === ws.WebSocket.OPEN && lastRenderedStoryIdByClient.get(client) === storyId)) {
295
+ if (await Promise.race([waitForRender.promise.then(() => "rendered"), new Promise((resolve) => {
296
+ setTimeout(() => resolve("alreadyRendered"), LAST_RENDERED_STORY_TIMEOUT_MS);
297
+ })]) === "alreadyRendered") waitForRender.cancel();
298
+ } else await waitForRender.promise;
299
+ res.writeHead(200, { "Content-Type": "application/json" });
300
+ res.end(JSON.stringify({
301
+ success: true,
302
+ storyId
303
+ }));
304
+ } catch (error) {
305
+ res.writeHead(408, { "Content-Type": "application/json" });
306
+ res.end(JSON.stringify({
307
+ success: false,
308
+ storyId,
309
+ error: error instanceof Error ? error.message : String(error)
310
+ }));
311
+ }
312
+ };
313
+ const onSocketMessage = (event, ws$2) => {
314
+ const renderedStoryId = getRenderedStoryId(event);
315
+ if (renderedStoryId) {
316
+ lastRenderedStoryIdByClient.set(ws$2, renderedStoryId);
317
+ resolveStorySelection(renderedStoryId);
318
+ }
319
+ };
320
+ const onSocketClose = (ws$3) => {
321
+ lastRenderedStoryIdByClient.delete(ws$3);
322
+ };
323
+ return {
324
+ handleRequest,
325
+ onSocketMessage,
326
+ onSocketClose
327
+ };
328
+ }
329
+ //#endregion
330
+ //#region src/metro/channelServer.ts
331
+ /**
332
+ * Creates a channel server for syncing storybook instances and sending events.
333
+ * The server provides both WebSocket and REST endpoints:
334
+ * - WebSocket: broadcasts all received messages to all connected clients
335
+ * - POST /send-event: sends an event to all WebSocket clients
336
+ * - POST /select-story-sync/{storyId}: sets the current story and waits for a storyRendered event
337
+ * - GET /index.json: returns the story index built from story files
338
+ * - POST /mcp: MCP endpoint for AI agent integration (when experimental_mcp option is enabled)
339
+ *
340
+ * @param options - Configuration options for the channel server.
341
+ * @param options.port - The port to listen on.
342
+ * @param options.host - The host to bind to.
343
+ * @param options.configPath - The path to the Storybook config folder.
344
+ * @param options.experimental_mcp - Whether to enable MCP server support.
345
+ * @param options.websockets - Whether to enable WebSocket server support.
346
+ * @param options.secured - Whether to use HTTPS/WSS for the channel server.
347
+ * @param options.ssl - TLS credentials used when `secured` is true.
348
+ * @returns The created WebSocketServer instance, or null when websockets are disabled.
349
+ */
350
+ function createChannelServer({ port = 7007, host = void 0, configPath, experimental_mcp = false, websockets = true, secured = false, ssl }) {
351
+ if (secured && (!ssl?.key || !ssl?.cert)) throw new Error("[Storybook] Secure channel server requires both `ssl.key` and `ssl.cert`.");
352
+ const httpServer = secured ? (0, node_https.createServer)(ssl) : (0, node_http.createServer)();
353
+ const wss = websockets ? new ws.WebSocketServer({ server: httpServer }) : null;
354
+ const mcpServer = experimental_mcp ? createMcpHandler(configPath, wss ?? void 0) : null;
355
+ const selectStorySyncEndpoint = wss ? createSelectStorySyncEndpoint(wss) : null;
356
+ httpServer.on("request", async (req, res) => {
357
+ const protocol = "encrypted" in req.socket && req.socket.encrypted ? "https" : "http";
358
+ const requestUrl = new URL(req.url ?? "/", `${protocol}://${req.headers.host ?? "localhost"}`);
359
+ if (req.method === "OPTIONS") {
360
+ res.writeHead(204);
361
+ res.end();
362
+ return;
363
+ }
364
+ if (req.method === "GET" && requestUrl.pathname === "/index.json") {
365
+ try {
366
+ const index = await require_buildIndex.buildIndex({ configPath });
367
+ res.writeHead(200, { "Content-Type": "application/json" });
368
+ res.end(JSON.stringify(index));
369
+ } catch (error) {
370
+ console.error("Failed to build index:", error);
371
+ res.writeHead(500, { "Content-Type": "application/json" });
372
+ res.end(JSON.stringify({ error: "Failed to build story index" }));
373
+ }
374
+ return;
375
+ }
376
+ if (req.method === "POST" && requestUrl.pathname === "/send-event") {
377
+ if (!wss) {
378
+ res.writeHead(503, { "Content-Type": "application/json" });
379
+ res.end(JSON.stringify({
380
+ success: false,
381
+ error: "WebSockets are disabled"
382
+ }));
383
+ return;
384
+ }
385
+ let body = "";
386
+ req.on("data", (chunk) => {
387
+ body += chunk.toString();
388
+ });
389
+ req.on("end", () => {
390
+ try {
391
+ const json = JSON.parse(body);
392
+ wss.clients.forEach((wsClient) => wsClient.send(JSON.stringify(json)));
393
+ res.writeHead(200, { "Content-Type": "application/json" });
394
+ res.end(JSON.stringify({ success: true }));
395
+ } catch (error) {
396
+ console.error("Failed to parse event:", error);
397
+ res.writeHead(400, { "Content-Type": "application/json" });
398
+ res.end(JSON.stringify({
399
+ success: false,
400
+ error: "Invalid JSON"
401
+ }));
402
+ }
403
+ });
404
+ return;
405
+ }
406
+ if (req.method === "POST" && requestUrl.pathname.startsWith("/select-story-sync/")) {
407
+ if (!selectStorySyncEndpoint) {
408
+ res.writeHead(503, { "Content-Type": "application/json" });
409
+ res.end(JSON.stringify({
410
+ success: false,
411
+ error: "WebSockets are disabled"
412
+ }));
413
+ return;
414
+ }
415
+ await selectStorySyncEndpoint.handleRequest(requestUrl.pathname, res);
416
+ return;
417
+ }
418
+ if (mcpServer && requestUrl.pathname === "/mcp" && (req.method === "POST" || req.method === "GET")) {
419
+ await mcpServer.handleMcpRequest(req, res);
420
+ return;
421
+ }
422
+ res.writeHead(404, { "Content-Type": "application/json" });
423
+ res.end(JSON.stringify({ error: "Not found" }));
424
+ });
425
+ if (wss) {
426
+ wss.on("error", () => {});
427
+ setInterval(function ping() {
428
+ wss.clients.forEach(function each(client) {
429
+ if (client.readyState === ws.WebSocket.OPEN) client.send(JSON.stringify({
430
+ type: "ping",
431
+ args: []
432
+ }));
433
+ });
434
+ }, 1e4).unref?.();
435
+ wss.on("connection", function connection(ws$1) {
436
+ console.log("WebSocket connection established");
437
+ ws$1.on("error", console.error);
438
+ ws$1.on("message", function message(data) {
439
+ try {
440
+ const json = JSON.parse(data.toString());
441
+ selectStorySyncEndpoint?.onSocketMessage(json, ws$1);
442
+ const msg = JSON.stringify(json);
443
+ wss.clients.forEach((wsClient) => {
444
+ if (wsClient !== ws$1 && wsClient.readyState === ws.WebSocket.OPEN) wsClient.send(msg);
445
+ });
446
+ } catch (error) {
447
+ console.error(error);
448
+ }
449
+ });
450
+ ws$1.on("close", () => {
451
+ selectStorySyncEndpoint?.onSocketClose(ws$1);
452
+ });
453
+ });
454
+ }
455
+ httpServer.on("error", (error) => {
456
+ if (error.code === "EADDRINUSE") console.warn(`[Storybook] Port ${port} is already in use. The channel server will not start. Another instance may already be running.`);
457
+ else console.error(`[Storybook] Channel server error:`, error);
458
+ });
459
+ httpServer.listen(port, host, () => {
460
+ console.log(`${wss ? secured ? "WSS" : "WebSocket" : secured ? "HTTPS" : "HTTP"} server listening on ${host ?? "localhost"}:${port}`);
461
+ });
462
+ mcpServer?.preInit();
463
+ return wss;
464
+ }
465
+ //#endregion
466
+ Object.defineProperty(exports, "createChannelServer", {
467
+ enumerable: true,
468
+ get: function() {
469
+ return createChannelServer;
470
+ }
471
+ });