UNPKG

storybook

Version:

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

1,026 lines (1,014 loc) • 74.1 kB
import CJS_COMPAT_NODE_URL_yr66iw5gef from 'node:url'; import CJS_COMPAT_NODE_PATH_yr66iw5gef from 'node:path'; import CJS_COMPAT_NODE_MODULE_yr66iw5gef from "node:module"; var __filename = CJS_COMPAT_NODE_URL_yr66iw5gef.fileURLToPath(import.meta.url); var __dirname = CJS_COMPAT_NODE_PATH_yr66iw5gef.dirname(__filename); var require = CJS_COMPAT_NODE_MODULE_yr66iw5gef.createRequire(import.meta.url); // ------------------------------------------------------------ // end of CJS compatibility banner, injected by Storybook's esbuild configuration // ------------------------------------------------------------ import { require_dist } from "./chunk-76KIYDRU.js"; import { __toESM } from "./chunk-J4VC4I2M.js"; // 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 t2, traverse } from "storybook/internal/babel"; import { isExportStory, storyNameFromExport, toId, toTestId } from "storybook/internal/csf"; import { logger } from "storybook/internal/node-logger"; // src/csf-tools/findVarInitialization.ts import { types as t } from "storybook/internal/babel"; var findVarInitialization = (identifier, program) => { let init = null, declarations = null; return program.body.find((node) => (t.isVariableDeclaration(node) ? declarations = node.declarations : t.isExportNamedDeclaration(node) && t.isVariableDeclaration(node.declaration) && (declarations = node.declaration.declarations), declarations && declarations.find((decl) => t.isVariableDeclarator(decl) && t.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 (t2.isArrayExpression(prop)) return prop.elements.map((e) => { if (t2.isStringLiteral(e)) return e.value; throw new Error(`Expected string literal: ${e}`); }); if (t2.isStringLiteral(prop)) return new RegExp(prop.value); if (t2.isRegExpLiteral(prop)) return new RegExp(prop.pattern, prop.flags); throw new Error(`Unknown include/exclude: ${prop}`); } function parseTags(prop) { if (!t2.isArrayExpression(prop)) throw new Error("CSF: Expected tags array"); return prop.elements.map((e) => { if (t2.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 (t2.isIdentifier(node) && (node = findVarInitialization(node.name, program)), t2.isObjectExpression(node)) { let tagsProp = node.properties.find( (property) => t2.isObjectProperty(property) && t2.isIdentifier(property.key) && property.key.name === "tags" ); if (tagsProp) { let tagsNode = tagsProp.value; return t2.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 (t2.isCallExpression(init)) { let { callee, arguments: bindArguments } = init; if (t2.isProgram(parent) && t2.isMemberExpression(callee) && t2.isIdentifier(callee.object) && t2.isIdentifier(callee.property) && callee.property.name === "bind" && (bindArguments.length === 0 || bindArguments.length === 1 && t2.isObjectExpression(bindArguments[0]) && bindArguments[0].properties.length === 0)) { let boundIdentifier = callee.object.name, template = findVarInitialization(boundIdentifier, parent); template && (csf._templates[boundIdentifier] = template, storyFn = template); } } return t2.isArrowFunctionExpression(storyFn) || t2.isFunctionDeclaration(storyFn) ? storyFn.params.length > 0 : !1; }, parseExportsOrder = (init) => { if (t2.isArrayExpression(init)) return init.elements.map((item) => { if (t2.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 (t2.isArrowFunctionExpression(play) || t2.isFunctionDeclaration(play) || t2.isObjectMethod(play)) { let params = play.params; if (params.length >= 1) { let [arg] = params; if (t2.isObjectPattern(arg)) return !!arg.properties.find((prop) => { if (t2.isObjectProperty(prop) && t2.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 = t2.isIdentifier(value) ? findVarInitialization(value.name, this._ast.program) : value; if (t2.isStringLiteral(node)) return node.value; if (t2.isTSSatisfiesExpression(node) && t2.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 (t2.isIdentifier(p.key)) { let value = t2.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 (t2.isIdentifier(n)) { let id = n.name, importStmt = program.body.find( (stmt) => t2.isImportDeclaration(stmt) && stmt.specifiers.find((spec) => spec.local.name === id) ); if (importStmt) { let { source } = importStmt, specifier = importStmt.specifiers.find((spec) => spec.local.name === id); t2.isStringLiteral(source) && specifier && (this._rawComponentPath = source.value, (t2.isImportSpecifier(specifier) || t2.isImportDefaultSpecifier(specifier)) && (this._componentImportSpecifier = specifier)); } } let { code } = recast.print(p.value, {}); meta.component = code; } else if (p.key.name === "tags") { let node = p.value; t2.isIdentifier(node) && (node = findVarInitialization(node.name, this._ast.program)), meta.tags = parseTags(node); } else if (p.key.name === "id") if (t2.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]; if (node = t2.isVariableDeclarator(node) ? node.init : node, t2.isCallExpression(node)) { let { callee, arguments: bindArguments } = node; if (t2.isMemberExpression(callee) && t2.isIdentifier(callee.object) && t2.isIdentifier(callee.property) && callee.property.name === "bind" && (bindArguments.length === 0 || bindArguments.length === 1 && t2.isObjectExpression(bindArguments[0]) && bindArguments[0].properties.length === 0)) { let { name } = callee.object; node = this._templates[name]; } } return node; } parse() { let self = this; if (traverse(this._ast, { ExportDefaultDeclaration: { enter(path) { let { node, parent } = path, isVariableReference = t2.isIdentifier(node.declaration) && t2.isProgram(parent); if (self._options.transformInlineMeta && !isVariableReference && t2.isExpression(node.declaration)) { let metaId = path.scope.generateUidIdentifier("meta"); self._metaVariableName = metaId.name; let nodes = [ t2.variableDeclaration("const", [t2.variableDeclarator(metaId, node.declaration)]), t2.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) => t2.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 (t2.isObjectExpression(decl) ? metaNode = decl : /* export default { ... } as Meta<...> */ /* export default { ... } satisfies Meta<...> */ (t2.isTSAsExpression(decl) || t2.isTSSatisfiesExpression(decl)) && t2.isObjectExpression(decl.expression) ? metaNode = decl.expression : ( // export default { ... } satisfies Meta as Meta<...> t2.isTSAsExpression(decl) && t2.isTSSatisfiesExpression(decl.expression) && t2.isObjectExpression(decl.expression.expression) && (metaNode = decl.expression.expression) ), metaNode && t2.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 (t2.isIdentifier(id)) { let storyIsFactory = !1, { name: exportName } = id; if (exportName === "__namedExportsOrder" && declPath.isVariableDeclarator()) { self._namedExportsOrder = parseExportsOrder(declPath.node.init); return; } 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 storyNode; if (t2.isVariableDeclarator(decl) ? t2.isTSAsExpression(decl.init) && t2.isTSSatisfiesExpression(decl.init.expression) ? storyNode = decl.init.expression.expression : t2.isTSAsExpression(decl.init) || t2.isTSSatisfiesExpression(decl.init) ? storyNode = decl.init.expression : storyNode = decl.init : storyNode = decl, t2.isCallExpression(storyNode) && t2.isMemberExpression(storyNode.callee) && t2.isIdentifier(storyNode.callee.property) && (storyNode.callee.property.name === "story" || storyNode.callee.property.name === "extend") && (storyIsFactory = !0, storyNode = storyNode.arguments[0]), self._metaIsFactory && !storyIsFactory) throw new MixedFactoryError( "expected factory story", storyNode, self._options.fileName ); 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 ); let parameters = {}; t2.isObjectExpression(storyNode) ? (parameters.__isArgsStory = !0, storyNode.properties.forEach((p) => { if (t2.isIdentifier(p.key)) { let key = p.key.name; if (t2.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" && t2.isStringLiteral(p.value)) name = p.value.value; else if (p.key.name === "storyName" && t2.isStringLiteral(p.value)) logger.warn( `Unexpected usage of "storyName" in "${exportName}". Please use "name" instead.` ); else if (p.key.name === "parameters" && t2.isObjectExpression(p.value)) { let idProperty = p.value.properties.find( (property) => t2.isObjectProperty(property) && t2.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 (t2.isExportSpecifier(specifier) && t2.isIdentifier(specifier.exported)) { let { name: exportName } = specifier.exported, { name: localName } = specifier.local, decl = t2.isProgram(parent) ? findVarInitialization(localName, parent) : specifier.local; if (exportName === "default") { let metaNode; t2.isObjectExpression(decl) ? metaNode = decl : /* export default { ... } as Meta<...> */ /* export default { ... } satisfies Meta<...> */ (t2.isTSAsExpression(decl) || t2.isTSSatisfiesExpression(decl)) && t2.isObjectExpression(decl.expression) ? metaNode = decl.expression : ( // export default { ... } satisfies Meta as Meta<...> t2.isTSAsExpression(decl) && t2.isTSSatisfiesExpression(decl.expression) && t2.isObjectExpression(decl.expression.expression) && (metaNode = decl.expression.expression) ), metaNode && t2.isProgram(parent) && self._parseMeta(metaNode, parent); } else { let annotations = {}, storyNode = decl; t2.isObjectExpression(storyNode) && storyNode.properties.forEach((p) => { t2.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 (t2.isProgram(parent) && t2.isAssignmentExpression(expression) && t2.isMemberExpression(expression.left) && t2.isIdentifier(expression.left.object) && t2.isIdentifier(expression.left.property)) { let exportName = expression.left.object.name, annotationKey = expression.left.property.name, annotationValue = expression.right; if (self._storyAnnotations[exportName] && (annotationKey === "story" && t2.isObjectExpression(annotationValue) ? annotationValue.properties.forEach((prop) => { t2.isIdentifier(prop.key) && (self._storyAnnotations[exportName][prop.key.name] = prop.value); }) : self._storyAnnotations[exportName][annotationKey] = annotationValue), annotationKey === "storyName" && t2.isStringLiteral(annotationValue)) { let storyName = annotationValue.value, story = self._stories[exportName]; if (!story) return; story.name = storyName; } } if (t2.isCallExpression(expression) && t2.isMemberExpression(expression.callee) && t2.isIdentifier(expression.callee.object) && t2.isIdentifier(expression.callee.property) && expression.callee.property.name === "test" && expression.arguments.length >= 2 && t2.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 (t2.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 (t2.isMemberExpression(callee) && t2.isIdentifier(callee.property) && callee.property.name === "meta" && t2.isIdentifier(callee.object) && node.arguments.length > 0) { let configParent = path.scope.getBinding(callee.object.name)?.path?.parentPath?.node; if (t2.isImportDeclaration(configParent)) if (isValidPreviewPath(configParent.source.value)) { self._metaIsFactory = !0; let metaDeclarator = path.findParent( (p) => p.isVariableDeclarator() ); self._metaVariableName = t2.isIdentifier(metaDeclarator.node.id) ? metaDeclarator.node.id.name : callee.property.name; let metaNode = node.arguments[0]; self._parseMeta(metaNode, self._ast.program); } else throw new BadMetaError( "meta() factory must be imported from .storybook/preview configuration", configParent, self._options.fileName ); } } }, ImportDeclaration: { enter({ node }) { let { source } = node; if (t2.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 || [], "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 = t2.isIdentifier(tags) ? findVarInitialization(tags.name, this._ast.program) : tags; acc[key].tags = parseTags(node); } play && (acc[key].tags = [...acc[key].tags || [], "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 = !!(t2.isArrowFunctionExpression(storyExport) || t2.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 = { importPath: fileName, 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 "!autodocs", ...test.tags, // this tag comes after test tags so users can't change it "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/ConfigFile.ts var import_ts_dedent2 = __toESM(require_dist(), 1); import { readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises"; import { babelParse as babelParse2, generate as generate2, recast as recast2, types as t3, traverse as traverse2 } from "storybook/internal/babel"; import { logger as logger2 } from "storybook/internal/node-logger"; var getCsfParsingErrorMessage = ({ expectedType, foundType, node }) => import_ts_dedent2.dedent` CSF Parsing error: Expected '${expectedType}' but found '${foundType}' instead in '${node?.type}'. `, propKey = (p) => t3.isIdentifier(p.key) ? p.key.name : t3.isStringLiteral(p.key) ? p.key.value : null, _getPath = (path, node) => { if (path.length === 0) return node; if (t3.isObjectExpression(node)) { let [first, ...rest] = path, field = node.properties.find((p) => propKey(p) === first); if (field) return _getPath(rest, field.value); } }, _getPathProperties = (path, node) => { if (path.length === 0) { if (t3.isObjectExpression(node)) return node.properties; throw new Error("Expected object expression"); } if (t3.isObjectExpression(node)) { let [first, ...rest] = path, field = node.properties.find((p) => propKey(p) === first); if (field) return rest.length === 0 ? node.properties : _getPathProperties(rest, field.value); } }, _findVarDeclarator = (identifier, program) => { let declarator = null, declarations = null; return program.body.find((node) => (t3.isVariableDeclaration(node) ? declarations = node.declarations : t3.isExportNamedDeclaration(node) && t3.isVariableDeclaration(node.declaration) && (declarations = node.declaration.declarations), declarations && declarations.find((decl) => t3.isVariableDeclarator(decl) && t3.isIdentifier(decl.id) && decl.id.name === identifier ? (declarator = decl, !0) : !1))), declarator; }, _findVarInitialization = (identifier, program) => _findVarDeclarator(identifier, program)?.init, _makeObjectExpression = (path, value) => { if (path.length === 0) return value; let [first, ...rest] = path, innerExpression = _makeObjectExpression(rest, value); return t3.objectExpression([t3.objectProperty(t3.identifier(first), innerExpression)]); }, _updateExportNode = (path, expr, existing) => { let [first, ...rest] = path, existingField = existing.properties.find( (p) => propKey(p) === first ); existingField ? t3.isObjectExpression(existingField.value) && rest.length > 0 ? _updateExportNode(rest, expr, existingField.value) : existingField.value = _makeObjectExpression(rest, expr) : existing.properties.push( t3.objectProperty(t3.identifier(first), _makeObjectExpression(rest, expr)) ); }, ConfigFile = class { constructor(ast, code, fileName) { this._exports = {}; // FIXME: this is a hack. this is only used in the case where the user is // modifying a named export that's a scalar. The _exports map is not suitable // for that. But rather than refactor the whole thing, we just use this as a stopgap. this._exportDecls = {}; this.hasDefaultExport = !1; /** Unwraps TS assertions/satisfies from a node, to get the underlying node. */ this._unwrap = (node) => t3.isTSAsExpression(node) || t3.isTSSatisfiesExpression(node) ? this._unwrap(node.expression) : node; /** * Resolve a declaration node by unwrapping TS assertions/satisfies and following identifiers to * resolve the correct node in case it's an identifier. */ this._resolveDeclaration = (node, parent = this._ast.program) => { let decl = this._unwrap(node); if (t3.isIdentifier(decl) && t3.isProgram(parent)) { let initialization = _findVarInitialization(decl.name, parent); return initialization ? this._unwrap(initialization) : decl; } return decl; }; this._ast = ast, this._code = code, this.fileName = fileName; } _parseExportsObject(exportsObject) { this._exportsObject = exportsObject, exportsObject.properties.forEach((p) => { let exportName = propKey(p); if (exportName) { let exportVal = this._resolveDeclaration(p.value); this._exports[exportName] = exportVal; } }); } parse() { let self = this; return traverse2(this._ast, { ExportDefaultDeclaration: { enter({ node, parent }) { self.hasDefaultExport = !0; let decl = self._resolveDeclaration(node.declaration, parent); t3.isCallExpression(decl) && t3.isObjectExpression(decl.arguments[0]) && (decl = decl.arguments[0]), t3.isObjectExpression(decl) ? self._parseExportsObject(decl) : logger2.warn( getCsfParsingErrorMessage({ expectedType: "ObjectExpression", foundType: decl?.type, node: decl || node.declaration }) ); } }, ExportNamedDeclaration: { enter({ node, parent }) { if (t3.isVariableDeclaration(node.declaration)) node.declaration.declarations.forEach((decl) => { if (t3.isVariableDeclarator(decl) && t3.isIdentifier(decl.id)) { let { name: exportName } = decl.id, exportVal = self._resolveDeclaration(decl.init, parent); self._exports[exportName] = exportVal, self._exportDecls[exportName] = decl; } }); else if (t3.isFunctionDeclaration(node.declaration)) { let decl = node.declaration; if (t3.isIdentifier(decl.id)) { let { name: exportName } = decl.id; self._exportDecls[exportName] = decl; } } else node.specifiers ? node.specifiers.forEach((spec) => { if (t3.isExportSpecifier(spec) && t3.isIdentifier(spec.local) && t3.isIdentifier(spec.exported)) { let { name: localName } = spec.local, { name: exportName } = spec.exported, decl = _findVarDeclarator(localName, parent); decl && (self._exports[exportName] = self._resolveDeclaration(decl.init, parent), self._exportDecls[exportName] = decl); } }) : logger2.warn( getCsfParsingErrorMessage({ expectedType: "VariableDeclaration", foundType: node.declaration?.type, node: node.declaration }) ); } }, ExpressionStatement: { enter({ node, parent }) { if (t3.isAssignmentExpression(node.expression) && node.expression.operator === "=") { let { left, right } = node.expression; if (t3.isMemberExpression(left) && t3.isIdentifier(left.object) && left.object.name === "module" && t3.isIdentifier(left.property) && left.property.name === "exports") { let exportObject = right; exportObject = self._resolveDeclaration(exportObject, parent), t3.isObjectExpression(exportObject) ? (self._exportsObject = exportObject, exportObject.properties.forEach((p) => { let exportName = propKey(p); if (exportName) { let exportVal = self._resolveDeclaration(p.value, parent); self._exports[exportName] = exportVal; } })) : logger2.warn( getCsfParsingErrorMessage({ expectedType: "ObjectExpression", foundType: exportObject?.type, node: exportObject }) ); } } } }, CallExpression: { enter: ({ node }) => { t3.isIdentifier(node.callee) && node.callee.name === "definePreview" && node.arguments.length === 1 && t3.isObjectExpression(node.arguments[0]) && self._parseExportsObject(node.arguments[0]); } } }), self; } getFieldNode(path) { let [root, ...rest] = path, exported = this._exports[root]; if (exported) return _getPath(rest, exported); } getFieldProperties(path) { let [root, ...rest] = path, exported = this._exports[root]; if (exported) return _getPathProperties(rest, exported); } getFieldValue(path) { let node = this.getFieldNode(path); if (node) { let { code } = generate2(node, {}); return (0, eval)(`(() => (${code}))()`); } } getSafeFieldValue(path) { try { return this.getFieldValue(path); } catch { } } setFieldNode(path, expr) { let [first, ...rest] = path, exportNode = this._exports[first]; if (this._exportsObject) { let existingProp = this._exportsObject.properties.find((p) => propKey(p) === first); if (existingProp && t3.isIdentifier(existingProp.value)) { let varDecl2 = _findVarDeclarator(existingProp.value.name, this._ast.program); if (varDecl2 && t3.isObjectExpression(varDecl2.init)) { _updateExportNode(rest, expr, varDecl2.init); return; } } _updateExportNode(path, expr, this._exportsObject), this._exports[path[0]] = expr; return; } if (exportNode && t3.isObjectExpression(exportNode) && rest.length > 0) { _updateExportNode(rest, expr, exportNode); return; } let varDecl = _findVarDeclarator(first, this._ast.program); if (varDecl && t3.isObjectExpression(varDecl.init)) { _updateExportNode(rest, expr, varDecl.init); return; } if (exportNode && rest.length === 0 && this._exportDecls[path[0]]) { let decl = this._exportDecls[path[0]]; t3.isVariableDeclarator(decl) && (decl.init = _makeObjectExpression([], expr)); } else { if (this.hasDefaultExport) throw new Error( `Could not set the "${path.join( "." )}" field as the default export is not an object in this file.` ); { let exportObj = _makeObjectExpression(rest, expr), newExport = t3.exportNamedDeclaration( t3.variableDeclaration("const", [t3.variableDeclarator(t3.identifier(first), exportObj)]) ); this._exports[first] = exportObj, this._ast.program.body.push(newExport); } } } /** * @example * * ```ts * // 1. { framework: 'framework-name' } * // 2. { framework: { name: 'framework-name', options: {} } * getNameFromPath(['framework']); // => 'framework-name' * ``` * * @returns The name of a node in a given path, supporting the following formats: */ getNameFromPath(path) { let node = this.getFieldNode(path); if (node) return this._getPresetValue(node, "name"); } /** * Returns an array of names of a node in a given path, supporting the following formats: * * @example * * ```ts * const config = { * addons: ['first-addon', { name: 'second-addon', options: {} }], * }; * // => ['first-addon', 'second-addon'] * getNamesFromPath(['addons']); * ``` */ getNamesFromPath(path) { let node = this.getFieldNode(path); if (!node) return; let pathNames = []; return t3.isArrayExpression(node) && node.elements.forEach((element) => { pathNames.push(this._getPresetValue(element, "name")); }), pathNames; } _getPnpWrappedValue(node) { if (t3.isCallExpression(node)) { let arg = node.arguments[0]; if (t3.isStringLiteral(arg)) return arg.value; } } /** * Given a node and a fallback property, returns a **non-evaluated** string value of the node. * * 1. `{ node: 'value' }` * 2. `{ node: { fallbackProperty: 'value' } }` */ _getPresetValue(node, fallbackProperty) { let value; if (t3.isStringLiteral(node) ? value = node.value : t3.isObjectExpression(node) ? node.properties.forEach((prop) => { t3.isObjectProperty(prop) && t3.isIdentifier(prop.key) && prop.key.name === fallbackProperty && (t3.isStringLiteral(prop.value) ? value = prop.value.value : value = this._getPnpWrappedValue(prop.value)), t3.isObjectProperty(prop) && t3.isStringLiteral(prop.key) && prop.key.value === "name" && t3.isStringLiteral(prop.value) && (value = prop.value.value); }) : t3.isCallExpression(node) && (value = this._getPnpWrappedValue(node)), !value) throw new Error( `The given node must be a string literal or an object expression with a "${fallbackProperty}" property that is a string literal.` ); return value; } removeField(path) { let removeProperty = (properties2, prop) => { let index = properties2.findIndex( (p) => t3.isIdentifier(p.key) && p.key.name === prop || t3.isStringLiteral(p.key) && p.key.value === prop ); index >= 0 && properties2.splice(index, 1); }; if (path.length === 1) { let removedRootProperty = !1; if (this._ast.program.body.forEach((node) => { if (t3.isExportNamedDeclaration(node) && t3.isVariableDeclaration(node.declaration)) { let decl = node.declaration.declarations[0]; t3.isIdentifier(decl.id) && decl.id.name === path[0] && (this._ast.program.body.splice(this._ast.program.body.indexOf(node), 1), removedRootProperty = !0); } if (t3.isExportDefaultDeclaration(node)) { let resolved = this._resolveDeclaration(node.declaration); if (t3.isObjectExpression(resolved)) { let properties2 = resolved.properties; removeProperty(properties2, path[0]), removedRootProperty = !0; } } if (t3.isExpressionStatement(node) && t3.isAssignmentExpression(node.expression) && t3.isMemberExpression(node.expression.left) && t3.isIdentifier(node.expression.left.object) && node.expression.left.object.name === "module" && t3.isIdentifier(node.expression.left.property) && node.expression.left.property.name === "exports" && t3.isObjectExpression(node.expression.right)) { let properties2 = node.expression.right.properties; removeProperty(properties2, path[0]), removedRootProperty = !0; } }), removedRootProperty) return; } let properties = this.getFieldProperties(path); if (properties) { let lastPath = path.at(-1); removeProperty(properties, lastPath); } } appendValueToArray(path, value) { let node = this.valueToNode(value); node && this.appendNodeToArray(path, node); } appendNodeToArray(path, node) { let current = this.getFieldNode(path); if (!current) this.setFieldNode(path, t3.arrayExpression([node])); else if (t3.isArrayExpression(current)) current.elements.push(node); else throw new Error(`Expected array at '${path.join(".")}', got '${current.type}'`); } /** * Specialized helper to remove addons or other array entries that can either be strings or * objects with a name property. */ removeEntryFromArray(path, value) { let current = this.getFieldNode(path); if (current) if (t3.isArrayExpression(current)) { let index = current.elements.findIndex((element) => t3.isStringLiteral(element) ? element.value === value : t3.isObjectExpression(element) ? this._getPresetValue(element, "name") === value : this._getPnpWrappedValue(element) === value); if (index >= 0) current.elements.splice(index, 1); else throw new Error(`Could not find '${value}' in array at '${path.join(".")}'`); } else throw new Error(`Expected array at '${path.join(".")}', got '${current.type}'`); } _inferQuotes() { if (!this._quotes) { let occurrences = (this._ast.tokens || []).slice(0, 500).reduce( (acc, token) => (token.type.label === "string" && (acc[this._code[token.start]] += 1), acc), { "'": 0, '"': 0 } ); this._quotes = occurrences["'"] > occurrences['"'] ? "single" : "double"; } return this._quotes; } valueToNode(value) { let quotes = this._inferQuotes(), valueNode; if (quotes === "single") { let { code } = generate2(t3.valueToNode(value), { jsescOption: { quotes } }), program = babelParse2(`const __x = ${code}`); traverse2(program, { VariableDeclaration: { enter({ node }) { node.declarations.length === 1 && t3.isVariableDeclarator(node.declarations[0]) && t3.isIdentifier(node.declarations[0].id) && node.declarations[0].id.name === "__x" && (valueNode = node.declarations[0].init); } } }); } else valueNode = t3.valueToNode(value); return valueNode; } setFieldValue(path, value) { let valueNode = this.valueToNode(value); if (!valueNode) throw new Error(`Unexpected value ${JSON.stringify(value)}`); this.setFieldNode(path, valueNode); } getBodyDeclarations() { return this._ast.program.body; } setBodyDeclaration(declaration) { this._ast.program.body.push(declaration); } /** * Import specifiers for a specific require import * * @example * * ```ts * // const { foo } = require('bar'); * setRequireImport(['foo'], 'bar'); * * // const foo = require('bar'); * setRequireImport('foo', 'bar'); * ``` * * @param importSpecifiers - The import specifiers to set. If a string is passed in, a default * import will be set. Otherwise, an array of named imports will be set * @param fromImport - The module to import from */ setRequireImport(importSpecifier, fromImport) { let requireDeclaration = this._ast.program.body.find((node) => { let hasDeclaration = t3.isVariableDeclaration(node) && node.declarations.length === 1 && t3.isVariableDeclarator(node.declarations[0]) && t3.isCallExpression(node.declarations[0].init) && t3.isIdentifier(node.declarations[0].init.callee) && node.declarations[0].init.callee.name === "require" && t3.isStringLiteral(node.declarations[0].init.arguments[0]) && (node.declarations[0].init.arguments[0].value === fromImport || node.declarations[0].init.arguments[0].value === fromImport.split("node:")[1]); return hasDeclaration && (fromImport = node.declarations[0].init.arguments[0].value), hasDeclaration; }), hasRequireSpecifier = (name) => t3.isObjectPattern(requireDeclaration?.declarations[0].id) && requireDeclaration?.declarations[0].id.properties.find( (specifier) => t3.isObjectProperty(specifier) && t3.isIdentifier(specifier.key) && specifier.key.name === name ), hasDefaultRequireSpecifier = (declaration, name) => declaration.declarations.length === 1 && t3.isVariableDeclarator(declaration.declarations[0]) && t3.isIdentifier(declaration.declarations[0].id) && declaration.declarations[0].id.name === name; if (typeof importSpecifier == "string") { let addDefaultRequireSpecifier = () => { this._ast.program.body.unshift( t3.variableDeclaration("const", [ t3.variableDeclarator( t3.identifier(importSpecifier), t3.callExpression(t3.identifier("require"), [t3.stringLiteral(fromImport)]) ) ]) ); }; requireDeclaration && hasDefaultRequireSpecifier(requireDeclaration, importSpecifier) || addDefaultRequireSpecifier(); } else requireDeclaration ? importSpecifier.forEach((specifier) => { hasRequireSpecifier(specifier) || requireDeclaration.declarations[0].id.properties.push( t3.objectProperty(t3.identifier(specifier), t3.identifier(specifier), void 0, !0) ); }) : this._ast.program.body.unshift( t3.variableDeclaration("const", [ t3.variableDeclarator( t3.objectPattern( importSpecifier.map( (specifier) => t3.objectProperty(t3.identifier(specifier), t3.identifier(specifier), void 0, !0) ) ), t3.callExpression(t3.identifier("require"), [t3.stringLiteral(fromImport)]) ) ]) ); } /** * Set import specifiers for a given import statement. * * Does not support setting type imports (yet) * * @example * * ```ts * // import { foo } from 'bar'; * setImport(['foo'], 'bar'); * * // import foo from 'bar'; * setImport('foo', 'bar'); * * // import * as foo from 'bar'; * setImport({ namespace: 'foo' }, 'bar'); * * // import 'bar'; * setImport(null, 'bar'); * ``` * * @param importSpecifiers - The import specifiers to set. If a string is passed in, a default * import will be set. Otherwise, an array of named imports will be set * @param fromImport - The module to import from */ setImport(importSpecifier, fromImport) { let importDeclaration = this._ast.program.body.find((node) => { let hasDeclaration = t3.isImportDeclaration(node) && (node.source.value === fromImport || node.source.value === fromImport.split("node:")[1]); return hasDeclaration && (fromImport = node.source.value), hasDeclaration; }), getNewImportSpecifier = (specifier) => t3.importSpecifier(t3.identifier(specifier), t3.identifier(specifier)), hasImportSpecifier = (declaration, name) => declaration.specifiers.find( (specifier) => t3.isImportSpecifier(specifier) && t3.isIdentifier(specifier.imported) && specifier.imported.name === name ), hasNamespaceImportSpecifier = (declaration, name) => declaration.specifiers.find( (specifier) => t3.isImportNamespaceSpecifier(specifier) && t3.isIdentifier(specifier.local) && specifier.local.name === name ); importSpecifier === null ? importDeclaration || this._ast.program.body.unshift(t3.importDeclaration([], t3.stringLiteral(fromImport))) : typeof importSpecifier == "string" ? importDeclaration ? ((declaration, name) => declaration.specifiers.find( (specifier) => t3.isImportDefault