storybook
Version:
Storybook: Develop, document, and test UI components in isolation
1,141 lines • 53.2 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 {
jsTsSourceExtensions
} from "./chunk-7LZUPCQB.js";
import {
require_dist
} from "./chunk-FE5AV6EP.js";
import {
__toESM
} from "./chunk-4US4PNS3.js";
// src/shared/constants/tags.ts
var Tag = {
/** Indicates that autodocs should be generated for this component */
AUTODOCS: "autodocs",
/** MDX documentation attached to a component's stories file */
ATTACHED_MDX: "attached-mdx",
/** Standalone MDX documentation not attached to stories */
UNATTACHED_MDX: "unattached-mdx",
/** Story has a play function */
PLAY_FN: "play-fn",
/** Story has a test function */
TEST_FN: "test-fn",
/** Development environment tag */
DEV: "dev",
/** Test environment tag */
TEST: "test",
/** Manifest generation tag */
MANIFEST: "manifest"
};
// src/common/utils/module-resolver.ts
import { ResolverFactory } from "oxc-resolver";
var defaultResolveConditionNames = ["storybook", "import", "module", "default"];
function createModuleResolver(options = {}) {
let factory = new ResolverFactory({
...options,
conditionNames: options.conditionNames ?? defaultResolveConditionNames
}), unwrap = (result, specifier, from) => {
if (result.path)
return result.path;
throw new Error(result.error ?? `Cannot resolve module '${specifier}' from '${from}'`);
};
return {
resolveFileSync: (fromFile, specifier) => unwrap(factory.resolveFileSync(fromFile, specifier), specifier, fromFile),
resolveSync: (fromDirectory, specifier) => unwrap(factory.sync(fromDirectory, specifier), specifier, fromDirectory)
};
}
// src/csf-tools/story-shape/utils.ts
import { types as t } from "storybook/internal/babel";
var unwrapExpression = (node) => t.isTSAsExpression(node) || t.isTSSatisfiesExpression(node) || t.isTSNonNullExpression(node) || t.isTSTypeAssertion(node) || t.isParenthesizedExpression(node) ? unwrapExpression(node.expression) : node, isCanonicalCsf2BindCall = (node) => t.isCallExpression(node) && t.isMemberExpression(node.callee) && !node.callee.computed && t.isIdentifier(node.callee.object) && t.isIdentifier(node.callee.property, { name: "bind" }) && (node.arguments.length === 0 || node.arguments.length === 1 && t.isObjectExpression(node.arguments[0]) && node.arguments[0].properties.length === 0), isCsfFactoryCall = (node) => t.isCallExpression(node) && t.isMemberExpression(node.callee) && !node.callee.computed && t.isIdentifier(node.callee.object) && t.isIdentifier(node.callee.property) && (node.callee.property.name === "story" || node.callee.property.name === "extend"), keyOf = (p) => t.isStringLiteral(p.key) ? p.key.value : !p.computed && t.isIdentifier(p.key) ? p.key.name : null, propertyValue = (object, name) => object?.properties.find(
(candidate) => t.isObjectProperty(candidate) && keyOf(candidate) === name
)?.value, soleReturnedExpression = (body) => {
let [statement, ...rest] = body.body;
return rest.length === 0 && t.isReturnStatement(statement) && t.isExpression(statement.argument) ? statement.argument : void 0;
}, returnedExpression = (fn) => {
if (t.isFunction(fn))
return t.isExpression(fn.body) ? fn.body : soleReturnedExpression(fn.body);
}, returnedExpressionPath = (renderFunction) => {
if (!returnedExpression(renderFunction.node))
return;
let body = renderFunction.get("body");
if (body.isExpression())
return body;
let [statement] = body.isBlockStatement() ? body.get("body") : [], argument = statement?.isReturnStatement() ? statement.get("argument") : void 0;
return argument?.isExpression() ? argument : void 0;
}, resolveReturnedObjectExpression = (renderFunction) => {
let returned = returnedExpressionPath(renderFunction);
if (returned?.isObjectExpression())
return returned.node;
if (!returned?.isIdentifier())
return;
let resolved = resolveIdentifierInit(renderFunction, returned);
return resolved?.isObjectExpression() ? resolved.node : void 0;
};
function resolveIdentifierInit(storyPath, identifier) {
let programPath = storyPath.findParent((p) => p.isProgram());
if (!programPath)
return null;
for (let stmt of programPath.get("body")) {
if (stmt.isFunctionDeclaration() && stmt.node.id?.name === identifier.node.name)
return stmt;
if (stmt.isExportNamedDeclaration()) {
let decl = stmt.get("declaration");
if (decl.isFunctionDeclaration() && decl.node.id?.name === identifier.node.name)
return decl;
}
}
let match = programPath.get("body").flatMap((stmt) => {
if (stmt.isVariableDeclaration())
return stmt.get("declarations");
if (stmt.isExportNamedDeclaration()) {
let decl = stmt.get("declaration");
if (decl && decl.isVariableDeclaration())
return decl.get("declarations");
}
return [];
}).find((d) => {
let id = d.get("id");
return id.isIdentifier() && id.node.name === identifier.node.name;
});
if (!match)
return null;
let init = match.get("init");
return init && init.isExpression() ? init : null;
}
function pathForNode(program, target) {
if (!target)
return;
let found;
return program.traverse({
enter(p) {
p.node && p.node === target && (found = p, p.stop());
}
}), found;
}
function metaObjectPath(csf) {
let metaPath = pathForNode(csf._file.path, csf._metaNode);
return metaPath?.isObjectExpression() ? metaPath : void 0;
}
// src/csf-tools/CsfFile.ts
var import_ts_dedent = __toESM(require_dist(), 1);
import { readFile, writeFile } from "node:fs/promises";
import {
BabelFileClass,
babelParse,
generate,
recast,
types as t3,
traverse
} from "storybook/internal/babel";
import {
isExportStory,
storyNameFromExport,
toId,
toTestId
} from "storybook/internal/csf/csf-utils";
import { logger } from "storybook/internal/node-logger";
// src/csf-tools/findVarInitialization.ts
import { types as t2 } from "storybook/internal/babel";
var findVarInitialization = (identifier, program) => {
let init = null, declarations = null;
return program.body.find((node) => (t2.isVariableDeclaration(node) ? declarations = node.declarations : t2.isExportNamedDeclaration(node) && t2.isVariableDeclaration(node.declaration) && (declarations = node.declaration.declarations), declarations && declarations.find((decl) => t2.isVariableDeclarator(decl) && t2.isIdentifier(decl.id) && decl.id.name === identifier ? (init = decl.init, !0) : !1))), init;
};
// src/csf-tools/CsfFile.ts
var PREVIEW_FILE_REGEX = /\/preview(.(js|jsx|mjs|ts|tsx))?$/, isValidPreviewPath = (filepath) => PREVIEW_FILE_REGEX.test(filepath);
function parseIncludeExclude(prop) {
if (t3.isArrayExpression(prop))
return prop.elements.map((e) => {
if (t3.isStringLiteral(e))
return e.value;
throw new Error(`Expected string literal: ${e}`);
});
if (t3.isStringLiteral(prop))
return new RegExp(prop.value);
if (t3.isRegExpLiteral(prop))
return new RegExp(prop.pattern, prop.flags);
throw new Error(`Unknown include/exclude: ${prop}`);
}
function parseTags(prop) {
if (!t3.isArrayExpression(prop))
throw new Error("CSF: Expected tags array");
return prop.elements.map((e) => {
if (t3.isStringLiteral(e))
return e.value;
throw new Error("CSF: Expected tag to be string literal");
});
}
function parseTestTags(optionsNode, program) {
if (!optionsNode)
return [];
let node = optionsNode;
if (t3.isIdentifier(node) && (node = findVarInitialization(node.name, program)), t3.isObjectExpression(node)) {
let tagsProp = node.properties.find(
(property) => t3.isObjectProperty(property) && t3.isIdentifier(property.key) && property.key.name === "tags"
);
if (tagsProp) {
let tagsNode = tagsProp.value;
return t3.isIdentifier(tagsNode) && (tagsNode = findVarInitialization(tagsNode.name, program)), parseTags(tagsNode);
}
}
return [];
}
var formatLocation = (node, fileName) => {
let loc = "";
if (node.loc) {
let { line, column } = node.loc?.start || {};
loc = `(line ${line}, col ${column})`;
}
return `${fileName || ""} ${loc}`.trim();
}, isModuleMock = (importPath) => MODULE_MOCK_REGEX.test(importPath), isArgsStory = (init, parent, csf) => {
let storyFn = init;
if (t3.isProgram(parent) && isCanonicalCsf2BindCall(init)) {
let boundIdentifier = init.callee.object.name, template = findVarInitialization(boundIdentifier, parent);
template && (csf._templates[boundIdentifier] = template, storyFn = template);
}
return t3.isArrowFunctionExpression(storyFn) || t3.isFunctionDeclaration(storyFn) ? storyFn.params.length > 0 : !1;
}, parseExportsOrder = (init) => {
if (t3.isArrayExpression(init))
return init.elements.map((item) => {
if (t3.isStringLiteral(item))
return item.value;
throw new Error(`Expected string literal named export: ${item}`);
});
throw new Error(`Expected array of string literals: ${init}`);
}, sortExports = (exportByName, order) => order.reduce(
(acc, name) => {
let namedExport = exportByName[name];
return namedExport && (acc[name] = namedExport), acc;
},
{}
), hasMount = (play) => {
if (t3.isArrowFunctionExpression(play) || t3.isFunctionDeclaration(play) || t3.isObjectMethod(play)) {
let params = play.params;
if (params.length >= 1) {
let [arg] = params;
if (t3.isObjectPattern(arg))
return !!arg.properties.find((prop) => {
if (t3.isObjectProperty(prop) && t3.isIdentifier(prop.key))
return prop.key.name === "mount";
});
}
}
return !1;
}, MODULE_MOCK_REGEX = /^[.\/#].*\.mock($|\.[^.]*$)/i, NoMetaError = class extends Error {
constructor(message, ast, fileName) {
let msg = message.trim();
super(import_ts_dedent.dedent`
CSF: ${msg} ${formatLocation(ast, fileName)}
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
`), this.name = this.constructor.name;
}
}, MultipleMetaError = class extends Error {
constructor(message, ast, fileName) {
let msg = `${message} ${formatLocation(ast, fileName)}`.trim();
super(import_ts_dedent.dedent`
CSF: ${message} ${formatLocation(ast, fileName)}
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
`), this.name = this.constructor.name;
}
}, MixedFactoryError = class extends Error {
constructor(message, ast, fileName) {
let msg = `${message} ${formatLocation(ast, fileName)}`.trim();
super(import_ts_dedent.dedent`
CSF: ${message} ${formatLocation(ast, fileName)}
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
`), this.name = this.constructor.name;
}
}, BadMetaError = class extends Error {
constructor(message, ast, fileName) {
let msg = "".trim();
super(import_ts_dedent.dedent`
CSF: ${message} ${formatLocation(ast, fileName)}
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
`), this.name = this.constructor.name;
}
}, CsfFile = class {
constructor(ast, options, file) {
this._stories = {};
this._metaAnnotations = {};
this._storyExports = {};
this._storyDeclarationPath = {};
this._storyPaths = {};
this._storyStatements = {};
this._storyAnnotations = {};
this._templates = {};
this._tests = [];
this._ast = ast, this._file = file, this._options = options, this.imports = [];
}
_parseTitle(value) {
let node = t3.isIdentifier(value) ? findVarInitialization(value.name, this._ast.program) : value;
if (t3.isStringLiteral(node))
return node.value;
if (t3.isTSSatisfiesExpression(node) && t3.isStringLiteral(node.expression))
return node.expression.value;
throw new Error(import_ts_dedent.dedent`
CSF: unexpected dynamic title ${formatLocation(node, this._options.fileName)}
More info: https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#string-literal-titles
`);
}
_parseMeta(declaration, program) {
if (this._metaNode)
throw new MultipleMetaError("multiple meta objects", declaration, this._options.fileName);
this._metaNode = declaration;
let meta = {};
declaration.properties.forEach((p) => {
if (t3.isIdentifier(p.key)) {
let value = t3.isObjectMethod(p) ? p : p.value;
if (this._metaAnnotations[p.key.name] = value, p.key.name === "title")
meta.title = this._parseTitle(p.value);
else if (["includeStories", "excludeStories"].includes(p.key.name))
meta[p.key.name] = parseIncludeExclude(p.value);
else if (p.key.name === "component") {
let n = p.value;
if (t3.isIdentifier(n)) {
let id = n.name, importStmt = program.body.find(
(stmt) => t3.isImportDeclaration(stmt) && stmt.specifiers.find((spec) => spec.local.name === id)
);
if (importStmt) {
let { source } = importStmt, specifier = importStmt.specifiers.find((spec) => spec.local.name === id);
t3.isStringLiteral(source) && specifier && (this._rawComponentPath = source.value, (t3.isImportSpecifier(specifier) || t3.isImportDefaultSpecifier(specifier)) && (this._componentImportSpecifier = specifier));
}
}
let { code } = recast.print(p.value, {});
meta.component = code;
} else if (p.key.name === "tags") {
let node = p.value;
t3.isIdentifier(node) && (node = findVarInitialization(node.name, this._ast.program)), meta.tags = parseTags(node);
} else if (p.key.name === "id")
if (t3.isStringLiteral(p.value))
meta.id = p.value.value;
else
throw new Error(`Unexpected component id: ${p.value}`);
}
}), this._meta = meta;
}
getStoryExport(key) {
let node = this._storyExports[key];
return node = t3.isVariableDeclarator(node) ? node.init : node, isCanonicalCsf2BindCall(node) && (node = this._templates[node.callee.object.name]), node;
}
parse() {
let self = this;
if (traverse(this._ast, {
ExportDefaultDeclaration: {
enter(path) {
let { node, parent } = path, isVariableReference = t3.isIdentifier(node.declaration) && t3.isProgram(parent);
if (self._options.transformInlineMeta && !isVariableReference && t3.isExpression(node.declaration)) {
let metaId = path.scope.generateUidIdentifier("meta");
self._metaVariableName = metaId.name;
let nodes = [
t3.variableDeclaration("const", [t3.variableDeclarator(metaId, node.declaration)]),
t3.exportDefaultDeclaration(metaId)
];
nodes.forEach((_node) => _node.loc = path.node.loc), path.replaceWithMultiple(nodes);
return;
}
let metaNode, decl;
if (isVariableReference) {
let variableName = node.declaration.name;
self._metaVariableName = variableName;
let isMetaVariable = (declaration) => t3.isIdentifier(declaration.id) && declaration.id.name === variableName;
self._metaStatementPath = self._file.path.get("body").find(
(path2) => path2.isVariableDeclaration() && path2.node.declarations.some(isMetaVariable)
), self._metaStatement = self._metaStatementPath?.node, decl = (self?._metaStatement?.declarations || []).find(
isMetaVariable
)?.init;
} else
self._metaStatement = node, self._metaStatementPath = path, decl = node.declaration;
if (t3.isObjectExpression(decl) ? metaNode = decl : /* export default { ... } as Meta<...> */ /* export default { ... } satisfies Meta<...> */ (t3.isTSAsExpression(decl) || t3.isTSSatisfiesExpression(decl)) && t3.isObjectExpression(decl.expression) ? metaNode = decl.expression : (
// export default { ... } satisfies Meta as Meta<...>
t3.isTSAsExpression(decl) && t3.isTSSatisfiesExpression(decl.expression) && t3.isObjectExpression(decl.expression.expression) && (metaNode = decl.expression.expression)
), metaNode && t3.isProgram(parent) && self._parseMeta(metaNode, parent), self._metaStatement && !self._metaNode)
throw new NoMetaError(
"default export must be an object",
self._metaStatement,
self._options.fileName
);
self._metaPath = path;
}
},
ExportNamedDeclaration: {
enter(path) {
let { node, parent } = path, declaration = path.get("declaration"), declarations;
declaration.isVariableDeclaration() ? declarations = declaration.get("declarations").filter((d) => d.isVariableDeclarator()) : declaration.isFunctionDeclaration() && (declarations = [declaration]), declarations ? declarations.forEach((declPath) => {
let decl = declPath.node, id = declPath.node.id;
if (t3.isIdentifier(id)) {
let { name: exportName } = id;
if (exportName === "__namedExportsOrder" && declPath.isVariableDeclarator()) {
self._namedExportsOrder = parseExportsOrder(declPath.node.init);
return;
}
let storyNode;
t3.isVariableDeclarator(decl) ? t3.isTSAsExpression(decl.init) && t3.isTSSatisfiesExpression(decl.init.expression) ? storyNode = decl.init.expression.expression : t3.isTSAsExpression(decl.init) || t3.isTSSatisfiesExpression(decl.init) ? storyNode = decl.init.expression : storyNode = decl.init : storyNode = decl;
let storyIsFactory = !1;
if (storyNode && isCsfFactoryCall(storyNode) && (storyIsFactory = !0, storyNode = storyNode.arguments[0]), self._metaIsFactory && !storyIsFactory)
return;
if (!self._metaIsFactory && storyIsFactory)
throw self._metaNode ? new MixedFactoryError(
"expected non-factory story",
storyNode,
self._options.fileName
) : new BadMetaError(
"meta() factory must be imported from .storybook/preview configuration",
storyNode,
self._options.fileName
);
self._storyExports[exportName] = decl, self._storyDeclarationPath[exportName] = declPath, self._storyPaths[exportName] = path, self._storyStatements[exportName] = node;
let name = storyNameFromExport(exportName);
self._storyAnnotations[exportName] ? logger.warn(
`Unexpected annotations for "${exportName}" before story declaration`
) : self._storyAnnotations[exportName] = {};
let parameters = {};
t3.isObjectExpression(storyNode) ? (parameters.__isArgsStory = !0, storyNode.properties.forEach((p) => {
if (t3.isIdentifier(p.key)) {
let key = p.key.name;
if (t3.isObjectMethod(p))
self._storyAnnotations[exportName][key] = p;
else {
if (p.key.name === "render")
parameters.__isArgsStory = isArgsStory(
p.value,
parent,
self
);
else if (p.key.name === "name" && t3.isStringLiteral(p.value))
name = p.value.value;
else if (p.key.name === "storyName" && t3.isStringLiteral(p.value))
logger.warn(
`Unexpected usage of "storyName" in "${exportName}". Please use "name" instead.`
);
else if (p.key.name === "parameters" && t3.isObjectExpression(p.value)) {
let idProperty = p.value.properties.find(
(property) => t3.isObjectProperty(property) && t3.isIdentifier(property.key) && property.key.name === "__id"
);
idProperty && (parameters.__id = idProperty.value.value);
}
self._storyAnnotations[exportName][p.key.name] = p.value;
}
}
})) : parameters.__isArgsStory = isArgsStory(storyNode, parent, self), self._stories[exportName] = {
id: "FIXME",
name,
parameters,
__stats: {
factory: storyIsFactory
}
};
}
}) : node.specifiers.length > 0 && node.specifiers.forEach((specifier) => {
if (t3.isExportSpecifier(specifier) && (t3.isIdentifier(specifier.exported) || t3.isStringLiteral(specifier.exported))) {
let exportName = t3.isIdentifier(specifier.exported) ? specifier.exported.name : specifier.exported.value, { name: localName } = specifier.local, decl = t3.isProgram(parent) ? findVarInitialization(localName, parent) : specifier.local;
if (exportName === "default") {
let metaNode;
t3.isObjectExpression(decl) ? metaNode = decl : /* export default { ... } as Meta<...> */ /* export default { ... } satisfies Meta<...> */ (t3.isTSAsExpression(decl) || t3.isTSSatisfiesExpression(decl)) && t3.isObjectExpression(decl.expression) ? metaNode = decl.expression : (
// export default { ... } satisfies Meta as Meta<...>
t3.isTSAsExpression(decl) && t3.isTSSatisfiesExpression(decl.expression) && t3.isObjectExpression(decl.expression.expression) && (metaNode = decl.expression.expression)
), metaNode && t3.isProgram(parent) && self._parseMeta(metaNode, parent);
} else {
let annotations = {}, storyNode = decl;
t3.isObjectExpression(storyNode) && storyNode.properties.forEach((p) => {
t3.isIdentifier(p.key) && (annotations[p.key.name] = p.value);
}), self._storyAnnotations[exportName] = annotations, self._storyStatements[exportName] = decl, self._storyPaths[exportName] = path, self._stories[exportName] = {
id: "FIXME",
name: exportName,
localName,
parameters: {},
__stats: {}
};
}
}
});
}
},
ExpressionStatement: {
enter({ node, parent }) {
let { expression } = node;
if (t3.isProgram(parent) && t3.isAssignmentExpression(expression) && t3.isMemberExpression(expression.left) && t3.isIdentifier(expression.left.object) && t3.isIdentifier(expression.left.property)) {
let exportName = expression.left.object.name, annotationKey = expression.left.property.name, annotationValue = expression.right;
if (self._storyAnnotations[exportName] && (annotationKey === "story" && t3.isObjectExpression(annotationValue) ? annotationValue.properties.forEach((prop) => {
t3.isIdentifier(prop.key) && (self._storyAnnotations[exportName][prop.key.name] = prop.value);
}) : self._storyAnnotations[exportName][annotationKey] = annotationValue), annotationKey === "storyName" && t3.isStringLiteral(annotationValue)) {
let storyName = annotationValue.value, story = self._stories[exportName];
if (!story)
return;
story.name = storyName;
}
}
if (t3.isCallExpression(expression) && t3.isMemberExpression(expression.callee) && t3.isIdentifier(expression.callee.object) && t3.isIdentifier(expression.callee.property) && expression.callee.property.name === "test" && expression.arguments.length >= 2 && t3.isStringLiteral(expression.arguments[0])) {
let exportName = expression.callee.object.name, testName = expression.arguments[0].value, testFunction = expression.arguments.length === 2 ? expression.arguments[1] : expression.arguments[2], testArguments = expression.arguments.length === 2 ? null : expression.arguments[1], tags = parseTestTags(testArguments, self._ast.program);
self._tests.push({
function: testFunction,
name: testName,
node: expression,
// can't set id because meta title isn't available yet
// so it's set later on
id: "FIXME",
tags,
parent: { node: self._storyStatements[exportName] }
}), self._stories[exportName].__stats.tests = !0;
}
}
},
CallExpression: {
enter(path) {
let { node } = path, { callee } = node;
if (t3.isIdentifier(callee) && callee.name === "storiesOf")
throw new Error(import_ts_dedent.dedent`
Unexpected \`storiesOf\` usage: ${formatLocation(node, self._options.fileName)}.
SB8 does not support \`storiesOf\`.
`);
if (t3.isMemberExpression(callee) && t3.isIdentifier(callee.property) && callee.property.name === "meta" && node.arguments.length > 0) {
let rootObject = callee.object;
if (t3.isCallExpression(rootObject) && t3.isMemberExpression(rootObject.callee) && (rootObject = rootObject.callee.object), t3.isIdentifier(rootObject)) {
let configParent = path.scope.getBinding(rootObject.name)?.path?.parentPath?.node;
if (t3.isImportDeclaration(configParent)) {
if (isValidPreviewPath(configParent.source.value)) {
self._metaIsFactory = !0;
let metaDeclarator = path.findParent(
(p) => p.isVariableDeclarator()
);
self._metaVariableName = t3.isIdentifier(metaDeclarator.node.id) ? metaDeclarator.node.id.name : callee.property.name;
let metaNode = node.arguments[0];
self._parseMeta(metaNode, self._ast.program);
} else if (rootObject.name === "preview")
throw new BadMetaError(
"meta() factory must be imported from .storybook/preview configuration",
configParent,
self._options.fileName
);
}
}
}
}
},
ImportDeclaration: {
enter({ node }) {
let { source } = node;
if (t3.isStringLiteral(source))
self.imports.push(source.value);
else
throw new Error("CSF: unexpected import source");
}
}
}), !self._meta)
throw new NoMetaError("missing default export", self._ast, self._options.fileName);
let entries = Object.entries(self._stories);
if (self._meta.title = this._options.makeTitle(self._meta?.title), self._metaAnnotations.play && (self._meta.tags = [...self._meta.tags || [], Tag.PLAY_FN]), self._stories = entries.reduce(
(acc, [key, story]) => {
if (!isExportStory(key, self._meta))
return acc;
let id = story.parameters?.__id ?? toId(self._meta?.id || self._meta?.title, storyNameFromExport(key)), parameters = { ...story.parameters, __id: id }, { includeStories } = self._meta || {};
key === "__page" && (entries.length === 1 || Array.isArray(includeStories) && includeStories.length === 1) && (parameters.docsOnly = !0), acc[key] = { ...story, id, parameters };
let storyAnnotations = self._storyAnnotations[key], { tags, play } = storyAnnotations;
if (tags) {
let node = t3.isIdentifier(tags) ? findVarInitialization(tags.name, this._ast.program) : tags;
acc[key].tags = parseTags(node);
}
play && (acc[key].tags = [...acc[key].tags || [], Tag.PLAY_FN]);
let stats = acc[key].__stats;
["play", "render", "loaders", "beforeEach", "globals", "tags"].forEach((annotation) => {
stats[annotation] = !!storyAnnotations[annotation] || !!self._metaAnnotations[annotation];
});
let storyExport = self.getStoryExport(key);
stats.storyFn = !!(t3.isArrowFunctionExpression(storyExport) || t3.isFunctionDeclaration(storyExport)), stats.mount = hasMount(storyAnnotations.play ?? self._metaAnnotations.play), stats.moduleMock = !!self.imports.find((fname) => isModuleMock(fname));
let storyNode = self._storyStatements[key], storyTests = self._tests.filter((t7) => t7.parent.node === storyNode);
return storyTests.length > 0 && (stats.tests = !0, storyTests.forEach((test) => {
test.id = toTestId(id, test.name);
})), acc;
},
{}
), Object.keys(self._storyExports).forEach((key) => {
isExportStory(key, self._meta) || (delete self._storyExports[key], delete self._storyAnnotations[key], delete self._storyStatements[key]);
}), self._namedExportsOrder) {
let unsortedExports = Object.keys(self._storyExports);
self._storyExports = sortExports(self._storyExports, self._namedExportsOrder), self._stories = sortExports(self._stories, self._namedExportsOrder);
let sortedExports = Object.keys(self._storyExports);
if (unsortedExports.length !== sortedExports.length)
throw new Error(
`Missing exports after sort: ${unsortedExports.filter(
(key) => !sortedExports.includes(key)
)}`
);
}
return self;
}
get meta() {
return this._meta;
}
get stories() {
return Object.values(this._stories);
}
getStoryTests(story) {
let storyNode = typeof story == "string" ? this._storyStatements[story] : story;
return storyNode ? this._tests.filter((t7) => t7.parent.node === storyNode) : [];
}
get indexInputs() {
let { fileName } = this._options;
if (!fileName)
throw new Error(
import_ts_dedent.dedent`Cannot automatically create index inputs with CsfFile.indexInputs because the CsfFile instance was created without a the fileName option.
Either add the fileName option when creating the CsfFile instance, or create the index inputs manually.`
);
let index = [];
return Object.entries(this._stories).map(([exportName, story]) => {
let tags = [...this._meta?.tags ?? [], ...story.tags ?? []], storyInput = {
rawComponentPath: this._rawComponentPath,
exportName,
title: this.meta?.title,
metaId: this.meta?.id,
tags,
__id: story.id,
__stats: story.__stats
}, tests = this.getStoryTests(exportName), hasTests = tests.length > 0;
index.push({
...storyInput,
type: "story",
subtype: "story",
name: story.name
}), hasTests && tests.forEach((test) => {
index.push({
...storyInput,
// TODO implementent proper title => path behavior in `transformStoryIndexToStoriesHash`
// title: `${storyInput.title}/${story.name}`,
type: "story",
subtype: "test",
name: test.name,
parent: story.id,
parentName: story.name,
tags: [
...storyInput.tags,
// this tag comes before test tags so users can invert if they like
`!${Tag.AUTODOCS}`,
...test.tags,
// this tag comes after test tags so users can't change it
Tag.TEST_FN
],
__id: test.id
});
});
}), index;
}
}, babelParseFile = ({
code,
filename = "",
ast
}) => new BabelFileClass(
{ filename, highlightCode: !1 },
{ code, ast: ast ?? babelParse(code) }
), loadCsf = (code, options) => {
let ast = babelParse(code), file = babelParseFile({ code, filename: options.fileName, ast });
return new CsfFile(ast, options, file);
}, formatCsf = (csf, options = { sourceMaps: !1 }, code) => {
let result = generate(csf._ast, options, code);
return options.sourceMaps ? result : result.code;
}, printCsf = (csf, options = {}) => recast.print(csf._ast, options), readCsf = async (fileName, options) => {
let code = (await readFile(fileName, "utf-8")).toString();
return loadCsf(code, { ...options, fileName });
}, writeCsf = async (csf, fileName) => {
if (!(fileName || csf._options.fileName))
throw new Error("Please specify a fileName for writeCsf");
await writeFile(fileName, printCsf(csf).code);
};
// src/csf-tools/story-shape/imports.ts
import { types as t4 } from "storybook/internal/babel";
var isTypeSpecifier = (s) => t4.isImportSpecifier(s) && s.importKind === "type", importedName = (im) => t4.isIdentifier(im) ? im.name : im.value;
function collectImportBindings(program) {
let localToImport = /* @__PURE__ */ new Map();
for (let stmt of program.get("body")) {
if (!stmt.isImportDeclaration())
continue;
let decl = stmt.node;
if (decl.importKind !== "type")
for (let s of decl.specifiers ?? []) {
if (!("local" in s) || !s.local || isTypeSpecifier(s))
continue;
let importId = decl.source.value;
t4.isImportDefaultSpecifier(s) ? localToImport.set(s.local.name, { importId, importName: "default" }) : t4.isImportNamespaceSpecifier(s) ? localToImport.set(s.local.name, { importId, importName: "*" }) : t4.isImportSpecifier(s) && localToImport.set(s.local.name, { importId, importName: importedName(s.imported) });
}
}
return localToImport;
}
// src/csf-tools/story-shape/resolve-members.ts
import { generate as generate2, types as t5 } from "storybook/internal/babel";
var sourceOf = (node) => generate2(node, { concise: !0, comments: !1 }).code, complete = (properties = {}) => ({
properties,
shadowed: [],
unresolved: []
}), resolveObjectMembers = (object, ctx) => membersOf(object, ctx, /* @__PURE__ */ new Set()), resolveArgsRecord = (node, ctx) => {
if (node === void 0)
return complete();
let unwrapped = unwrapExpression(node);
if (t5.isObjectExpression(unwrapped))
return asArgsRecord(membersOf(unwrapped, ctx, /* @__PURE__ */ new Set()));
let referenced = resolveReference(ctx, unwrapped, node.start ?? void 0, /* @__PURE__ */ new Set());
return referenced ? asArgsRecord(referenced) : { properties: {}, shadowed: [], unresolved: [`args: ${sourceOf(unwrapped)}`] };
}, asArgsRecord = (members) => {
let methods = Object.entries(members.properties).filter(([, node]) => t5.isObjectMethod(node));
if (methods.length === 0)
return members;
let properties = { ...members.properties }, unresolved = [...members.unresolved];
for (let [key, node] of methods)
delete properties[key], unresolved.push(sourceOf(node));
return {
properties,
shadowed: members.shadowed.filter((key) => key in properties),
unresolved
};
}, resolveBindingMembers = (ctx, name) => {
let bound = bindingMembers(ctx, name, void 0, /* @__PURE__ */ new Set());
return bound === void 0 || bound.kind === "namespace" ? void 0 : bound.members;
}, resolveReferencedValue = (ctx, expression) => {
let chain = memberChain(expression);
if (!chain || chain.path.length === 0)
return;
let visited = /* @__PURE__ */ new Set(), located = locate(ctx, chain, void 0, visited);
if (!located || located.path.length === 0)
return;
let members = located.members, scope = located.ctx;
for (let [index, key] of located.path.entries()) {
if (members.shadowed.includes(key))
return;
let value = members.properties[key];
if (value === void 0)
return;
if (index === located.path.length - 1)
return { node: value, ctx: scope };
let unwrapped = unwrapExpression(value);
if (!t5.isObjectExpression(unwrapped))
return;
members = membersOf(unwrapped, scope, visited);
}
}, membersOf = (object, ctx, visited) => {
let properties = {}, unresolved = [], shadowed = /* @__PURE__ */ new Set(), shadowKnownMembers = (source) => {
unresolved.push(source);
for (let key of Object.keys(properties))
shadowed.add(key);
};
for (let property of object.properties) {
if (t5.isSpreadElement(property)) {
let spread = spreadMembers(ctx, property, visited);
if (spread === void 0 || spread.unresolved.length > 0) {
shadowKnownMembers(sourceOf(property));
continue;
}
for (let [key2, value] of Object.entries(spread.properties))
properties[key2] = value, shadowed.delete(key2);
continue;
}
let key = keyOf(property);
if (key === null) {
shadowKnownMembers(sourceOf(property));
continue;
}
if (t5.isObjectMethod(property) && (property.kind !== "method" || property.generator)) {
delete properties[key], shadowed.delete(key), unresolved.push(sourceOf(property));
continue;
}
properties[key] = t5.isObjectMethod(property) ? property : property.value, shadowed.delete(key);
}
return { properties, shadowed: [...shadowed], unresolved };
}, spreadMembers = (ctx, spread, visited) => {
let argument = unwrapExpression(spread.argument);
return t5.isObjectExpression(argument) ? membersOf(argument, ctx, visited) : resolveReference(ctx, argument, spread.start ?? void 0, visited);
}, memberChain = (node) => {
let path = [], current = unwrapExpression(node);
for (; t5.isMemberExpression(current); ) {
let key = t5.isIdentifier(current.property) && !current.computed ? current.property.name : t5.isStringLiteral(current.property) ? current.property.value : void 0;
if (key === void 0)
return;
path.unshift(key), current = unwrapExpression(current.object);
}
return t5.isIdentifier(current) ? { root: current.name, path } : void 0;
}, resolveReference = (ctx, expression, position, visited) => {
let chain = memberChain(expression);
if (!chain)
return;
let key = `ref:${ctx.filePath}#${chain.root}.${chain.path.join(".")}`;
if (!visited.has(key)) {
visited.add(key);
try {
return unguardedResolveReference(ctx, chain, position, visited);
} finally {
visited.delete(key);
}
}
}, unguardedResolveReference = (ctx, chain, position, visited) => {
let located = locate(ctx, chain, position, visited);
if (!located)
return;
let members = located.members, scope = located.ctx;
for (let [index, key] of located.path.entries()) {
if (members.unresolved.length > 0)
return;
let value = members.properties[key];
if (value === void 0)
return index === located.path.length - 1 ? complete() : void 0;
let unwrapped = unwrapExpression(value);
if (!t5.isObjectExpression(unwrapped))
return;
members = membersOf(unwrapped, scope, visited);
}
return located.external ? externalized(members, scope) : members;
}, locate = (ctx, chain, position, visited) => {
let binding = bindingMembers(ctx, chain.root, position, visited);
if (!binding)
return;
if (binding.kind === "namespace") {
let [exportName, ...path] = chain.path;
if (exportName === void 0)
return;
let exported = bindingMembers(binding.ctx, exportName, void 0, visited);
if (exported === void 0 || exported.kind === "namespace")
return;
let located2 = withAccessor(exported, path);
return located2 === void 0 ? void 0 : { ...located2, external: !0 };
}
let located = withAccessor(binding, chain.path);
return located === void 0 ? void 0 : { ...located, external: binding.external };
}, withAccessor = (binding, path) => binding.accessor === void 0 ? { members: binding.members, ctx: binding.ctx, path } : path[0] === binding.accessor ? { members: binding.members, ctx: binding.ctx, path: path.slice(1) } : void 0, externalized = (members, ctx) => {
if (!ctx.externalize)
return members;
let properties = {};
for (let [key, node] of Object.entries(members.properties)) {
let value = ctx.externalize(node);
if (value === void 0)
return;
properties[key] = value;
}
return {
properties,
shadowed: members.shadowed,
unresolved: members.unresolved
};
}, bindingMembers = (ctx, name, position, visited) => {
let key = `${ctx.filePath}#${name}`;
if (!visited.has(key)) {
visited.add(key);
try {
return unguardedBindingMembers(ctx, name, position, visited);
} finally {
visited.delete(key);
}
}
}, unguardedBindingMembers = (ctx, name, position, visited) => {
let binding = ctx.program.scope.getBinding(name);
if (!binding)
return;
if (binding.kind === "module")
return importedBinding(ctx, binding.path, visited);
if (!binding.constant)
return;
let declared = declaredBindingMembers(ctx, binding.path.node, position, visited);
if (declared === void 0)
return;
let assigned = assignedMembers(ctx, name, position);
return {
kind: "members",
ctx,
external: !1,
...declared.accessor ? { accessor: declared.accessor } : {},
members: {
properties: { ...declared.members.properties, ...assigned.properties },
shadowed: declared.members.shadowed.filter((key) => !(key in assigned.properties)),
unresolved: [...declared.members.unresolved, ...assigned.unresolved]
}
};
}, declaredBindingMembers = (ctx, node, position, visited) => {
if (t5.isFunctionDeclaration(node))
return { members: complete() };
if (t5.isVariableDeclarator(node) && !(position !== void 0 && (node.start ?? Number.POSITIVE_INFINITY) > position))
return node.init ? declaredMembers(ctx, node.init, position, visited) : { members: complete() };
}, declaredMembers = (ctx, init, position, visited) => {
let unwrapped = unwrapExpression(init);
if (t5.isObjectExpression(unwrapped))
return { members: membersOf(unwrapped, ctx, visited) };
let factory = factoryCall(unwrapped);
if (factory === void 0 && (t5.isFunction(unwrapped) || isCanonicalCsf2BindCall(unwrapped)))
return { members: complete() };
if (factory === void 0)
return {
members: { properties: {}, shadowed: [], unresolved: [sourceOf(unwrapped)] }
};
let config = factory.config ? membersOf(factory.config, ctx, visited) : complete();
if (factory.method === "story")
return { members: config, accessor: "input" };
let parent = bindingMembers(ctx, factory.parent, position, visited);
return parent === void 0 || parent.kind === "namespace" || parent.accessor !== "input" || parent.external ? void 0 : {
members: {
properties: mergedAnnotations(parent.members, config).properties,
shadowed: [
...parent.members.shadowed.filter((key) => !(key in config.properties)),
...config.shadowed
],
unresolved: [...parent.members.unresolved, ...config.unresolved]
},
accessor: "input"
};
}, MERGED_ANNOTATIONS = ["args", "argTypes", "parameters", "globals"], mergedAnnotations = (parent, child) => {
let properties = { ...parent.properties, ...child.properties };
for (let key of MERGED_ANNOTATIONS) {
let from = parent.properties[key], over = child.properties[key];
from === void 0 || over === void 0 || !t5.isExpression(from) || !t5.isExpression(over) || (properties[key] = t5.objectExpression([t5.spreadElement(from), t5.spreadElement(over)]));
}
return { properties };
}, factoryCall = (node) => {
if (!isCsfFactoryCall(node))
return;
let method = node.callee.property.name;
if (method !== "story" && method !== "extend")
return;
let [argument] = node.arguments, config = argument && unwrapExpression(argument);
if (!(argument !== void 0 && (config === void 0 || !t5.isObjectExpression(config))))
return {
method,
parent: node.callee.object.name,
...config && t5.isObjectExpression(config) ? { config } : {}
};
}, assignedMembers = (ctx, name, position) => {
let properties = {}, unresolved = [];
for (let statement of ctx.program.node.body) {
if (!t5.isExpressionStatement(statement) || !t5.isAssignmentExpression(statement.expression))
continue;
let assignment = statement.expression, target = assignment.left, depth = 0, outermost;
for (; t5.isMemberExpression(target); )
depth += 1, outermost = t5.isIdentifier(target.property) && !target.computed ? target.property.name : t5.isStringLiteral(target.property) ? target.property.value : void 0, target = target.object;
if (!(depth === 0 || !t5.isIdentifier(target) || target.name !== name) && !(position !== void 0 && (assignment.start ?? 0) > position)) {
if (depth > 1 || outermost === void 0 || assignment.operator !== "=") {
unresolved.push(sourceOf(assignment));
continue;
}
properties[outermost] = assignment.right;
}
}
return { properties, unresolved };
}, importedBinding = (ctx, specifierPath, visited) => {
let specifier = specifierPath.node, declaration = specifierPath.parent;
if (!t5.isImportDeclaration(declaration) || declaration.importKind === "type" || !(t5.isImportSpecifier(specifier) || t5.isImportDefaultSpecifier(specifier) || t5.isImportNamespaceSpecifier(specifier)) || isTypeSpecifier(specifier))
return;
let target = resolveTargetModule(ctx, declaration.source.value);
if (!target)
return;
if (t5.isImportNamespaceSpecifier(specifier))
return { kind: "namespace", ctx: target };
let exportName = t5.isImportDefaultSpecifier(specifier) ? "default" : importedName(specifier.imported);
return exportedBinding(target, exportName, visited);
}, resolveTargetModule = (ctx, specifier) => {
let target = ctx.resolveModule?.(ctx.filePath, specifier);
return target ? { ...ctx, ...target } : void 0;
}, exportedBinding = (ctx, exportName, visited) => {
let asExternal = (bound) => bound === void 0 || bound.kind === "namespace" ? bound : { ...bound, external: !0 };
for (let statement of ctx.program.node.body) {
if (t5.isExportDefaultDeclaration(statement) && exportName === "default") {
let declaration = unwrapExpression(statement.declaration);
return t5.isIdentifier(declaration) ? asExternal(bindingMembers(ctx, declaration.name, void 0, visited)) : t5.isObjectExpression(declaration) ? {
kind: "members",
ctx,
external: !0,
members: membersOf(declaration, ctx, visited)
} : void 0;
}
if (!t5.isExportNamedDeclaration(statement) || statement.exportKind === "type")
continue;
let specifier = statement.specifiers.find(
(candidate) => t5.isExportSpecifier(candidate) && candidate.exportKind !== "type" && importedName(candidate.exported) === exportName
);
if (!specifier)
continue;
if (!statement.source)
return asExternal(bindingMembers(ctx, specifier.local.name, void 0, visited));
let target = resolveTargetModule(ctx, statement.source.value);
return target ? exportedBinding(target, specifier.local.name, visited) : void 0;
}
return exportName === "default" ? void 0 : asExternal(bindingMembers(ctx, exportName, void 0, visited));
};
// src/csf-tools/story-shape/resolve-arg-value.ts
import { traverse as traverse2, types as t6 } from "storybook/internal/babel";
var resolveArgValue = (node, ctx) => {
let unresolved = [], resolved = inlineSpreads(
followValue(unwrapExpression(node), ctx, /* @__PURE__ */ new Set()),
ctx,
unresolved
), bindings = importBindingsOf(ctx.program), imports = [];
for (let name of freeNames(resolved)) {
let imported = bindings.get(name);
if (imported) {
imports.push({
localImportName: name,
importId: imported.importId,
importName: imported.importName,
...imported.importName === "*" ? { namespace: name } : {}
});
continue;
}
ctx.program.scope.getBinding(name) && unresolved.push(name);
}
return { node: resolved, imports, unresolved };
}, isSelfContained = (node) => freeNames(node).size === 0, importBindings = /* @__PURE__ */ new WeakMap(), importBindingsOf = (program) => {
let bindings = importBindings.get(program.node);
return bindings === void 0 && (bindings = collectImportBindings(program), importBindings.set(program.node, bindings)), bindings;
}, inlineSpreads = (node, ctx, unresolved) => {
if (!hasNamedSpread(node))
return node;
if (t6.isArrayExpression(node))
return t6.arrayExpression(
node.elements.map(
(element) => element && t6.isExpression(element) ? inlineSpreads(element, ctx, unresolved) : element
)
);
if (!t6.isObjectExpression(node))
return node;
if (!node.properties.some((property) => t6.isSpreadElement(property)))
return objectFrom(node.properties, ctx, unresolved) ?? node;
let members = resolveArgsRecord(node, ctx);
return members.unresolved.length > 0 ? (unresolved.push(...members.unresolved), node) : objectFrom(
Object.entries(members.properties).map(
([key, value]) => t6.objectProperty(
t6.isValidIdentifier(key) ? t6.identifier(key) : t6.stringLiteral(key),
value
)
),
ctx,
unresolved
) ?? node;
}, hasNamedSpread = (node) => t6.isArrayExpression(node) ? node.elements.some(
(element) => element !== null && t6.isExpression(element) && hasNamedSpread(element)
) : t6.isObjectExpression(node) && node.properties.some(
(property) => t6.isSpreadElement(property) ? !t6.isObjectExpression(unwrapExpression(property.argument)) : t6.isObjectProperty(property) && t6.isExpression(property.value) && hasNamedSpread(property.value)
), objectFrom = (properties, ctx, unresolved) => {
let rebuilt = [];
for (let property of properties) {
if (!t6.isObjectProperty(property) || !t6.isExpression(property.value))
return;
let value = inlineSpreads(property.value, ctx, unresolved);
rebuilt.push(
t6.objectProperty(
property.key,
value,
property.computed,
property.shorthand && value === property.value
)
);
}
return t6.objectExpression(rebuilt);
}, followValue = (node, ctx, seen) => {
if (!t6.isIdentifier(node) || seen.has(node.name))
return node;
let binding = ctx.program.scope.getBinding(node.name);
return !binding || binding.kind === "module" || !binding.constant || !t6.isVariableDeclarator(binding.path.node) || !binding.path.node.init ? node : (seen.add(node.name), followValue(unwrapExpression(binding.path.node.init), ctx, seen));
}, freeNames = (node) => {
let expression = t6.isExpression(node) ? node : t6.isObjectMethod(node) ? t6.objectExpression([node]) : void 0;
if (expression === void 0)
throw new Error(`Cannot read the names a ${node.type} depends on: it is not an expression`);
let wrapped = t6.file(
t6.program([t6.expressionStatement(t6.cloneNode(expression, !0))])
), names = /* @__PURE__ */ new Set();
return traverse2(wrapped, {
ReferencedIdentifier(path) {
path.scope.hasBinding(path.node.name) || names.add(path.node.name);
}
}), names;
};
// src/csf-tools/story-shape/reference-context.ts
import { readFileSync } from "node:fs";
function createStoryReferenceResolver(options = {}) {
let resolver = createModuleResolver({
extensions: [...options.extensions ?? [], ...jsTsSourceExtensions],
mainFields: ["module", "main"],
tsconfig: "auto"
});
return function() {
let parsed = /* @__PURE__ */ new Map();
return {
externalize: options.externalize ?? selfContained,
resolveModule: (fromFile, specifier) => {
let filePath;
try {
filePath = resolver.resolveFileSync(fromFile, specifier);
} catch {
return;
}
return parsed.has(filePath) || parsed.set(filePath, parseReferenceModule(filePath)), parsed.get(filePath);
}
};
};
}
var parseReferenceModule = (filePath) => {
try {
let code = readFileSync(filePath, "utf8");
return { program: babelParseFile({ code, filename: filePath }).path, filePath };
} catch {
return;
}
}, selfContained = (node) => isSelfContained(node) ? node : void 0;
export {
Tag,
defaultResolveConditionNames,
createModuleResolver,
findVarInitialization,
unwrapExpression,
isCanonicalCsf2BindCall,
isCsfFactoryCall,
keyOf,
propertyValue,
returnedExpression,
returnedExpressionPath,
resolveReturnedObjectExpression,
resolveIdentifierInit,
pathForNode,
metaObjectPath,
isValidPreviewPath,
isModuleMock,
NoMetaError,
MultipleMetaError,
MixedFactoryError,
BadMetaError,
CsfFile,
babelParseFile,
loadCsf,
formatCsf,
printCsf,
readCsf,
writeCsf,
isTypeSpecifier,
importedName,
collectImportBindings,
sourceOf,
resolveObjectMembers,
resolveArgsRecord,
resolveBindingMembers,
resolveReferencedValue,
resolveArgValue,
isSelfContained,
createStoryReferenceResolver,
parseReferenceModule
};