storybook
Version:
Storybook: Develop, document, and test UI components in isolation
193 lines (185 loc) • 6.94 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 {
projectPathsEqual
} from "./chunk-F5UFNM2U.js";
import {
CLAUDE_AGENT_NAME,
CLAUDE_PREVIEW_AGENT_NAME
} from "./chunk-UF3LYQOO.js";
import {
integer,
literal,
maxValue,
minValue,
number,
object,
optional,
picklist,
pipe,
safeParse,
string
} from "./chunk-5EYYAT2W.js";
// src/cli/tools/instances/registry.ts
import * as fs from "node:fs/promises";
import { homedir } from "node:os";
import { join } from "node:path";
// src/cli/tools/instances/types.ts
var McpStatusSchema = picklist(["not-installed", "starting", "ready", "error"]), StorybookInstanceRecordSchema = object({
schemaVersion: literal(1),
instanceId: string(),
pid: pipe(number(), minValue(1), integer()),
cwd: string(),
/**
* Resolved config directory of the running Storybook, used as a second matching key so
* monorepo instances are found from a different cwd (storybookjs/storybook#35359). Optional:
* records written by Storybooks older than 10.5 lack it.
*/
configDir: optional(string()),
url: string(),
port: pipe(number(), minValue(1), maxValue(65535), integer()),
/**
* Token authenticating clients against the instance's WebSocket channel. Optional: records
* written by older Storybooks lack it.
*/
token: optional(string()),
agent: optional(string()),
storybookVersion: optional(string()),
/**
* Realpathed root of the `storybook` package the dev server actually runs, recorded by the
* server from its own module location. The caller attaches in-process when this equals its own
* root, and spawns its child host from this root otherwise. Optional: records written by older
* Storybooks lack it, which makes attach refuse.
*/
storybookPath: optional(string()),
startedAt: optional(string()),
updatedAt: optional(string()),
mcp: object({
status: McpStatusSchema,
endpoint: optional(string())
})
});
// src/cli/tools/instances/registry.ts
var DEFAULT_REGISTRY_DIR = join(homedir(), ".storybook", "instances"), SOFT_REGISTRY_ERRORS = /* @__PURE__ */ new Set(["ENOENT", "EACCES", "EPERM", "ENOTDIR"]);
async function readRegistry(registryDir = DEFAULT_REGISTRY_DIR) {
let entries;
try {
entries = await fs.readdir(registryDir);
} catch (error) {
if (SOFT_REGISTRY_ERRORS.has(error.code ?? ""))
return [];
throw error;
}
return (await Promise.all(
entries.filter((name) => name.endsWith(".json")).map(async (name) => {
try {
let raw = await fs.readFile(join(registryDir, name), "utf-8"), parsed = safeParse(StorybookInstanceRecordSchema, JSON.parse(raw));
return parsed.success ? isProcessAlive(parsed.output.pid) ? parsed.output : (await fs.rm(join(registryDir, name), { force: !0 }).catch(() => {
}), null) : null;
} catch {
return null;
}
})
)).filter((r) => r !== null);
}
function isProcessAlive(pid) {
if (!Number.isInteger(pid) || pid <= 0)
return !1;
try {
return process.kill(pid, 0), !0;
} catch (error) {
return error.code === "EPERM";
}
}
// src/cli/tools/instances/resolve.ts
function resolveInstance(records, target) {
let selection = selectInstances(records, target);
if (selection.kind === "port-mismatch")
return {
kind: "intercept",
reason: "port-mismatch",
records: selection.candidates,
matches: []
};
if (selection.kind === "no-instance")
return {
kind: "intercept",
reason: "no-instance",
records: selection.records,
matches: []
};
let sortedMatches = selection.matches, selected = sortedMatches.find((r) => r.mcp.status === "ready") ?? sortedMatches[0];
switch (selected.mcp.status) {
case "ready":
return {
kind: "instance",
record: selected,
matches: sortedMatches
};
case "starting":
return {
kind: "intercept",
reason: "mcp-starting",
matches: sortedMatches
};
case "not-installed":
return {
kind: "intercept",
reason: "addon-missing",
matches: sortedMatches
};
case "error":
return {
kind: "intercept",
reason: "mcp-error",
matches: sortedMatches
};
default: {
let unhandled = selected.mcp.status;
throw new Error(`Unhandled MCP status: ${unhandled}`);
}
}
}
function selectInstances(records, target) {
let { port: targetPort, agent: currentAgent } = target;
if (targetPort != null) {
let candidates = target.configDirExplicit ? records.filter((record) => matchesTargetConfigDir(record, target)) : records, matches = candidates.filter((record) => record.port === targetPort);
return matches.length === 0 ? candidates.length > 0 ? { kind: "port-mismatch", port: targetPort, candidates } : { kind: "no-instance", records } : { kind: "match", matches: [...matches].sort(byMostRecentlyStarted) };
}
let projectMatches = listProjectMatches(records, target);
return projectMatches.length === 0 ? { kind: "no-instance", records } : { kind: "match", matches: selectCompetingBucket(projectMatches, currentAgent) };
}
function listProjectMatches(records, target) {
return target.configDirExplicit ? records.filter((record) => matchesTargetConfigDir(record, target)) : records.filter(
(record) => projectPathsEqual(record.cwd, target.cwd) || matchesTargetConfigDir(record, target)
);
}
function matchesTargetConfigDir(record, target) {
return target.configDir != null && record.configDir != null && projectPathsEqual(record.configDir, target.configDir);
}
function selectCompetingBucket(matches, currentAgent) {
let selectedAgent = (currentAgent === CLAUDE_AGENT_NAME ? [CLAUDE_PREVIEW_AGENT_NAME, CLAUDE_AGENT_NAME] : currentAgent ? [currentAgent] : []).find((agent) => matches.some((r) => r.agent === agent));
return [...selectedAgent ? matches.filter((r) => r.agent === selectedAgent) : matches].sort(byMostRecentlyStarted);
}
function startedAtMs(r) {
if (!r.startedAt)
return Number.NEGATIVE_INFINITY;
let t = Date.parse(r.startedAt);
return Number.isNaN(t) ? Number.NEGATIVE_INFINITY : t;
}
function byMostRecentlyStarted(a, b) {
let ta = startedAtMs(a), tb = startedAtMs(b);
return ta !== tb ? tb > ta ? 1 : -1 : a.pid - b.pid;
}
export {
readRegistry,
resolveInstance,
selectInstances
};