UNPKG

storybook

Version:

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

1,136 lines (1,121 loc) • 46.6 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 { formatAttachFallback } from "./chunk-4CTPDGB7.js"; import { AttachUnavailableError, EnvironmentMismatchError, SpawnFailedError, ToolsRuntimeError, attachGateReasonFromError, isAttachGateError, projectPathsEqual } from "./chunk-F5UFNM2U.js"; import { resolveToolsetDescription } from "./chunk-EEWXQXZP.js"; import { parseToolsetMethodId, toCliMethodName } from "./chunk-52XR72NR.js"; import { deserializeError } from "./chunk-6WZPWJR4.js"; import { StorybookDevServerDisconnectedError, formatIssues } from "./chunk-DW727PJG.js"; // src/cli/tools/sdk/child-protocol.ts function isChildMessage(value) { return typeof value == "object" && value !== null && "type" in value; } // src/cli/tools/sdk/command-telemetry.ts import { telemetry } from "storybook/internal/telemetry"; function toolsCommandDimensions(args) { return { client: args.clientInfo.kind, requestedMode: args.requestedMode, attachMode: args.resolvedMode ?? args.requestedMode, ...args.resolvedMode ? { resolvedMode: args.resolvedMode } : {}, ...args.host ? { host: args.host } : {}, ...args.fallbackReason ? { attachGate: args.fallbackReason } : {} }; } function commandNameFromRef(ref) { try { let { toolsetId, methodName } = parseToolsetMethodId(ref); return `${toolsetId} ${toCliMethodName(methodName)}`; } catch { return "(invalid)"; } } function wrapMethodTelemetry(sink, dimensions) { return async (event, payload) => { await sink(event, { ...dimensions, ...payload }); }; } function defaultMethodTelemetrySink(configDir) { return async (event, payload) => { await telemetry("tools-command", { event, ...payload }, { configDir }); }; } function resolveCallTelemetry(options, dimensions, args) { let isChildHost = process.env.STORYBOOK_TOOLS_CHILD_HOST === "true", sink = options.telemetry ?? (!isChildHost && shouldReportSdkInvocation(args.clientInfo.kind) ? defaultMethodTelemetrySink(args.configDir) : void 0); if (sink) return isChildHost ? sink : wrapMethodTelemetry(sink, dimensions); } async function reportToolsCommandEvent(payload, options) { try { await telemetry("tools-command", payload, options); } catch { } } function shouldReportSdkInvocation(kind) { return kind === "sdk" && process.env.STORYBOOK_TOOLS_CHILD_HOST !== "true"; } async function reportSdkAttachGate(args) { if (!shouldReportSdkInvocation(args.clientInfo.kind)) return; let attachGate = attachGateReasonFromError(args.error); await reportToolsCommandEvent( { command: "(none)", success: !1, outcome: "attach-gate", ...toolsCommandDimensions({ clientInfo: args.clientInfo, requestedMode: args.requestedMode, fallbackReason: attachGate }) }, { configDir: args.configDir } ); } async function reportSdkInvocation(args) { if (!shouldReportSdkInvocation(args.clientInfo.kind)) return; let dimensions = toolsCommandDimensions(args); if (!("ok" in args.result)) { let attachGate = attachGateReasonFromError(args.result.error); await reportToolsCommandEvent( { command: commandNameFromRef(args.ref), success: !1, outcome: attachGate ? "attach-gate" : "error", duration: args.duration, ...dimensions, ...attachGate ? { attachGate } : {} }, { configDir: args.configDir } ); return; } let success = args.result.ok; await reportToolsCommandEvent( { command: commandNameFromRef(args.ref), success, outcome: success ? "success" : "failure", duration: args.duration, ...dimensions }, { configDir: args.configDir } ); } // src/cli/tools/sdk/create-tools.ts import { resolve } from "node:path"; import { versions } from "storybook/internal/common"; // ../../node_modules/@valibot/to-json-schema/dist/index.mjs function addError(errors, message) { return errors ? (errors.push(message), errors) : [message]; } var ESCAPE_REGEX = /[.*+?^${}()|[\]\\]/g; function escapeRegExp(string) { return string.replace(ESCAPE_REGEX, "\\$&"); } function handleError(message, config) { switch (config?.errorMode) { case "ignore": break; case "warn": console.warn(message); break; default: throw new Error(message); } } function isJsonConstValue(value) { return typeof value == "boolean" || typeof value == "number" && Number.isFinite(value) || typeof value == "string"; } function isJsonEnumValues(values) { return values.every(isJsonConstValue); } function convertAction(jsonSchema, valibotAction, config) { if (config?.ignoreActions?.includes(valibotAction.type)) return jsonSchema; let errors; switch (valibotAction.type) { case "base64": jsonSchema.contentEncoding = "base64"; break; case "bic": case "cuid2": case "decimal": case "digits": case "domain": case "emoji": case "hash": case "hexadecimal": case "hex_color": case "isrc": case "iso_time_second": case "iso_week": case "mac": case "mac48": case "mac64": case "nanoid": case "octal": case "slug": case "ulid": jsonSchema.pattern ? errors = addError(errors, `The "${valibotAction.type}" action is not supported in combination with another regex action.`) : jsonSchema.pattern = valibotAction.requirement.source; break; case "description": jsonSchema.description = valibotAction.description; break; case "email": case "rfc_email": jsonSchema.format = "email"; break; case "ends_with": jsonSchema.pattern ? errors = addError(errors, `The "${valibotAction.type}" action is not supported in combination with another regex action.`) : jsonSchema.pattern = `${escapeRegExp(valibotAction.requirement)}$`; break; case "empty": jsonSchema.type === "array" ? jsonSchema.maxItems = 0 : (jsonSchema.type !== "string" && (errors = addError(errors, `The "${valibotAction.type}" action is not supported on type "${jsonSchema.type}".`)), jsonSchema.maxLength = 0); break; case "entries": jsonSchema.minProperties = valibotAction.requirement, jsonSchema.maxProperties = valibotAction.requirement; break; case "examples": Array.isArray(jsonSchema.examples) ? jsonSchema.examples = [...jsonSchema.examples, ...valibotAction.examples] : jsonSchema.examples = valibotAction.examples; break; case "gt_value": if (jsonSchema.type !== "number" && jsonSchema.type !== "integer" && (errors = addError(errors, `The "gt_value" action is not supported on type "${jsonSchema.type}".`)), config?.target === "openapi-3.0") { errors = addError(errors, 'The "gt_value" action is not supported for OpenAPI 3.0.'); break; } jsonSchema.exclusiveMinimum = valibotAction.requirement; break; case "includes": jsonSchema.pattern ? errors = addError(errors, `The "${valibotAction.type}" action is not supported in combination with another regex action.`) : jsonSchema.pattern = escapeRegExp(valibotAction.requirement); break; case "integer": jsonSchema.type = "integer"; break; case "ipv4": jsonSchema.format = "ipv4"; break; case "ipv6": jsonSchema.format = "ipv6"; break; case "iso_date": jsonSchema.format = "date"; break; case "iso_date_time": case "iso_timestamp": jsonSchema.format = "date-time"; break; case "iso_time": jsonSchema.format = "time"; break; case "jws_compact": jsonSchema.pattern ? errors = addError(errors, `The "${valibotAction.type}" action is not supported in combination with another regex action.`) : jsonSchema.pattern = valibotAction.requirement.source; break; case "length": jsonSchema.type === "array" ? (jsonSchema.minItems = valibotAction.requirement, jsonSchema.maxItems = valibotAction.requirement) : (jsonSchema.type !== "string" && (errors = addError(errors, `The "${valibotAction.type}" action is not supported on type "${jsonSchema.type}".`)), jsonSchema.minLength = valibotAction.requirement, jsonSchema.maxLength = valibotAction.requirement); break; case "lt_value": if (jsonSchema.type !== "number" && jsonSchema.type !== "integer" && (errors = addError(errors, `The "lt_value" action is not supported on type "${jsonSchema.type}".`)), config?.target === "openapi-3.0") { errors = addError(errors, 'The "lt_value" action is not supported for OpenAPI 3.0.'); break; } jsonSchema.exclusiveMaximum = valibotAction.requirement; break; case "max_entries": jsonSchema.maxProperties = valibotAction.requirement; break; case "max_length": jsonSchema.type === "array" ? jsonSchema.maxItems = valibotAction.requirement : (jsonSchema.type !== "string" && (errors = addError(errors, `The "${valibotAction.type}" action is not supported on type "${jsonSchema.type}".`)), jsonSchema.maxLength = valibotAction.requirement); break; case "max_value": jsonSchema.type !== "number" && jsonSchema.type !== "integer" && (errors = addError(errors, `The "max_value" action is not supported on type "${jsonSchema.type}".`)), jsonSchema.maximum = valibotAction.requirement; break; case "metadata": typeof valibotAction.metadata.title == "string" && (jsonSchema.title = valibotAction.metadata.title), typeof valibotAction.metadata.description == "string" && (jsonSchema.description = valibotAction.metadata.description), Array.isArray(valibotAction.metadata.examples) && (Array.isArray(jsonSchema.examples) ? jsonSchema.examples = [...jsonSchema.examples, ...valibotAction.metadata.examples] : jsonSchema.examples = valibotAction.metadata.examples); break; case "min_entries": jsonSchema.minProperties = valibotAction.requirement; break; case "min_length": jsonSchema.type === "array" ? jsonSchema.minItems = valibotAction.requirement : (jsonSchema.type !== "string" && (errors = addError(errors, `The "${valibotAction.type}" action is not supported on type "${jsonSchema.type}".`)), jsonSchema.minLength = valibotAction.requirement); break; case "min_value": jsonSchema.type !== "number" && jsonSchema.type !== "integer" && (errors = addError(errors, `The "min_value" action is not supported on type "${jsonSchema.type}".`)), jsonSchema.minimum = valibotAction.requirement; break; case "multiple_of": jsonSchema.multipleOf = valibotAction.requirement; break; case "non_empty": jsonSchema.type === "array" ? jsonSchema.minItems = 1 : (jsonSchema.type !== "string" && (errors = addError(errors, `The "${valibotAction.type}" action is not supported on type "${jsonSchema.type}".`)), jsonSchema.minLength = 1); break; case "not_value": if (!isJsonConstValue(valibotAction.requirement)) { errors = addError(errors, 'The requirement of the "not_value" action is not JSON compatible.'); break; } config?.target === "openapi-3.0" ? jsonSchema.not = { enum: [valibotAction.requirement] } : jsonSchema.not = { const: valibotAction.requirement }; break; case "not_values": if (!isJsonEnumValues(valibotAction.requirement)) { errors = addError(errors, 'A requirement of the "not_values" action is not JSON compatible.'); break; } jsonSchema.not = { enum: valibotAction.requirement }; break; case "regex": valibotAction.requirement.flags && (errors = addError(errors, "RegExp flags are not supported by JSON Schema.")), jsonSchema.pattern ? errors = addError(errors, `The "${valibotAction.type}" action is not supported in combination with another regex action.`) : jsonSchema.pattern = valibotAction.requirement.source; break; case "safe_integer": jsonSchema.type = "integer", (typeof jsonSchema.minimum != "number" || jsonSchema.minimum < Number.MIN_SAFE_INTEGER) && (jsonSchema.minimum = Number.MIN_SAFE_INTEGER), (typeof jsonSchema.maximum != "number" || jsonSchema.maximum > Number.MAX_SAFE_INTEGER) && (jsonSchema.maximum = Number.MAX_SAFE_INTEGER); break; case "starts_with": jsonSchema.pattern ? errors = addError(errors, `The "${valibotAction.type}" action is not supported in combination with another regex action.`) : jsonSchema.pattern = `^${escapeRegExp(valibotAction.requirement)}`; break; case "title": jsonSchema.title = valibotAction.title; break; case "url": jsonSchema.format = "uri"; break; case "uuid": jsonSchema.format = "uuid"; break; case "value": if (!isJsonConstValue(valibotAction.requirement)) { errors = addError(errors, 'The requirement of the "value" action is not JSON compatible.'); break; } config?.target === "openapi-3.0" ? jsonSchema.enum = [valibotAction.requirement] : jsonSchema.const = valibotAction.requirement; break; case "values": if (!isJsonEnumValues(valibotAction.requirement)) { errors = addError(errors, 'A requirement of the "values" action is not JSON compatible.'); break; } jsonSchema.enum = valibotAction.requirement; break; default: errors = addError(errors, `The "${valibotAction.type}" action cannot be converted to JSON Schema.`); } if (config?.overrideAction) { let actionOverride = config.overrideAction({ valibotAction, jsonSchema, errors }); if (actionOverride) return { ...actionOverride }; } if (errors) for (let message of errors) handleError(message, config); return jsonSchema; } function flattenPipe(pipe) { return pipe.flatMap((item) => "pipe" in item ? flattenPipe(item.pipe) : item); } function getDefinitionRef(referenceId) { return `#/$defs/${referenceId.replaceAll("~", "~0").replaceAll("/", "~1")}`; } var refCount = 0; function convertSchema(jsonSchema, valibotSchema, config, context, skipRef = !1) { if (!skipRef) { let referenceId = context.referenceMap.get(valibotSchema); if (referenceId) { if (jsonSchema.$ref = getDefinitionRef(referenceId), config?.overrideRef) { let refOverride = config.overrideRef({ ...context, referenceId, valibotSchema, jsonSchema }); refOverride && (jsonSchema.$ref = refOverride); } return jsonSchema; } } if ("pipe" in valibotSchema) { let flatPipe = flattenPipe(valibotSchema.pipe), startIndex = 0, stopIndex = flatPipe.length - 1; if (config?.typeMode === "input") { let inputStopIndex = flatPipe.slice(1).findIndex((item) => item.kind === "schema" || item.kind === "transformation" && (item.type === "find_item" || item.type === "parse_json" || item.type === "raw_transform" || item.type === "reduce_items" || item.type === "stringify_json" || item.type === "to_bigint" || item.type === "to_boolean" || item.type === "to_date" || item.type === "to_number" || item.type === "to_string" || item.type === "transform")); inputStopIndex !== -1 && (stopIndex = inputStopIndex); } else if (config?.typeMode === "output") { let outputStartIndex = flatPipe.findLastIndex((item) => item.kind === "schema"); outputStartIndex !== -1 && (startIndex = outputStartIndex); } for (let index = startIndex; index <= stopIndex; index++) { let valibotPipeItem = flatPipe[index]; valibotPipeItem.kind === "schema" ? (index > startIndex && handleError('Set the "typeMode" config to "input" or "output" to convert pipelines with multiple schemas.', config), jsonSchema = convertSchema(jsonSchema, valibotPipeItem, config, context, !0)) : jsonSchema = convertAction(jsonSchema, valibotPipeItem, config); } return jsonSchema; } let errors; switch (valibotSchema.type) { case "boolean": jsonSchema.type = "boolean"; break; case "null": config?.target === "openapi-3.0" ? jsonSchema.enum = [null] : jsonSchema.type = "null"; break; case "number": jsonSchema.type = "number"; break; case "string": jsonSchema.type = "string"; break; case "array": jsonSchema.type = "array", jsonSchema.items = convertSchema({}, valibotSchema.item, config, context); break; case "tuple": case "tuple_with_rest": case "loose_tuple": case "strict_tuple": if (jsonSchema.type = "array", config?.target === "openapi-3.0") { jsonSchema.items = { anyOf: [] }, jsonSchema.minItems = valibotSchema.items.length; for (let item of valibotSchema.items) jsonSchema.items.anyOf.push(convertSchema({}, item, config, context)); valibotSchema.type === "tuple_with_rest" ? jsonSchema.items.anyOf.push(convertSchema({}, valibotSchema.rest, config, context)) : (valibotSchema.type === "strict_tuple" || valibotSchema.type === "tuple") && (jsonSchema.maxItems = valibotSchema.items.length); } else if (config?.target === "draft-2020-12") { jsonSchema.prefixItems = [], jsonSchema.minItems = valibotSchema.items.length; for (let item of valibotSchema.items) jsonSchema.prefixItems.push(convertSchema({}, item, config, context)); valibotSchema.type === "tuple_with_rest" ? jsonSchema.items = convertSchema({}, valibotSchema.rest, config, context) : valibotSchema.type === "strict_tuple" && (jsonSchema.items = !1); } else { jsonSchema.items = [], jsonSchema.minItems = valibotSchema.items.length; for (let item of valibotSchema.items) jsonSchema.items.push(convertSchema({}, item, config, context)); valibotSchema.type === "tuple_with_rest" ? jsonSchema.additionalItems = convertSchema({}, valibotSchema.rest, config, context) : valibotSchema.type === "strict_tuple" && (jsonSchema.additionalItems = !1); } break; case "object": case "object_with_rest": case "loose_object": case "strict_object": jsonSchema.type = "object", jsonSchema.properties = {}, jsonSchema.required = []; for (let key in valibotSchema.entries) { let entry = valibotSchema.entries[key]; jsonSchema.properties[key] = convertSchema({}, entry, config, context), entry.type !== "exact_optional" && entry.type !== "nullish" && entry.type !== "optional" && jsonSchema.required.push(key); } valibotSchema.type === "object_with_rest" ? jsonSchema.additionalProperties = convertSchema({}, valibotSchema.rest, config, context) : valibotSchema.type === "strict_object" && (jsonSchema.additionalProperties = !1); break; case "record": config?.target === "openapi-3.0" && "pipe" in valibotSchema.key && (errors = addError(errors, 'The "record" schema with a schema for the key that contains a "pipe" cannot be converted to JSON Schema.')), valibotSchema.key.type !== "string" && (errors = addError(errors, `The "record" schema with the "${valibotSchema.key.type}" schema for the key cannot be converted to JSON Schema.`)), jsonSchema.type = "object", config?.target !== "openapi-3.0" && (jsonSchema.propertyNames = convertSchema({}, valibotSchema.key, config, context)), jsonSchema.additionalProperties = convertSchema({}, valibotSchema.value, config, context); break; case "any": case "unknown": break; case "never": jsonSchema.not = {}; break; case "nullable": case "nullish": if (config?.target === "openapi-3.0") { let innerSchema = convertSchema({}, valibotSchema.wrapped, config, context); Object.assign(jsonSchema, innerSchema), jsonSchema.nullable = !0; } else jsonSchema.anyOf = [convertSchema({}, valibotSchema.wrapped, config, context), { type: "null" }]; valibotSchema.default !== void 0 && (jsonSchema.default = typeof valibotSchema.default == "function" ? valibotSchema.default() : valibotSchema.default); break; case "exact_optional": case "optional": case "undefinedable": jsonSchema = convertSchema(jsonSchema, valibotSchema.wrapped, config, context), valibotSchema.default !== void 0 && (jsonSchema.default = typeof valibotSchema.default == "function" ? valibotSchema.default() : valibotSchema.default); break; case "literal": typeof valibotSchema.literal != "boolean" && typeof valibotSchema.literal != "number" && typeof valibotSchema.literal != "string" && (errors = addError(errors, 'The value of the "literal" schema is not JSON compatible.')), config?.target === "openapi-3.0" ? jsonSchema.enum = [valibotSchema.literal] : jsonSchema.const = valibotSchema.literal; break; case "enum": jsonSchema.enum = valibotSchema.options, valibotSchema.options.every((option) => typeof option == "string") ? jsonSchema.type = "string" : valibotSchema.options.every((option) => typeof option == "number") ? jsonSchema.type = "number" : config?.target !== "openapi-3.0" && (jsonSchema.type = ["string", "number"]); break; case "picklist": { let hasInvalidOption = valibotSchema.options.some((option) => typeof option != "number" && typeof option != "string"); hasInvalidOption && (errors = addError(errors, 'An option of the "picklist" schema is not JSON compatible.')), jsonSchema.enum = valibotSchema.options, valibotSchema.options.every((option) => typeof option == "string") ? jsonSchema.type = "string" : valibotSchema.options.every((option) => typeof option == "number") ? jsonSchema.type = "number" : !hasInvalidOption && config?.target !== "openapi-3.0" && (jsonSchema.type = ["string", "number"]); break; } case "union": jsonSchema.anyOf = valibotSchema.options.map((option) => convertSchema({}, option, config, context)); break; case "variant": jsonSchema.oneOf = valibotSchema.options.map((option) => convertSchema({}, option, config, context)); break; case "intersect": jsonSchema.allOf = valibotSchema.options.map((option) => convertSchema({}, option, config, context)); break; case "lazy": { let wrappedValibotSchema = context.getterMap.get(valibotSchema.getter); wrappedValibotSchema || (wrappedValibotSchema = valibotSchema.getter(void 0), context.getterMap.set(valibotSchema.getter, wrappedValibotSchema)); let referenceId = context.referenceMap.get(wrappedValibotSchema); if (referenceId || (referenceId = `${refCount++}`, context.referenceMap.set(wrappedValibotSchema, referenceId), context.definitions[referenceId] = convertSchema({}, wrappedValibotSchema, config, context, !0)), jsonSchema.$ref = getDefinitionRef(referenceId), config?.overrideRef) { let refOverride = config.overrideRef({ ...context, referenceId, valibotSchema: wrappedValibotSchema, jsonSchema }); refOverride && (jsonSchema.$ref = refOverride); } break; } default: errors = addError(errors, `The "${valibotSchema.type}" schema cannot be converted to JSON Schema.`); } if (config?.overrideSchema) { let schemaOverride = config.overrideSchema({ ...context, referenceId: context.referenceMap.get(valibotSchema), valibotSchema, jsonSchema, errors }); if (schemaOverride) return { ...schemaOverride }; } if (errors) for (let message of errors) handleError(message, config); return jsonSchema; } var store; function getGlobalDefs() { return store; } function toJsonSchema(schema, config) { let context = { definitions: {}, referenceMap: /* @__PURE__ */ new Map(), getterMap: /* @__PURE__ */ new Map() }, definitions = config?.definitions ?? getGlobalDefs(); if (definitions) { for (let key in definitions) context.referenceMap.set(definitions[key], key); for (let key in definitions) context.definitions[key] = convertSchema({}, definitions[key], config, context, !0); } let jsonSchema = convertSchema({}, schema, config, context), target = config?.target ?? "draft-07"; return target === "draft-2020-12" ? jsonSchema.$schema = "https://json-schema.org/draft/2020-12/schema" : target === "draft-07" && (jsonSchema.$schema = "http://json-schema.org/draft-07/schema#"), context.referenceMap.size && (jsonSchema.$defs = context.definitions), jsonSchema; } // src/cli/tools/sdk/json-schema.ts function toToolsetJsonSchema(schema) { if (schema["~standard"].vendor === "valibot") try { return toJsonSchema(schema, { errorMode: "ignore" }); } catch { return; } } // src/cli/tools/sdk/catalog.ts function toCatalogEntry(toolset, ctx) { return { id: toolset.id, description: toolset.description, methods: Object.entries(toolset.methods).map(([methodName, method]) => ({ ref: `${toolset.id}.${methodName}`, title: method.title, description: resolveToolsetDescription(method.description, ctx).trim(), requiresDevServer: method.requiresDevServer === !0, input: toToolsetJsonSchema(method.input), ...method.output ? { output: toToolsetJsonSchema(method.output) } : {} })) }; } // src/cli/tools/sdk/child-client.ts import { fork } from "node:child_process"; import { logger } from "storybook/internal/node-logger"; // src/cli/tools/sdk/resolve-project-storybook.ts import { createRequire } from "node:module"; import { join } from "node:path"; var CHILD_HOST_ENTRY = "storybook/internal/tools/child-host"; function resolveChildHostScript(resolutionRoot) { return createRequire(join(resolutionRoot, "package.json")).resolve(CHILD_HOST_ENTRY, { paths: [resolutionRoot] }); } // src/cli/tools/sdk/child-client.ts async function spawnChildHost(args, deps = {}) { let log = deps.logger ?? logger, forkChild = deps.fork ?? fork, cwd = args.cwd, resolvedMode = args.options.mode === "local" ? "local" : "attached", resolutionRoot = args.installationPath ?? cwd, scriptPath; try { scriptPath = (deps.resolveScript ?? resolveChildHostScript)(resolutionRoot); } catch (cause) { throw new SpawnFailedError({ reason: args.installationPath ? `The running Storybook's installation at ${resolutionRoot} can no longer resolve \`storybook/internal/tools/child-host\`. From your project directory, restart Storybook (for example \`npx storybook dev\`) and re-run this command from there.` : `Could not resolve \`storybook/internal/tools/child-host\` from ${resolutionRoot}. Install Storybook in that project, then retry.`, cause }); } let env = { ...process.env, STORYBOOK_TOOLS_CHILD_HOST: "true" }; resolvedMode === "attached" ? env.STORYBOOK_ATTACHED_TOOLS = "true" : delete env.STORYBOOK_ATTACHED_TOOLS; let child; try { child = forkChild(scriptPath, [], { cwd, stdio: ["ignore", "pipe", "pipe", "ipc"], env }); } catch (cause) { throw new SpawnFailedError({ reason: `Could not start a tools child host for ${cwd}.`, cause }); } child.stdout?.on("data", (chunk) => { log.log(String(chunk)); }), child.stderr?.on("data", (chunk) => { log.warn(String(chunk)); }); let pending = /* @__PURE__ */ new Map(), pendingTelemetry = /* @__PURE__ */ new Map(), closed = !1, nextId = 0, failPending = (error) => { for (let [, waiter] of pending) waiter.reject(error); pending.clear(); }, disconnected = new Promise((_, reject) => { child.once("exit", (code, signal) => { if (closed) return; let error = new ToolsRuntimeError({ reason: "connection-lost", message: `The tools child host for ${cwd} exited${signal ? ` (${signal})` : code != null ? ` (code ${code})` : ""}.` }); failPending(error), reject(error); }), child.once("error", (cause) => { if (closed) return; let error = new SpawnFailedError({ reason: `The tools child host for ${cwd} failed.`, cause }); failPending(error), reject(error); }); }); disconnected.catch(() => { }), child.on("message", (raw) => { if (!isChildMessage(raw) || raw.type === "hello") return; if (raw.type === "telemetry") { Promise.resolve().then(() => pendingTelemetry.get(raw.id)?.(raw.event, raw.payload)).catch(() => { }); return; } let waiter = pending.get(raw.id); if (waiter) { if (pending.delete(raw.id), raw.type === "result") { waiter.resolve(raw.value); return; } waiter.reject(rehydrateSerializedToolsError(raw.error)); } }); let send = (message) => { if (!child.send) throw new SpawnFailedError({ reason: `The tools child host for ${cwd} has no IPC channel.` }); child.send(message); }, request = (message) => closed ? Promise.reject( new ToolsRuntimeError({ reason: "closed", message: "This tools host is closed. Create a new one with `createTools`." }) ) : Promise.race([ new Promise((resolve2, reject) => { pending.set(message.id, { resolve: resolve2, reject }), send(message); }), disconnected ]), hello; try { hello = await waitForHello(child, send, { cwd, options: args.options, clientInfo: args.clientInfo, resolvedMode }); } catch (error) { throw closed = !0, child.kill(), error; } if (hello.version !== 1) throw closed = !0, child.kill(), new SpawnFailedError({ reason: `The tools child host at ${cwd} speaks protocol ${hello.version}, but this process expects ${1}. Restart your Storybook so both sides match.` }); let runtime = { configDir: hello.storybook.configDir, toolsets: [], getService: () => { throw new ToolsRuntimeError({ reason: "command-unhandled", message: "Toolset services are served by the project-local child host." }); }, close: async () => { } }, assertOpen = () => { if (closed) throw new ToolsRuntimeError({ reason: "closed", message: "This tools host is closed. Create a new one with `createTools`." }); }, dimensions = toolsCommandDimensions({ clientInfo: args.clientInfo, requestedMode: args.requestedMode, resolvedMode, host: "child" }); return { mode: resolvedMode, host: "child", requestedMode: args.requestedMode, clientInfo: hello.clientInfo, storybook: hello.storybook, runtime, async describe(options = {}) { return assertOpen(), await request({ type: "describe", id: String(++nextId), options }); }, async call(ref, input = {}, options = {}) { assertOpen(), options.signal?.throwIfAborted(); let id = String(++nextId), telemetry2 = resolveCallTelemetry(options, dimensions, { clientInfo: args.clientInfo, configDir: hello.storybook.configDir }); telemetry2 && pendingTelemetry.set(id, telemetry2); let onAbort, aborted = options.signal ? new Promise((_, reject) => { onAbort = () => { try { send({ type: "cancel", id }); } catch { } reject(options.signal.reason); }, options.signal.addEventListener("abort", onAbort, { once: !0 }); }) : void 0, start = Date.now(); try { let work = request({ type: "call", id, ref, input }), outcome = await (aborted ? Promise.race([work, aborted]) : work); return await reportSdkInvocation({ ref, clientInfo: args.clientInfo, requestedMode: args.requestedMode, resolvedMode, host: "child", result: outcome, duration: Date.now() - start, configDir: hello.storybook.configDir }), outcome; } catch (error) { throw await reportSdkInvocation({ ref, clientInfo: args.clientInfo, requestedMode: args.requestedMode, resolvedMode, host: "child", result: { error }, duration: Date.now() - start, configDir: hello.storybook.configDir }), error; } finally { pendingTelemetry.delete(id), onAbort && options.signal?.removeEventListener("abort", onAbort); } }, async close() { if (!closed) { closed = !0, failPending( new ToolsRuntimeError({ reason: "closed", message: "This tools host is closed. Create a new one with `createTools`." }) ); try { send({ type: "close" }); } catch { } child.kill(); } } }; } var HELLO_TIMEOUT_MS = 1e4; async function waitForHello(child, send, args) { let { cwd, options, clientInfo, resolvedMode } = args; return new Promise((resolve2, reject) => { let onMessage = (raw) => { if (isChildMessage(raw) && raw.type === "hello") { cleanup(), resolve2(raw); return; } isChildMessage(raw) && raw.type === "error" && raw.id === "init" && (cleanup(), reject(rehydrateSerializedToolsError(raw.error))); }, onExit = (code, signal) => { cleanup(), reject( new SpawnFailedError({ reason: `The tools child host for ${cwd} exited before it was ready${signal ? ` (${signal})` : code != null ? ` (code ${code})` : ""}.` }) ); }, onError = (cause) => { cleanup(), reject( new SpawnFailedError({ reason: `The tools child host for ${cwd} failed to start.`, cause }) ); }, cleanup = () => { clearTimeout(timer), child.off("message", onMessage), child.off("exit", onExit), child.off("error", onError); }, timer = setTimeout(() => { cleanup(), reject( new SpawnFailedError({ reason: `The tools child host for ${cwd} did not become ready in time.` }) ); }, HELLO_TIMEOUT_MS); child.on("message", onMessage), child.once("exit", onExit), child.once("error", onError); try { send({ type: "init", options: { ...options, cwd, mode: resolvedMode, autoSpawn: !1, clientInfo } }); } catch (cause) { cleanup(), reject( new SpawnFailedError({ reason: `Could not initialize a tools child host for ${cwd}.`, cause }) ); } }); } function rehydrateSerializedToolsError(serialized) { let plain = deserializeError(serialized), data = plain.data; if (data == null || typeof data != "object") return plain; switch (toolsErrorKind(serialized)) { case "AttachUnavailableError": return new AttachUnavailableError(data); case "EnvironmentMismatchError": return new EnvironmentMismatchError(data); case "SpawnFailedError": return new SpawnFailedError(data); case "ToolsRuntimeError": return new ToolsRuntimeError(data); default: return plain; } } function toolsErrorKind(serialized) { let fromProps = serialized.properties?._name; return typeof fromProps == "string" ? fromProps : serialized.name.match(/\(([^)]+)\)$/)?.[1] ?? serialized.name; } // src/cli/tools/sdk/create-tools.ts async function createTools(options = {}, deps = {}) { let mode = options.mode ?? "auto", clientInfo = { name: options.clientInfo?.name ?? "storybook-tools-sdk", version: options.clientInfo?.version ?? versions.storybook, kind: options.clientInfo?.kind ?? "sdk" }; switch (mode) { case "attached": try { return await createAttachedTools(options, deps, clientInfo, mode); } catch (error) { throw isAttachGateError(error) && await reportSdkAttachGate({ error, clientInfo, requestedMode: mode, configDir: options.configDir }), error; } case "auto": try { return await createAttachedTools(options, deps, clientInfo, mode); } catch (error) { if (!isAttachGateError(error)) throw error; let fallbackReason = attachGateReasonFromError(error), notice = fallbackReason === "no-instance" ? void 0 : formatAttachFallback(error.message); try { return await createLocalTools(options, deps, clientInfo, mode, { ...notice ? { fallbackNotice: notice } : {}, fallbackReason }); } catch (localError) { throw shouldWrapAutoLocalFailure(localError) ? wrapAutoLocalFailure(notice ?? formatAttachFallback(error.message), localError) : localError; } } case "local": return createLocalTools(options, deps, clientInfo, mode); default: throw mode; } } async function createAttachedTools(options, deps, clientInfo, requestedMode) { process.env.STORYBOOK_ATTACHED_TOOLS = "true"; let { bootstrapAttachedRuntime } = await import("./attached-runtime-OD2MHCQ2.js"), autoSpawn = process.env.STORYBOOK_TOOLS_CHILD_HOST === "true" ? !1 : options.autoSpawn ?? !0, attached = await (deps.attach ?? ((target) => bootstrapAttachedRuntime({ ...target, autoSpawn })))({ cwd: options.cwd, configDir: options.configDir, port: options.port }); if ("kind" in attached && attached.kind === "spawn") return (deps.spawnChild ?? spawnChildHost)({ cwd: attached.record.cwd, installationPath: attached.storybookPath, options: { ...options, mode: "attached", autoSpawn: !1, cwd: attached.record.cwd, port: attached.record.port }, clientInfo, requestedMode }); let inProcess = attached, siblings = inProcess.siblings?.length ? inProcess.siblings.map(toSiblingInstance) : void 0; return createToolsHost({ mode: "attached", host: "in-process", requestedMode, runtime: inProcess.runtime, clientInfo, storybook: { version: versions.storybook, configDir: inProcess.runtime.configDir, url: inProcess.record.url, pid: inProcess.record.pid, ...inProcess.record.port != null ? { port: inProcess.record.port } : {}, ...inProcess.record.cwd ? { cwd: inProcess.record.cwd } : {}, ...siblings ? { siblings } : {} }, close: () => inProcess.connection.close(), disconnected: inProcess.connection.disconnected }); } function toSiblingInstance(record) { return { url: record.url, port: record.port, pid: record.pid, cwd: record.cwd, ...record.configDir ? { configDir: record.configDir } : {} }; } async function createLocalTools(options, deps, clientInfo, requestedMode, fallback) { delete process.env.STORYBOOK_ATTACHED_TOOLS; let cwd = resolve(options.cwd ?? process.cwd()), autoSpawn = process.env.STORYBOOK_TOOLS_CHILD_HOST === "true" ? !1 : options.autoSpawn ?? !0; if (!projectPathsEqual(cwd, process.cwd())) { if (!autoSpawn) throw new ToolsRuntimeError({ reason: "mode-unavailable", message: `This process is running from ${process.cwd()}, but the target Storybook is at ${cwd}. Re-run from that directory, or omit \`autoSpawn: false\` so a child host can load the project.` }); let child = await (deps.spawnChild ?? spawnChildHost)({ cwd, options: { ...options, mode: "local", autoSpawn: !1, cwd }, clientInfo, requestedMode }); return fallback ? { ...child, ...fallback } : child; } let { bootstrapToolsRuntime } = await import("./local-runtime-QL5INIZ2.js"), runtime; try { runtime = await (deps.bootstrap ?? bootstrapToolsRuntime)({ cwd, configDir: options.configDir }); } catch (error) { throw 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 createToolsHost({ mode: "local", host: "in-process", requestedMode, ...fallback, runtime, clientInfo, storybook: { version: versions.storybook, configDir: runtime.configDir } }); } function shouldWrapAutoLocalFailure(error) { return error instanceof SpawnFailedError ? !0 : error instanceof ToolsRuntimeError && (error.data.reason === "config-load-failed" || error.data.reason === "mode-unavailable"); } function wrapAutoLocalFailure(notice, localError) { let message = `${notice} ${localError.message}`; if (localError instanceof SpawnFailedError) return new SpawnFailedError({ reason: message, cause: localError.data.cause ?? localError }); if (localError instanceof ToolsRuntimeError) return new ToolsRuntimeError({ reason: localError.data.reason, message, cause: localError.data.cause }); throw localError; } function transportFor(kind) { return kind === "cli" ? "cli" : "sdk"; } function createToolsHost(args) { let { mode, host, requestedMode, runtime, clientInfo, storybook } = args, baseCtx = { transport: transportFor(clientInfo.kind), getService: runtime.getService, ...storybook.url ? { origin: storybook.url } : {} }, closed = !1, assertOpen = () => { if (closed) throw new ToolsRuntimeError({ reason: "closed", message: "This tools host is closed. Create a new one with `createTools`." }); }, invoke = async (ref, input, options) => { options.signal?.throwIfAborted(); let { toolsetId, methodName } = splitRef(ref), method = findMethod(findToolset(runtime, toolsetId), methodName); if (mode === "local" && method.requiresDevServer) throw new AttachUnavailableError({ reason: "no-instance", instances: [], remediation: `\`${ref}\` needs a running Storybook dev server, and this tools host loaded the project's configuration on its own. Start Storybook (for example \`npm run storybook\`), then retry.` }); let validation = await method.input["~standard"].validate(input); if (validation.issues) throw new ToolsRuntimeError({ reason: "invalid-input", message: `Invalid input for \`${ref}\`: ${formatIssues(validation.issues)}`, issues: validation.issues }); return raceAbort( options.signal, method.handler(validation.value, { ...baseCtx, ...options.origin !== void 0 ? { origin: options.origin } : {}, ...options.telemetry ? { telemetry: options.telemetry } : {} }) ); }, dimensions = toolsCommandDimensions({ clientInfo, requestedMode, resolvedMode: mode, host, fallbackReason: args.fallbackReason }); return { mode, host, requestedMode, clientInfo, runtime, storybook, ...args.fallbackNotice ? { fallbackNotice: args.fallbackNotice } : {}, ...args.fallbackReason ? { fallbackReason: args.fallbackReason } : {}, async describe(options = {}) { assertOpen(); let toolsets = options.toolset === void 0 ? runtime.toolsets : [findToolset(runtime, options.toolset)]; return { configDir: runtime.configDir, toolsets: toolsets.map((toolset) => toCatalogEntry(toolset, baseCtx)) }; }, async call(ref, input = {}, options = {}) { assertOpen(); let telemetry2 = resolveCallTelemetry(options, dimensions, { clientInfo, configDir: runtime.configDir }), callOptions = { ...options, ...telemetry2 ? { telemetry: telemetry2 } : {} }, start = Date.now(); try { let outcome = args.disconnected ? await Promise.race([invoke(ref, input, callOptions), args.disconnected]) : await invoke(ref, input, callOptions); return await reportSdkInvocation({ ref, clientInfo, requestedMode, resolvedMode: mode, host, fallbackReason: args.fallbackReason, result: outcome, duration: Date.now() - start, configDir: runtime.configDir }), outcome; } catch (error) { let mapped = error instanceof StorybookDevServerDisconnectedError ? new ToolsRuntimeError({ reason: "connection-lost", message: error.message, cause: error }) : error; throw await reportSdkInvocation({ ref, clientInfo, requestedMode, resolvedMode: mode, host, fallbackReason: args.fallbackReason, result: { error: mapped }, duration: Date.now() - start, configDir: runtime.configDir }), mapped; } }, async close() { closed || (closed = !0, args.close?.(), await runtime.close()); } }; } function raceAbort(signal, work) { let pending = Promise.resolve(work); if (!signal) return pending; signal.throwIfAborted(); let onAbort, aborted = new Promise((_, reject) => { onAbort = () => reject(signal.reason), signal.addEventListener("abort", onAbort, { once: !0 }); }); return Promise.race([pending, aborted]).finally(() => { signal.removeEventListener("abort", onAbort); }); } function splitRef(ref) { try { return parseToolsetMethodId(ref); } catch { throw new ToolsRuntimeError({ reason: "unknown-method", message: `Invalid tool reference \`${ref}\`. Expected \`toolsetId.methodName\`.` }); } } function findToolset(runtime, toolsetId) { let toolset = runtime.toolsets.find((candidate) => candidate.id === toolsetId); if (!toolset) throw new ToolsRuntimeError({ reason: "unknown-toolset", message: `Unknown toolset \`${toolsetId}\`. The Storybook configuration at ${runtime.configDir} provides: ${runtime.toolsets.map((candidate) => candidate.id).join(", ")}.` }); return toolset; } function findMethod(toolset, methodName) { if (!Object.hasOwn(toolset.methods, methodName)) throw new ToolsRuntimeError({ reason: "unknown-method", message: `Unknown tool \`${toolset.id}.${methodName}\`. The \`${toolset.id}\` toolset provides: ${Object.keys(toolset.methods).join(", ")}.` }); return toolset.methods[methodName]; } export { toolsCommandDimensions, wrapMethodTelemetry, createTools };