UNPKG

storybook

Version:

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

1,178 lines (1,143 loc) • 64.7 kB
import CJS_COMPAT_NODE_URL_ret6ivvuly from 'node:url'; import CJS_COMPAT_NODE_PATH_ret6ivvuly from 'node:path'; import CJS_COMPAT_NODE_MODULE_ret6ivvuly from "node:module"; var __filename = CJS_COMPAT_NODE_URL_ret6ivvuly.fileURLToPath(import.meta.url); var __dirname = CJS_COMPAT_NODE_PATH_ret6ivvuly.dirname(__filename); var require = CJS_COMPAT_NODE_MODULE_ret6ivvuly.createRequire(import.meta.url); // ------------------------------------------------------------ // end of CJS compatibility banner, injected by Storybook's esbuild configuration // ------------------------------------------------------------ import { require_build } from "./chunk-UQRAUAGM.js"; import { STORYBOOK_FN_PLACEHOLDER, generateDummyArgsFromArgTypes, loadConfig, printConfig } from "./chunk-RYS45SZ5.js"; import { StorybookError } from "./chunk-3LVSS6GN.js"; import { isI18nPackage, isRouterPackage, isStateManagementPackage, isStylingPackage } from "./chunk-UZQ7S6LH.js"; import { optionalEnvToBoolean } from "./chunk-ZJMC6JVU.js"; import { up } from "./chunk-XQ72YXLB.js"; import { resolvePackageDir } from "./chunk-UHVMRW65.js"; import { join, relative } from "./chunk-TGEJ6REI.js"; import { require_dist } from "./chunk-XSYEGQMM.js"; import { require_picocolors } from "./chunk-BV5YQP4B.js"; import { glob } from "./chunk-2E2JVUNS.js"; import { __toESM } from "./chunk-5IHDTMLC.js"; // src/core-server/utils/server-statics.ts import { existsSync, statSync } from "node:fs"; import { readFile, stat } from "node:fs/promises"; import { basename, isAbsolute, join as join2, posix, resolve, sep, win32 } from "node:path"; import { getDirectoryFromWorkingDir, getProjectRoot, resolvePathInStorybookCache } from "storybook/internal/common"; import { CLI_COLORS, logger, once } from "storybook/internal/node-logger"; var import_picocolors = __toESM(require_picocolors(), 1), import_sirv = __toESM(require_build(), 1), import_ts_dedent = __toESM(require_dist(), 1); var cacheDir = resolvePathInStorybookCache("", "ignored-sub").split("ignored-sub")[0], files = /* @__PURE__ */ new Map(), readFileOnce = async (path) => { if (files.has(path)) return files.get(path); { let [data, stats] = await Promise.all([readFile(path, "utf-8"), stat(path)]), result = { data, mtime: stats.mtimeMs }; return files.set(path, result), result; } }, faviconWrapperPath = join2( resolvePackageDir("storybook"), "/assets/browser/favicon-wrapper.svg" ), prepareNestedSvg = (svg) => { let [, openingTag, contents, closingTag] = svg?.match(/(<svg[^>]*>)(.*?)(<\/svg>)/s) ?? []; if (!openingTag || !contents || !closingTag) return svg; let width, height, modifiedTag = openingTag.replace(/width=["']([^"']*)["']/g, (_, value) => (width = parseFloat(value), 'width="32px"')).replace(/height=["']([^"']*)["']/g, (_, value) => (height = parseFloat(value), 'height="32px"')); return !/viewBox=["'][^"']*["']/.test(modifiedTag) && width && height && (modifiedTag = modifiedTag.replace(/>$/, ` viewBox="0 0 ${width} ${height}">`)), modifiedTag = modifiedTag.replace(/preserveAspectRatio=["'][^"']*["']/g, "").replace(/>$/, ' preserveAspectRatio="xMidYMid meet">'), modifiedTag + contents + closingTag; }; async function useStatics(app, options) { let staticDirs = await options.presets.apply("staticDirs") ?? [], faviconPath = await options.presets.apply("favicon"), faviconDir = resolve(faviconPath, ".."), faviconFile = basename(faviconPath); app.use(`/${faviconFile}`, async (req, res, next) => { let status = req.query.status; if (status && faviconFile.endsWith(".svg") && ["active", "critical", "negative", "positive", "warning"].includes(status)) { let [faviconInfo, faviconWrapperInfo] = await Promise.all([ readFileOnce(join2(faviconDir, faviconFile)), readFileOnce(faviconWrapperPath) ]).catch((e) => (e instanceof Error && once.warn(`Failed to read favicon: ${e.message}`), [null, null])); if (faviconInfo && faviconWrapperInfo) { let svg = faviconWrapperInfo.data.replace('<g id="mask"', `<g mask="url(#${status}-mask)"`).replace('<use id="status"', `<use href="#${status}"`).replace('<use id="icon" />', prepareNestedSvg(faviconInfo.data)); res.setHeader("Content-Type", "image/svg+xml"), res.setHeader("ETag", `"${faviconWrapperInfo.mtime}-${faviconInfo.mtime}"`), res.end(svg); return; } } return req.url = `/${faviconFile}`, sirvWorkaround(faviconDir)(req, res, next); }), staticDirs.map((dir) => { try { let { staticDir, staticPath, targetEndpoint } = mapStaticDir(dir, options.configDir); if (!targetEndpoint.startsWith("/sb-") && !staticDir.startsWith(cacheDir)) { let relativeStaticDir = relative(getProjectRoot(), staticDir); logger.debug( `Serving static files from ${CLI_COLORS.info(relativeStaticDir)} at ${CLI_COLORS.info(targetEndpoint)}` ); } if (existsSync(staticPath) && statSync(staticPath).isFile()) { let staticPathDir = resolve(staticPath, ".."), staticPathFile = basename(staticPath); app.use(targetEndpoint, (req, res, next) => { req.url = `/${staticPathFile}`, sirvWorkaround(staticPathDir)(req, res, next); }); } else app.use(targetEndpoint, sirvWorkaround(staticPath)); } catch (e) { e instanceof Error && logger.warn(e.message); } }); } var sirvWorkaround = (dir, opts = {}) => (req, res, next) => { let originalParsedUrl = req._parsedUrl, maybeNext = next ? () => { req._parsedUrl = originalParsedUrl, next(); } : void 0; (0, import_sirv.default)(dir, { dev: !0, etag: !0, extensions: [], ...opts })(req, res, maybeNext); }, parseStaticDir = (arg) => { let lastColonIndex = arg.lastIndexOf(":"), isWindowsRawDirOnly = win32.isAbsolute(arg) && lastColonIndex === 1, splitIndex = lastColonIndex !== -1 && !isWindowsRawDirOnly ? lastColonIndex : arg.length, [from, to] = [arg.slice(0, splitIndex), arg.slice(splitIndex + 1)], staticDir = isAbsolute(from) ? from : `./${from}`, staticPath = resolve(staticDir); if (!existsSync(staticPath)) throw new Error( import_ts_dedent.dedent` Failed to load static files, no such directory: ${import_picocolors.default.cyan(staticPath)} Make sure this directory exists. ` ); let targetDir = (to || (statSync(staticPath).isFile() ? basename(staticPath) : "/")).split(sep).join(posix.sep).replace(/^\/?/, "./"), targetEndpoint = targetDir.substring(1); return { staticDir, staticPath, targetDir, targetEndpoint }; }, mapStaticDir = (staticDir, configDir) => { let specifier = typeof staticDir == "string" ? staticDir : `${staticDir.from}:${staticDir.to}`, normalizedDir = isAbsolute(specifier) ? specifier : getDirectoryFromWorkingDir({ configDir, workingDir: process.cwd(), directory: specifier }); return parseStaticDir(normalizedDir); }; // src/shared/universal-store/index.ts var import_ts_dedent2 = __toESM(require_dist(), 1); // src/shared/universal-store/instances.ts var instances = /* @__PURE__ */ new Map(); // src/shared/universal-store/index.ts var CHANNEL_EVENT_PREFIX = "UNIVERSAL_STORE:", ProgressState = { PENDING: "PENDING", RESOLVED: "RESOLVED", REJECTED: "REJECTED" }, UniversalStore = class _UniversalStore { constructor(options, environmentOverrides) { /** Enable debug logs for this store */ this.debugging = !1; // TODO: narrow type of listeners based on event type this.listeners = /* @__PURE__ */ new Map([["*", /* @__PURE__ */ new Set()]]); /** Gets the current state */ this.getState = () => (this.debug("getState", { state: this.state }), this.state); /** * Subscribes to store events * * @returns A function to unsubscribe */ this.subscribe = (eventTypeOrListener, maybeListener) => { let subscribesToAllEvents = typeof eventTypeOrListener == "function", eventType = subscribesToAllEvents ? "*" : eventTypeOrListener, listener = subscribesToAllEvents ? eventTypeOrListener : maybeListener; if (this.debug("subscribe", { eventType, listener }), !listener) throw new TypeError( `Missing first subscribe argument, or second if first is the event type, when subscribing to a UniversalStore with id '${this.id}'` ); return this.listeners.has(eventType) || this.listeners.set(eventType, /* @__PURE__ */ new Set()), this.listeners.get(eventType).add(listener), () => { this.debug("unsubscribe", { eventType, listener }), this.listeners.has(eventType) && (this.listeners.get(eventType).delete(listener), this.listeners.get(eventType)?.size === 0 && this.listeners.delete(eventType)); }; }; /** Sends a custom event to the other stores */ this.send = (event) => { if (this.debug("send", { event }), this.status !== _UniversalStore.Status.READY) throw new TypeError( import_ts_dedent2.dedent`Cannot send event before store is ready. You can get the current status with store.status, or await store.readyPromise to wait for the store to be ready before sending events. ${JSON.stringify( { event, id: this.id, actor: this.actor, environment: this.environment }, null, 2 )}` ); this.emitToListeners(event, { actor: this.actor }), this.emitToChannel(event, { actor: this.actor }); }; if (this.debugging = options.debug ?? !1, !_UniversalStore.isInternalConstructing) throw new TypeError( "UniversalStore is not constructable - use UniversalStore.create() instead" ); if (_UniversalStore.isInternalConstructing = !1, this.id = options.id, this.actorId = Date.now().toString(36) + Math.random().toString(36).substring(2), this.actorType = options.leader ? _UniversalStore.ActorType.LEADER : _UniversalStore.ActorType.FOLLOWER, this.state = options.initialState, this.channelEventName = `${CHANNEL_EVENT_PREFIX}${this.id}`, this.debug("constructor", { options, environmentOverrides, channelEventName: this.channelEventName }), this.actor.type === _UniversalStore.ActorType.LEADER) this.syncing = { state: ProgressState.RESOLVED, promise: Promise.resolve() }; else { let syncingResolve, syncingReject, syncingPromise = new Promise((resolve2, reject) => { syncingResolve = () => { this.syncing.state === ProgressState.PENDING && (this.syncing.state = ProgressState.RESOLVED, resolve2()); }, syncingReject = (reason) => { this.syncing.state === ProgressState.PENDING && (this.syncing.state = ProgressState.REJECTED, reject(reason)); }; }); this.syncing = { state: ProgressState.PENDING, promise: syncingPromise, resolve: syncingResolve, reject: syncingReject }; } this.getState = this.getState.bind(this), this.setState = this.setState.bind(this), this.subscribe = this.subscribe.bind(this), this.onStateChange = this.onStateChange.bind(this), this.send = this.send.bind(this), this.emitToChannel = this.emitToChannel.bind(this), this.prepareThis = this.prepareThis.bind(this), this.emitToListeners = this.emitToListeners.bind(this), this.handleChannelEvents = this.handleChannelEvents.bind(this), this.debug = this.debug.bind(this), this.channel = environmentOverrides?.channel ?? _UniversalStore.preparation.channel, this.environment = environmentOverrides?.environment ?? _UniversalStore.preparation.environment, this.channel && this.environment ? (_UniversalStore.preparation.resolve({ channel: this.channel, environment: this.environment }), this.prepareThis({ channel: this.channel, environment: this.environment })) : _UniversalStore.preparation.promise.then(this.prepareThis); } static { /** * Defines the possible actor types in the store system * * @readonly */ this.ActorType = { LEADER: "LEADER", FOLLOWER: "FOLLOWER" }; } static { /** * Defines the possible environments the store can run in * * @readonly */ this.Environment = { SERVER: "SERVER", MANAGER: "MANAGER", PREVIEW: "PREVIEW", UNKNOWN: "UNKNOWN", MOCK: "MOCK" }; } static { /** * Internal event types used for store synchronization * * @readonly */ this.InternalEventType = { EXISTING_STATE_REQUEST: "__EXISTING_STATE_REQUEST", EXISTING_STATE_RESPONSE: "__EXISTING_STATE_RESPONSE", SET_STATE: "__SET_STATE", LEADER_CREATED: "__LEADER_CREATED", FOLLOWER_CREATED: "__FOLLOWER_CREATED" }; } static { this.Status = { UNPREPARED: "UNPREPARED", SYNCING: "SYNCING", READY: "READY", ERROR: "ERROR" }; } static { // This is used to check if constructor was called from the static factory create() this.isInternalConstructing = !1; } static { _UniversalStore.setupPreparationPromise(); } static setupPreparationPromise() { let resolveRef, rejectRef, promise = new Promise( (resolve2, reject) => { resolveRef = (args) => { resolve2(args); }, rejectRef = (...args) => { reject(args); }; } ); _UniversalStore.preparation = { resolve: resolveRef, reject: rejectRef, promise }; } /** The actor object representing the store instance with a unique ID and a type */ get actor() { return Object.freeze({ id: this.actorId, type: this.actorType, environment: this.environment ?? _UniversalStore.Environment.UNKNOWN }); } /** * The current state of the store, that signals both if the store is prepared by Storybook and * also - in the case of a follower - if the state has been synced with the leader's state. */ get status() { if (!this.channel || !this.environment) return _UniversalStore.Status.UNPREPARED; switch (this.syncing?.state) { case ProgressState.PENDING: case void 0: return _UniversalStore.Status.SYNCING; case ProgressState.REJECTED: return _UniversalStore.Status.ERROR; case ProgressState.RESOLVED: default: return _UniversalStore.Status.READY; } } /** * A promise that resolves when the store is fully ready. A leader will be ready when the store * has been prepared by Storybook, which is almost instantly. * * A follower will be ready when the state has been synced with the leader's state, within a few * hundred milliseconds. */ untilReady() { return Promise.all([_UniversalStore.preparation.promise, this.syncing?.promise]); } /** Creates a new instance of UniversalStore */ static create(options) { if (!options || typeof options?.id != "string") throw new TypeError("id is required and must be a string, when creating a UniversalStore"); options.debug && console.debug( import_ts_dedent2.dedent`[UniversalStore] create`, { options } ); let existing = instances.get(options.id); if (existing) return console.warn(import_ts_dedent2.dedent`UniversalStore with id "${options.id}" already exists in this environment, re-using existing. You should reuse the existing instance instead of trying to create a new one.`), existing; _UniversalStore.isInternalConstructing = !0; let store = new _UniversalStore(options); return instances.set(options.id, store), store; } /** * Used by Storybook to set the channel for all instances of UniversalStore in the given * environment. * * @internal */ static __prepare(channel, environment) { _UniversalStore.preparation.channel = channel, _UniversalStore.preparation.environment = environment, _UniversalStore.preparation.resolve({ channel, environment }); } /** * Updates the store's state * * Either a new state or a state updater function can be passed to the method. */ setState(updater) { let previousState = this.state, newState = typeof updater == "function" ? updater(previousState) : updater; if (this.debug("setState", { newState, previousState, updater }), this.status !== _UniversalStore.Status.READY) throw new TypeError( import_ts_dedent2.dedent`Cannot set state before store is ready. You can get the current status with store.status, or await store.readyPromise to wait for the store to be ready before sending events. ${JSON.stringify( { newState, id: this.id, actor: this.actor, environment: this.environment }, null, 2 )}` ); this.state = newState; let event = { type: _UniversalStore.InternalEventType.SET_STATE, payload: { state: newState, previousState } }; this.emitToChannel(event, { actor: this.actor }), this.emitToListeners(event, { actor: this.actor }); } /** * Subscribes to state changes * * @returns Unsubscribe function */ onStateChange(listener) { return this.debug("onStateChange", { listener }), this.subscribe( _UniversalStore.InternalEventType.SET_STATE, ({ payload }, eventInfo) => { listener(payload.state, payload.previousState, eventInfo); } ); } emitToChannel(event, eventInfo) { this.debug("emitToChannel", { event, eventInfo, channel: !!this.channel }), this.channel?.emit(this.channelEventName, { event, eventInfo }); } prepareThis({ channel, environment }) { this.channel = channel, this.environment = environment, this.debug("prepared", { channel: !!channel, environment }), this.channel.on(this.channelEventName, this.handleChannelEvents), this.actor.type === _UniversalStore.ActorType.LEADER ? this.emitToChannel( { type: _UniversalStore.InternalEventType.LEADER_CREATED }, { actor: this.actor } ) : (this.emitToChannel( { type: _UniversalStore.InternalEventType.FOLLOWER_CREATED }, { actor: this.actor } ), this.emitToChannel( { type: _UniversalStore.InternalEventType.EXISTING_STATE_REQUEST }, { actor: this.actor } ), setTimeout(() => { this.syncing.reject( new TypeError( `No existing state found for follower with id: '${this.id}'. Make sure a leader with the same id exists before creating a follower.` ) ); }, 1e3)); } emitToListeners(event, eventInfo) { let eventTypeListeners = this.listeners.get(event.type), everythingListeners = this.listeners.get("*"); this.debug("emitToListeners", { event, eventInfo, eventTypeListeners, everythingListeners }), [...eventTypeListeners ?? [], ...everythingListeners ?? []].forEach( (listener) => listener(event, eventInfo) ); } handleChannelEvents(channelEvent) { let { event, eventInfo } = channelEvent; if ([eventInfo.actor.id, eventInfo.forwardingActor?.id].includes(this.actor.id)) { this.debug("handleChannelEvents: Ignoring event from self", { channelEvent }); return; } else if (this.syncing?.state === ProgressState.PENDING && event.type !== _UniversalStore.InternalEventType.EXISTING_STATE_RESPONSE) { this.debug("handleChannelEvents: Ignoring event while syncing", { channelEvent }); return; } if (this.debug("handleChannelEvents", { channelEvent }), this.actor.type === _UniversalStore.ActorType.LEADER) { let shouldForwardEvent = !0; switch (event.type) { case _UniversalStore.InternalEventType.EXISTING_STATE_REQUEST: shouldForwardEvent = !1; let responseEvent = { type: _UniversalStore.InternalEventType.EXISTING_STATE_RESPONSE, payload: this.state }; this.debug("handleChannelEvents: responding to existing state request", { responseEvent }), this.emitToChannel(responseEvent, { actor: this.actor }), this.emitToListeners(responseEvent, { actor: this.actor }); break; case _UniversalStore.InternalEventType.LEADER_CREATED: shouldForwardEvent = !1, this.syncing.state = ProgressState.REJECTED, this.debug("handleChannelEvents: erroring due to second leader being created", { event }), console.error( import_ts_dedent2.dedent`Detected multiple UniversalStore leaders created with the same id "${this.id}". Only one leader can exists at a time, your stores are now in an invalid state. Leaders detected: this: ${JSON.stringify(this.actor, null, 2)} other: ${JSON.stringify(eventInfo.actor, null, 2)}` ); break; } shouldForwardEvent && (this.debug("handleChannelEvents: forwarding event", { channelEvent }), this.emitToChannel(event, { actor: eventInfo.actor, forwardingActor: this.actor })); } if (this.actor.type === _UniversalStore.ActorType.FOLLOWER) switch (event.type) { case _UniversalStore.InternalEventType.EXISTING_STATE_RESPONSE: if (this.debug("handleChannelEvents: Setting state from leader's existing state response", { event }), this.syncing?.state !== ProgressState.PENDING) break; this.syncing.resolve?.(); let setStateEvent = { type: _UniversalStore.InternalEventType.SET_STATE, payload: { state: event.payload, previousState: this.state } }; this.state = event.payload, this.emitToListeners(setStateEvent, eventInfo); break; } switch (event.type) { case _UniversalStore.InternalEventType.SET_STATE: this.debug("handleChannelEvents: Setting state", { event }), this.state = event.payload.state; break; } this.emitToListeners(event, { actor: eventInfo.actor }); } debug(message, data) { this.debugging && console.debug( import_ts_dedent2.dedent`[UniversalStore::${this.id}::${this.environment ?? _UniversalStore.Environment.UNKNOWN}] ${message}`, JSON.stringify( { data, actor: this.actor, state: this.state, status: this.status }, null, 2 ) ); } /** * Used to reset the static fields of the UniversalStore class when cleaning up tests * * @internal */ static __reset() { _UniversalStore.preparation.reject(new Error("reset")), _UniversalStore.setupPreparationPromise(), _UniversalStore.isInternalConstructing = !1; } }; // src/core-server/presets/wsToken.ts import { randomUUID } from "crypto"; var getWsToken = () => (globalThis.STORYBOOK_WEBSOCKET_TOKEN || (globalThis.STORYBOOK_WEBSOCKET_TOKEN = randomUUID()), globalThis.STORYBOOK_WEBSOCKET_TOKEN); // src/core-server/withTelemetry.ts import { HandledError, cache, loadMainConfig, isCI, loadAllPresets } from "storybook/internal/common"; import { logger as logger2, prompt } from "storybook/internal/node-logger"; import { ErrorCollector, getPrecedingUpgrade, isTelemetryStateResolved, oneWayHash, onPayloadError, setTelemetryEnabled, telemetry } from "storybook/internal/telemetry"; var promptCrashReports = async () => { if (isCI() || !process.stdout.isTTY) return; let enableCrashReports = await prompt.confirm({ message: "Would you like to send anonymous crash reports to improve Storybook and fix bugs faster?", initialValue: !0 }); return await cache.set("enableCrashReports", enableCrashReports), enableCrashReports; }; async function getErrorLevel({ cliOptions, presetOptions, skipPrompt, eventType }) { if (cliOptions.disableTelemetry) return "none"; if (!presetOptions && eventType !== "init") return "error"; if (presetOptions) { let core = await (await loadAllPresets(presetOptions)).apply("core"); if (core?.enableCrashReports !== void 0) return core.enableCrashReports ? "full" : "error"; if (core?.disableTelemetry) return "none"; } let valueFromCache = await cache.get("enableCrashReports") ?? await cache.get("enableCrashreports"); if (valueFromCache !== void 0) return valueFromCache ? "full" : "error"; if (skipPrompt) return "error"; let valueFromPrompt = await promptCrashReports(); return valueFromPrompt !== void 0 ? valueFromPrompt ? "full" : "error" : "full"; } async function sendTelemetryError(_error, eventType, options, blocking = !0, parent) { try { let errorLevel = "error"; try { errorLevel = await getErrorLevel({ ...options, eventType, skipPrompt: options.skipPrompt || eventType === "init" && !blocking }); } catch { } if (errorLevel !== "none") { let precedingUpgrade = await getPrecedingUpgrade(), error = _error, errorHash; "message" in error ? errorHash = error.message ? oneWayHash(error.message) : "EMPTY_MESSAGE" : errorHash = "NO_MESSAGE"; let { code, name, category } = error; if (await telemetry( "error", { code, name, category, eventType, blocking, precedingUpgrade, error: errorLevel === "full" ? error : void 0, errorHash, // if we ever end up sending a non-error instance, we'd like to know isErrorInstance: error instanceof Error, // Include parent error information if this is a sub-error ...parent ? { parent: parent.fullErrorCode } : {} }, { immediate: !0, configDir: options.cliOptions.configDir || options.presetOptions?.configDir, enableCrashReports: errorLevel === "full", force: !0 } ), error && "subErrors" in error && error.subErrors.length > 0) for (let subError of error.subErrors) await sendTelemetryError(subError, eventType, options, blocking, error); } } catch { } } async function resolveTelemetryState(options) { if (options.cliOptions.disableTelemetry !== void 0) return await setTelemetryEnabled(!options.cliOptions.disableTelemetry); let mainConfig, configDir = options.cliOptions.configDir ?? options.presetOptions?.configDir ?? ".storybook"; try { mainConfig = await loadMainConfig({ configDir }); } catch { } if (mainConfig) return await setTelemetryEnabled(!mainConfig?.core?.disableTelemetry); await setTelemetryEnabled(options.fallbackTelemetryState ?? !1); } function isInterruptionError(error) { if (!error || typeof error != "object") return !1; let signal = "signal" in error ? error.signal : void 0, code = "code" in error ? error.code : void 0, name = "name" in error ? error.name : void 0, message = "message" in error && typeof error.message == "string" ? error.message : void 0, cause = "cause" in error ? error.cause : void 0; return signal === "SIGINT" || code === "ABORT_ERR" || code === "ERR_CANCELED" || name === "AbortError" || message?.includes("Command was killed with SIGINT") || message?.includes("The operation was aborted") || isInterruptionError(cause); } async function withTelemetry(eventType, options, run) { isTelemetryStateResolved() || await resolveTelemetryState(options); let canceled = !1; async function cancelTelemetry() { canceled = !0, await telemetry("canceled", { eventType }, { stripMetadata: !0, immediate: !0 }), process.exit(0); } eventType === "init" && process.on("SIGINT", cancelTelemetry), onPayloadError(async (error, evtType) => { await sendTelemetryError(error, evtType, options); }), telemetry("boot", { eventType }, { stripMetadata: !0 }); try { return await run(); } catch (error) { if (canceled) return; if (eventType === "init" && isInterruptionError(error)) { await cancelTelemetry(); return; } if (!(error instanceof HandledError || error instanceof StorybookError && error.isHandledError)) { let { printError = logger2.error } = options; printError(error); } throw await sendTelemetryError(error, eventType, options), error; } finally { let errors = ErrorCollector.getErrors(); for (let error of errors) await sendTelemetryError(error, eventType, options, !1); process.off("SIGINT", cancelTelemetry), onPayloadError(void 0); } } // src/core-server/utils/generate-story.ts import { existsSync as existsSync3 } from "node:fs"; import { writeFile } from "node:fs/promises"; import { relative as relative3 } from "node:path"; import { getProjectRoot as getProjectRoot3, getStoryId } from "storybook/internal/common"; // src/core-server/utils/get-new-story-file.ts import { existsSync as existsSync2 } from "node:fs"; import { readFile as readFile2 } from "node:fs/promises"; import { basename as basename2, dirname as dirname2, extname, join as join3, relative as relative2 } from "node:path"; import { types as t, traverse } from "storybook/internal/babel"; import { extractFrameworkPackageName, findConfigFile, formatFileContent, getFrameworkName, getProjectRoot as getProjectRoot2 } from "storybook/internal/common"; import { isCsfFactoryPreview } from "storybook/internal/csf-tools"; import { logger as logger3 } from "storybook/internal/node-logger"; // src/core-server/utils/new-story-templates/csf-factory-template.ts var import_ts_dedent3 = __toESM(require_dist(), 1); // src/core-server/utils/get-component-variable-name.ts var getComponentVariableName = async (name) => (await import("./camelcase-3C63TIAT.js")).default(name.replace(/^[^a-zA-Z_$]*/, ""), { pascalCase: !0 }).replace(/[^a-zA-Z_$]+/, ""); // src/core-server/utils/new-story-templates/csf-factory-template.ts async function getCsfFactoryTemplateForNewStoryFile(data) { let importName = data.componentIsDefaultExport ? await getComponentVariableName(data.basenameWithoutExtension) : data.componentExportName, importStatement = data.componentIsDefaultExport ? `import ${importName} from './${data.basenameWithoutExtension}';` : `import { ${importName} } from './${data.basenameWithoutExtension}';`, previewImport = data.previewImportPath ? `import preview from '${data.previewImportPath}';` : "import preview from '#.storybook/preview';", argsString = data.args && Object.keys(data.args).length > 0 ? `{ args: ${JSON.stringify(data.args, null, 2)} }` : "{}"; return import_ts_dedent3.dedent` ${previewImport} ${importStatement} const meta = preview.meta({ component: ${importName}, }); export const ${data.exportedStoryName} = meta.story(${argsString}); `; } // src/core-server/utils/new-story-templates/javascript.ts var import_ts_dedent4 = __toESM(require_dist(), 1); async function getJavaScriptTemplateForNewStoryFile(data) { let importName = data.componentIsDefaultExport ? await getComponentVariableName(data.basenameWithoutExtension) : data.componentExportName, importStatement = data.componentIsDefaultExport ? `import ${importName} from './${data.basenameWithoutExtension}';` : `import { ${importName} } from './${data.basenameWithoutExtension}';`, hasArgs = !!(data.args && Object.keys(data.args).length > 0), argsString = hasArgs ? `args: ${JSON.stringify(data.args, null, 2)},` : "", storyExport = hasArgs ? import_ts_dedent4.dedent` export const ${data.exportedStoryName} = { ${argsString} }; ` : `export const ${data.exportedStoryName} = {};`; return import_ts_dedent4.dedent` ${importStatement} const meta = { component: ${importName}, }; export default meta; ${storyExport} `; } // src/core-server/utils/new-story-templates/typescript.ts var import_ts_dedent5 = __toESM(require_dist(), 1); async function getTypeScriptTemplateForNewStoryFile(data) { let importName = data.componentIsDefaultExport ? await getComponentVariableName(data.basenameWithoutExtension) : data.componentExportName, importStatement = data.componentIsDefaultExport ? `import ${importName} from './${data.basenameWithoutExtension}'` : `import { ${importName} } from './${data.basenameWithoutExtension}'`, hasArgs = !!(data.args && Object.keys(data.args).length > 0), argsString = hasArgs ? `args: ${JSON.stringify(data.args, null, 2)},` : "", storyExport = hasArgs ? import_ts_dedent5.dedent` export const ${data.exportedStoryName}: Story = { ${argsString} }; ` : `export const ${data.exportedStoryName}: Story = {};`; return import_ts_dedent5.dedent` import type { Meta, StoryObj } from '${data.frameworkPackage}'; ${importStatement}; const meta = { component: ${importName}, } satisfies Meta<typeof ${importName}>; export default meta; type Story = StoryObj<typeof meta>; ${storyExport} `; } // src/core-server/utils/safeString.ts function escapeForTemplate(str) { return str.replace(/\\/g, "\\\\").replace(/(['"$`])/g, "\\$&").replace(/[\n\r]/g, "\\$&"); } // src/core-server/utils/get-new-story-file.ts async function getNewStoryFile({ componentFilePath, componentExportName, componentIsDefaultExport, componentExportCount }, options) { let frameworkPackageName = await getFrameworkName(options), sanitizedFrameworkPackageName = extractFrameworkPackageName(frameworkPackageName), base = basename2(componentFilePath), extension = extname(componentFilePath), basenameWithoutExtension = escapeForTemplate(base.replace(extension, "")), dir = dirname2(componentFilePath), { storyFileName, isTypescript, storyFileExtension } = getStoryMetadata(componentFilePath), storyFileNameWithExtension = `${storyFileName}.${storyFileExtension}`, alternativeStoryFileNameWithExtension = `${basenameWithoutExtension}.${componentExportName}.stories.${storyFileExtension}`, exportedStoryName = "Default", useCsfFactory = !1, previewConfigPath; try { let previewConfig = findConfigFile("preview", options.configDir); if (previewConfig) { let previewContent = await readFile2(previewConfig, "utf-8"); useCsfFactory = isCsfFactoryPreview(loadConfig(previewContent)), previewConfigPath = previewConfig; } } catch { } let args; try { let argTypes = await options.presets.apply("internal_getArgTypesData", null, { ...options, componentFilePath, componentExportName }); if (logger3.debug(`Extracted argTypes for ${componentExportName}: ${JSON.stringify(argTypes)}`), argTypes) { let { required } = generateDummyArgsFromArgTypes(argTypes); Object.keys(required).length > 0 && (args = required, logger3.debug( `Generated dummy data using ArgTypes for ${componentExportName}: ${JSON.stringify(args)}` )); } } catch (error) { logger3.debug(`Could not generate dummy data for ${componentExportName}: ${error}`); } let storyFileContent = ""; if (useCsfFactory) { let previewImportPath; if (previewConfigPath && !await checkForImportsMap(options.configDir)) { let storyFilePath2 = join3(getProjectRoot2(), dir), pathWithoutExt = relative2(storyFilePath2, previewConfigPath).replace(/\.(ts|js|mts|cts|tsx|jsx)$/, ""); previewImportPath = escapeForTemplate( pathWithoutExt.startsWith(".") ? pathWithoutExt : `./${pathWithoutExt}` ); } storyFileContent = await getCsfFactoryTemplateForNewStoryFile({ basenameWithoutExtension, componentExportName, componentIsDefaultExport, exportedStoryName, previewImportPath, args }); } else storyFileContent = isTypescript && frameworkPackageName ? await getTypeScriptTemplateForNewStoryFile({ basenameWithoutExtension, componentExportName, componentIsDefaultExport, frameworkPackage: sanitizedFrameworkPackageName, exportedStoryName, args }) : await getJavaScriptTemplateForNewStoryFile({ basenameWithoutExtension, componentExportName, componentIsDefaultExport, exportedStoryName, args }); storyFileContent = replaceArgsPlaceholders(storyFileContent); let storyFilePath = doesStoryFileExist(join3(getProjectRoot2(), dir), storyFileName) && componentExportCount > 1 ? join3(getProjectRoot2(), dir, alternativeStoryFileNameWithExtension) : join3(getProjectRoot2(), dir, storyFileNameWithExtension), formattedStoryFileContent = await formatFileContent(storyFilePath, storyFileContent); return { storyFilePath, exportedStoryName, storyFileContent: formattedStoryFileContent, dirname: dir }; } var getStoryMetadata = (componentFilePath) => { let isTypescript = /\.(ts|tsx|mts|cts)$/.test(componentFilePath), base = basename2(componentFilePath), extension = extname(componentFilePath), basenameWithoutExtension = base.replace(extension, ""), storyFileExtension = isTypescript ? "tsx" : "jsx"; return { storyFileName: `${basenameWithoutExtension}.stories`, storyFileExtension, isTypescript }; }, doesStoryFileExist = (parentFolder, storyFileName) => existsSync2(join3(parentFolder, `${storyFileName}.ts`)) || existsSync2(join3(parentFolder, `${storyFileName}.tsx`)) || existsSync2(join3(parentFolder, `${storyFileName}.js`)) || existsSync2(join3(parentFolder, `${storyFileName}.jsx`)); async function checkForImportsMap(configDir) { try { for (let directory of up(configDir, { last: getProjectRoot2() })) { let packageJsonPath = join3(directory, "package.json"); if (existsSync2(packageJsonPath)) { let packageJsonContent = await readFile2(packageJsonPath, "utf-8"); if (JSON.parse(packageJsonContent).imports) return !0; } } return !1; } catch { return !1; } } function replaceArgsPlaceholders(storyFileContent) { if (!storyFileContent.includes(STORYBOOK_FN_PLACEHOLDER)) return storyFileContent; let storyFile = loadConfig(storyFileContent).parse(), needsFnImport = !1; return traverse(storyFile._ast, { StringLiteral(path) { path.node.value === STORYBOOK_FN_PLACEHOLDER && (needsFnImport = !0, path.replaceWith(t.callExpression(t.identifier("fn"), []))); } }), needsFnImport && storyFile.setImport(["fn"], "storybook/test"), printConfig(storyFile).code; } // src/core-server/utils/generate-story.ts async function generateStoryFile(payload, options, generateOptions = {}) { let { checkFileExists = !0 } = generateOptions; try { let { storyFilePath, exportedStoryName, storyFileContent } = await getNewStoryFile( payload, options ), relativeStoryFilePath = relative3(getProjectRoot3(), storyFilePath), { storyId, kind } = await getStoryId({ storyFilePath, exportedStoryName }, options); return checkFileExists && existsSync3(storyFilePath) ? { success: !1, kind, storyFilePath: relativeStoryFilePath, error: `A story file already exists at ${relativeStoryFilePath}`, errorType: "STORY_FILE_EXISTS" } : (await writeFile(storyFilePath, storyFileContent, "utf-8"), { success: !0, storyId, kind, storyFilePath: relativeStoryFilePath, exportedStoryName }); } catch (e) { return { success: !1, error: e?.message || "Unknown error occurred", errorType: "UNKNOWN" }; } } // src/shared/test-provider-store/index.ts var UNIVERSAL_TEST_PROVIDER_STORE_OPTIONS = { id: "storybook/test-provider", leader: !0, initialState: {} }; function createTestProviderStore({ universalTestProviderStore: universalTestProviderStore2, useUniversalStore }) { let baseStore = { settingsChanged: () => { universalTestProviderStore2.untilReady().then(() => { universalTestProviderStore2.send({ type: "settings-changed" }); }); }, onRunAll: (listener) => universalTestProviderStore2.subscribe("run-all", listener), onClearAll: (listener) => universalTestProviderStore2.subscribe("clear-all", listener) }, fullTestProviderStore2 = { ...baseStore, getFullState: universalTestProviderStore2.getState, setFullState: universalTestProviderStore2.setState, onSettingsChanged: (listener) => universalTestProviderStore2.subscribe("settings-changed", listener), runAll: async () => { await universalTestProviderStore2.untilReady(), universalTestProviderStore2.send({ type: "run-all" }); }, clearAll: async () => { await universalTestProviderStore2.untilReady(), universalTestProviderStore2.send({ type: "clear-all" }); } }, getTestProviderStoreById2 = (testProviderId) => { let getStateForTestProvider = () => universalTestProviderStore2.getState()[testProviderId] ?? "test-provider-state:pending", setStateForTestProvider = (state) => { universalTestProviderStore2.untilReady().then(() => { universalTestProviderStore2.setState((currentState) => ({ ...currentState, [testProviderId]: state })); }); }; return { ...baseStore, testProviderId, getState: getStateForTestProvider, setState: setStateForTestProvider, runWithState: async (callback) => { setStateForTestProvider("test-provider-state:running"); try { await callback(), setStateForTestProvider("test-provider-state:succeeded"); } catch { setStateForTestProvider("test-provider-state:crashed"); } } }; }; return useUniversalStore ? { getTestProviderStoreById: getTestProviderStoreById2, fullTestProviderStore: fullTestProviderStore2, universalTestProviderStore: universalTestProviderStore2, useTestProviderStore: (selector) => useUniversalStore(universalTestProviderStore2, selector)[0] } : { getTestProviderStoreById: getTestProviderStoreById2, fullTestProviderStore: fullTestProviderStore2, universalTestProviderStore: universalTestProviderStore2 }; } // src/core-server/stores/test-provider.ts var testProviderStore = createTestProviderStore({ universalTestProviderStore: UniversalStore.create({ ...UNIVERSAL_TEST_PROVIDER_STORE_OPTIONS, /* This is a temporary workaround, to ensure that the store is not created in the vitest sub-process in addon-vitest, even though it imports from core-server If it was created in the sub-process, it would try to connect to the leader in the dev server before it was ready. This will be fixed when we do the planned UniversalStore v0.2. */ leader: !optionalEnvToBoolean(process.env.VITEST_CHILD_PROCESS) }) }), { fullTestProviderStore, getTestProviderStoreById, universalTestProviderStore } = testProviderStore; // src/core-server/utils/ghost-stories/get-candidates.ts import { readFile as readFile3 } from "node:fs/promises"; import { babelParse, traverse as traverse2 } from "storybook/internal/babel"; // src/core-server/utils/ghost-stories/component-analyzer.ts var COMPLEXITY_CONFIG = { /** Weight applied to non-empty lines */ locWeight: 1, /** Imports can be cheap, so they get a lower weight */ importWeight: 0.5, /** * Defines what raw complexity value should map to the upper bound of a "simple" file For instance * 30 LOC + 4 imports = 32. This would result in a score of 0.3 */ simpleBaseline: 32, simpleScore: 0.3 }, getComponentComplexity = (fileContent) => { let lines = fileContent.split(` `), nonEmptyLines = lines.filter((line) => line.trim() !== "").length, importCount = lines.filter((line) => line.trim().startsWith("import")).length, normalizedScore = (nonEmptyLines * COMPLEXITY_CONFIG.locWeight + importCount * COMPLEXITY_CONFIG.importWeight) / (COMPLEXITY_CONFIG.simpleBaseline / COMPLEXITY_CONFIG.simpleScore); return Math.min(normalizedScore, 1); }; // src/core-server/utils/ghost-stories/get-candidates.ts function isValidCandidate(source) { let ast = babelParse(source), hasJSX = !1, hasExport = !1; return traverse2(ast, { JSXElement(path) { hasJSX = !0, hasExport && path.stop(); }, JSXFragment(path) { hasJSX = !0, hasExport && path.stop(); }, ExportNamedDeclaration(path) { hasExport = !0, hasJSX && path.stop(); }, ExportDefaultDeclaration(path) { hasExport = !0, hasJSX && path.stop(); }, ExportAllDeclaration(path) { hasExport = !0, hasJSX && path.stop(); } }), hasJSX && hasExport; } async function getCandidatesForStorybook(files2, sampleCount) { let simpleCandidates = [], analyzedCandidates = []; for (let file of files2) { let source; try { if (source = await readFile3(file, "utf-8"), !isValidCandidate(source)) continue; } catch { continue; } let complexity = getComponentComplexity(source); if (analyzedCandidates.push({ file, complexity }), complexity < 0.3 && (simpleCandidates.push({ file, complexity }), simpleCandidates.length >= sampleCount)) break; } let selectedCandidates = []; simpleCandidates.length >= sampleCount ? selectedCandidates = simpleCandidates.sort((a, b) => a.complexity - b.complexity).slice(0, sampleCount) : selectedCandidates = analyzedCandidates.sort((a, b) => a.complexity - b.complexity).slice(0, sampleCount); let avgComplexity = selectedCandidates.length > 0 ? Number( (selectedCandidates.reduce((acc, curr) => acc + curr.complexity, 0) / selectedCandidates.length).toFixed(2) ) : 0; return { candidates: selectedCandidates.map(({ file }) => file), analyzedCount: analyzedCandidates.length, avgComplexity }; } async function getComponentCandidates({ sampleSize = 20, globPattern = "**/*.{tsx,jsx}", cwd = process.cwd() } = {}) { let globMatchCount = 0; try { let files2 = []; if (files2 = await glob(globPattern, { cwd, absolute: !0, ignore: [ "**/node_modules/**", "**/.git/**", "**/dist/**", "**/__mocks__/**", "**/build/**", "**/storybook-static/**", "**/*.test.*", "**/*.d.*", "**/*.config.*", "**/*.spec.*", "**/*.stories.*", // skip example story files that come from the CLI "**/stories/{Button,Header,Page}.*", "**/stories/{button,header,page}.*" ] }), globMatchCount = files2.length, globMatchCount === 0) return { candidates: [], globMatchCount }; let { analyzedCount, avgComplexity, candidates } = await getCandidatesForStorybook( files2, sampleSize ); return { analyzedCount, avgComplexity, candidates, globMatchCount }; } catch { return { candidates: [], error: "Failed to find candidates", globMatchCount }; } } // src/shared/utils/categorize-render-errors.ts var ERROR_CATEGORIES = { MISSING_PROVIDER: "MISSING_PROVIDER", MISSING_STATE_PROVIDER: "MISSING_STATE_PROVIDER", MISSING_ROUTER_PROVIDER: "MISSING_ROUTER_PROVIDER", MISSING_THEME_PROVIDER: "MISSING_THEME_PROVIDER", MISSING_TRANSLATION_PROVIDER: "MISSING_TRANSLATION_PROVIDER", MISSING_PORTAL_ROOT: "MISSING_PORTAL_ROOT", HOOK_USAGE_ERROR: "HOOK_USAGE_ERROR", MODULE_IMPORT_ERROR: "MODULE_IMPORT_ERROR", COMPONENT_RENDER_ERROR: "COMPONENT_RENDER_ERROR", SERVER_COMPONENTS_ERROR: "SERVER_COMPONENTS_ERROR", UNKNOWN_ERROR: "UNKNOWN_ERROR", // Vite related errors DYNAMIC_MODULE_IMPORT_ERROR: "DYNAMIC_MODULE_IMPORT_ERROR", // Vitest test run related errors TEST_FILE_IMPORT_ERROR: "TEST_FILE_IMPORT_ERROR" }; function buildErrorContext(message, stack) { let normalizedMessage = message.toLowerCase(), normalizedStack = (stack ?? "").toLowerCase(), stackDeps = /* @__PURE__ */ new Set(), stackLines = normalizedStack.split(` `).filter(Boolean); for (let line of stackLines) { let depMatch = line.match(/\/deps\/([^:]+)\.js/); depMatch && stackDeps.add(depMatch[1]); } return { message, stack, normalizedMessage, normalizedStack, stackDeps }; } var CATEGORIZATION_RULES = [ { category: ERROR_CATEGORIES.MODULE_IMPORT_ERROR, priority: 100, match: (ctx) => ctx.normalizedMessage.includes("cannot find module") || ctx.normalizedMessage.includes("module not found") || ctx.normalizedMessage.includes("cannot resolve module") }, { category: ERROR_CATEGORIES.TEST_FILE_IMPORT_ERROR, priority: 95, match: (ctx) => ctx.normalizedMessage.includes("failed to import test file") }, { category: ERROR_CATEGORIES.DYNAMIC_MODULE_IMPORT_ERROR, priority: 95, match: (ctx) => ctx.normalizedMessage.includes("failed to fetch dynamically imported module") }, { category: ERROR_CATEGORIES.HOOK_USAGE_ERROR, priority: 90, match: (ctx) => ctx.normalizedMessage.includes("invalid hook call") || ctx.normalizedMessage.includes("rendered more hooks") || ctx.normalizedMessage.includes("hooks can only be called") || ctx.normalizedMessage.includes("too many re-renders") || ctx.normalizedMessage.includes("maximum update depth exceeded") || ctx.normalizedMessage.includes("hook") && ctx.normalizedMessage.includes("function component") }, { category: ERROR_CATEGORIES.MISSING_STATE_PROVIDER, priority: 85, match: (ctx) => Array.from(ctx.stackDeps).some(isStateManagementPackage) && (ctx.normalizedMessage.includes("context") || ctx.normalizedMessage.includes("undefined") || ctx.normalizedMessage.includes("null")) }, { category: ERROR_CATEGORIES.MISSING_ROUTER_PROVIDER, priority: 85, match: (ctx) => Array.from(ctx.stackDeps).some(isRouterPackage) || ctx.normalizedMessage.includes("usenavigate") || ctx.normalizedMessage.includes("router") }, { category: ERROR_CATEGORIES.SERVER_COMPONENTS_ERROR, priority: 85, match: (ctx) => ctx.normalizedMessage.includes("server components") || ctx.normalizedMessage.includes("use client") || ctx.normalizedMessage.includes("async/await") && ctx.normalizedMessage.includes("not supported") }, { category: ERROR_CATEGORIES.MISSING_THEME_PROVIDER, priority: 80, match: (ctx) => Array.from(