storybook
Version:
Storybook: Develop, document, and test UI components in isolation
279 lines (272 loc) • 10.3 kB
JavaScript
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
};