UNPKG

storybook

Version:

Storybook: Develop, document, and test UI components in isolation

279 lines (272 loc) • 10.3 kB
import CJS_COMPAT_NODE_URL_qjinduibsyq from 'node:url'; import CJS_COMPAT_NODE_PATH_qjinduibsyq from 'node:path'; import CJS_COMPAT_NODE_MODULE_qjinduibsyq from "node:module"; var __filename = CJS_COMPAT_NODE_URL_qjinduibsyq.fileURLToPath(import.meta.url); var __dirname = CJS_COMPAT_NODE_PATH_qjinduibsyq.dirname(__filename); var require = CJS_COMPAT_NODE_MODULE_qjinduibsyq.createRequire(import.meta.url); // ------------------------------------------------------------ // end of CJS compatibility banner, injected by Storybook's esbuild configuration // ------------------------------------------------------------ import { readRegistry, selectInstances } from "./chunk-K36QW2O3.js"; import { resolveStorybookConfigDir } from "./chunk-S7XIM54L.js"; import { formatConnectionFailed, formatInstallationMismatch, formatNoInstance, formatOldServer, formatPortMismatch, formatUnknownInstallation } from "./chunk-4CTPDGB7.js"; import { AttachUnavailableError, EnvironmentMismatchError, ToolsRuntimeError, projectPathsEqual } from "./chunk-F5UFNM2U.js"; import { SERVER_CHANNEL_PATH, WebsocketTransport } from "./chunk-6EPLEZFZ.js"; import "./chunk-UF3LYQOO.js"; import { detectAgent } from "./chunk-GVZSAAZJ.js"; import { UniversalStore } from "./chunk-HFXQZX54.js"; import { getRegisteredToolsets } from "./chunk-5ZQGOLDE.js"; import "./chunk-4C4AXOGG.js"; import "./chunk-52XR72NR.js"; import "./chunk-NQDYICFM.js"; import { Channel, getChannel, setChannel, setDelegatedMode } from "./chunk-5EYYAT2W.js"; import "./chunk-2J2NU66Q.js"; import "./chunk-6WZPWJR4.js"; import { StorybookDevServerDisconnectedError } from "./chunk-DW727PJG.js"; import "./chunk-FE5AV6EP.js"; import "./chunk-VGN5LGMI.js"; import { normalize } from "./chunk-PPVCVHOS.js"; import "./chunk-4US4PNS3.js"; // src/cli/tools/sdk/attached-runtime.ts import { resolve } from "node:path"; import { findStorybookPackageRoot, versions } from "storybook/internal/common"; // src/cli/tools/sdk/node-channel.ts import { CHANNEL_WS_DISCONNECT } from "storybook/internal/core-events"; import { WebSocket } from "ws"; var LOOPBACK_HOSTNAMES = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1", "[::1]"]), installedNodeChannels = [], displacedSlot; function installNodeChannel(channel) { installedNodeChannels.length === 0 && (displacedSlot = { channel: getChannel(), environment: UniversalStore.preparedEnvironment }), installedNodeChannels.push(channel), setChannel(channel), UniversalStore.__prepare(channel, UniversalStore.Environment.UNKNOWN); } function uninstallNodeChannel(channel) { let index = installedNodeChannels.indexOf(channel); if (index >= 0 && installedNodeChannels.splice(index, 1), getChannel() !== channel) { installedNodeChannels.length === 0 && (displacedSlot = void 0); return; } let remaining = installedNodeChannels.at(-1); if (remaining) { setChannel(remaining), UniversalStore.__prepare(remaining, UniversalStore.Environment.UNKNOWN); return; } let previous = displacedSlot; displacedSlot = void 0, setChannel(previous?.channel ?? null), previous?.channel && previous.environment ? UniversalStore.__prepare(previous.channel, previous.environment) : UniversalStore.__reset(); } function createNodeChannel({ url, token }) { let socketUrl = new URL(url); socketUrl.protocol = socketUrl.protocol === "https:" ? "wss:" : "ws:", socketUrl.pathname = SERVER_CHANNEL_PATH, socketUrl.search = new URLSearchParams({ token }).toString(); let rejectUnauthorized = !LOOPBACK_HOSTNAMES.has(socketUrl.hostname), socket = new WebSocket(socketUrl.href, { rejectUnauthorized }), connected = waitUntilOpen(socket); connected.catch(() => { }); let transport = new WebsocketTransport({ url: socketUrl.href, onError: () => { }, createSocket: () => socket, // Config load and tool calls occupy this event loop longer than the 20s receive watchdog. enableHeartbeat: !1 }), channel = new Channel({ transports: [transport] }); installNodeChannel(channel); let disconnected = new Promise((_, reject) => { channel.once(CHANNEL_WS_DISCONNECT, ({ code, reason }) => { reject(new StorybookDevServerDisconnectedError({ code, reason })); }); }); return disconnected.catch(() => { }), { channel, connected, disconnected, close: () => { socket.close(), uninstallNodeChannel(channel); } }; } function waitUntilOpen(socket) { return new Promise((resolve2, reject) => { if (socket.readyState === WebSocket.OPEN) { resolve2(); return; } let fail = (reason) => reject(new StorybookDevServerDisconnectedError({ reason })); if (socket.readyState === WebSocket.CLOSED || socket.readyState === WebSocket.CLOSING) { fail("WebSocket closed before the Storybook channel opened"); return; } let settle = (action) => { socket.off("open", onOpen), socket.off("error", onError), socket.off("close", onClose), action(); }, onOpen = () => settle(resolve2), onError = (error) => settle(() => fail(error.message)), onClose = () => settle(() => fail("WebSocket closed before the Storybook channel opened")); socket.once("open", onOpen), socket.once("error", onError), socket.once("close", onClose); }); } // src/cli/tools/sdk/installation.ts import { realpathSync } from "node:fs"; function checkInstallation(record, callerPath) { if (!callerPath || !record.storybookPath) return { ok: !1, reason: "unknown-installation" }; let instancePath; try { instancePath = normalize(realpathSync(record.storybookPath)); } catch { return { ok: !1, reason: "unknown-installation" }; } return projectPathsEqual(instancePath, callerPath) ? { ok: !0 } : { ok: !1, reason: "different-installation", callerPath, instancePath }; } // src/cli/tools/sdk/attached-runtime.ts async function bootstrapAttachedRuntime(options = {}, deps = {}) { let discoveryCwd = resolve(options.cwd ?? process.cwd()), resolvedConfigDir = resolveStorybookConfigDir({ cwd: discoveryCwd, configDir: options.configDir }), records = await (deps.readRegistry ?? readRegistry)(), selection = selectInstances(records, { cwd: discoveryCwd, configDir: resolvedConfigDir, configDirExplicit: options.configDir != null, port: options.port, agent: (deps.detectAgentName ?? (() => detectAgent()?.name))() }); if (selection.kind === "no-instance") throw new AttachUnavailableError({ reason: "no-instance", instances: records, remediation: formatNoInstance(records) }); if (selection.kind === "port-mismatch") throw new AttachUnavailableError({ reason: "port-mismatch", instances: selection.candidates, remediation: formatPortMismatch(selection.port, selection.candidates) }); let [record, ...siblings] = selection.matches, callerVersion = deps.version ?? versions.storybook; if (!record.token) throw new AttachUnavailableError({ reason: "old-server", instances: [record], remediation: formatOldServer(callerVersion) }); let callerStorybookPath = (deps.storybookPath ?? findStorybookPackageRoot)(), installation = checkInstallation(record, callerStorybookPath); if (!installation.ok) { let autoSpawn = options.autoSpawn ?? !1, isChildHost = deps.isChildHost ?? process.env.STORYBOOK_TOOLS_CHILD_HOST === "true"; if (installation.reason === "different-installation" && autoSpawn && !isChildHost) return { kind: "spawn", record, storybookPath: installation.instancePath, siblings }; throw new EnvironmentMismatchError({ reason: installation.reason === "different-installation" ? formatInstallationMismatch({ callerPath: installation.callerPath, callerVersion, instancePath: installation.instancePath, instanceVersion: record.storybookVersion, configDir: record.configDir }) : formatUnknownInstallation() }); } let connection; try { connection = await (deps.createNodeChannel ?? createNodeChannel)({ url: record.url, token: record.token }), await waitForHandshake(connection); } catch { throw connection?.close(), new AttachUnavailableError({ reason: "connection-failed", instances: [record], remediation: formatConnectionFailed(record) }); } let enableDelegatedMode = deps.setDelegatedMode ?? setDelegatedMode; enableDelegatedMode(!0); let configDir = record.configDir ?? resolve(record.cwd, ".storybook"), { loadStorybook, getService } = await resolveLoaders(deps); try { await loadStorybook({ configDir, channel: connection.channel }); } catch (error) { throw enableDelegatedMode(!1), connection.close(), new ToolsRuntimeError({ reason: "config-load-failed", message: `Could not load the Storybook configuration for this project: ${error instanceof Error ? error.message : String(error)}`, cause: error }); } return { kind: "in-process", runtime: { configDir, toolsets: (deps.getRegisteredToolsets ?? getRegisteredToolsets)(), getService, close: async () => { } }, record, siblings, connection }; } async function resolveLoaders(deps) { if (deps.loadStorybook && deps.getService) return { loadStorybook: deps.loadStorybook, getService: deps.getService }; let core = await import("storybook/internal/core-server"); return { loadStorybook: deps.loadStorybook ?? ((options) => core.experimental_loadStorybook({ configDir: options.configDir, channel: options.channel })), getService: deps.getService ?? ((id, options) => core.getService(id, options)) }; } var ATTACH_HANDSHAKE_TIMEOUT_MS = 1e4; async function waitForHandshake(connection) { let timer; try { await Promise.race([ connection.connected, new Promise((_, reject) => { timer = setTimeout( () => reject( new StorybookDevServerDisconnectedError({ reason: "Timed out waiting for the Storybook channel to open" }) ), ATTACH_HANDSHAKE_TIMEOUT_MS ); }) ]); } finally { clearTimeout(timer); } } export { bootstrapAttachedRuntime };