UNPKG

@storybook/react-native

Version:

A better way to develop React Native Components for your app

858 lines (819 loc) 30.4 kB
var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __esm = (fn, res) => function __init() { return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; }; var __commonJS = (cb, mod) => function __require() { return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; }; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // scripts/common.js var require_common = __commonJS({ "scripts/common.js"(exports2, module2) { var { globToRegexp } = require("storybook/internal/common"); var path2 = require("path"); var fs = require("fs"); var cwd2 = process.cwd(); var toRequireContext = (specifier) => { const { directory, files } = specifier; const match = globToRegexp(`./${files}`); return { path: directory, recursive: files.includes("**") || files.split("/").length > 1, match }; }; var supportedExtensions = ["js", "jsx", "ts", "tsx", "cjs", "mjs"]; function getFilePathExtension({ configPath }, fileName) { for (const ext of supportedExtensions) { const filePath = path2.resolve(cwd2, configPath, `${fileName}.${ext}`); if (fs.existsSync(filePath)) { return ext; } } return null; } function getFilePathWithExtension2({ configPath }, fileName) { for (const ext of supportedExtensions) { const filePath = path2.resolve(cwd2, configPath, `${fileName}.${ext}`); if (fs.existsSync(filePath)) { return filePath; } } return null; } function ensureRelativePathHasDot2(relativePath) { return relativePath.startsWith(".") ? relativePath : `./${relativePath}`; } function getPreviewExists({ configPath }) { return !!getFilePathExtension({ configPath }, "preview"); } function resolveAddonFile(addon, file, extensions = ["js", "mjs", "ts"], configPath) { if (!addon || typeof addon !== "string") return null; const resolvePaths = { paths: [cwd2] }; try { const basePath = `${addon}/${file}`; require.resolve(basePath, resolvePaths); return basePath; } catch (_error) { } for (const ext of extensions) { try { const filePath = `${addon}/${file}.${ext}`; require.resolve(filePath, resolvePaths); return filePath; } catch (_error) { } } if (addon.startsWith("./") || addon.startsWith("../")) { try { const extension = getFilePathExtension({ configPath }, `${addon}/${file}`); if (extension) { return `${addon}/${file}`; } } catch (_error) { } } return null; } function getAddonName(addon) { if (typeof addon === "string") return addon; if (typeof addon === "object" && addon.name && typeof addon.name === "string") return addon.name; console.error("Invalid addon configuration", addon); return null; } module2.exports = { toRequireContext, getFilePathExtension, ensureRelativePathHasDot: ensureRelativePathHasDot2, getPreviewExists, resolveAddonFile, getAddonName, getFilePathWithExtension: getFilePathWithExtension2 }; } }); // src/metro/buildIndex.ts var buildIndex_exports = {}; __export(buildIndex_exports, { buildIndex: () => buildIndex }); function ensureRelativePathHasDot(relativePath) { return relativePath.startsWith(".") ? relativePath : `./${relativePath}`; } async function buildIndex({ configPath }) { const main = await (0, import_common.loadMainConfig)({ configDir: configPath, cwd }); if (!main.stories || !Array.isArray(main.stories)) { throw new Error("No stories found"); } const storiesSpecifiers = (0, import_common.normalizeStories)(main.stories, { configDir: configPath, workingDir: cwd }); const specifierStoryPaths = storiesSpecifiers.map((specifier) => { return (0, import_glob.sync)(specifier.files, { cwd: import_path.default.resolve(process.cwd(), specifier.directory), absolute: true, // default to always ignore (exclude) anything in node_modules ignore: ["**/node_modules"] }).map((storyPath) => { const normalizePathForWindows = (str) => import_path.default.sep === "\\" ? str.replace(/\\/g, "/") : str; return normalizePathForWindows(storyPath); }); }); const csfStories = specifierStoryPaths.reduce( (acc, specifierStoryPathList, specifierIndex) => { const paths = specifierStoryPathList.map((storyPath) => { const code = (0, import_node_fs.readFileSync)(storyPath, { encoding: "utf-8" }).toString(); const relativePath = ensureRelativePathHasDot(import_path.default.posix.relative(cwd, storyPath)); return { result: (0, import_csf_tools.loadCsf)(code, { fileName: storyPath, makeTitle: (userTitle) => makeTitle(relativePath, storiesSpecifiers[specifierIndex], userTitle) }).parse(), specifier: storiesSpecifiers[specifierIndex], fileName: relativePath }; }); return [...acc, ...paths]; }, new Array() ); const index = { v: 5, entries: {} }; for (const { result, specifier, fileName } of csfStories) { const { meta, stories } = result; if (stories && stories.length > 0) { for (const story of stories) { const id = story.id ?? (0, import_csf.toId)(meta.title, story.name); if (!id) { throw new Error(`Failed to generate id for story ${story.name} in file ${fileName}`); } index.entries[id] = { type: "story", subtype: "story", id, name: story.name, title: meta.title, importPath: `${specifier.directory}/${import_path.default.posix.relative(specifier.directory, fileName)}`, tags: ["story"] }; } } else { console.log(`No stories found for ${fileName}`); } } try { const previewPath = (0, import_common2.getFilePathWithExtension)({ configPath }, "preview"); const previewSourceCode = (0, import_node_fs.readFileSync)(previewPath, { encoding: "utf-8" }).toString(); const storySort = (0, import_csf_tools.getStorySortParameter)(previewSourceCode); const sortableStories = Object.values(index.entries); (0, import_preview_api.sortStoriesV7)( sortableStories, storySort, sortableStories.map((entry) => entry.importPath) ); const sorted = sortableStories.reduce( (acc, item) => { acc[item.id] = item; return acc; }, {} ); return { v: 5, entries: sorted }; } catch { console.warn("Failed to sort stories, using unordered index"); return index; } } var import_common, import_node_fs, import_glob, import_path, import_csf_tools, import_csf, import_preview_api, import_common2, cwd, makeTitle; var init_buildIndex = __esm({ "src/metro/buildIndex.ts"() { import_common = require("storybook/internal/common"); import_node_fs = require("fs"); import_glob = require("glob"); import_path = __toESM(require("path")); import_csf_tools = require("storybook/internal/csf-tools"); import_csf = require("storybook/internal/csf"); import_preview_api = require("storybook/internal/preview-api"); import_common2 = __toESM(require_common()); cwd = process.cwd(); makeTitle = (fileName, specifier, userTitle) => { const title = (0, import_preview_api.userOrAutoTitleFromSpecifier)(fileName, specifier, userTitle); if (title) { return title.replace("./", ""); } else if (userTitle) { return userTitle.replace("./", ""); } else { console.error("Could not generate title!!"); process.exit(1); } }; } }); // src/metro/manifest/storyInstructions.ts var storyInstructions_exports = {}; __export(storyInstructions_exports, { storyInstructions: () => storyInstructions }); var storyInstructions; var init_storyInstructions = __esm({ "src/metro/manifest/storyInstructions.ts"() { storyInstructions = `# Writing React Native UI Components When writing UI, prefer breaking larger components up into smaller parts. ALWAYS write a Storybook story for any component written. If editing a component, ensure appropriate changes have been made to stories for that component. ## How to write good stories 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. 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. 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. 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. Accessibility: Use semantic roles/labels where applicable. Naming/structure: Use clear story names that describe the scenario ("Error state after failed submit"). Group related variants logically; don't duplicate. Imports/format: Import Meta/StoryObj from the framework package. Keep stories minimal\u2014only what's needed to demonstrate behavior. ## React Native Storybook Essentials ### Framework and Renderer React Native Storybook uses \`@storybook/react-native\` as the framework. Stories use the same CSF (Component Story Format) as web Storybook. ### Meta and StoryObj imports \`\`\`ts import type { Meta, StoryObj } from '@storybook/react-native'; \`\`\` ### Story file structure \`\`\`tsx import type { Meta, StoryObj } from '@storybook/react-native'; import { MyComponent } from './MyComponent'; const meta: Meta<typeof MyComponent> = { title: 'Components/MyComponent', component: MyComponent, args: { // default args }, }; export default meta; type Story = StoryObj<typeof meta>; export const Default: Story = {}; export const WithCustomProps: Story = { args: { label: 'Custom Label', variant: 'secondary', }, }; \`\`\` ### Global State Changes The \`globals\` annotation has been renamed to \`initialGlobals\`: \`\`\`diff // .rnstorybook/preview.js export default { - globals: { theme: 'light' } + initialGlobals: { theme: 'light' } }; \`\`\` ### React Native Specific Considerations - The config directory is \`.rnstorybook\` (not \`.storybook\`) - Stories run on-device (iOS/Android), not in a browser - Use React Native components (\`View\`, \`Text\`, \`Pressable\`, etc.), not HTML elements - \`StyleSheet\` or inline styles instead of CSS - No DOM APIs \u2014 use React Native's layout system (Flexbox) - For navigation-dependent components, mock the navigation context - For platform-specific stories, use \`Platform.OS\` checks or separate story files - Test on both iOS and Android when possible ### Key Requirements - **Node.js 20+**, **TypeScript 4.9+** - React Native 0.72+ - Storybook 10+ `; } }); // src/node.ts var node_exports = {}; __export(node_exports, { buildIndex: () => buildIndex, createChannelServer: () => createChannelServer }); module.exports = __toCommonJS(node_exports); // src/metro/channelServer.ts var import_ws2 = require("ws"); var import_node_http = require("http"); var import_node_https = require("https"); init_buildIndex(); // src/metro/mcpServer.ts var import_consumers = require("stream/consumers"); function toHeaderEntries(nodeHeaders) { const entries = []; for (const [key, value] of Object.entries(nodeHeaders)) { if (value === void 0) continue; entries.push([key, Array.isArray(value) ? value.join(", ") : value]); } return entries; } async function incomingMessageToWebRequest(req) { const host = req.headers.host || "localhost"; const isTLS = "encrypted" in req.socket && req.socket.encrypted; const protocol = isTLS ? "https" : "http"; const url = new URL(req.url || "/", `${protocol}://${host}`); const bodyBuffer = await (0, import_consumers.buffer)(req); return new Request(url, { method: req.method, headers: toHeaderEntries(req.headers), body: bodyBuffer.length > 0 ? new Uint8Array(bodyBuffer) : void 0 }); } async function webResponseToServerResponse(webResponse, nodeResponse) { nodeResponse.statusCode = webResponse.status; webResponse.headers.forEach((value, key) => { nodeResponse.setHeader(key, value); }); if (webResponse.body) { const reader = webResponse.body.getReader(); try { while (true) { const { done, value } = await reader.read(); if (done) break; nodeResponse.write(value); } } finally { reader.releaseLock(); } } nodeResponse.end(); } function createMcpHandler(configPath, wss) { let handler = null; let initPromise = null; async function init() { if (handler) return; if (initPromise) { await initPromise; return; } initPromise = (async () => { try { const [ { McpServer }, { ValibotJsonSchemaAdapter }, { HttpTransport }, { addListAllDocumentationTool, addGetDocumentationTool, addGetStoryDocumentationTool }, { storyInstructions: storyInstructions2 }, { buildIndex: buildIndex2 }, valibot, { experimental_manifests } ] = await Promise.all([ import("tmcp"), import("@tmcp/adapter-valibot"), import("@tmcp/transport-http"), import("@storybook/mcp"), Promise.resolve().then(() => (init_storyInstructions(), storyInstructions_exports)), Promise.resolve().then(() => (init_buildIndex(), buildIndex_exports)), import("valibot"), import("@storybook/react/preset") ]); const manifestProvider = async (_request, manifestPath) => { if (manifestPath.includes("docs.json")) { throw new Error("Docs manifest not available in React Native Storybook"); } const index = await buildIndex2({ configPath }); const entries = Object.values(index.entries); const manifest = await experimental_manifests({}, { manifestEntries: entries }); return JSON.stringify(manifest.components); }; const server = new McpServer( { name: "@storybook/react-native", version: "1.0.0", description: "Storybook React Native MCP server" }, { adapter: new ValibotJsonSchemaAdapter(), capabilities: { tools: { listChanged: true } } } ).withContext(); addListAllDocumentationTool(server); addGetDocumentationTool(server); addGetStoryDocumentationTool(server); server.tool( { name: "get-storybook-story-instructions", title: "React Native Storybook Story Instructions", description: "Get instructions for writing React Native Storybook stories. Call this before creating or modifying story files (.stories.tsx, .stories.ts)." }, async () => ({ content: [{ type: "text", text: storyInstructions2 }] }) ); if (wss) { const broadcastEvent = (event) => { const message = JSON.stringify(event); wss.clients.forEach((client) => { if (client.readyState === 1) { client.send(message); } }); }; server.tool( { name: "select-story", title: "Select Story", 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.', schema: valibot.object({ storyId: valibot.string() }) }, async ({ storyId }) => { try { const index = await buildIndex2({ configPath }); if (!index.entries[storyId]) { const availableIds = Object.keys(index.entries).slice(0, 10); return { content: [ { type: "text", text: `Story "${storyId}" not found. Available stories include: ${availableIds.join(", ")}` + (Object.keys(index.entries).length > 10 ? ", ..." : "") } ], isError: true }; } broadcastEvent({ type: "setCurrentStory", args: [{ storyId, viewMode: "story" }] }); const entry = index.entries[storyId]; return { content: [ { type: "text", text: `Selected story "${entry.name}" (${entry.title}) on connected devices.` } ] }; } catch (error) { return { content: [ { type: "text", text: `Failed to select story: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); } const transport = new HttpTransport(server, { path: null }); handler = (req) => transport.respond(req, { request: req, manifestProvider }); console.log("[Storybook] MCP server initialized"); } catch (error) { initPromise = null; console.error("[Storybook] Failed to initialize MCP server:", error); throw error; } })(); await initPromise; } async function handleMcpRequest(req, res) { try { await init(); if (!handler) { res.writeHead(500, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "MCP handler not initialized" })); return; } const webRequest = await incomingMessageToWebRequest(req); const webResponse = await handler(webRequest); await webResponseToServerResponse(webResponse, res); } catch (error) { console.error("[Storybook] MCP request failed:", error); res.writeHead(500, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "MCP request failed" })); } } function preInit() { init().catch( (e) => console.warn("[Storybook] MCP pre-initialization failed (will retry on first request):", e) ); } return { handleMcpRequest, preInit }; } // src/metro/selectStorySyncEndpoint.ts var import_ws = require("ws"); var SELECT_STORY_SYNC_ROUTE = "/select-story-sync/"; var SELECT_STORY_SYNC_TIMEOUT_MS = 1e3; var LAST_RENDERED_STORY_TIMEOUT_MS = 500; function getRenderedStoryId(event) { if (!event || typeof event !== "object") { return null; } const { type, args } = event; if (type !== "storyRendered" || !Array.isArray(args) || args.length === 0) { return null; } const [firstArg] = args; if (typeof firstArg === "string") { return firstArg; } if (firstArg && typeof firstArg === "object" && "storyId" in firstArg) { const { storyId } = firstArg; return typeof storyId === "string" ? storyId : null; } return null; } function parseStoryIdFromPath(pathname) { const match = pathname.match(/^\/select-story-sync\/([^/]+)$/); if (!match) { return null; } try { const storyId = decodeURIComponent(match[1]); return storyId || null; } catch { return null; } } function createSelectStorySyncEndpoint(wss) { const pendingStorySelections = /* @__PURE__ */ new Map(); const lastRenderedStoryIdByClient = /* @__PURE__ */ new Map(); const waitForStoryRender = (storyId, timeoutMs) => { let cancelSelection = () => { }; let resolveWait = () => { }; const promise = new Promise((resolve, reject) => { resolveWait = resolve; let selections = pendingStorySelections.get(storyId); if (!selections) { selections = /* @__PURE__ */ new Set(); pendingStorySelections.set(storyId, selections); } const cleanup = () => { clearTimeout(selection.timeout); selections.delete(selection); if (selections.size === 0) { pendingStorySelections.delete(storyId); } }; const selection = { resolve: () => { if (selection.settled) { return; } selection.settled = true; cleanup(); resolve(); }, timeout: setTimeout(() => { if (selection.settled) { return; } selection.settled = true; cleanup(); reject(new Error(`Story "${storyId}" did not render in time`)); }, timeoutMs), settled: false }; cancelSelection = () => { if (selection.settled) { return; } selection.settled = true; cleanup(); resolveWait(); }; selections.add(selection); }); return { promise, cancel: cancelSelection }; }; const resolveStorySelection = (storyId) => { const selections = pendingStorySelections.get(storyId); if (!selections) { return; } [...selections].forEach((selection) => selection.resolve()); }; const handleRequest = async (pathname, res) => { const storyId = parseStoryIdFromPath(pathname); if (!storyId) { res.writeHead(400, { "Content-Type": "application/json" }); res.end(JSON.stringify({ success: false, error: "Invalid story id" })); return; } const waitForRender = waitForStoryRender(storyId, SELECT_STORY_SYNC_TIMEOUT_MS); const message = JSON.stringify({ type: "setCurrentStory", args: [{ viewMode: "story", storyId }] }); wss.clients.forEach((wsClient) => { if (wsClient.readyState === import_ws.WebSocket.OPEN) { wsClient.send(message); } }); try { const hasConnectedClientWithRenderedStory = [...wss.clients].some( (client) => client.readyState === import_ws.WebSocket.OPEN && lastRenderedStoryIdByClient.get(client) === storyId ); if (hasConnectedClientWithRenderedStory) { const raceResult = await Promise.race([ waitForRender.promise.then(() => "rendered"), new Promise((resolve) => { setTimeout(() => resolve("alreadyRendered"), LAST_RENDERED_STORY_TIMEOUT_MS); }) ]); if (raceResult === "alreadyRendered") { waitForRender.cancel(); } } else { await waitForRender.promise; } res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ success: true, storyId })); } catch (error) { res.writeHead(408, { "Content-Type": "application/json" }); res.end( JSON.stringify({ success: false, storyId, error: error instanceof Error ? error.message : String(error) }) ); } }; const onSocketMessage = (event, ws) => { const renderedStoryId = getRenderedStoryId(event); if (renderedStoryId) { lastRenderedStoryIdByClient.set(ws, renderedStoryId); resolveStorySelection(renderedStoryId); } }; const onSocketClose = (ws) => { lastRenderedStoryIdByClient.delete(ws); }; return { handleRequest, onSocketMessage, onSocketClose }; } // src/metro/channelServer.ts function createChannelServer({ port = 7007, host = void 0, configPath, experimental_mcp = false, websockets = true, secured = false, ssl }) { if (secured && (!ssl?.key || !ssl?.cert)) { throw new Error("[Storybook] Secure channel server requires both `ssl.key` and `ssl.cert`."); } const httpServer = secured ? (0, import_node_https.createServer)(ssl) : (0, import_node_http.createServer)(); const wss = websockets ? new import_ws2.WebSocketServer({ server: httpServer }) : null; const mcpServer = experimental_mcp ? createMcpHandler(configPath, wss ?? void 0) : null; const selectStorySyncEndpoint = wss ? createSelectStorySyncEndpoint(wss) : null; httpServer.on("request", async (req, res) => { const protocol = "encrypted" in req.socket && req.socket.encrypted ? "https" : "http"; const requestUrl = new URL(req.url ?? "/", `${protocol}://${req.headers.host ?? "localhost"}`); if (req.method === "OPTIONS") { res.writeHead(204); res.end(); return; } if (req.method === "GET" && requestUrl.pathname === "/index.json") { try { const index = await buildIndex({ configPath }); res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(index)); } catch (error) { console.error("Failed to build index:", error); res.writeHead(500, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Failed to build story index" })); } return; } if (req.method === "POST" && requestUrl.pathname === "/send-event") { if (!wss) { res.writeHead(503, { "Content-Type": "application/json" }); res.end(JSON.stringify({ success: false, error: "WebSockets are disabled" })); return; } let body = ""; req.on("data", (chunk) => { body += chunk.toString(); }); req.on("end", () => { try { const json = JSON.parse(body); wss.clients.forEach((wsClient) => wsClient.send(JSON.stringify(json))); res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ success: true })); } catch (error) { console.error("Failed to parse event:", error); res.writeHead(400, { "Content-Type": "application/json" }); res.end(JSON.stringify({ success: false, error: "Invalid JSON" })); } }); return; } if (req.method === "POST" && requestUrl.pathname.startsWith(SELECT_STORY_SYNC_ROUTE)) { if (!selectStorySyncEndpoint) { res.writeHead(503, { "Content-Type": "application/json" }); res.end(JSON.stringify({ success: false, error: "WebSockets are disabled" })); return; } await selectStorySyncEndpoint.handleRequest(requestUrl.pathname, res); return; } if (mcpServer && requestUrl.pathname === "/mcp" && (req.method === "POST" || req.method === "GET")) { await mcpServer.handleMcpRequest(req, res); return; } res.writeHead(404, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Not found" })); }); if (wss) { wss.on("error", () => { }); const pingInterval = setInterval(function ping() { wss.clients.forEach(function each(client) { if (client.readyState === import_ws2.WebSocket.OPEN) { client.send(JSON.stringify({ type: "ping", args: [] })); } }); }, 1e4); pingInterval.unref?.(); wss.on("connection", function connection(ws) { console.log("WebSocket connection established"); ws.on("error", console.error); ws.on("message", function message(data) { try { const json = JSON.parse(data.toString()); selectStorySyncEndpoint?.onSocketMessage(json, ws); const msg = JSON.stringify(json); wss.clients.forEach((wsClient) => { if (wsClient !== ws && wsClient.readyState === import_ws2.WebSocket.OPEN) { wsClient.send(msg); } }); } catch (error) { console.error(error); } }); ws.on("close", () => { selectStorySyncEndpoint?.onSocketClose(ws); }); }); } httpServer.on("error", (error) => { 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.` ); } else { console.error(`[Storybook] Channel server error:`, error); } }); httpServer.listen(port, host, () => { const protocol = wss ? secured ? "WSS" : "WebSocket" : secured ? "HTTPS" : "HTTP"; console.log(`${protocol} server listening on ${host ?? "localhost"}:${port}`); }); mcpServer?.preInit(); return wss; } // src/node.ts init_buildIndex(); // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { buildIndex, createChannelServer });