storybook-addon-dependency-previews 0.5.1 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/setup/patchers/preview.cjs +2 -6
- package/dist/cli/setup/patchers/preview.cjs.map +1 -1
- package/dist/cli/setup/patchers/preview.mjs +2 -6
- package/dist/cli/setup/patchers/preview.mjs.map +1 -1
- package/dist/components/PrimaryPreview.cjs +4 -1
- package/dist/components/PrimaryPreview.cjs.map +1 -1
- package/dist/components/PrimaryPreview.mjs +4 -1
- package/dist/components/PrimaryPreview.mjs.map +1 -1
- package/dist/hooks/useDynamicStory.cjs +33 -5
- package/dist/hooks/useDynamicStory.cjs.map +1 -1
- package/dist/hooks/useDynamicStory.mjs +33 -5
- package/dist/hooks/useDynamicStory.mjs.map +1 -1
- package/dist/types.d.cts +1 -1
- package/dist/types.d.mts +1 -1
- package/package.json +1 -1
|
@@ -6,11 +6,7 @@ let node_path = require("node:path");
|
|
|
6
6
|
node_path = require_rolldown_runtime.__toESM(node_path);
|
|
7
7
|
|
|
8
8
|
//#region cli/setup/patchers/preview.ts
|
|
9
|
-
const
|
|
10
|
-
"react-vite": "/src/**/*.stories.@(tsx|ts|jsx|js)",
|
|
11
|
-
sveltekit: "/src/**/*.stories.@(ts|js|svelte)",
|
|
12
|
-
"svelte-vite": "/src/**/*.stories.@(ts|js|svelte)"
|
|
13
|
-
};
|
|
9
|
+
const STORY_GLOB = "/src/**/*.stories.{tsx,ts,jsx,js,svelte}";
|
|
14
10
|
function dependencyPreviewsBlock(framework, sourceRootUrl, indent = " ", eol = "\n", quote = "'") {
|
|
15
11
|
const l2 = indent.repeat(2);
|
|
16
12
|
const l3 = indent.repeat(3);
|
|
@@ -21,7 +17,7 @@ function dependencyPreviewsBlock(framework, sourceRootUrl, indent = " ", eol = "
|
|
|
21
17
|
`${l3}dependenciesJson,`,
|
|
22
18
|
`${l3}projectRootPath: new URL(${quote}..${quote}, import.meta.url).pathname,`,
|
|
23
19
|
`${l3}storyModules: import.meta.glob(`,
|
|
24
|
-
`${l4}${quote}${
|
|
20
|
+
`${l4}${quote}${STORY_GLOB}${quote},`,
|
|
25
21
|
`${l4}{ eager: false },`,
|
|
26
22
|
`${l3}),`,
|
|
27
23
|
`${l3}sourceRootUrl: ${serialisedSourceRootUrl},`,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preview.cjs","names":["STORY_GLOBS: Record<SupportedFramework, string>","content: string","stripCommentsRespectingStrings","detectFileIndent","detectEol","detectQuoteStyle","existingEntries: Array<Entry>","mergedEntries: Array<Entry>","importsToInsert: string[]","findMatchingBrace","paramsCreatedEndOffset: number | null","findTopLevelKey","style: TemplateStyle"],"sources":["../../../../cli/setup/patchers/preview.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\n\nimport type { Framework, MainFile, PreviewFile } from '../detect.js'\nimport {\n\tdetectEol,\n\tdetectFileIndent,\n\tdetectQuoteStyle,\n\tfindMatchingBrace,\n\tfindTopLevelKey,\n\tstripCommentsRespectingStrings,\n} from '../util.js'\n\nexport type PreviewPatchResult =\n\t| { kind: 'created'; path: string }\n\t| { kind: 'skipped'; reason: string }\n\t| { kind: 'patched'; path: string }\n\t| { kind: 'failed'; reason: string }\n\ntype SupportedFramework = 'react-vite' | 'sveltekit' | 'svelte-vite'\n\nconst STORY_GLOBS: Record<SupportedFramework, string> = {\n\t'react-vite': '/src/**/*.stories.@(tsx|ts|jsx|js)',\n\tsveltekit: '/src/**/*.stories.@(ts|js|svelte)',\n\t'svelte-vite': '/src/**/*.stories.@(ts|js|svelte)',\n}\n\nfunction dependencyPreviewsBlock(\n\tframework: SupportedFramework,\n\tsourceRootUrl: string,\n\tindent: string = '\\t',\n\teol: string = '\\n',\n\tquote: \"'\" | '\"' = \"'\",\n): string {\n\tconst l2 = indent.repeat(2)\n\tconst l3 = indent.repeat(3)\n\tconst l4 = indent.repeat(4)\n\t// `sourceRootUrl` is required by `DependencyPreviewStorybookParameters`, so\n\t// always emit it — even an empty string is better than a TS error.\n\t// JSON.stringify always emits double quotes; convert when the file uses single\n\t// quotes so the inserted line matches surrounding style. URLs in practice contain\n\t// no characters that need re-escaping, but we still escape backslashes / single\n\t// quotes / line terminators for safety.\n\tconst serialisedSourceRootUrl =\n\t\tquote === '\"'\n\t\t\t? JSON.stringify(sourceRootUrl)\n\t\t\t: `'${sourceRootUrl\n\t\t\t\t\t.replace(/\\\\/g, '\\\\\\\\')\n\t\t\t\t\t.replace(/'/g, \"\\\\'\")\n\t\t\t\t\t.replace(/\\n/g, '\\\\n')\n\t\t\t\t\t.replace(/\\r/g, '\\\\r')}'`\n\tconst lines = [\n\t\t`${l2}dependencyPreviews: {`,\n\t\t`${l3}dependenciesJson,`,\n\t\t`${l3}projectRootPath: new URL(${quote}..${quote}, import.meta.url).pathname,`,\n\t\t`${l3}storyModules: import.meta.glob(`,\n\t\t`${l4}${quote}${STORY_GLOBS[framework]}${quote},`,\n\t\t`${l4}{ eager: false },`,\n\t\t`${l3}),`,\n\t\t`${l3}sourceRootUrl: ${serialisedSourceRootUrl},`,\n\t\t`${l2}},`,\n\t]\n\treturn lines.join(eol)\n}\n\ntype TemplateStyle = { indent: string; eol: string }\n\nfunction buildTemplate(\n\tframework: SupportedFramework,\n\tsourceRootUrl: string,\n\tstyle: TemplateStyle,\n\tisTs: boolean,\n): string {\n\tconst { indent, eol } = style\n\tconst l1 = indent\n\tconst l2 = indent.repeat(2)\n\tconst lines = [\n\t\t// `/// <reference types=\"vite/client\" />` is a TypeScript-only directive;\n\t\t// Vite-flavoured JS preview files don't need it.\n\t\t...(isTs ? [`/// <reference types=\"vite/client\" />`, ``] : []),\n\t\t`import {`,\n\t\t`${l1}defaultPreviewParameters,`,\n\t\t// The `type StorybookPreviewConfig` import is meaningless in JS — drop it.\n\t\t`${l1}dependencyPreviewDecorators,${isTs ? `${eol}${l1}type StorybookPreviewConfig,` : ''}`,\n\t\t`} from 'storybook-addon-dependency-previews'`,\n\t\t``,\n\t\t`import dependenciesJson from './dependency-previews.json'`,\n\t\t``,\n\t\t// Same: skip the type annotation in JS.\n\t\tisTs\n\t\t\t? `const previewConfig: StorybookPreviewConfig = {`\n\t\t\t: `const previewConfig = {`,\n\t\t`${l1}parameters: {`,\n\t\t`${l2}...defaultPreviewParameters,`,\n\t\tdependencyPreviewsBlock(framework, sourceRootUrl, indent, eol),\n\t\t`${l1}},`,\n\t\t`${l1}decorators: [...dependencyPreviewDecorators],`,\n\t\t`}`,\n\t\t``,\n\t\t`export default previewConfig`,\n\t\t``,\n\t]\n\treturn lines.join(eol)\n}\n\n/**\n * Map the detected `.storybook/main.{ts,js,mjs,cjs}` extension to the matching\n * preview-file extension. The `PreviewFile['lang']` type only models the four\n * extensions Storybook actually loads as a preview module — `mjs`/`cjs` mains\n * still imply a `js` preview, so they collapse to `'js'` here.\n */\nfunction previewLangForMainLang(\n\tmainLang: MainFile['lang'],\n): PreviewFile['lang'] {\n\treturn mainLang === 'ts' ? 'ts' : 'js'\n}\n\nfunction templateForFramework(\n\tframework: SupportedFramework,\n\tsourceRootUrl: string,\n\tstyle: TemplateStyle,\n\tmainLang: MainFile['lang'],\n): { content: string; lang: PreviewFile['lang'] } {\n\tconst lang = previewLangForMainLang(mainLang)\n\treturn {\n\t\tcontent: buildTemplate(framework, sourceRootUrl, style, lang === 'ts'),\n\t\tlang,\n\t}\n}\n\nfunction findImportInsertionIndex(content: string): number {\n\tconst lines = content.split('\\n')\n\tlet idx = 0\n\tlet inBlockComment = false\n\n\tfor (let i = 0; i < lines.length; i++) {\n\t\tconst rawLine = lines[i]!\n\t\tconst trimmed = rawLine.trim()\n\t\tconst advance = rawLine.length + 1 // +1 for the consumed '\\n'\n\n\t\tif (inBlockComment) {\n\t\t\tidx += advance\n\t\t\tif (rawLine.includes('*/')) inBlockComment = false\n\t\t\tcontinue\n\t\t}\n\n\t\tif (\n\t\t\ttrimmed === '' ||\n\t\t\ttrimmed.startsWith('//') || // line comment & triple-slash directive\n\t\t\ttrimmed.startsWith(\"'use client'\") ||\n\t\t\ttrimmed.startsWith('\"use client\"')\n\t\t) {\n\t\t\tidx += advance\n\t\t\tcontinue\n\t\t}\n\n\t\tif (trimmed.startsWith('/*')) {\n\t\t\tidx += advance\n\t\t\t// Multi-line block comment if no `*/` later on the same line.\n\t\t\tif (!trimmed.slice(2).includes('*/')) inBlockComment = true\n\t\t\tcontinue\n\t\t}\n\n\t\tbreak\n\t}\n\treturn idx\n}\n\nfunction patchExistingPreview(\n\tpreviewFile: PreviewFile,\n\tframework: SupportedFramework,\n\tsourceRootUrl: string,\n): PreviewPatchResult {\n\tlet content: string\n\ttry {\n\t\tcontent = readFileSync(previewFile.path, 'utf8')\n\t} catch (e) {\n\t\treturn {\n\t\t\tkind: 'failed',\n\t\t\treason: `Could not read ${previewFile.path}: ${(e as Error).message}`,\n\t\t}\n\t}\n\n\t// Strip comments before doing identifier checks so commented-out example code\n\t// can't false-positive the idempotency / CJS guards. (Strings and template\n\t// literals are left intact so a URL containing `//` doesn't get truncated.)\n\tconst codeOnly = stripCommentsRespectingStrings(content)\n\n\t// `dependencyPreviews:` is the unique parameters key the wizard injects, so its\n\t// presence means the addon is already wired in. Other markers like the bare\n\t// `dependencyPreviewDecorators` identifier are too lenient — they'd false-positive\n\t// on `import { dependencyPreviewDecorators as dpd } …` where the name appears in\n\t// the import declaration but isn't actually used in any decorators array yet.\n\tif (/\\bdependencyPreviews\\s*:/.test(codeOnly)) {\n\t\treturn { kind: 'skipped', reason: 'addon already configured in preview' }\n\t}\n\n\tif (/\\bmodule\\.exports\\s*=/.test(codeOnly)) {\n\t\treturn {\n\t\t\tkind: 'failed',\n\t\t\treason:\n\t\t\t\t'Preview file uses CommonJS (module.exports). The wizard only patches ESM preview files — please convert to ESM or follow the manual setup docs.',\n\t\t}\n\t}\n\n\tconst isTs = previewFile.lang === 'ts' || previewFile.lang === 'tsx'\n\tconst indent = detectFileIndent(content)\n\tconst eol = detectEol(content)\n\tconst quote = detectQuoteStyle(content)\n\tconst l1 = indent\n\tconst l2 = indent.repeat(2)\n\n\tlet newContent = content\n\n\t// ─── Imports: collect all imports from the addon package, merge them into one,\n\t// and either replace the first / delete the rest, or insert a new one if none exist.\n\tconst PKG = 'storybook-addon-dependency-previews'\n\t// Match the whole import statement including any trailing semicolon and the\n\t// terminating newline, so that deletions of additional imports don't leave\n\t// stray `;` lines behind in semicolon-using projects.\n\tconst ADDON_IMPORT_REGEX = new RegExp(\n\t\tString.raw`^[\\t ]*import\\s*(type\\s+)?\\{([\\s\\S]*?)\\}\\s*from\\s*['\"]storybook-addon-dependency-previews['\"]\\s*;?[ \\t]*(?:\\r?\\n|$)`,\n\t\t'gm',\n\t)\n\tconst allAddonImports = [...newContent.matchAll(ADDON_IMPORT_REGEX)]\n\t// Preserve the project's semicolon style on the merged import.\n\tconst usesSemicolons =\n\t\t/from\\s*['\"][^'\"]*['\"]\\s*;/.test(newContent) ||\n\t\tallAddonImports.some((m) => /;\\s*(?:\\r?\\n|$)/.test(m[0]!))\n\tconst trailingSemi = usesSemicolons ? ';' : ''\n\t// Use the comment-stripped content so a commented-out `// import dependenciesJson …`\n\t// doesn't trick us into skipping the real import (which would leave the inserted\n\t// `dependencyPreviews` block referencing an undefined identifier).\n\tconst hasDependenciesJsonImport =\n\t\t/import\\s+dependenciesJson\\s+from\\s*['\"]\\.\\/dependency-previews\\.json['\"]/.test(\n\t\t\tcodeOnly,\n\t\t)\n\n\tconst requiredValueNames = [\n\t\t'defaultPreviewParameters',\n\t\t'dependencyPreviewDecorators',\n\t]\n\tconst requiredTypeNames = isTs ? ['StorybookPreviewConfig'] : []\n\n\t// The local binding names for the addon's value imports — usually identical\n\t// to the original names, but if the user has aliased an import (e.g.\n\t// `import { defaultPreviewParameters as dp } from '…'`) then the local name\n\t// is the alias and that's what later spreads need to reference.\n\tlet defaultsLocalName = 'defaultPreviewParameters'\n\tlet decoratorsLocalName = 'dependencyPreviewDecorators'\n\n\tif (allAddonImports.length > 0) {\n\t\ttype Entry = { name: string; alias?: string; isType: boolean }\n\t\tconst parseEntry = (raw: string, wasTypeOnly: boolean): Entry => {\n\t\t\t// `import type { A, B }` makes every name a type, so respect that.\n\t\t\tconst isType = wasTypeOnly || /^type\\s+/.test(raw)\n\t\t\tconst stripped = raw.replace(/^type\\s+/, '')\n\t\t\tconst [name, alias] = stripped.split(/\\s+as\\s+/).map((s) => s.trim())\n\t\t\treturn { name: name!, alias, isType }\n\t\t}\n\t\tconst formatEntry = (e: Entry): string => {\n\t\t\tconst inner = e.alias ? `${e.name} as ${e.alias}` : e.name\n\t\t\treturn e.isType ? `type ${inner}` : inner\n\t\t}\n\n\t\t// Collect every named import from every `from 'storybook-addon-dependency-previews'`\n\t\t// statement in the file. Stripping comments inside `{ … }` first so that\n\t\t// `import { foo, /* note */ bar }` doesn't produce `/* note */ bar` as a name.\n\t\tconst existingEntries: Array<Entry> = []\n\t\tfor (const m of allAddonImports) {\n\t\t\tconst wasTypeOnly = !!m[1]\n\t\t\tconst importContents = m[2]!\n\t\t\t\t.replace(/\\/\\*[\\s\\S]*?\\*\\//g, '')\n\t\t\t\t.replace(/\\/\\/.*$/gm, '')\n\t\t\tfor (const raw of importContents\n\t\t\t\t.split(',')\n\t\t\t\t.map((s) => s.trim())\n\t\t\t\t.filter(Boolean)) {\n\t\t\t\texistingEntries.push(parseEntry(raw, wasTypeOnly))\n\t\t\t}\n\t\t}\n\n\t\t// Deduplicate by name. Value-imports beat type-imports if both exist for the\n\t\t// same name (you can use a value at runtime AND in type positions, but not\n\t\t// vice-versa).\n\t\tconst byName = new Map<string, Entry>()\n\t\tfor (const e of existingEntries) {\n\t\t\tconst prev = byName.get(e.name)\n\t\t\tif (!prev || (prev.isType && !e.isType)) byName.set(e.name, e)\n\t\t}\n\t\tconst dedupedExisting = Array.from(byName.values())\n\n\t\tconst requiredValueSet = new Set(requiredValueNames)\n\n\t\t// Promote any existing entry whose name matches a required value to a value\n\t\t// import. This handles e.g. `import type { defaultPreviewParameters } from ...` —\n\t\t// without promotion we'd leave it as a type and the runtime spread would fail.\n\t\tconst mergedEntries: Array<Entry> = dedupedExisting.map((e) =>\n\t\t\trequiredValueSet.has(e.name) && e.isType ? { ...e, isType: false } : e,\n\t\t)\n\t\tconst handled = new Set(mergedEntries.map((e) => e.name))\n\t\tfor (const n of requiredValueNames) {\n\t\t\tif (!handled.has(n)) {\n\t\t\t\tmergedEntries.push({ name: n, isType: false })\n\t\t\t\thandled.add(n)\n\t\t\t}\n\t\t}\n\t\tfor (const n of requiredTypeNames) {\n\t\t\tif (!handled.has(n)) {\n\t\t\t\tmergedEntries.push({ name: n, isType: true })\n\t\t\t\thandled.add(n)\n\t\t\t}\n\t\t}\n\n\t\tconst isMultipleImports = allAddonImports.length > 1\n\t\tconst noPromotionNeeded = dedupedExisting.every(\n\t\t\t(e) => mergedEntries.find((m) => m.name === e.name)?.isType === e.isType,\n\t\t)\n\t\tconst allRequiredAlreadyValueImported = requiredValueNames.every((n) =>\n\t\t\tdedupedExisting.some((e) => e.name === n && !e.isType),\n\t\t)\n\t\tconst allRequiredAlreadyTypeImported = requiredTypeNames.every((n) =>\n\t\t\tdedupedExisting.some((e) => e.name === n && e.isType),\n\t\t)\n\t\t// Skip the rewrite only when there's a single import AND nothing about it\n\t\t// needs to change. With multiple imports we always merge to avoid duplicate\n\t\t// identifier bindings between them.\n\t\tconst nothingToDo =\n\t\t\t!isMultipleImports &&\n\t\t\tnoPromotionNeeded &&\n\t\t\tallRequiredAlreadyValueImported &&\n\t\t\tallRequiredAlreadyTypeImported\n\n\t\tif (!nothingToDo) {\n\t\t\t// The regex consumes the trailing newline, so include one in the\n\t\t\t// replacement; also tack on the project's semicolon style.\n\t\t\tconst replacement = `import {${eol}${mergedEntries\n\t\t\t\t.map((e) => `${indent}${formatEntry(e)},`)\n\t\t\t\t.join(eol)}${eol}} from ${quote}${PKG}${quote}${trailingSemi}${eol}`\n\t\t\t// Replace the first import with the merged version; delete the rest.\n\t\t\tlet firstReplaced = false\n\t\t\tnewContent = newContent.replace(ADDON_IMPORT_REGEX, () => {\n\t\t\t\tif (!firstReplaced) {\n\t\t\t\t\tfirstReplaced = true\n\t\t\t\t\treturn replacement\n\t\t\t\t}\n\t\t\t\treturn ''\n\t\t\t})\n\t\t\t// Tidy up any blank-line runs left behind by deleted imports.\n\t\t\tnewContent = newContent.replace(/(\\r?\\n){3,}/g, `${eol}${eol}`)\n\t\t}\n\n\t\t// Resolve the local binding names — if the user aliased an import we need\n\t\t// to reference the alias in the inserted spreads, not the original name.\n\t\tconst findLocalName = (name: string): string => {\n\t\t\tconst entry = mergedEntries.find((m) => m.name === name && !m.isType)\n\t\t\treturn entry?.alias ?? name\n\t\t}\n\t\tdefaultsLocalName = findLocalName('defaultPreviewParameters')\n\t\tdecoratorsLocalName = findLocalName('dependencyPreviewDecorators')\n\t}\n\n\tconst importsToInsert: string[] = []\n\tif (allAddonImports.length === 0) {\n\t\tconst addonImportBlock = [\n\t\t\t`import {`,\n\t\t\t`${indent}defaultPreviewParameters,`,\n\t\t\t`${indent}dependencyPreviewDecorators,${isTs ? `${eol}${indent}type StorybookPreviewConfig,` : ''}`,\n\t\t\t`} from ${quote}${PKG}${quote}${trailingSemi}`,\n\t\t].join(eol)\n\t\timportsToInsert.push(addonImportBlock)\n\t}\n\tif (!hasDependenciesJsonImport) {\n\t\timportsToInsert.push(\n\t\t\t`import dependenciesJson from ${quote}./dependency-previews.json${quote}${trailingSemi}`,\n\t\t)\n\t}\n\tif (importsToInsert.length > 0) {\n\t\tconst insertAt = findImportInsertionIndex(newContent)\n\t\tconst insertion = importsToInsert.join(eol + eol) + eol + eol\n\t\tnewContent =\n\t\t\tnewContent.slice(0, insertAt) + insertion + newContent.slice(insertAt)\n\t}\n\n\tconst block = dependencyPreviewsBlock(\n\t\tframework,\n\t\tsourceRootUrl,\n\t\tindent,\n\t\teol,\n\t\tquote,\n\t)\n\n\t/**\n\t * Locate the preview config object's body so all the key lookups below\n\t * are scoped to *that* object. Without this, a `parameters:` /\n\t * `decorators:` belonging to some unrelated object earlier in the file\n\t * would be matched instead of the one we want to patch.\n\t *\n\t * The opener regex runs against the position-preserving comment-stripped\n\t * text so an example like `// const preview: Preview = { ... }` in a\n\t * comment can't hijack the search — `match.index` from the stripped\n\t * content lines up with the original.\n\t */\n\tconst findPreviewBody = (\n\t\ttext: string,\n\t): { from: number; to: number } | null => {\n\t\tconst stripped = stripCommentsRespectingStrings(text)\n\t\tconst bodyAt = (\n\t\t\tmatch: RegExpMatchArray | null,\n\t\t): { from: number; to: number } | null => {\n\t\t\tif (!match || match.index === undefined) return null\n\t\t\tconst openBraceIdx = match.index + match[0].length - 1\n\t\t\tif (text[openBraceIdx] !== '{') return null\n\t\t\tconst closeIdx = findMatchingBrace(text, openBraceIdx)\n\t\t\tif (closeIdx === null) return null\n\t\t\treturn { from: openBraceIdx + 1, to: closeIdx }\n\t\t}\n\n\t\t// Direct patterns: typed preview (`Preview = {`, `StorybookPreviewConfig = {`)\n\t\t// or anonymous default export (`export default {`).\n\t\tconst direct = bodyAt(\n\t\t\tstripped.match(\n\t\t\t\t/(StorybookPreviewConfig\\s*=\\s*\\{|Preview\\s*=\\s*\\{|export\\s+default\\s*\\{)/,\n\t\t\t),\n\t\t)\n\t\tif (direct) return direct\n\n\t\t// Fallback: untyped `const preview = { … }; export default preview` (common\n\t\t// in `.js` / `.jsx` preview files where there's no type annotation).\n\t\t// Resolve `export default <ident>` to its `(const|let|var) <ident> = {…}`\n\t\t// declaration and use that object's body.\n\t\tconst exportIdent = stripped.match(\n\t\t\t/export\\s+default\\s+([A-Za-z_$][\\w$]*)\\b/,\n\t\t)?.[1]\n\t\tif (!exportIdent) return null\n\t\tconst escaped = exportIdent.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n\t\treturn bodyAt(\n\t\t\tstripped.match(new RegExp(`(?:const|let|var)\\\\s+${escaped}\\\\s*=\\\\s*\\\\{`)),\n\t\t)\n\t}\n\n\tconst bodyRange = findPreviewBody(newContent)\n\tif (!bodyRange) {\n\t\treturn {\n\t\t\tkind: 'failed',\n\t\t\treason:\n\t\t\t\t'Could not locate the preview config object — please add the dependencyPreviews parameters and decorators manually.',\n\t\t}\n\t}\n\n\t// If we end up creating a brand-new `parameters:` key, remember the position\n\t// right after it so a brand-new `decorators:` insertion can land *after* it\n\t// rather than at the same body-start position.\n\tlet paramsCreatedEndOffset: number | null = null\n\n\tconst paramsKey = findTopLevelKey(newContent, 'parameters', bodyRange)\n\tif (paramsKey && newContent[paramsKey.valueStart] === '{') {\n\t\t// Check for an existing `...<local>` spread, but only inside the parameters\n\t\t// object itself (and only in real code, not comments or strings). A match\n\t\t// anywhere else in the file would falsely suppress the spread we need to\n\t\t// insert. `<local>` is the project's local binding for `defaultPreviewParameters`,\n\t\t// which differs from the canonical name when the user has aliased the import.\n\t\tconst paramsBodyEnd = findMatchingBrace(newContent, paramsKey.valueStart)\n\t\tconst paramsBodyStart = paramsKey.valueStart + 1\n\t\tconst localSpreadRegex = new RegExp(\n\t\t\tString.raw`\\.\\.\\.${defaultsLocalName.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}\\b`,\n\t\t)\n\t\tconst hasDefaultParamsSpread =\n\t\t\tparamsBodyEnd !== null &&\n\t\t\tlocalSpreadRegex.test(\n\t\t\t\tstripCommentsRespectingStrings(\n\t\t\t\t\tnewContent.slice(paramsBodyStart, paramsBodyEnd),\n\t\t\t\t),\n\t\t\t)\n\t\tconst insertAt = paramsBodyStart\n\t\tconst insertion = hasDefaultParamsSpread\n\t\t\t? `${eol}${block}`\n\t\t\t: `${eol}${l2}...${defaultsLocalName},${eol}${block}`\n\t\tnewContent =\n\t\t\tnewContent.slice(0, insertAt) + insertion + newContent.slice(insertAt)\n\t} else if (paramsKey) {\n\t\t// `parameters:` exists but isn't a literal `{ … }` object (probably a\n\t\t// variable, spread, or function call). Inserting another `parameters:`\n\t\t// would produce a duplicate key — bail with guidance instead.\n\t\treturn {\n\t\t\tkind: 'failed',\n\t\t\treason:\n\t\t\t\t'Preview config already defines `parameters` in a non-literal-object form — please manually add `...defaultPreviewParameters` and the `dependencyPreviews` block to the existing parameters definition.',\n\t\t}\n\t} else {\n\t\tconst insertAt = bodyRange.from\n\t\tconst insertion = `${eol}${l1}parameters: {${eol}${l2}...${defaultsLocalName},${eol}${block}${eol}${l1}},`\n\t\tnewContent =\n\t\t\tnewContent.slice(0, insertAt) + insertion + newContent.slice(insertAt)\n\t\t// Track where the just-inserted parameters block ends, so a subsequent\n\t\t// \"create new decorators\" can be placed *after* it (the bodyRange.from\n\t\t// position is shared with parameters; without this offset, decorators\n\t\t// would be inserted at body-start and end up *before* parameters).\n\t\tparamsCreatedEndOffset = insertAt + insertion.length\n\t}\n\n\t// Re-find the body range after the params insertion — the body's closing\n\t// brace position has shifted but the helper handles that transparently.\n\tconst bodyRangeAfterParams = findPreviewBody(newContent) ?? bodyRange\n\tconst decoratorsKey = findTopLevelKey(\n\t\tnewContent,\n\t\t'decorators',\n\t\tbodyRangeAfterParams,\n\t)\n\tif (decoratorsKey && newContent[decoratorsKey.valueStart] === '[') {\n\t\t// Mirror the parameters-spread idempotency: only insert the spread if the\n\t\t// decorators array doesn't already contain `...<localName>`. Catches the\n\t\t// \"user partially set up by hand\" case (the wizard may have just added\n\t\t// `dependencyPreviews:` parameters and the user might already have wired\n\t\t// the decorators).\n\t\tconst decoratorsBodyEnd = findMatchingBrace(\n\t\t\tnewContent,\n\t\t\tdecoratorsKey.valueStart,\n\t\t)\n\t\tconst decoratorsBodyStart = decoratorsKey.valueStart + 1\n\t\tconst localDecoratorsSpreadRegex = new RegExp(\n\t\t\tString.raw`\\.\\.\\.${decoratorsLocalName.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}\\b`,\n\t\t)\n\t\tconst hasDecoratorsSpread =\n\t\t\tdecoratorsBodyEnd !== null &&\n\t\t\tlocalDecoratorsSpreadRegex.test(\n\t\t\t\tstripCommentsRespectingStrings(\n\t\t\t\t\tnewContent.slice(decoratorsBodyStart, decoratorsBodyEnd),\n\t\t\t\t),\n\t\t\t)\n\t\tif (!hasDecoratorsSpread) {\n\t\t\tconst insertAt = decoratorsBodyStart\n\t\t\tconst insertion = `${eol}${l2}...${decoratorsLocalName},`\n\t\t\tnewContent =\n\t\t\t\tnewContent.slice(0, insertAt) + insertion + newContent.slice(insertAt)\n\t\t}\n\t} else if (decoratorsKey) {\n\t\treturn {\n\t\t\tkind: 'failed',\n\t\t\treason:\n\t\t\t\t'Preview config already defines `decorators` in a non-literal-array form — please manually add `...dependencyPreviewDecorators` to the existing decorators definition.',\n\t\t}\n\t} else {\n\t\t// If we just inserted a brand-new `parameters:` key in this same run,\n\t\t// position the new `decorators:` *after* it. Otherwise place it at the\n\t\t// start of the preview config body.\n\t\tconst insertAt = paramsCreatedEndOffset ?? bodyRangeAfterParams.from\n\t\tconst insertion = `${eol}${l1}decorators: [...${decoratorsLocalName}],`\n\t\tnewContent =\n\t\t\tnewContent.slice(0, insertAt) + insertion + newContent.slice(insertAt)\n\t}\n\n\ttry {\n\t\twriteFileSync(previewFile.path, newContent, 'utf8')\n\t} catch (e) {\n\t\treturn {\n\t\t\tkind: 'failed',\n\t\t\treason: `Could not write ${previewFile.path}: ${(e as Error).message}`,\n\t\t}\n\t}\n\treturn { kind: 'patched', path: previewFile.path }\n}\n\nexport interface PatchPreviewFileOptions {\n\t/** Absolute path to the project's `.storybook/` directory. */\n\tstorybookDir: string\n\t/**\n\t * Existing preview file detected in `.storybook/` (any of preview.ts /\n\t * .tsx / .js / .jsx), or `null` if no preview file exists yet — in which\n\t * case the patcher creates a fresh one from the framework template.\n\t */\n\tpreviewFile: PreviewFile | null\n\t/**\n\t * Detected `.storybook/main.{ts,js,…}`. Used to derive the indent and EOL\n\t * style for any newly-created preview file so it matches the project's\n\t * existing code style.\n\t */\n\tmainFile: MainFile\n\t/**\n\t * Detected (or user-confirmed) Storybook framework. Determines the story\n\t * glob pattern injected into `dependencyPreviews.storyModules` and which\n\t * framework template is used when creating a new preview file.\n\t */\n\tframework: Framework\n\t/**\n\t * URL pointing at the project's source-code root (e.g. a GitHub blob\n\t * URL). Powers the addon's \"view source\" links in the Storybook UI. May\n\t * be empty — the addon's runtime treats `''` as \"no source links\" and\n\t * the field is required by the addon's parameter type so we always emit\n\t * it.\n\t */\n\tsourceRootUrl: string\n}\n\n/**\n * Patch (or create) the project's `.storybook/preview.{ts,tsx,js,jsx}` so\n * the addon's parameters and decorators are wired in. Idempotent —\n * re-runs against an already-configured preview return `{ kind: 'skipped' }`.\n */\nexport function patchPreviewFile(\n\topts: PatchPreviewFileOptions,\n): PreviewPatchResult {\n\tconst { storybookDir, previewFile, mainFile, framework, sourceRootUrl } = opts\n\n\tif (\n\t\tframework !== 'react-vite' &&\n\t\tframework !== 'sveltekit' &&\n\t\tframework !== 'svelte-vite'\n\t) {\n\t\treturn {\n\t\t\tkind: 'failed',\n\t\t\treason: `Preview patcher does not support framework \"${framework}\".`,\n\t\t}\n\t}\n\n\tif (previewFile) {\n\t\treturn patchExistingPreview(previewFile, framework, sourceRootUrl)\n\t}\n\n\t// Derive indent and EOL from the existing main.{ts,js,...} so the brand-new\n\t// preview file matches the project's style instead of always using tabs / LF.\n\tlet style: TemplateStyle = { indent: '\\t', eol: '\\n' }\n\ttry {\n\t\tconst mainContent = readFileSync(mainFile.path, 'utf8')\n\t\tstyle = {\n\t\t\tindent: detectFileIndent(mainContent),\n\t\t\teol: detectEol(mainContent),\n\t\t}\n\t} catch {\n\t\t// If main.ts can't be read for some reason, fall back to the defaults.\n\t}\n\n\tconst { content, lang } = templateForFramework(\n\t\tframework,\n\t\tsourceRootUrl,\n\t\tstyle,\n\t\tmainFile.lang,\n\t)\n\tconst path = resolve(storybookDir, `preview.${lang}`)\n\tif (existsSync(path)) {\n\t\treturn { kind: 'skipped', reason: `${path} already exists` }\n\t}\n\ttry {\n\t\twriteFileSync(path, content, 'utf8')\n\t} catch (e) {\n\t\treturn {\n\t\t\tkind: 'failed',\n\t\t\treason: `Could not create ${path}: ${(e as Error).message}`,\n\t\t}\n\t}\n\treturn { kind: 'created', path }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"preview.cjs","names":["content: string","stripCommentsRespectingStrings","detectFileIndent","detectEol","detectQuoteStyle","existingEntries: Array<Entry>","mergedEntries: Array<Entry>","importsToInsert: string[]","findMatchingBrace","paramsCreatedEndOffset: number | null","findTopLevelKey","style: TemplateStyle"],"sources":["../../../../cli/setup/patchers/preview.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\n\nimport type { Framework, MainFile, PreviewFile } from '../detect.js'\nimport {\n\tdetectEol,\n\tdetectFileIndent,\n\tdetectQuoteStyle,\n\tfindMatchingBrace,\n\tfindTopLevelKey,\n\tstripCommentsRespectingStrings,\n} from '../util.js'\n\nexport type PreviewPatchResult =\n\t| { kind: 'created'; path: string }\n\t| { kind: 'skipped'; reason: string }\n\t| { kind: 'patched'; path: string }\n\t| { kind: 'failed'; reason: string }\n\ntype SupportedFramework = 'react-vite' | 'sveltekit' | 'svelte-vite'\n\n// Brace expansion `{a,b,c}` works under both Vite 7 (micromatch) and Vite 8\n// (tinyglobby). Extglob `@(a|b|c)` silently returns zero matches under tinyglobby,\n// which was the root cause of every wizard-generated preview.ts on Vite 8 showing\n// \"No story module found at this path: …\" for every story.\n//\n// Every currently-supported framework uses the same union extension list, so this is\n// a single const rather than a per-framework record. `dependencyPreviewsBlock` still\n// takes `framework` so future per-framework divergence (a glob a framework should\n// strictly exclude, say) is a one-line change here, not a signature shuffle.\nconst STORY_GLOB = '/src/**/*.stories.{tsx,ts,jsx,js,svelte}'\n\nfunction dependencyPreviewsBlock(\n\tframework: SupportedFramework,\n\tsourceRootUrl: string,\n\tindent: string = '\\t',\n\teol: string = '\\n',\n\tquote: \"'\" | '\"' = \"'\",\n): string {\n\tconst l2 = indent.repeat(2)\n\tconst l3 = indent.repeat(3)\n\tconst l4 = indent.repeat(4)\n\t// `sourceRootUrl` is required by `DependencyPreviewStorybookParameters`, so\n\t// always emit it — even an empty string is better than a TS error.\n\t// JSON.stringify always emits double quotes; convert when the file uses single\n\t// quotes so the inserted line matches surrounding style. URLs in practice contain\n\t// no characters that need re-escaping, but we still escape backslashes / single\n\t// quotes / line terminators for safety.\n\tconst serialisedSourceRootUrl =\n\t\tquote === '\"'\n\t\t\t? JSON.stringify(sourceRootUrl)\n\t\t\t: `'${sourceRootUrl\n\t\t\t\t\t.replace(/\\\\/g, '\\\\\\\\')\n\t\t\t\t\t.replace(/'/g, \"\\\\'\")\n\t\t\t\t\t.replace(/\\n/g, '\\\\n')\n\t\t\t\t\t.replace(/\\r/g, '\\\\r')}'`\n\tconst lines = [\n\t\t`${l2}dependencyPreviews: {`,\n\t\t`${l3}dependenciesJson,`,\n\t\t`${l3}projectRootPath: new URL(${quote}..${quote}, import.meta.url).pathname,`,\n\t\t`${l3}storyModules: import.meta.glob(`,\n\t\t`${l4}${quote}${STORY_GLOB}${quote},`,\n\t\t`${l4}{ eager: false },`,\n\t\t`${l3}),`,\n\t\t`${l3}sourceRootUrl: ${serialisedSourceRootUrl},`,\n\t\t`${l2}},`,\n\t]\n\treturn lines.join(eol)\n}\n\ntype TemplateStyle = { indent: string; eol: string }\n\nfunction buildTemplate(\n\tframework: SupportedFramework,\n\tsourceRootUrl: string,\n\tstyle: TemplateStyle,\n\tisTs: boolean,\n): string {\n\tconst { indent, eol } = style\n\tconst l1 = indent\n\tconst l2 = indent.repeat(2)\n\tconst lines = [\n\t\t// `/// <reference types=\"vite/client\" />` is a TypeScript-only directive;\n\t\t// Vite-flavoured JS preview files don't need it.\n\t\t...(isTs ? [`/// <reference types=\"vite/client\" />`, ``] : []),\n\t\t`import {`,\n\t\t`${l1}defaultPreviewParameters,`,\n\t\t// The `type StorybookPreviewConfig` import is meaningless in JS — drop it.\n\t\t`${l1}dependencyPreviewDecorators,${isTs ? `${eol}${l1}type StorybookPreviewConfig,` : ''}`,\n\t\t`} from 'storybook-addon-dependency-previews'`,\n\t\t``,\n\t\t`import dependenciesJson from './dependency-previews.json'`,\n\t\t``,\n\t\t// Same: skip the type annotation in JS.\n\t\tisTs\n\t\t\t? `const previewConfig: StorybookPreviewConfig = {`\n\t\t\t: `const previewConfig = {`,\n\t\t`${l1}parameters: {`,\n\t\t`${l2}...defaultPreviewParameters,`,\n\t\tdependencyPreviewsBlock(framework, sourceRootUrl, indent, eol),\n\t\t`${l1}},`,\n\t\t`${l1}decorators: [...dependencyPreviewDecorators],`,\n\t\t`}`,\n\t\t``,\n\t\t`export default previewConfig`,\n\t\t``,\n\t]\n\treturn lines.join(eol)\n}\n\n/**\n * Map the detected `.storybook/main.{ts,js,mjs,cjs}` extension to the matching\n * preview-file extension. The `PreviewFile['lang']` type only models the four\n * extensions Storybook actually loads as a preview module — `mjs`/`cjs` mains\n * still imply a `js` preview, so they collapse to `'js'` here.\n */\nfunction previewLangForMainLang(\n\tmainLang: MainFile['lang'],\n): PreviewFile['lang'] {\n\treturn mainLang === 'ts' ? 'ts' : 'js'\n}\n\nfunction templateForFramework(\n\tframework: SupportedFramework,\n\tsourceRootUrl: string,\n\tstyle: TemplateStyle,\n\tmainLang: MainFile['lang'],\n): { content: string; lang: PreviewFile['lang'] } {\n\tconst lang = previewLangForMainLang(mainLang)\n\treturn {\n\t\tcontent: buildTemplate(framework, sourceRootUrl, style, lang === 'ts'),\n\t\tlang,\n\t}\n}\n\nfunction findImportInsertionIndex(content: string): number {\n\tconst lines = content.split('\\n')\n\tlet idx = 0\n\tlet inBlockComment = false\n\n\tfor (let i = 0; i < lines.length; i++) {\n\t\tconst rawLine = lines[i]!\n\t\tconst trimmed = rawLine.trim()\n\t\tconst advance = rawLine.length + 1 // +1 for the consumed '\\n'\n\n\t\tif (inBlockComment) {\n\t\t\tidx += advance\n\t\t\tif (rawLine.includes('*/')) inBlockComment = false\n\t\t\tcontinue\n\t\t}\n\n\t\tif (\n\t\t\ttrimmed === '' ||\n\t\t\ttrimmed.startsWith('//') || // line comment & triple-slash directive\n\t\t\ttrimmed.startsWith(\"'use client'\") ||\n\t\t\ttrimmed.startsWith('\"use client\"')\n\t\t) {\n\t\t\tidx += advance\n\t\t\tcontinue\n\t\t}\n\n\t\tif (trimmed.startsWith('/*')) {\n\t\t\tidx += advance\n\t\t\t// Multi-line block comment if no `*/` later on the same line.\n\t\t\tif (!trimmed.slice(2).includes('*/')) inBlockComment = true\n\t\t\tcontinue\n\t\t}\n\n\t\tbreak\n\t}\n\treturn idx\n}\n\nfunction patchExistingPreview(\n\tpreviewFile: PreviewFile,\n\tframework: SupportedFramework,\n\tsourceRootUrl: string,\n): PreviewPatchResult {\n\tlet content: string\n\ttry {\n\t\tcontent = readFileSync(previewFile.path, 'utf8')\n\t} catch (e) {\n\t\treturn {\n\t\t\tkind: 'failed',\n\t\t\treason: `Could not read ${previewFile.path}: ${(e as Error).message}`,\n\t\t}\n\t}\n\n\t// Strip comments before doing identifier checks so commented-out example code\n\t// can't false-positive the idempotency / CJS guards. (Strings and template\n\t// literals are left intact so a URL containing `//` doesn't get truncated.)\n\tconst codeOnly = stripCommentsRespectingStrings(content)\n\n\t// `dependencyPreviews:` is the unique parameters key the wizard injects, so its\n\t// presence means the addon is already wired in. Other markers like the bare\n\t// `dependencyPreviewDecorators` identifier are too lenient — they'd false-positive\n\t// on `import { dependencyPreviewDecorators as dpd } …` where the name appears in\n\t// the import declaration but isn't actually used in any decorators array yet.\n\tif (/\\bdependencyPreviews\\s*:/.test(codeOnly)) {\n\t\treturn { kind: 'skipped', reason: 'addon already configured in preview' }\n\t}\n\n\tif (/\\bmodule\\.exports\\s*=/.test(codeOnly)) {\n\t\treturn {\n\t\t\tkind: 'failed',\n\t\t\treason:\n\t\t\t\t'Preview file uses CommonJS (module.exports). The wizard only patches ESM preview files — please convert to ESM or follow the manual setup docs.',\n\t\t}\n\t}\n\n\tconst isTs = previewFile.lang === 'ts' || previewFile.lang === 'tsx'\n\tconst indent = detectFileIndent(content)\n\tconst eol = detectEol(content)\n\tconst quote = detectQuoteStyle(content)\n\tconst l1 = indent\n\tconst l2 = indent.repeat(2)\n\n\tlet newContent = content\n\n\t// ─── Imports: collect all imports from the addon package, merge them into one,\n\t// and either replace the first / delete the rest, or insert a new one if none exist.\n\tconst PKG = 'storybook-addon-dependency-previews'\n\t// Match the whole import statement including any trailing semicolon and the\n\t// terminating newline, so that deletions of additional imports don't leave\n\t// stray `;` lines behind in semicolon-using projects.\n\tconst ADDON_IMPORT_REGEX = new RegExp(\n\t\tString.raw`^[\\t ]*import\\s*(type\\s+)?\\{([\\s\\S]*?)\\}\\s*from\\s*['\"]storybook-addon-dependency-previews['\"]\\s*;?[ \\t]*(?:\\r?\\n|$)`,\n\t\t'gm',\n\t)\n\tconst allAddonImports = [...newContent.matchAll(ADDON_IMPORT_REGEX)]\n\t// Preserve the project's semicolon style on the merged import.\n\tconst usesSemicolons =\n\t\t/from\\s*['\"][^'\"]*['\"]\\s*;/.test(newContent) ||\n\t\tallAddonImports.some((m) => /;\\s*(?:\\r?\\n|$)/.test(m[0]!))\n\tconst trailingSemi = usesSemicolons ? ';' : ''\n\t// Use the comment-stripped content so a commented-out `// import dependenciesJson …`\n\t// doesn't trick us into skipping the real import (which would leave the inserted\n\t// `dependencyPreviews` block referencing an undefined identifier).\n\tconst hasDependenciesJsonImport =\n\t\t/import\\s+dependenciesJson\\s+from\\s*['\"]\\.\\/dependency-previews\\.json['\"]/.test(\n\t\t\tcodeOnly,\n\t\t)\n\n\tconst requiredValueNames = [\n\t\t'defaultPreviewParameters',\n\t\t'dependencyPreviewDecorators',\n\t]\n\tconst requiredTypeNames = isTs ? ['StorybookPreviewConfig'] : []\n\n\t// The local binding names for the addon's value imports — usually identical\n\t// to the original names, but if the user has aliased an import (e.g.\n\t// `import { defaultPreviewParameters as dp } from '…'`) then the local name\n\t// is the alias and that's what later spreads need to reference.\n\tlet defaultsLocalName = 'defaultPreviewParameters'\n\tlet decoratorsLocalName = 'dependencyPreviewDecorators'\n\n\tif (allAddonImports.length > 0) {\n\t\ttype Entry = { name: string; alias?: string; isType: boolean }\n\t\tconst parseEntry = (raw: string, wasTypeOnly: boolean): Entry => {\n\t\t\t// `import type { A, B }` makes every name a type, so respect that.\n\t\t\tconst isType = wasTypeOnly || /^type\\s+/.test(raw)\n\t\t\tconst stripped = raw.replace(/^type\\s+/, '')\n\t\t\tconst [name, alias] = stripped.split(/\\s+as\\s+/).map((s) => s.trim())\n\t\t\treturn { name: name!, alias, isType }\n\t\t}\n\t\tconst formatEntry = (e: Entry): string => {\n\t\t\tconst inner = e.alias ? `${e.name} as ${e.alias}` : e.name\n\t\t\treturn e.isType ? `type ${inner}` : inner\n\t\t}\n\n\t\t// Collect every named import from every `from 'storybook-addon-dependency-previews'`\n\t\t// statement in the file. Stripping comments inside `{ … }` first so that\n\t\t// `import { foo, /* note */ bar }` doesn't produce `/* note */ bar` as a name.\n\t\tconst existingEntries: Array<Entry> = []\n\t\tfor (const m of allAddonImports) {\n\t\t\tconst wasTypeOnly = !!m[1]\n\t\t\tconst importContents = m[2]!\n\t\t\t\t.replace(/\\/\\*[\\s\\S]*?\\*\\//g, '')\n\t\t\t\t.replace(/\\/\\/.*$/gm, '')\n\t\t\tfor (const raw of importContents\n\t\t\t\t.split(',')\n\t\t\t\t.map((s) => s.trim())\n\t\t\t\t.filter(Boolean)) {\n\t\t\t\texistingEntries.push(parseEntry(raw, wasTypeOnly))\n\t\t\t}\n\t\t}\n\n\t\t// Deduplicate by name. Value-imports beat type-imports if both exist for the\n\t\t// same name (you can use a value at runtime AND in type positions, but not\n\t\t// vice-versa).\n\t\tconst byName = new Map<string, Entry>()\n\t\tfor (const e of existingEntries) {\n\t\t\tconst prev = byName.get(e.name)\n\t\t\tif (!prev || (prev.isType && !e.isType)) byName.set(e.name, e)\n\t\t}\n\t\tconst dedupedExisting = Array.from(byName.values())\n\n\t\tconst requiredValueSet = new Set(requiredValueNames)\n\n\t\t// Promote any existing entry whose name matches a required value to a value\n\t\t// import. This handles e.g. `import type { defaultPreviewParameters } from ...` —\n\t\t// without promotion we'd leave it as a type and the runtime spread would fail.\n\t\tconst mergedEntries: Array<Entry> = dedupedExisting.map((e) =>\n\t\t\trequiredValueSet.has(e.name) && e.isType ? { ...e, isType: false } : e,\n\t\t)\n\t\tconst handled = new Set(mergedEntries.map((e) => e.name))\n\t\tfor (const n of requiredValueNames) {\n\t\t\tif (!handled.has(n)) {\n\t\t\t\tmergedEntries.push({ name: n, isType: false })\n\t\t\t\thandled.add(n)\n\t\t\t}\n\t\t}\n\t\tfor (const n of requiredTypeNames) {\n\t\t\tif (!handled.has(n)) {\n\t\t\t\tmergedEntries.push({ name: n, isType: true })\n\t\t\t\thandled.add(n)\n\t\t\t}\n\t\t}\n\n\t\tconst isMultipleImports = allAddonImports.length > 1\n\t\tconst noPromotionNeeded = dedupedExisting.every(\n\t\t\t(e) => mergedEntries.find((m) => m.name === e.name)?.isType === e.isType,\n\t\t)\n\t\tconst allRequiredAlreadyValueImported = requiredValueNames.every((n) =>\n\t\t\tdedupedExisting.some((e) => e.name === n && !e.isType),\n\t\t)\n\t\tconst allRequiredAlreadyTypeImported = requiredTypeNames.every((n) =>\n\t\t\tdedupedExisting.some((e) => e.name === n && e.isType),\n\t\t)\n\t\t// Skip the rewrite only when there's a single import AND nothing about it\n\t\t// needs to change. With multiple imports we always merge to avoid duplicate\n\t\t// identifier bindings between them.\n\t\tconst nothingToDo =\n\t\t\t!isMultipleImports &&\n\t\t\tnoPromotionNeeded &&\n\t\t\tallRequiredAlreadyValueImported &&\n\t\t\tallRequiredAlreadyTypeImported\n\n\t\tif (!nothingToDo) {\n\t\t\t// The regex consumes the trailing newline, so include one in the\n\t\t\t// replacement; also tack on the project's semicolon style.\n\t\t\tconst replacement = `import {${eol}${mergedEntries\n\t\t\t\t.map((e) => `${indent}${formatEntry(e)},`)\n\t\t\t\t.join(eol)}${eol}} from ${quote}${PKG}${quote}${trailingSemi}${eol}`\n\t\t\t// Replace the first import with the merged version; delete the rest.\n\t\t\tlet firstReplaced = false\n\t\t\tnewContent = newContent.replace(ADDON_IMPORT_REGEX, () => {\n\t\t\t\tif (!firstReplaced) {\n\t\t\t\t\tfirstReplaced = true\n\t\t\t\t\treturn replacement\n\t\t\t\t}\n\t\t\t\treturn ''\n\t\t\t})\n\t\t\t// Tidy up any blank-line runs left behind by deleted imports.\n\t\t\tnewContent = newContent.replace(/(\\r?\\n){3,}/g, `${eol}${eol}`)\n\t\t}\n\n\t\t// Resolve the local binding names — if the user aliased an import we need\n\t\t// to reference the alias in the inserted spreads, not the original name.\n\t\tconst findLocalName = (name: string): string => {\n\t\t\tconst entry = mergedEntries.find((m) => m.name === name && !m.isType)\n\t\t\treturn entry?.alias ?? name\n\t\t}\n\t\tdefaultsLocalName = findLocalName('defaultPreviewParameters')\n\t\tdecoratorsLocalName = findLocalName('dependencyPreviewDecorators')\n\t}\n\n\tconst importsToInsert: string[] = []\n\tif (allAddonImports.length === 0) {\n\t\tconst addonImportBlock = [\n\t\t\t`import {`,\n\t\t\t`${indent}defaultPreviewParameters,`,\n\t\t\t`${indent}dependencyPreviewDecorators,${isTs ? `${eol}${indent}type StorybookPreviewConfig,` : ''}`,\n\t\t\t`} from ${quote}${PKG}${quote}${trailingSemi}`,\n\t\t].join(eol)\n\t\timportsToInsert.push(addonImportBlock)\n\t}\n\tif (!hasDependenciesJsonImport) {\n\t\timportsToInsert.push(\n\t\t\t`import dependenciesJson from ${quote}./dependency-previews.json${quote}${trailingSemi}`,\n\t\t)\n\t}\n\tif (importsToInsert.length > 0) {\n\t\tconst insertAt = findImportInsertionIndex(newContent)\n\t\tconst insertion = importsToInsert.join(eol + eol) + eol + eol\n\t\tnewContent =\n\t\t\tnewContent.slice(0, insertAt) + insertion + newContent.slice(insertAt)\n\t}\n\n\tconst block = dependencyPreviewsBlock(\n\t\tframework,\n\t\tsourceRootUrl,\n\t\tindent,\n\t\teol,\n\t\tquote,\n\t)\n\n\t/**\n\t * Locate the preview config object's body so all the key lookups below\n\t * are scoped to *that* object. Without this, a `parameters:` /\n\t * `decorators:` belonging to some unrelated object earlier in the file\n\t * would be matched instead of the one we want to patch.\n\t *\n\t * The opener regex runs against the position-preserving comment-stripped\n\t * text so an example like `// const preview: Preview = { ... }` in a\n\t * comment can't hijack the search — `match.index` from the stripped\n\t * content lines up with the original.\n\t */\n\tconst findPreviewBody = (\n\t\ttext: string,\n\t): { from: number; to: number } | null => {\n\t\tconst stripped = stripCommentsRespectingStrings(text)\n\t\tconst bodyAt = (\n\t\t\tmatch: RegExpMatchArray | null,\n\t\t): { from: number; to: number } | null => {\n\t\t\tif (!match || match.index === undefined) return null\n\t\t\tconst openBraceIdx = match.index + match[0].length - 1\n\t\t\tif (text[openBraceIdx] !== '{') return null\n\t\t\tconst closeIdx = findMatchingBrace(text, openBraceIdx)\n\t\t\tif (closeIdx === null) return null\n\t\t\treturn { from: openBraceIdx + 1, to: closeIdx }\n\t\t}\n\n\t\t// Direct patterns: typed preview (`Preview = {`, `StorybookPreviewConfig = {`)\n\t\t// or anonymous default export (`export default {`).\n\t\tconst direct = bodyAt(\n\t\t\tstripped.match(\n\t\t\t\t/(StorybookPreviewConfig\\s*=\\s*\\{|Preview\\s*=\\s*\\{|export\\s+default\\s*\\{)/,\n\t\t\t),\n\t\t)\n\t\tif (direct) return direct\n\n\t\t// Fallback: untyped `const preview = { … }; export default preview` (common\n\t\t// in `.js` / `.jsx` preview files where there's no type annotation).\n\t\t// Resolve `export default <ident>` to its `(const|let|var) <ident> = {…}`\n\t\t// declaration and use that object's body.\n\t\tconst exportIdent = stripped.match(\n\t\t\t/export\\s+default\\s+([A-Za-z_$][\\w$]*)\\b/,\n\t\t)?.[1]\n\t\tif (!exportIdent) return null\n\t\tconst escaped = exportIdent.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n\t\treturn bodyAt(\n\t\t\tstripped.match(new RegExp(`(?:const|let|var)\\\\s+${escaped}\\\\s*=\\\\s*\\\\{`)),\n\t\t)\n\t}\n\n\tconst bodyRange = findPreviewBody(newContent)\n\tif (!bodyRange) {\n\t\treturn {\n\t\t\tkind: 'failed',\n\t\t\treason:\n\t\t\t\t'Could not locate the preview config object — please add the dependencyPreviews parameters and decorators manually.',\n\t\t}\n\t}\n\n\t// If we end up creating a brand-new `parameters:` key, remember the position\n\t// right after it so a brand-new `decorators:` insertion can land *after* it\n\t// rather than at the same body-start position.\n\tlet paramsCreatedEndOffset: number | null = null\n\n\tconst paramsKey = findTopLevelKey(newContent, 'parameters', bodyRange)\n\tif (paramsKey && newContent[paramsKey.valueStart] === '{') {\n\t\t// Check for an existing `...<local>` spread, but only inside the parameters\n\t\t// object itself (and only in real code, not comments or strings). A match\n\t\t// anywhere else in the file would falsely suppress the spread we need to\n\t\t// insert. `<local>` is the project's local binding for `defaultPreviewParameters`,\n\t\t// which differs from the canonical name when the user has aliased the import.\n\t\tconst paramsBodyEnd = findMatchingBrace(newContent, paramsKey.valueStart)\n\t\tconst paramsBodyStart = paramsKey.valueStart + 1\n\t\tconst localSpreadRegex = new RegExp(\n\t\t\tString.raw`\\.\\.\\.${defaultsLocalName.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}\\b`,\n\t\t)\n\t\tconst hasDefaultParamsSpread =\n\t\t\tparamsBodyEnd !== null &&\n\t\t\tlocalSpreadRegex.test(\n\t\t\t\tstripCommentsRespectingStrings(\n\t\t\t\t\tnewContent.slice(paramsBodyStart, paramsBodyEnd),\n\t\t\t\t),\n\t\t\t)\n\t\tconst insertAt = paramsBodyStart\n\t\tconst insertion = hasDefaultParamsSpread\n\t\t\t? `${eol}${block}`\n\t\t\t: `${eol}${l2}...${defaultsLocalName},${eol}${block}`\n\t\tnewContent =\n\t\t\tnewContent.slice(0, insertAt) + insertion + newContent.slice(insertAt)\n\t} else if (paramsKey) {\n\t\t// `parameters:` exists but isn't a literal `{ … }` object (probably a\n\t\t// variable, spread, or function call). Inserting another `parameters:`\n\t\t// would produce a duplicate key — bail with guidance instead.\n\t\treturn {\n\t\t\tkind: 'failed',\n\t\t\treason:\n\t\t\t\t'Preview config already defines `parameters` in a non-literal-object form — please manually add `...defaultPreviewParameters` and the `dependencyPreviews` block to the existing parameters definition.',\n\t\t}\n\t} else {\n\t\tconst insertAt = bodyRange.from\n\t\tconst insertion = `${eol}${l1}parameters: {${eol}${l2}...${defaultsLocalName},${eol}${block}${eol}${l1}},`\n\t\tnewContent =\n\t\t\tnewContent.slice(0, insertAt) + insertion + newContent.slice(insertAt)\n\t\t// Track where the just-inserted parameters block ends, so a subsequent\n\t\t// \"create new decorators\" can be placed *after* it (the bodyRange.from\n\t\t// position is shared with parameters; without this offset, decorators\n\t\t// would be inserted at body-start and end up *before* parameters).\n\t\tparamsCreatedEndOffset = insertAt + insertion.length\n\t}\n\n\t// Re-find the body range after the params insertion — the body's closing\n\t// brace position has shifted but the helper handles that transparently.\n\tconst bodyRangeAfterParams = findPreviewBody(newContent) ?? bodyRange\n\tconst decoratorsKey = findTopLevelKey(\n\t\tnewContent,\n\t\t'decorators',\n\t\tbodyRangeAfterParams,\n\t)\n\tif (decoratorsKey && newContent[decoratorsKey.valueStart] === '[') {\n\t\t// Mirror the parameters-spread idempotency: only insert the spread if the\n\t\t// decorators array doesn't already contain `...<localName>`. Catches the\n\t\t// \"user partially set up by hand\" case (the wizard may have just added\n\t\t// `dependencyPreviews:` parameters and the user might already have wired\n\t\t// the decorators).\n\t\tconst decoratorsBodyEnd = findMatchingBrace(\n\t\t\tnewContent,\n\t\t\tdecoratorsKey.valueStart,\n\t\t)\n\t\tconst decoratorsBodyStart = decoratorsKey.valueStart + 1\n\t\tconst localDecoratorsSpreadRegex = new RegExp(\n\t\t\tString.raw`\\.\\.\\.${decoratorsLocalName.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}\\b`,\n\t\t)\n\t\tconst hasDecoratorsSpread =\n\t\t\tdecoratorsBodyEnd !== null &&\n\t\t\tlocalDecoratorsSpreadRegex.test(\n\t\t\t\tstripCommentsRespectingStrings(\n\t\t\t\t\tnewContent.slice(decoratorsBodyStart, decoratorsBodyEnd),\n\t\t\t\t),\n\t\t\t)\n\t\tif (!hasDecoratorsSpread) {\n\t\t\tconst insertAt = decoratorsBodyStart\n\t\t\tconst insertion = `${eol}${l2}...${decoratorsLocalName},`\n\t\t\tnewContent =\n\t\t\t\tnewContent.slice(0, insertAt) + insertion + newContent.slice(insertAt)\n\t\t}\n\t} else if (decoratorsKey) {\n\t\treturn {\n\t\t\tkind: 'failed',\n\t\t\treason:\n\t\t\t\t'Preview config already defines `decorators` in a non-literal-array form — please manually add `...dependencyPreviewDecorators` to the existing decorators definition.',\n\t\t}\n\t} else {\n\t\t// If we just inserted a brand-new `parameters:` key in this same run,\n\t\t// position the new `decorators:` *after* it. Otherwise place it at the\n\t\t// start of the preview config body.\n\t\tconst insertAt = paramsCreatedEndOffset ?? bodyRangeAfterParams.from\n\t\tconst insertion = `${eol}${l1}decorators: [...${decoratorsLocalName}],`\n\t\tnewContent =\n\t\t\tnewContent.slice(0, insertAt) + insertion + newContent.slice(insertAt)\n\t}\n\n\ttry {\n\t\twriteFileSync(previewFile.path, newContent, 'utf8')\n\t} catch (e) {\n\t\treturn {\n\t\t\tkind: 'failed',\n\t\t\treason: `Could not write ${previewFile.path}: ${(e as Error).message}`,\n\t\t}\n\t}\n\treturn { kind: 'patched', path: previewFile.path }\n}\n\nexport interface PatchPreviewFileOptions {\n\t/** Absolute path to the project's `.storybook/` directory. */\n\tstorybookDir: string\n\t/**\n\t * Existing preview file detected in `.storybook/` (any of preview.ts /\n\t * .tsx / .js / .jsx), or `null` if no preview file exists yet — in which\n\t * case the patcher creates a fresh one from the framework template.\n\t */\n\tpreviewFile: PreviewFile | null\n\t/**\n\t * Detected `.storybook/main.{ts,js,…}`. Used to derive the indent and EOL\n\t * style for any newly-created preview file so it matches the project's\n\t * existing code style.\n\t */\n\tmainFile: MainFile\n\t/**\n\t * Detected (or user-confirmed) Storybook framework. Determines the story\n\t * glob pattern injected into `dependencyPreviews.storyModules` and which\n\t * framework template is used when creating a new preview file.\n\t */\n\tframework: Framework\n\t/**\n\t * URL pointing at the project's source-code root (e.g. a GitHub blob\n\t * URL). Powers the addon's \"view source\" links in the Storybook UI. May\n\t * be empty — the addon's runtime treats `''` as \"no source links\" and\n\t * the field is required by the addon's parameter type so we always emit\n\t * it.\n\t */\n\tsourceRootUrl: string\n}\n\n/**\n * Patch (or create) the project's `.storybook/preview.{ts,tsx,js,jsx}` so\n * the addon's parameters and decorators are wired in. Idempotent —\n * re-runs against an already-configured preview return `{ kind: 'skipped' }`.\n */\nexport function patchPreviewFile(\n\topts: PatchPreviewFileOptions,\n): PreviewPatchResult {\n\tconst { storybookDir, previewFile, mainFile, framework, sourceRootUrl } = opts\n\n\tif (\n\t\tframework !== 'react-vite' &&\n\t\tframework !== 'sveltekit' &&\n\t\tframework !== 'svelte-vite'\n\t) {\n\t\treturn {\n\t\t\tkind: 'failed',\n\t\t\treason: `Preview patcher does not support framework \"${framework}\".`,\n\t\t}\n\t}\n\n\tif (previewFile) {\n\t\treturn patchExistingPreview(previewFile, framework, sourceRootUrl)\n\t}\n\n\t// Derive indent and EOL from the existing main.{ts,js,...} so the brand-new\n\t// preview file matches the project's style instead of always using tabs / LF.\n\tlet style: TemplateStyle = { indent: '\\t', eol: '\\n' }\n\ttry {\n\t\tconst mainContent = readFileSync(mainFile.path, 'utf8')\n\t\tstyle = {\n\t\t\tindent: detectFileIndent(mainContent),\n\t\t\teol: detectEol(mainContent),\n\t\t}\n\t} catch {\n\t\t// If main.ts can't be read for some reason, fall back to the defaults.\n\t}\n\n\tconst { content, lang } = templateForFramework(\n\t\tframework,\n\t\tsourceRootUrl,\n\t\tstyle,\n\t\tmainFile.lang,\n\t)\n\tconst path = resolve(storybookDir, `preview.${lang}`)\n\tif (existsSync(path)) {\n\t\treturn { kind: 'skipped', reason: `${path} already exists` }\n\t}\n\ttry {\n\t\twriteFileSync(path, content, 'utf8')\n\t} catch (e) {\n\t\treturn {\n\t\t\tkind: 'failed',\n\t\t\treason: `Could not create ${path}: ${(e as Error).message}`,\n\t\t}\n\t}\n\treturn { kind: 'created', path }\n}\n"],"mappings":"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"}
|
|
@@ -3,11 +3,7 @@ import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
4
|
|
|
5
5
|
//#region cli/setup/patchers/preview.ts
|
|
6
|
-
const
|
|
7
|
-
"react-vite": "/src/**/*.stories.@(tsx|ts|jsx|js)",
|
|
8
|
-
sveltekit: "/src/**/*.stories.@(ts|js|svelte)",
|
|
9
|
-
"svelte-vite": "/src/**/*.stories.@(ts|js|svelte)"
|
|
10
|
-
};
|
|
6
|
+
const STORY_GLOB = "/src/**/*.stories.{tsx,ts,jsx,js,svelte}";
|
|
11
7
|
function dependencyPreviewsBlock(framework, sourceRootUrl, indent = " ", eol = "\n", quote = "'") {
|
|
12
8
|
const l2 = indent.repeat(2);
|
|
13
9
|
const l3 = indent.repeat(3);
|
|
@@ -18,7 +14,7 @@ function dependencyPreviewsBlock(framework, sourceRootUrl, indent = " ", eol = "
|
|
|
18
14
|
`${l3}dependenciesJson,`,
|
|
19
15
|
`${l3}projectRootPath: new URL(${quote}..${quote}, import.meta.url).pathname,`,
|
|
20
16
|
`${l3}storyModules: import.meta.glob(`,
|
|
21
|
-
`${l4}${quote}${
|
|
17
|
+
`${l4}${quote}${STORY_GLOB}${quote},`,
|
|
22
18
|
`${l4}{ eager: false },`,
|
|
23
19
|
`${l3}),`,
|
|
24
20
|
`${l3}sourceRootUrl: ${serialisedSourceRootUrl},`,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preview.mjs","names":["STORY_GLOBS: Record<SupportedFramework, string>","content: string","existingEntries: Array<Entry>","mergedEntries: Array<Entry>","importsToInsert: string[]","paramsCreatedEndOffset: number | null","style: TemplateStyle"],"sources":["../../../../cli/setup/patchers/preview.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\n\nimport type { Framework, MainFile, PreviewFile } from '../detect.js'\nimport {\n\tdetectEol,\n\tdetectFileIndent,\n\tdetectQuoteStyle,\n\tfindMatchingBrace,\n\tfindTopLevelKey,\n\tstripCommentsRespectingStrings,\n} from '../util.js'\n\nexport type PreviewPatchResult =\n\t| { kind: 'created'; path: string }\n\t| { kind: 'skipped'; reason: string }\n\t| { kind: 'patched'; path: string }\n\t| { kind: 'failed'; reason: string }\n\ntype SupportedFramework = 'react-vite' | 'sveltekit' | 'svelte-vite'\n\nconst STORY_GLOBS: Record<SupportedFramework, string> = {\n\t'react-vite': '/src/**/*.stories.@(tsx|ts|jsx|js)',\n\tsveltekit: '/src/**/*.stories.@(ts|js|svelte)',\n\t'svelte-vite': '/src/**/*.stories.@(ts|js|svelte)',\n}\n\nfunction dependencyPreviewsBlock(\n\tframework: SupportedFramework,\n\tsourceRootUrl: string,\n\tindent: string = '\\t',\n\teol: string = '\\n',\n\tquote: \"'\" | '\"' = \"'\",\n): string {\n\tconst l2 = indent.repeat(2)\n\tconst l3 = indent.repeat(3)\n\tconst l4 = indent.repeat(4)\n\t// `sourceRootUrl` is required by `DependencyPreviewStorybookParameters`, so\n\t// always emit it — even an empty string is better than a TS error.\n\t// JSON.stringify always emits double quotes; convert when the file uses single\n\t// quotes so the inserted line matches surrounding style. URLs in practice contain\n\t// no characters that need re-escaping, but we still escape backslashes / single\n\t// quotes / line terminators for safety.\n\tconst serialisedSourceRootUrl =\n\t\tquote === '\"'\n\t\t\t? JSON.stringify(sourceRootUrl)\n\t\t\t: `'${sourceRootUrl\n\t\t\t\t\t.replace(/\\\\/g, '\\\\\\\\')\n\t\t\t\t\t.replace(/'/g, \"\\\\'\")\n\t\t\t\t\t.replace(/\\n/g, '\\\\n')\n\t\t\t\t\t.replace(/\\r/g, '\\\\r')}'`\n\tconst lines = [\n\t\t`${l2}dependencyPreviews: {`,\n\t\t`${l3}dependenciesJson,`,\n\t\t`${l3}projectRootPath: new URL(${quote}..${quote}, import.meta.url).pathname,`,\n\t\t`${l3}storyModules: import.meta.glob(`,\n\t\t`${l4}${quote}${STORY_GLOBS[framework]}${quote},`,\n\t\t`${l4}{ eager: false },`,\n\t\t`${l3}),`,\n\t\t`${l3}sourceRootUrl: ${serialisedSourceRootUrl},`,\n\t\t`${l2}},`,\n\t]\n\treturn lines.join(eol)\n}\n\ntype TemplateStyle = { indent: string; eol: string }\n\nfunction buildTemplate(\n\tframework: SupportedFramework,\n\tsourceRootUrl: string,\n\tstyle: TemplateStyle,\n\tisTs: boolean,\n): string {\n\tconst { indent, eol } = style\n\tconst l1 = indent\n\tconst l2 = indent.repeat(2)\n\tconst lines = [\n\t\t// `/// <reference types=\"vite/client\" />` is a TypeScript-only directive;\n\t\t// Vite-flavoured JS preview files don't need it.\n\t\t...(isTs ? [`/// <reference types=\"vite/client\" />`, ``] : []),\n\t\t`import {`,\n\t\t`${l1}defaultPreviewParameters,`,\n\t\t// The `type StorybookPreviewConfig` import is meaningless in JS — drop it.\n\t\t`${l1}dependencyPreviewDecorators,${isTs ? `${eol}${l1}type StorybookPreviewConfig,` : ''}`,\n\t\t`} from 'storybook-addon-dependency-previews'`,\n\t\t``,\n\t\t`import dependenciesJson from './dependency-previews.json'`,\n\t\t``,\n\t\t// Same: skip the type annotation in JS.\n\t\tisTs\n\t\t\t? `const previewConfig: StorybookPreviewConfig = {`\n\t\t\t: `const previewConfig = {`,\n\t\t`${l1}parameters: {`,\n\t\t`${l2}...defaultPreviewParameters,`,\n\t\tdependencyPreviewsBlock(framework, sourceRootUrl, indent, eol),\n\t\t`${l1}},`,\n\t\t`${l1}decorators: [...dependencyPreviewDecorators],`,\n\t\t`}`,\n\t\t``,\n\t\t`export default previewConfig`,\n\t\t``,\n\t]\n\treturn lines.join(eol)\n}\n\n/**\n * Map the detected `.storybook/main.{ts,js,mjs,cjs}` extension to the matching\n * preview-file extension. The `PreviewFile['lang']` type only models the four\n * extensions Storybook actually loads as a preview module — `mjs`/`cjs` mains\n * still imply a `js` preview, so they collapse to `'js'` here.\n */\nfunction previewLangForMainLang(\n\tmainLang: MainFile['lang'],\n): PreviewFile['lang'] {\n\treturn mainLang === 'ts' ? 'ts' : 'js'\n}\n\nfunction templateForFramework(\n\tframework: SupportedFramework,\n\tsourceRootUrl: string,\n\tstyle: TemplateStyle,\n\tmainLang: MainFile['lang'],\n): { content: string; lang: PreviewFile['lang'] } {\n\tconst lang = previewLangForMainLang(mainLang)\n\treturn {\n\t\tcontent: buildTemplate(framework, sourceRootUrl, style, lang === 'ts'),\n\t\tlang,\n\t}\n}\n\nfunction findImportInsertionIndex(content: string): number {\n\tconst lines = content.split('\\n')\n\tlet idx = 0\n\tlet inBlockComment = false\n\n\tfor (let i = 0; i < lines.length; i++) {\n\t\tconst rawLine = lines[i]!\n\t\tconst trimmed = rawLine.trim()\n\t\tconst advance = rawLine.length + 1 // +1 for the consumed '\\n'\n\n\t\tif (inBlockComment) {\n\t\t\tidx += advance\n\t\t\tif (rawLine.includes('*/')) inBlockComment = false\n\t\t\tcontinue\n\t\t}\n\n\t\tif (\n\t\t\ttrimmed === '' ||\n\t\t\ttrimmed.startsWith('//') || // line comment & triple-slash directive\n\t\t\ttrimmed.startsWith(\"'use client'\") ||\n\t\t\ttrimmed.startsWith('\"use client\"')\n\t\t) {\n\t\t\tidx += advance\n\t\t\tcontinue\n\t\t}\n\n\t\tif (trimmed.startsWith('/*')) {\n\t\t\tidx += advance\n\t\t\t// Multi-line block comment if no `*/` later on the same line.\n\t\t\tif (!trimmed.slice(2).includes('*/')) inBlockComment = true\n\t\t\tcontinue\n\t\t}\n\n\t\tbreak\n\t}\n\treturn idx\n}\n\nfunction patchExistingPreview(\n\tpreviewFile: PreviewFile,\n\tframework: SupportedFramework,\n\tsourceRootUrl: string,\n): PreviewPatchResult {\n\tlet content: string\n\ttry {\n\t\tcontent = readFileSync(previewFile.path, 'utf8')\n\t} catch (e) {\n\t\treturn {\n\t\t\tkind: 'failed',\n\t\t\treason: `Could not read ${previewFile.path}: ${(e as Error).message}`,\n\t\t}\n\t}\n\n\t// Strip comments before doing identifier checks so commented-out example code\n\t// can't false-positive the idempotency / CJS guards. (Strings and template\n\t// literals are left intact so a URL containing `//` doesn't get truncated.)\n\tconst codeOnly = stripCommentsRespectingStrings(content)\n\n\t// `dependencyPreviews:` is the unique parameters key the wizard injects, so its\n\t// presence means the addon is already wired in. Other markers like the bare\n\t// `dependencyPreviewDecorators` identifier are too lenient — they'd false-positive\n\t// on `import { dependencyPreviewDecorators as dpd } …` where the name appears in\n\t// the import declaration but isn't actually used in any decorators array yet.\n\tif (/\\bdependencyPreviews\\s*:/.test(codeOnly)) {\n\t\treturn { kind: 'skipped', reason: 'addon already configured in preview' }\n\t}\n\n\tif (/\\bmodule\\.exports\\s*=/.test(codeOnly)) {\n\t\treturn {\n\t\t\tkind: 'failed',\n\t\t\treason:\n\t\t\t\t'Preview file uses CommonJS (module.exports). The wizard only patches ESM preview files — please convert to ESM or follow the manual setup docs.',\n\t\t}\n\t}\n\n\tconst isTs = previewFile.lang === 'ts' || previewFile.lang === 'tsx'\n\tconst indent = detectFileIndent(content)\n\tconst eol = detectEol(content)\n\tconst quote = detectQuoteStyle(content)\n\tconst l1 = indent\n\tconst l2 = indent.repeat(2)\n\n\tlet newContent = content\n\n\t// ─── Imports: collect all imports from the addon package, merge them into one,\n\t// and either replace the first / delete the rest, or insert a new one if none exist.\n\tconst PKG = 'storybook-addon-dependency-previews'\n\t// Match the whole import statement including any trailing semicolon and the\n\t// terminating newline, so that deletions of additional imports don't leave\n\t// stray `;` lines behind in semicolon-using projects.\n\tconst ADDON_IMPORT_REGEX = new RegExp(\n\t\tString.raw`^[\\t ]*import\\s*(type\\s+)?\\{([\\s\\S]*?)\\}\\s*from\\s*['\"]storybook-addon-dependency-previews['\"]\\s*;?[ \\t]*(?:\\r?\\n|$)`,\n\t\t'gm',\n\t)\n\tconst allAddonImports = [...newContent.matchAll(ADDON_IMPORT_REGEX)]\n\t// Preserve the project's semicolon style on the merged import.\n\tconst usesSemicolons =\n\t\t/from\\s*['\"][^'\"]*['\"]\\s*;/.test(newContent) ||\n\t\tallAddonImports.some((m) => /;\\s*(?:\\r?\\n|$)/.test(m[0]!))\n\tconst trailingSemi = usesSemicolons ? ';' : ''\n\t// Use the comment-stripped content so a commented-out `// import dependenciesJson …`\n\t// doesn't trick us into skipping the real import (which would leave the inserted\n\t// `dependencyPreviews` block referencing an undefined identifier).\n\tconst hasDependenciesJsonImport =\n\t\t/import\\s+dependenciesJson\\s+from\\s*['\"]\\.\\/dependency-previews\\.json['\"]/.test(\n\t\t\tcodeOnly,\n\t\t)\n\n\tconst requiredValueNames = [\n\t\t'defaultPreviewParameters',\n\t\t'dependencyPreviewDecorators',\n\t]\n\tconst requiredTypeNames = isTs ? ['StorybookPreviewConfig'] : []\n\n\t// The local binding names for the addon's value imports — usually identical\n\t// to the original names, but if the user has aliased an import (e.g.\n\t// `import { defaultPreviewParameters as dp } from '…'`) then the local name\n\t// is the alias and that's what later spreads need to reference.\n\tlet defaultsLocalName = 'defaultPreviewParameters'\n\tlet decoratorsLocalName = 'dependencyPreviewDecorators'\n\n\tif (allAddonImports.length > 0) {\n\t\ttype Entry = { name: string; alias?: string; isType: boolean }\n\t\tconst parseEntry = (raw: string, wasTypeOnly: boolean): Entry => {\n\t\t\t// `import type { A, B }` makes every name a type, so respect that.\n\t\t\tconst isType = wasTypeOnly || /^type\\s+/.test(raw)\n\t\t\tconst stripped = raw.replace(/^type\\s+/, '')\n\t\t\tconst [name, alias] = stripped.split(/\\s+as\\s+/).map((s) => s.trim())\n\t\t\treturn { name: name!, alias, isType }\n\t\t}\n\t\tconst formatEntry = (e: Entry): string => {\n\t\t\tconst inner = e.alias ? `${e.name} as ${e.alias}` : e.name\n\t\t\treturn e.isType ? `type ${inner}` : inner\n\t\t}\n\n\t\t// Collect every named import from every `from 'storybook-addon-dependency-previews'`\n\t\t// statement in the file. Stripping comments inside `{ … }` first so that\n\t\t// `import { foo, /* note */ bar }` doesn't produce `/* note */ bar` as a name.\n\t\tconst existingEntries: Array<Entry> = []\n\t\tfor (const m of allAddonImports) {\n\t\t\tconst wasTypeOnly = !!m[1]\n\t\t\tconst importContents = m[2]!\n\t\t\t\t.replace(/\\/\\*[\\s\\S]*?\\*\\//g, '')\n\t\t\t\t.replace(/\\/\\/.*$/gm, '')\n\t\t\tfor (const raw of importContents\n\t\t\t\t.split(',')\n\t\t\t\t.map((s) => s.trim())\n\t\t\t\t.filter(Boolean)) {\n\t\t\t\texistingEntries.push(parseEntry(raw, wasTypeOnly))\n\t\t\t}\n\t\t}\n\n\t\t// Deduplicate by name. Value-imports beat type-imports if both exist for the\n\t\t// same name (you can use a value at runtime AND in type positions, but not\n\t\t// vice-versa).\n\t\tconst byName = new Map<string, Entry>()\n\t\tfor (const e of existingEntries) {\n\t\t\tconst prev = byName.get(e.name)\n\t\t\tif (!prev || (prev.isType && !e.isType)) byName.set(e.name, e)\n\t\t}\n\t\tconst dedupedExisting = Array.from(byName.values())\n\n\t\tconst requiredValueSet = new Set(requiredValueNames)\n\n\t\t// Promote any existing entry whose name matches a required value to a value\n\t\t// import. This handles e.g. `import type { defaultPreviewParameters } from ...` —\n\t\t// without promotion we'd leave it as a type and the runtime spread would fail.\n\t\tconst mergedEntries: Array<Entry> = dedupedExisting.map((e) =>\n\t\t\trequiredValueSet.has(e.name) && e.isType ? { ...e, isType: false } : e,\n\t\t)\n\t\tconst handled = new Set(mergedEntries.map((e) => e.name))\n\t\tfor (const n of requiredValueNames) {\n\t\t\tif (!handled.has(n)) {\n\t\t\t\tmergedEntries.push({ name: n, isType: false })\n\t\t\t\thandled.add(n)\n\t\t\t}\n\t\t}\n\t\tfor (const n of requiredTypeNames) {\n\t\t\tif (!handled.has(n)) {\n\t\t\t\tmergedEntries.push({ name: n, isType: true })\n\t\t\t\thandled.add(n)\n\t\t\t}\n\t\t}\n\n\t\tconst isMultipleImports = allAddonImports.length > 1\n\t\tconst noPromotionNeeded = dedupedExisting.every(\n\t\t\t(e) => mergedEntries.find((m) => m.name === e.name)?.isType === e.isType,\n\t\t)\n\t\tconst allRequiredAlreadyValueImported = requiredValueNames.every((n) =>\n\t\t\tdedupedExisting.some((e) => e.name === n && !e.isType),\n\t\t)\n\t\tconst allRequiredAlreadyTypeImported = requiredTypeNames.every((n) =>\n\t\t\tdedupedExisting.some((e) => e.name === n && e.isType),\n\t\t)\n\t\t// Skip the rewrite only when there's a single import AND nothing about it\n\t\t// needs to change. With multiple imports we always merge to avoid duplicate\n\t\t// identifier bindings between them.\n\t\tconst nothingToDo =\n\t\t\t!isMultipleImports &&\n\t\t\tnoPromotionNeeded &&\n\t\t\tallRequiredAlreadyValueImported &&\n\t\t\tallRequiredAlreadyTypeImported\n\n\t\tif (!nothingToDo) {\n\t\t\t// The regex consumes the trailing newline, so include one in the\n\t\t\t// replacement; also tack on the project's semicolon style.\n\t\t\tconst replacement = `import {${eol}${mergedEntries\n\t\t\t\t.map((e) => `${indent}${formatEntry(e)},`)\n\t\t\t\t.join(eol)}${eol}} from ${quote}${PKG}${quote}${trailingSemi}${eol}`\n\t\t\t// Replace the first import with the merged version; delete the rest.\n\t\t\tlet firstReplaced = false\n\t\t\tnewContent = newContent.replace(ADDON_IMPORT_REGEX, () => {\n\t\t\t\tif (!firstReplaced) {\n\t\t\t\t\tfirstReplaced = true\n\t\t\t\t\treturn replacement\n\t\t\t\t}\n\t\t\t\treturn ''\n\t\t\t})\n\t\t\t// Tidy up any blank-line runs left behind by deleted imports.\n\t\t\tnewContent = newContent.replace(/(\\r?\\n){3,}/g, `${eol}${eol}`)\n\t\t}\n\n\t\t// Resolve the local binding names — if the user aliased an import we need\n\t\t// to reference the alias in the inserted spreads, not the original name.\n\t\tconst findLocalName = (name: string): string => {\n\t\t\tconst entry = mergedEntries.find((m) => m.name === name && !m.isType)\n\t\t\treturn entry?.alias ?? name\n\t\t}\n\t\tdefaultsLocalName = findLocalName('defaultPreviewParameters')\n\t\tdecoratorsLocalName = findLocalName('dependencyPreviewDecorators')\n\t}\n\n\tconst importsToInsert: string[] = []\n\tif (allAddonImports.length === 0) {\n\t\tconst addonImportBlock = [\n\t\t\t`import {`,\n\t\t\t`${indent}defaultPreviewParameters,`,\n\t\t\t`${indent}dependencyPreviewDecorators,${isTs ? `${eol}${indent}type StorybookPreviewConfig,` : ''}`,\n\t\t\t`} from ${quote}${PKG}${quote}${trailingSemi}`,\n\t\t].join(eol)\n\t\timportsToInsert.push(addonImportBlock)\n\t}\n\tif (!hasDependenciesJsonImport) {\n\t\timportsToInsert.push(\n\t\t\t`import dependenciesJson from ${quote}./dependency-previews.json${quote}${trailingSemi}`,\n\t\t)\n\t}\n\tif (importsToInsert.length > 0) {\n\t\tconst insertAt = findImportInsertionIndex(newContent)\n\t\tconst insertion = importsToInsert.join(eol + eol) + eol + eol\n\t\tnewContent =\n\t\t\tnewContent.slice(0, insertAt) + insertion + newContent.slice(insertAt)\n\t}\n\n\tconst block = dependencyPreviewsBlock(\n\t\tframework,\n\t\tsourceRootUrl,\n\t\tindent,\n\t\teol,\n\t\tquote,\n\t)\n\n\t/**\n\t * Locate the preview config object's body so all the key lookups below\n\t * are scoped to *that* object. Without this, a `parameters:` /\n\t * `decorators:` belonging to some unrelated object earlier in the file\n\t * would be matched instead of the one we want to patch.\n\t *\n\t * The opener regex runs against the position-preserving comment-stripped\n\t * text so an example like `// const preview: Preview = { ... }` in a\n\t * comment can't hijack the search — `match.index` from the stripped\n\t * content lines up with the original.\n\t */\n\tconst findPreviewBody = (\n\t\ttext: string,\n\t): { from: number; to: number } | null => {\n\t\tconst stripped = stripCommentsRespectingStrings(text)\n\t\tconst bodyAt = (\n\t\t\tmatch: RegExpMatchArray | null,\n\t\t): { from: number; to: number } | null => {\n\t\t\tif (!match || match.index === undefined) return null\n\t\t\tconst openBraceIdx = match.index + match[0].length - 1\n\t\t\tif (text[openBraceIdx] !== '{') return null\n\t\t\tconst closeIdx = findMatchingBrace(text, openBraceIdx)\n\t\t\tif (closeIdx === null) return null\n\t\t\treturn { from: openBraceIdx + 1, to: closeIdx }\n\t\t}\n\n\t\t// Direct patterns: typed preview (`Preview = {`, `StorybookPreviewConfig = {`)\n\t\t// or anonymous default export (`export default {`).\n\t\tconst direct = bodyAt(\n\t\t\tstripped.match(\n\t\t\t\t/(StorybookPreviewConfig\\s*=\\s*\\{|Preview\\s*=\\s*\\{|export\\s+default\\s*\\{)/,\n\t\t\t),\n\t\t)\n\t\tif (direct) return direct\n\n\t\t// Fallback: untyped `const preview = { … }; export default preview` (common\n\t\t// in `.js` / `.jsx` preview files where there's no type annotation).\n\t\t// Resolve `export default <ident>` to its `(const|let|var) <ident> = {…}`\n\t\t// declaration and use that object's body.\n\t\tconst exportIdent = stripped.match(\n\t\t\t/export\\s+default\\s+([A-Za-z_$][\\w$]*)\\b/,\n\t\t)?.[1]\n\t\tif (!exportIdent) return null\n\t\tconst escaped = exportIdent.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n\t\treturn bodyAt(\n\t\t\tstripped.match(new RegExp(`(?:const|let|var)\\\\s+${escaped}\\\\s*=\\\\s*\\\\{`)),\n\t\t)\n\t}\n\n\tconst bodyRange = findPreviewBody(newContent)\n\tif (!bodyRange) {\n\t\treturn {\n\t\t\tkind: 'failed',\n\t\t\treason:\n\t\t\t\t'Could not locate the preview config object — please add the dependencyPreviews parameters and decorators manually.',\n\t\t}\n\t}\n\n\t// If we end up creating a brand-new `parameters:` key, remember the position\n\t// right after it so a brand-new `decorators:` insertion can land *after* it\n\t// rather than at the same body-start position.\n\tlet paramsCreatedEndOffset: number | null = null\n\n\tconst paramsKey = findTopLevelKey(newContent, 'parameters', bodyRange)\n\tif (paramsKey && newContent[paramsKey.valueStart] === '{') {\n\t\t// Check for an existing `...<local>` spread, but only inside the parameters\n\t\t// object itself (and only in real code, not comments or strings). A match\n\t\t// anywhere else in the file would falsely suppress the spread we need to\n\t\t// insert. `<local>` is the project's local binding for `defaultPreviewParameters`,\n\t\t// which differs from the canonical name when the user has aliased the import.\n\t\tconst paramsBodyEnd = findMatchingBrace(newContent, paramsKey.valueStart)\n\t\tconst paramsBodyStart = paramsKey.valueStart + 1\n\t\tconst localSpreadRegex = new RegExp(\n\t\t\tString.raw`\\.\\.\\.${defaultsLocalName.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}\\b`,\n\t\t)\n\t\tconst hasDefaultParamsSpread =\n\t\t\tparamsBodyEnd !== null &&\n\t\t\tlocalSpreadRegex.test(\n\t\t\t\tstripCommentsRespectingStrings(\n\t\t\t\t\tnewContent.slice(paramsBodyStart, paramsBodyEnd),\n\t\t\t\t),\n\t\t\t)\n\t\tconst insertAt = paramsBodyStart\n\t\tconst insertion = hasDefaultParamsSpread\n\t\t\t? `${eol}${block}`\n\t\t\t: `${eol}${l2}...${defaultsLocalName},${eol}${block}`\n\t\tnewContent =\n\t\t\tnewContent.slice(0, insertAt) + insertion + newContent.slice(insertAt)\n\t} else if (paramsKey) {\n\t\t// `parameters:` exists but isn't a literal `{ … }` object (probably a\n\t\t// variable, spread, or function call). Inserting another `parameters:`\n\t\t// would produce a duplicate key — bail with guidance instead.\n\t\treturn {\n\t\t\tkind: 'failed',\n\t\t\treason:\n\t\t\t\t'Preview config already defines `parameters` in a non-literal-object form — please manually add `...defaultPreviewParameters` and the `dependencyPreviews` block to the existing parameters definition.',\n\t\t}\n\t} else {\n\t\tconst insertAt = bodyRange.from\n\t\tconst insertion = `${eol}${l1}parameters: {${eol}${l2}...${defaultsLocalName},${eol}${block}${eol}${l1}},`\n\t\tnewContent =\n\t\t\tnewContent.slice(0, insertAt) + insertion + newContent.slice(insertAt)\n\t\t// Track where the just-inserted parameters block ends, so a subsequent\n\t\t// \"create new decorators\" can be placed *after* it (the bodyRange.from\n\t\t// position is shared with parameters; without this offset, decorators\n\t\t// would be inserted at body-start and end up *before* parameters).\n\t\tparamsCreatedEndOffset = insertAt + insertion.length\n\t}\n\n\t// Re-find the body range after the params insertion — the body's closing\n\t// brace position has shifted but the helper handles that transparently.\n\tconst bodyRangeAfterParams = findPreviewBody(newContent) ?? bodyRange\n\tconst decoratorsKey = findTopLevelKey(\n\t\tnewContent,\n\t\t'decorators',\n\t\tbodyRangeAfterParams,\n\t)\n\tif (decoratorsKey && newContent[decoratorsKey.valueStart] === '[') {\n\t\t// Mirror the parameters-spread idempotency: only insert the spread if the\n\t\t// decorators array doesn't already contain `...<localName>`. Catches the\n\t\t// \"user partially set up by hand\" case (the wizard may have just added\n\t\t// `dependencyPreviews:` parameters and the user might already have wired\n\t\t// the decorators).\n\t\tconst decoratorsBodyEnd = findMatchingBrace(\n\t\t\tnewContent,\n\t\t\tdecoratorsKey.valueStart,\n\t\t)\n\t\tconst decoratorsBodyStart = decoratorsKey.valueStart + 1\n\t\tconst localDecoratorsSpreadRegex = new RegExp(\n\t\t\tString.raw`\\.\\.\\.${decoratorsLocalName.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}\\b`,\n\t\t)\n\t\tconst hasDecoratorsSpread =\n\t\t\tdecoratorsBodyEnd !== null &&\n\t\t\tlocalDecoratorsSpreadRegex.test(\n\t\t\t\tstripCommentsRespectingStrings(\n\t\t\t\t\tnewContent.slice(decoratorsBodyStart, decoratorsBodyEnd),\n\t\t\t\t),\n\t\t\t)\n\t\tif (!hasDecoratorsSpread) {\n\t\t\tconst insertAt = decoratorsBodyStart\n\t\t\tconst insertion = `${eol}${l2}...${decoratorsLocalName},`\n\t\t\tnewContent =\n\t\t\t\tnewContent.slice(0, insertAt) + insertion + newContent.slice(insertAt)\n\t\t}\n\t} else if (decoratorsKey) {\n\t\treturn {\n\t\t\tkind: 'failed',\n\t\t\treason:\n\t\t\t\t'Preview config already defines `decorators` in a non-literal-array form — please manually add `...dependencyPreviewDecorators` to the existing decorators definition.',\n\t\t}\n\t} else {\n\t\t// If we just inserted a brand-new `parameters:` key in this same run,\n\t\t// position the new `decorators:` *after* it. Otherwise place it at the\n\t\t// start of the preview config body.\n\t\tconst insertAt = paramsCreatedEndOffset ?? bodyRangeAfterParams.from\n\t\tconst insertion = `${eol}${l1}decorators: [...${decoratorsLocalName}],`\n\t\tnewContent =\n\t\t\tnewContent.slice(0, insertAt) + insertion + newContent.slice(insertAt)\n\t}\n\n\ttry {\n\t\twriteFileSync(previewFile.path, newContent, 'utf8')\n\t} catch (e) {\n\t\treturn {\n\t\t\tkind: 'failed',\n\t\t\treason: `Could not write ${previewFile.path}: ${(e as Error).message}`,\n\t\t}\n\t}\n\treturn { kind: 'patched', path: previewFile.path }\n}\n\nexport interface PatchPreviewFileOptions {\n\t/** Absolute path to the project's `.storybook/` directory. */\n\tstorybookDir: string\n\t/**\n\t * Existing preview file detected in `.storybook/` (any of preview.ts /\n\t * .tsx / .js / .jsx), or `null` if no preview file exists yet — in which\n\t * case the patcher creates a fresh one from the framework template.\n\t */\n\tpreviewFile: PreviewFile | null\n\t/**\n\t * Detected `.storybook/main.{ts,js,…}`. Used to derive the indent and EOL\n\t * style for any newly-created preview file so it matches the project's\n\t * existing code style.\n\t */\n\tmainFile: MainFile\n\t/**\n\t * Detected (or user-confirmed) Storybook framework. Determines the story\n\t * glob pattern injected into `dependencyPreviews.storyModules` and which\n\t * framework template is used when creating a new preview file.\n\t */\n\tframework: Framework\n\t/**\n\t * URL pointing at the project's source-code root (e.g. a GitHub blob\n\t * URL). Powers the addon's \"view source\" links in the Storybook UI. May\n\t * be empty — the addon's runtime treats `''` as \"no source links\" and\n\t * the field is required by the addon's parameter type so we always emit\n\t * it.\n\t */\n\tsourceRootUrl: string\n}\n\n/**\n * Patch (or create) the project's `.storybook/preview.{ts,tsx,js,jsx}` so\n * the addon's parameters and decorators are wired in. Idempotent —\n * re-runs against an already-configured preview return `{ kind: 'skipped' }`.\n */\nexport function patchPreviewFile(\n\topts: PatchPreviewFileOptions,\n): PreviewPatchResult {\n\tconst { storybookDir, previewFile, mainFile, framework, sourceRootUrl } = opts\n\n\tif (\n\t\tframework !== 'react-vite' &&\n\t\tframework !== 'sveltekit' &&\n\t\tframework !== 'svelte-vite'\n\t) {\n\t\treturn {\n\t\t\tkind: 'failed',\n\t\t\treason: `Preview patcher does not support framework \"${framework}\".`,\n\t\t}\n\t}\n\n\tif (previewFile) {\n\t\treturn patchExistingPreview(previewFile, framework, sourceRootUrl)\n\t}\n\n\t// Derive indent and EOL from the existing main.{ts,js,...} so the brand-new\n\t// preview file matches the project's style instead of always using tabs / LF.\n\tlet style: TemplateStyle = { indent: '\\t', eol: '\\n' }\n\ttry {\n\t\tconst mainContent = readFileSync(mainFile.path, 'utf8')\n\t\tstyle = {\n\t\t\tindent: detectFileIndent(mainContent),\n\t\t\teol: detectEol(mainContent),\n\t\t}\n\t} catch {\n\t\t// If main.ts can't be read for some reason, fall back to the defaults.\n\t}\n\n\tconst { content, lang } = templateForFramework(\n\t\tframework,\n\t\tsourceRootUrl,\n\t\tstyle,\n\t\tmainFile.lang,\n\t)\n\tconst path = resolve(storybookDir, `preview.${lang}`)\n\tif (existsSync(path)) {\n\t\treturn { kind: 'skipped', reason: `${path} already exists` }\n\t}\n\ttry {\n\t\twriteFileSync(path, content, 'utf8')\n\t} catch (e) {\n\t\treturn {\n\t\t\tkind: 'failed',\n\t\t\treason: `Could not create ${path}: ${(e as Error).message}`,\n\t\t}\n\t}\n\treturn { kind: 'created', path }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"preview.mjs","names":["content: string","existingEntries: Array<Entry>","mergedEntries: Array<Entry>","importsToInsert: string[]","paramsCreatedEndOffset: number | null","style: TemplateStyle"],"sources":["../../../../cli/setup/patchers/preview.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\n\nimport type { Framework, MainFile, PreviewFile } from '../detect.js'\nimport {\n\tdetectEol,\n\tdetectFileIndent,\n\tdetectQuoteStyle,\n\tfindMatchingBrace,\n\tfindTopLevelKey,\n\tstripCommentsRespectingStrings,\n} from '../util.js'\n\nexport type PreviewPatchResult =\n\t| { kind: 'created'; path: string }\n\t| { kind: 'skipped'; reason: string }\n\t| { kind: 'patched'; path: string }\n\t| { kind: 'failed'; reason: string }\n\ntype SupportedFramework = 'react-vite' | 'sveltekit' | 'svelte-vite'\n\n// Brace expansion `{a,b,c}` works under both Vite 7 (micromatch) and Vite 8\n// (tinyglobby). Extglob `@(a|b|c)` silently returns zero matches under tinyglobby,\n// which was the root cause of every wizard-generated preview.ts on Vite 8 showing\n// \"No story module found at this path: …\" for every story.\n//\n// Every currently-supported framework uses the same union extension list, so this is\n// a single const rather than a per-framework record. `dependencyPreviewsBlock` still\n// takes `framework` so future per-framework divergence (a glob a framework should\n// strictly exclude, say) is a one-line change here, not a signature shuffle.\nconst STORY_GLOB = '/src/**/*.stories.{tsx,ts,jsx,js,svelte}'\n\nfunction dependencyPreviewsBlock(\n\tframework: SupportedFramework,\n\tsourceRootUrl: string,\n\tindent: string = '\\t',\n\teol: string = '\\n',\n\tquote: \"'\" | '\"' = \"'\",\n): string {\n\tconst l2 = indent.repeat(2)\n\tconst l3 = indent.repeat(3)\n\tconst l4 = indent.repeat(4)\n\t// `sourceRootUrl` is required by `DependencyPreviewStorybookParameters`, so\n\t// always emit it — even an empty string is better than a TS error.\n\t// JSON.stringify always emits double quotes; convert when the file uses single\n\t// quotes so the inserted line matches surrounding style. URLs in practice contain\n\t// no characters that need re-escaping, but we still escape backslashes / single\n\t// quotes / line terminators for safety.\n\tconst serialisedSourceRootUrl =\n\t\tquote === '\"'\n\t\t\t? JSON.stringify(sourceRootUrl)\n\t\t\t: `'${sourceRootUrl\n\t\t\t\t\t.replace(/\\\\/g, '\\\\\\\\')\n\t\t\t\t\t.replace(/'/g, \"\\\\'\")\n\t\t\t\t\t.replace(/\\n/g, '\\\\n')\n\t\t\t\t\t.replace(/\\r/g, '\\\\r')}'`\n\tconst lines = [\n\t\t`${l2}dependencyPreviews: {`,\n\t\t`${l3}dependenciesJson,`,\n\t\t`${l3}projectRootPath: new URL(${quote}..${quote}, import.meta.url).pathname,`,\n\t\t`${l3}storyModules: import.meta.glob(`,\n\t\t`${l4}${quote}${STORY_GLOB}${quote},`,\n\t\t`${l4}{ eager: false },`,\n\t\t`${l3}),`,\n\t\t`${l3}sourceRootUrl: ${serialisedSourceRootUrl},`,\n\t\t`${l2}},`,\n\t]\n\treturn lines.join(eol)\n}\n\ntype TemplateStyle = { indent: string; eol: string }\n\nfunction buildTemplate(\n\tframework: SupportedFramework,\n\tsourceRootUrl: string,\n\tstyle: TemplateStyle,\n\tisTs: boolean,\n): string {\n\tconst { indent, eol } = style\n\tconst l1 = indent\n\tconst l2 = indent.repeat(2)\n\tconst lines = [\n\t\t// `/// <reference types=\"vite/client\" />` is a TypeScript-only directive;\n\t\t// Vite-flavoured JS preview files don't need it.\n\t\t...(isTs ? [`/// <reference types=\"vite/client\" />`, ``] : []),\n\t\t`import {`,\n\t\t`${l1}defaultPreviewParameters,`,\n\t\t// The `type StorybookPreviewConfig` import is meaningless in JS — drop it.\n\t\t`${l1}dependencyPreviewDecorators,${isTs ? `${eol}${l1}type StorybookPreviewConfig,` : ''}`,\n\t\t`} from 'storybook-addon-dependency-previews'`,\n\t\t``,\n\t\t`import dependenciesJson from './dependency-previews.json'`,\n\t\t``,\n\t\t// Same: skip the type annotation in JS.\n\t\tisTs\n\t\t\t? `const previewConfig: StorybookPreviewConfig = {`\n\t\t\t: `const previewConfig = {`,\n\t\t`${l1}parameters: {`,\n\t\t`${l2}...defaultPreviewParameters,`,\n\t\tdependencyPreviewsBlock(framework, sourceRootUrl, indent, eol),\n\t\t`${l1}},`,\n\t\t`${l1}decorators: [...dependencyPreviewDecorators],`,\n\t\t`}`,\n\t\t``,\n\t\t`export default previewConfig`,\n\t\t``,\n\t]\n\treturn lines.join(eol)\n}\n\n/**\n * Map the detected `.storybook/main.{ts,js,mjs,cjs}` extension to the matching\n * preview-file extension. The `PreviewFile['lang']` type only models the four\n * extensions Storybook actually loads as a preview module — `mjs`/`cjs` mains\n * still imply a `js` preview, so they collapse to `'js'` here.\n */\nfunction previewLangForMainLang(\n\tmainLang: MainFile['lang'],\n): PreviewFile['lang'] {\n\treturn mainLang === 'ts' ? 'ts' : 'js'\n}\n\nfunction templateForFramework(\n\tframework: SupportedFramework,\n\tsourceRootUrl: string,\n\tstyle: TemplateStyle,\n\tmainLang: MainFile['lang'],\n): { content: string; lang: PreviewFile['lang'] } {\n\tconst lang = previewLangForMainLang(mainLang)\n\treturn {\n\t\tcontent: buildTemplate(framework, sourceRootUrl, style, lang === 'ts'),\n\t\tlang,\n\t}\n}\n\nfunction findImportInsertionIndex(content: string): number {\n\tconst lines = content.split('\\n')\n\tlet idx = 0\n\tlet inBlockComment = false\n\n\tfor (let i = 0; i < lines.length; i++) {\n\t\tconst rawLine = lines[i]!\n\t\tconst trimmed = rawLine.trim()\n\t\tconst advance = rawLine.length + 1 // +1 for the consumed '\\n'\n\n\t\tif (inBlockComment) {\n\t\t\tidx += advance\n\t\t\tif (rawLine.includes('*/')) inBlockComment = false\n\t\t\tcontinue\n\t\t}\n\n\t\tif (\n\t\t\ttrimmed === '' ||\n\t\t\ttrimmed.startsWith('//') || // line comment & triple-slash directive\n\t\t\ttrimmed.startsWith(\"'use client'\") ||\n\t\t\ttrimmed.startsWith('\"use client\"')\n\t\t) {\n\t\t\tidx += advance\n\t\t\tcontinue\n\t\t}\n\n\t\tif (trimmed.startsWith('/*')) {\n\t\t\tidx += advance\n\t\t\t// Multi-line block comment if no `*/` later on the same line.\n\t\t\tif (!trimmed.slice(2).includes('*/')) inBlockComment = true\n\t\t\tcontinue\n\t\t}\n\n\t\tbreak\n\t}\n\treturn idx\n}\n\nfunction patchExistingPreview(\n\tpreviewFile: PreviewFile,\n\tframework: SupportedFramework,\n\tsourceRootUrl: string,\n): PreviewPatchResult {\n\tlet content: string\n\ttry {\n\t\tcontent = readFileSync(previewFile.path, 'utf8')\n\t} catch (e) {\n\t\treturn {\n\t\t\tkind: 'failed',\n\t\t\treason: `Could not read ${previewFile.path}: ${(e as Error).message}`,\n\t\t}\n\t}\n\n\t// Strip comments before doing identifier checks so commented-out example code\n\t// can't false-positive the idempotency / CJS guards. (Strings and template\n\t// literals are left intact so a URL containing `//` doesn't get truncated.)\n\tconst codeOnly = stripCommentsRespectingStrings(content)\n\n\t// `dependencyPreviews:` is the unique parameters key the wizard injects, so its\n\t// presence means the addon is already wired in. Other markers like the bare\n\t// `dependencyPreviewDecorators` identifier are too lenient — they'd false-positive\n\t// on `import { dependencyPreviewDecorators as dpd } …` where the name appears in\n\t// the import declaration but isn't actually used in any decorators array yet.\n\tif (/\\bdependencyPreviews\\s*:/.test(codeOnly)) {\n\t\treturn { kind: 'skipped', reason: 'addon already configured in preview' }\n\t}\n\n\tif (/\\bmodule\\.exports\\s*=/.test(codeOnly)) {\n\t\treturn {\n\t\t\tkind: 'failed',\n\t\t\treason:\n\t\t\t\t'Preview file uses CommonJS (module.exports). The wizard only patches ESM preview files — please convert to ESM or follow the manual setup docs.',\n\t\t}\n\t}\n\n\tconst isTs = previewFile.lang === 'ts' || previewFile.lang === 'tsx'\n\tconst indent = detectFileIndent(content)\n\tconst eol = detectEol(content)\n\tconst quote = detectQuoteStyle(content)\n\tconst l1 = indent\n\tconst l2 = indent.repeat(2)\n\n\tlet newContent = content\n\n\t// ─── Imports: collect all imports from the addon package, merge them into one,\n\t// and either replace the first / delete the rest, or insert a new one if none exist.\n\tconst PKG = 'storybook-addon-dependency-previews'\n\t// Match the whole import statement including any trailing semicolon and the\n\t// terminating newline, so that deletions of additional imports don't leave\n\t// stray `;` lines behind in semicolon-using projects.\n\tconst ADDON_IMPORT_REGEX = new RegExp(\n\t\tString.raw`^[\\t ]*import\\s*(type\\s+)?\\{([\\s\\S]*?)\\}\\s*from\\s*['\"]storybook-addon-dependency-previews['\"]\\s*;?[ \\t]*(?:\\r?\\n|$)`,\n\t\t'gm',\n\t)\n\tconst allAddonImports = [...newContent.matchAll(ADDON_IMPORT_REGEX)]\n\t// Preserve the project's semicolon style on the merged import.\n\tconst usesSemicolons =\n\t\t/from\\s*['\"][^'\"]*['\"]\\s*;/.test(newContent) ||\n\t\tallAddonImports.some((m) => /;\\s*(?:\\r?\\n|$)/.test(m[0]!))\n\tconst trailingSemi = usesSemicolons ? ';' : ''\n\t// Use the comment-stripped content so a commented-out `// import dependenciesJson …`\n\t// doesn't trick us into skipping the real import (which would leave the inserted\n\t// `dependencyPreviews` block referencing an undefined identifier).\n\tconst hasDependenciesJsonImport =\n\t\t/import\\s+dependenciesJson\\s+from\\s*['\"]\\.\\/dependency-previews\\.json['\"]/.test(\n\t\t\tcodeOnly,\n\t\t)\n\n\tconst requiredValueNames = [\n\t\t'defaultPreviewParameters',\n\t\t'dependencyPreviewDecorators',\n\t]\n\tconst requiredTypeNames = isTs ? ['StorybookPreviewConfig'] : []\n\n\t// The local binding names for the addon's value imports — usually identical\n\t// to the original names, but if the user has aliased an import (e.g.\n\t// `import { defaultPreviewParameters as dp } from '…'`) then the local name\n\t// is the alias and that's what later spreads need to reference.\n\tlet defaultsLocalName = 'defaultPreviewParameters'\n\tlet decoratorsLocalName = 'dependencyPreviewDecorators'\n\n\tif (allAddonImports.length > 0) {\n\t\ttype Entry = { name: string; alias?: string; isType: boolean }\n\t\tconst parseEntry = (raw: string, wasTypeOnly: boolean): Entry => {\n\t\t\t// `import type { A, B }` makes every name a type, so respect that.\n\t\t\tconst isType = wasTypeOnly || /^type\\s+/.test(raw)\n\t\t\tconst stripped = raw.replace(/^type\\s+/, '')\n\t\t\tconst [name, alias] = stripped.split(/\\s+as\\s+/).map((s) => s.trim())\n\t\t\treturn { name: name!, alias, isType }\n\t\t}\n\t\tconst formatEntry = (e: Entry): string => {\n\t\t\tconst inner = e.alias ? `${e.name} as ${e.alias}` : e.name\n\t\t\treturn e.isType ? `type ${inner}` : inner\n\t\t}\n\n\t\t// Collect every named import from every `from 'storybook-addon-dependency-previews'`\n\t\t// statement in the file. Stripping comments inside `{ … }` first so that\n\t\t// `import { foo, /* note */ bar }` doesn't produce `/* note */ bar` as a name.\n\t\tconst existingEntries: Array<Entry> = []\n\t\tfor (const m of allAddonImports) {\n\t\t\tconst wasTypeOnly = !!m[1]\n\t\t\tconst importContents = m[2]!\n\t\t\t\t.replace(/\\/\\*[\\s\\S]*?\\*\\//g, '')\n\t\t\t\t.replace(/\\/\\/.*$/gm, '')\n\t\t\tfor (const raw of importContents\n\t\t\t\t.split(',')\n\t\t\t\t.map((s) => s.trim())\n\t\t\t\t.filter(Boolean)) {\n\t\t\t\texistingEntries.push(parseEntry(raw, wasTypeOnly))\n\t\t\t}\n\t\t}\n\n\t\t// Deduplicate by name. Value-imports beat type-imports if both exist for the\n\t\t// same name (you can use a value at runtime AND in type positions, but not\n\t\t// vice-versa).\n\t\tconst byName = new Map<string, Entry>()\n\t\tfor (const e of existingEntries) {\n\t\t\tconst prev = byName.get(e.name)\n\t\t\tif (!prev || (prev.isType && !e.isType)) byName.set(e.name, e)\n\t\t}\n\t\tconst dedupedExisting = Array.from(byName.values())\n\n\t\tconst requiredValueSet = new Set(requiredValueNames)\n\n\t\t// Promote any existing entry whose name matches a required value to a value\n\t\t// import. This handles e.g. `import type { defaultPreviewParameters } from ...` —\n\t\t// without promotion we'd leave it as a type and the runtime spread would fail.\n\t\tconst mergedEntries: Array<Entry> = dedupedExisting.map((e) =>\n\t\t\trequiredValueSet.has(e.name) && e.isType ? { ...e, isType: false } : e,\n\t\t)\n\t\tconst handled = new Set(mergedEntries.map((e) => e.name))\n\t\tfor (const n of requiredValueNames) {\n\t\t\tif (!handled.has(n)) {\n\t\t\t\tmergedEntries.push({ name: n, isType: false })\n\t\t\t\thandled.add(n)\n\t\t\t}\n\t\t}\n\t\tfor (const n of requiredTypeNames) {\n\t\t\tif (!handled.has(n)) {\n\t\t\t\tmergedEntries.push({ name: n, isType: true })\n\t\t\t\thandled.add(n)\n\t\t\t}\n\t\t}\n\n\t\tconst isMultipleImports = allAddonImports.length > 1\n\t\tconst noPromotionNeeded = dedupedExisting.every(\n\t\t\t(e) => mergedEntries.find((m) => m.name === e.name)?.isType === e.isType,\n\t\t)\n\t\tconst allRequiredAlreadyValueImported = requiredValueNames.every((n) =>\n\t\t\tdedupedExisting.some((e) => e.name === n && !e.isType),\n\t\t)\n\t\tconst allRequiredAlreadyTypeImported = requiredTypeNames.every((n) =>\n\t\t\tdedupedExisting.some((e) => e.name === n && e.isType),\n\t\t)\n\t\t// Skip the rewrite only when there's a single import AND nothing about it\n\t\t// needs to change. With multiple imports we always merge to avoid duplicate\n\t\t// identifier bindings between them.\n\t\tconst nothingToDo =\n\t\t\t!isMultipleImports &&\n\t\t\tnoPromotionNeeded &&\n\t\t\tallRequiredAlreadyValueImported &&\n\t\t\tallRequiredAlreadyTypeImported\n\n\t\tif (!nothingToDo) {\n\t\t\t// The regex consumes the trailing newline, so include one in the\n\t\t\t// replacement; also tack on the project's semicolon style.\n\t\t\tconst replacement = `import {${eol}${mergedEntries\n\t\t\t\t.map((e) => `${indent}${formatEntry(e)},`)\n\t\t\t\t.join(eol)}${eol}} from ${quote}${PKG}${quote}${trailingSemi}${eol}`\n\t\t\t// Replace the first import with the merged version; delete the rest.\n\t\t\tlet firstReplaced = false\n\t\t\tnewContent = newContent.replace(ADDON_IMPORT_REGEX, () => {\n\t\t\t\tif (!firstReplaced) {\n\t\t\t\t\tfirstReplaced = true\n\t\t\t\t\treturn replacement\n\t\t\t\t}\n\t\t\t\treturn ''\n\t\t\t})\n\t\t\t// Tidy up any blank-line runs left behind by deleted imports.\n\t\t\tnewContent = newContent.replace(/(\\r?\\n){3,}/g, `${eol}${eol}`)\n\t\t}\n\n\t\t// Resolve the local binding names — if the user aliased an import we need\n\t\t// to reference the alias in the inserted spreads, not the original name.\n\t\tconst findLocalName = (name: string): string => {\n\t\t\tconst entry = mergedEntries.find((m) => m.name === name && !m.isType)\n\t\t\treturn entry?.alias ?? name\n\t\t}\n\t\tdefaultsLocalName = findLocalName('defaultPreviewParameters')\n\t\tdecoratorsLocalName = findLocalName('dependencyPreviewDecorators')\n\t}\n\n\tconst importsToInsert: string[] = []\n\tif (allAddonImports.length === 0) {\n\t\tconst addonImportBlock = [\n\t\t\t`import {`,\n\t\t\t`${indent}defaultPreviewParameters,`,\n\t\t\t`${indent}dependencyPreviewDecorators,${isTs ? `${eol}${indent}type StorybookPreviewConfig,` : ''}`,\n\t\t\t`} from ${quote}${PKG}${quote}${trailingSemi}`,\n\t\t].join(eol)\n\t\timportsToInsert.push(addonImportBlock)\n\t}\n\tif (!hasDependenciesJsonImport) {\n\t\timportsToInsert.push(\n\t\t\t`import dependenciesJson from ${quote}./dependency-previews.json${quote}${trailingSemi}`,\n\t\t)\n\t}\n\tif (importsToInsert.length > 0) {\n\t\tconst insertAt = findImportInsertionIndex(newContent)\n\t\tconst insertion = importsToInsert.join(eol + eol) + eol + eol\n\t\tnewContent =\n\t\t\tnewContent.slice(0, insertAt) + insertion + newContent.slice(insertAt)\n\t}\n\n\tconst block = dependencyPreviewsBlock(\n\t\tframework,\n\t\tsourceRootUrl,\n\t\tindent,\n\t\teol,\n\t\tquote,\n\t)\n\n\t/**\n\t * Locate the preview config object's body so all the key lookups below\n\t * are scoped to *that* object. Without this, a `parameters:` /\n\t * `decorators:` belonging to some unrelated object earlier in the file\n\t * would be matched instead of the one we want to patch.\n\t *\n\t * The opener regex runs against the position-preserving comment-stripped\n\t * text so an example like `// const preview: Preview = { ... }` in a\n\t * comment can't hijack the search — `match.index` from the stripped\n\t * content lines up with the original.\n\t */\n\tconst findPreviewBody = (\n\t\ttext: string,\n\t): { from: number; to: number } | null => {\n\t\tconst stripped = stripCommentsRespectingStrings(text)\n\t\tconst bodyAt = (\n\t\t\tmatch: RegExpMatchArray | null,\n\t\t): { from: number; to: number } | null => {\n\t\t\tif (!match || match.index === undefined) return null\n\t\t\tconst openBraceIdx = match.index + match[0].length - 1\n\t\t\tif (text[openBraceIdx] !== '{') return null\n\t\t\tconst closeIdx = findMatchingBrace(text, openBraceIdx)\n\t\t\tif (closeIdx === null) return null\n\t\t\treturn { from: openBraceIdx + 1, to: closeIdx }\n\t\t}\n\n\t\t// Direct patterns: typed preview (`Preview = {`, `StorybookPreviewConfig = {`)\n\t\t// or anonymous default export (`export default {`).\n\t\tconst direct = bodyAt(\n\t\t\tstripped.match(\n\t\t\t\t/(StorybookPreviewConfig\\s*=\\s*\\{|Preview\\s*=\\s*\\{|export\\s+default\\s*\\{)/,\n\t\t\t),\n\t\t)\n\t\tif (direct) return direct\n\n\t\t// Fallback: untyped `const preview = { … }; export default preview` (common\n\t\t// in `.js` / `.jsx` preview files where there's no type annotation).\n\t\t// Resolve `export default <ident>` to its `(const|let|var) <ident> = {…}`\n\t\t// declaration and use that object's body.\n\t\tconst exportIdent = stripped.match(\n\t\t\t/export\\s+default\\s+([A-Za-z_$][\\w$]*)\\b/,\n\t\t)?.[1]\n\t\tif (!exportIdent) return null\n\t\tconst escaped = exportIdent.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n\t\treturn bodyAt(\n\t\t\tstripped.match(new RegExp(`(?:const|let|var)\\\\s+${escaped}\\\\s*=\\\\s*\\\\{`)),\n\t\t)\n\t}\n\n\tconst bodyRange = findPreviewBody(newContent)\n\tif (!bodyRange) {\n\t\treturn {\n\t\t\tkind: 'failed',\n\t\t\treason:\n\t\t\t\t'Could not locate the preview config object — please add the dependencyPreviews parameters and decorators manually.',\n\t\t}\n\t}\n\n\t// If we end up creating a brand-new `parameters:` key, remember the position\n\t// right after it so a brand-new `decorators:` insertion can land *after* it\n\t// rather than at the same body-start position.\n\tlet paramsCreatedEndOffset: number | null = null\n\n\tconst paramsKey = findTopLevelKey(newContent, 'parameters', bodyRange)\n\tif (paramsKey && newContent[paramsKey.valueStart] === '{') {\n\t\t// Check for an existing `...<local>` spread, but only inside the parameters\n\t\t// object itself (and only in real code, not comments or strings). A match\n\t\t// anywhere else in the file would falsely suppress the spread we need to\n\t\t// insert. `<local>` is the project's local binding for `defaultPreviewParameters`,\n\t\t// which differs from the canonical name when the user has aliased the import.\n\t\tconst paramsBodyEnd = findMatchingBrace(newContent, paramsKey.valueStart)\n\t\tconst paramsBodyStart = paramsKey.valueStart + 1\n\t\tconst localSpreadRegex = new RegExp(\n\t\t\tString.raw`\\.\\.\\.${defaultsLocalName.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}\\b`,\n\t\t)\n\t\tconst hasDefaultParamsSpread =\n\t\t\tparamsBodyEnd !== null &&\n\t\t\tlocalSpreadRegex.test(\n\t\t\t\tstripCommentsRespectingStrings(\n\t\t\t\t\tnewContent.slice(paramsBodyStart, paramsBodyEnd),\n\t\t\t\t),\n\t\t\t)\n\t\tconst insertAt = paramsBodyStart\n\t\tconst insertion = hasDefaultParamsSpread\n\t\t\t? `${eol}${block}`\n\t\t\t: `${eol}${l2}...${defaultsLocalName},${eol}${block}`\n\t\tnewContent =\n\t\t\tnewContent.slice(0, insertAt) + insertion + newContent.slice(insertAt)\n\t} else if (paramsKey) {\n\t\t// `parameters:` exists but isn't a literal `{ … }` object (probably a\n\t\t// variable, spread, or function call). Inserting another `parameters:`\n\t\t// would produce a duplicate key — bail with guidance instead.\n\t\treturn {\n\t\t\tkind: 'failed',\n\t\t\treason:\n\t\t\t\t'Preview config already defines `parameters` in a non-literal-object form — please manually add `...defaultPreviewParameters` and the `dependencyPreviews` block to the existing parameters definition.',\n\t\t}\n\t} else {\n\t\tconst insertAt = bodyRange.from\n\t\tconst insertion = `${eol}${l1}parameters: {${eol}${l2}...${defaultsLocalName},${eol}${block}${eol}${l1}},`\n\t\tnewContent =\n\t\t\tnewContent.slice(0, insertAt) + insertion + newContent.slice(insertAt)\n\t\t// Track where the just-inserted parameters block ends, so a subsequent\n\t\t// \"create new decorators\" can be placed *after* it (the bodyRange.from\n\t\t// position is shared with parameters; without this offset, decorators\n\t\t// would be inserted at body-start and end up *before* parameters).\n\t\tparamsCreatedEndOffset = insertAt + insertion.length\n\t}\n\n\t// Re-find the body range after the params insertion — the body's closing\n\t// brace position has shifted but the helper handles that transparently.\n\tconst bodyRangeAfterParams = findPreviewBody(newContent) ?? bodyRange\n\tconst decoratorsKey = findTopLevelKey(\n\t\tnewContent,\n\t\t'decorators',\n\t\tbodyRangeAfterParams,\n\t)\n\tif (decoratorsKey && newContent[decoratorsKey.valueStart] === '[') {\n\t\t// Mirror the parameters-spread idempotency: only insert the spread if the\n\t\t// decorators array doesn't already contain `...<localName>`. Catches the\n\t\t// \"user partially set up by hand\" case (the wizard may have just added\n\t\t// `dependencyPreviews:` parameters and the user might already have wired\n\t\t// the decorators).\n\t\tconst decoratorsBodyEnd = findMatchingBrace(\n\t\t\tnewContent,\n\t\t\tdecoratorsKey.valueStart,\n\t\t)\n\t\tconst decoratorsBodyStart = decoratorsKey.valueStart + 1\n\t\tconst localDecoratorsSpreadRegex = new RegExp(\n\t\t\tString.raw`\\.\\.\\.${decoratorsLocalName.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}\\b`,\n\t\t)\n\t\tconst hasDecoratorsSpread =\n\t\t\tdecoratorsBodyEnd !== null &&\n\t\t\tlocalDecoratorsSpreadRegex.test(\n\t\t\t\tstripCommentsRespectingStrings(\n\t\t\t\t\tnewContent.slice(decoratorsBodyStart, decoratorsBodyEnd),\n\t\t\t\t),\n\t\t\t)\n\t\tif (!hasDecoratorsSpread) {\n\t\t\tconst insertAt = decoratorsBodyStart\n\t\t\tconst insertion = `${eol}${l2}...${decoratorsLocalName},`\n\t\t\tnewContent =\n\t\t\t\tnewContent.slice(0, insertAt) + insertion + newContent.slice(insertAt)\n\t\t}\n\t} else if (decoratorsKey) {\n\t\treturn {\n\t\t\tkind: 'failed',\n\t\t\treason:\n\t\t\t\t'Preview config already defines `decorators` in a non-literal-array form — please manually add `...dependencyPreviewDecorators` to the existing decorators definition.',\n\t\t}\n\t} else {\n\t\t// If we just inserted a brand-new `parameters:` key in this same run,\n\t\t// position the new `decorators:` *after* it. Otherwise place it at the\n\t\t// start of the preview config body.\n\t\tconst insertAt = paramsCreatedEndOffset ?? bodyRangeAfterParams.from\n\t\tconst insertion = `${eol}${l1}decorators: [...${decoratorsLocalName}],`\n\t\tnewContent =\n\t\t\tnewContent.slice(0, insertAt) + insertion + newContent.slice(insertAt)\n\t}\n\n\ttry {\n\t\twriteFileSync(previewFile.path, newContent, 'utf8')\n\t} catch (e) {\n\t\treturn {\n\t\t\tkind: 'failed',\n\t\t\treason: `Could not write ${previewFile.path}: ${(e as Error).message}`,\n\t\t}\n\t}\n\treturn { kind: 'patched', path: previewFile.path }\n}\n\nexport interface PatchPreviewFileOptions {\n\t/** Absolute path to the project's `.storybook/` directory. */\n\tstorybookDir: string\n\t/**\n\t * Existing preview file detected in `.storybook/` (any of preview.ts /\n\t * .tsx / .js / .jsx), or `null` if no preview file exists yet — in which\n\t * case the patcher creates a fresh one from the framework template.\n\t */\n\tpreviewFile: PreviewFile | null\n\t/**\n\t * Detected `.storybook/main.{ts,js,…}`. Used to derive the indent and EOL\n\t * style for any newly-created preview file so it matches the project's\n\t * existing code style.\n\t */\n\tmainFile: MainFile\n\t/**\n\t * Detected (or user-confirmed) Storybook framework. Determines the story\n\t * glob pattern injected into `dependencyPreviews.storyModules` and which\n\t * framework template is used when creating a new preview file.\n\t */\n\tframework: Framework\n\t/**\n\t * URL pointing at the project's source-code root (e.g. a GitHub blob\n\t * URL). Powers the addon's \"view source\" links in the Storybook UI. May\n\t * be empty — the addon's runtime treats `''` as \"no source links\" and\n\t * the field is required by the addon's parameter type so we always emit\n\t * it.\n\t */\n\tsourceRootUrl: string\n}\n\n/**\n * Patch (or create) the project's `.storybook/preview.{ts,tsx,js,jsx}` so\n * the addon's parameters and decorators are wired in. Idempotent —\n * re-runs against an already-configured preview return `{ kind: 'skipped' }`.\n */\nexport function patchPreviewFile(\n\topts: PatchPreviewFileOptions,\n): PreviewPatchResult {\n\tconst { storybookDir, previewFile, mainFile, framework, sourceRootUrl } = opts\n\n\tif (\n\t\tframework !== 'react-vite' &&\n\t\tframework !== 'sveltekit' &&\n\t\tframework !== 'svelte-vite'\n\t) {\n\t\treturn {\n\t\t\tkind: 'failed',\n\t\t\treason: `Preview patcher does not support framework \"${framework}\".`,\n\t\t}\n\t}\n\n\tif (previewFile) {\n\t\treturn patchExistingPreview(previewFile, framework, sourceRootUrl)\n\t}\n\n\t// Derive indent and EOL from the existing main.{ts,js,...} so the brand-new\n\t// preview file matches the project's style instead of always using tabs / LF.\n\tlet style: TemplateStyle = { indent: '\\t', eol: '\\n' }\n\ttry {\n\t\tconst mainContent = readFileSync(mainFile.path, 'utf8')\n\t\tstyle = {\n\t\t\tindent: detectFileIndent(mainContent),\n\t\t\teol: detectEol(mainContent),\n\t\t}\n\t} catch {\n\t\t// If main.ts can't be read for some reason, fall back to the defaults.\n\t}\n\n\tconst { content, lang } = templateForFramework(\n\t\tframework,\n\t\tsourceRootUrl,\n\t\tstyle,\n\t\tmainFile.lang,\n\t)\n\tconst path = resolve(storybookDir, `preview.${lang}`)\n\tif (existsSync(path)) {\n\t\treturn { kind: 'skipped', reason: `${path} already exists` }\n\t}\n\ttry {\n\t\twriteFileSync(path, content, 'utf8')\n\t} catch (e) {\n\t\treturn {\n\t\t\tkind: 'failed',\n\t\t\treason: `Could not create ${path}: ${(e as Error).message}`,\n\t\t}\n\t}\n\treturn { kind: 'created', path }\n}\n"],"mappings":"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"}
|
|
@@ -10,7 +10,10 @@ function PrimaryPreview({ storyInfo }) {
|
|
|
10
10
|
const [hasLoaded, setHasLoaded] = (0, react.useState)(false);
|
|
11
11
|
const { csfModule, primaryExport, primaryId, error, isLoading } = require_useDynamicStory.useDynamicStory(storyInfo);
|
|
12
12
|
const message = error || isLoading && "Loading preview…" || !csfModule && "Module could not be loaded." || !primaryExport && "No story export found.";
|
|
13
|
-
if (message) return /* @__PURE__ */ react.default.createElement("p", { style: {
|
|
13
|
+
if (message) return /* @__PURE__ */ react.default.createElement("p", { style: {
|
|
14
|
+
opacity: .7,
|
|
15
|
+
whiteSpace: "pre-wrap"
|
|
16
|
+
} }, message);
|
|
14
17
|
return /* @__PURE__ */ react.default.createElement("div", { className: "grid grid-cols-1 grid-rows-1" }, !hasLoaded && /* @__PURE__ */ react.default.createElement("div", { className: "col-start-1 row-start-1 grid place-items-center" }, /* @__PURE__ */ react.default.createElement(require_LoadingRipples.LoadingRipples, null)), /* @__PURE__ */ react.default.createElement("div", { className: "col-start-1 row-start-1" }, /* @__PURE__ */ react.default.createElement(require_FullParityStory.FullParityStory, {
|
|
15
18
|
storyId: primaryId,
|
|
16
19
|
args: primaryExport.args,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PrimaryPreview.cjs","names":["useDynamicStory","LoadingRipples","FullParityStory"],"sources":["../../src/components/PrimaryPreview.tsx"],"sourcesContent":["import React, { useState } from 'react'\nimport { useDynamicStory } from '../hooks/useDynamicStory'\nimport type { StoryInfo } from '../types'\nimport { FullParityStory } from './FullParityStory'\nimport { LoadingRipples } from './icons/LoadingRipples'\n\ninterface PropsForPrimaryPreview {\n\tstoryInfo: StoryInfo\n}\n\nexport function PrimaryPreview({ storyInfo }: PropsForPrimaryPreview) {\n\tconst [hasLoaded, setHasLoaded] = useState(false)\n\tconst { csfModule, primaryExport, primaryId, error, isLoading } =\n\t\tuseDynamicStory(storyInfo)\n\tconst message =\n\t\terror ||\n\t\t(isLoading && 'Loading preview…') ||\n\t\t(!csfModule && 'Module could not be loaded.') ||\n\t\t(!primaryExport && 'No story export found.')\n\n\tif (message)
|
|
1
|
+
{"version":3,"file":"PrimaryPreview.cjs","names":["useDynamicStory","LoadingRipples","FullParityStory"],"sources":["../../src/components/PrimaryPreview.tsx"],"sourcesContent":["import React, { useState } from 'react'\nimport { useDynamicStory } from '../hooks/useDynamicStory'\nimport type { StoryInfo } from '../types'\nimport { FullParityStory } from './FullParityStory'\nimport { LoadingRipples } from './icons/LoadingRipples'\n\ninterface PropsForPrimaryPreview {\n\tstoryInfo: StoryInfo\n}\n\nexport function PrimaryPreview({ storyInfo }: PropsForPrimaryPreview) {\n\tconst [hasLoaded, setHasLoaded] = useState(false)\n\tconst { csfModule, primaryExport, primaryId, error, isLoading } =\n\t\tuseDynamicStory(storyInfo)\n\tconst message =\n\t\terror ||\n\t\t(isLoading && 'Loading preview…') ||\n\t\t(!csfModule && 'Module could not be loaded.') ||\n\t\t(!primaryExport && 'No story export found.')\n\n\t// `pre-wrap` preserves the line breaks in multi-line diagnostics like the\n\t// `buildMissingStoryModuleError` output (lookup key, key count, sample keys).\n\t// Single-line statuses (\"Loading preview…\", \"Module could not be loaded.\")\n\t// render identically either way.\n\tif (message)\n\t\treturn (\n\t\t\t<p style={{ opacity: 0.7, whiteSpace: 'pre-wrap' }}>{message}</p>\n\t\t)\n\n\treturn (\n\t\t<div className=\"grid grid-cols-1 grid-rows-1\">\n\t\t\t{!hasLoaded && (\n\t\t\t\t<div className=\"col-start-1 row-start-1 grid place-items-center\">\n\t\t\t\t\t<LoadingRipples />\n\t\t\t\t</div>\n\t\t\t)}\n\t\t\t<div className=\"col-start-1 row-start-1\">\n\t\t\t\t<FullParityStory\n\t\t\t\t\tstoryId={primaryId!}\n\t\t\t\t\targs={primaryExport.args}\n\t\t\t\t\tonLoad={() => setHasLoaded(true)}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n"],"mappings":";;;;;;;;AAUA,SAAgB,eAAe,EAAE,aAAqC;CACrE,MAAM,CAAC,WAAW,oCAAyB,MAAM;CACjD,MAAM,EAAE,WAAW,eAAe,WAAW,OAAO,cACnDA,wCAAgB,UAAU;CAC3B,MAAM,UACL,SACC,aAAa,sBACb,CAAC,aAAa,iCACd,CAAC,iBAAiB;AAMpB,KAAI,QACH,QACC,4CAAC,OAAE,OAAO;EAAE,SAAS;EAAK,YAAY;EAAY,IAAG,QAAY;AAGnE,QACC,4CAAC,SAAI,WAAU,kCACb,CAAC,aACD,4CAAC,SAAI,WAAU,qDACd,4CAACC,4CAAiB,CACb,EAEP,4CAAC,SAAI,WAAU,6BACd,4CAACC;EACA,SAAS;EACT,MAAM,cAAc;EACpB,cAAc,aAAa,KAAK;GAC/B,CACG,CACD"}
|
|
@@ -8,7 +8,10 @@ function PrimaryPreview({ storyInfo }) {
|
|
|
8
8
|
const [hasLoaded, setHasLoaded] = useState(false);
|
|
9
9
|
const { csfModule, primaryExport, primaryId, error, isLoading } = useDynamicStory(storyInfo);
|
|
10
10
|
const message = error || isLoading && "Loading preview…" || !csfModule && "Module could not be loaded." || !primaryExport && "No story export found.";
|
|
11
|
-
if (message) return /* @__PURE__ */ React.createElement("p", { style: {
|
|
11
|
+
if (message) return /* @__PURE__ */ React.createElement("p", { style: {
|
|
12
|
+
opacity: .7,
|
|
13
|
+
whiteSpace: "pre-wrap"
|
|
14
|
+
} }, message);
|
|
12
15
|
return /* @__PURE__ */ React.createElement("div", { className: "grid grid-cols-1 grid-rows-1" }, !hasLoaded && /* @__PURE__ */ React.createElement("div", { className: "col-start-1 row-start-1 grid place-items-center" }, /* @__PURE__ */ React.createElement(LoadingRipples, null)), /* @__PURE__ */ React.createElement("div", { className: "col-start-1 row-start-1" }, /* @__PURE__ */ React.createElement(FullParityStory, {
|
|
13
16
|
storyId: primaryId,
|
|
14
17
|
args: primaryExport.args,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PrimaryPreview.mjs","names":[],"sources":["../../src/components/PrimaryPreview.tsx"],"sourcesContent":["import React, { useState } from 'react'\nimport { useDynamicStory } from '../hooks/useDynamicStory'\nimport type { StoryInfo } from '../types'\nimport { FullParityStory } from './FullParityStory'\nimport { LoadingRipples } from './icons/LoadingRipples'\n\ninterface PropsForPrimaryPreview {\n\tstoryInfo: StoryInfo\n}\n\nexport function PrimaryPreview({ storyInfo }: PropsForPrimaryPreview) {\n\tconst [hasLoaded, setHasLoaded] = useState(false)\n\tconst { csfModule, primaryExport, primaryId, error, isLoading } =\n\t\tuseDynamicStory(storyInfo)\n\tconst message =\n\t\terror ||\n\t\t(isLoading && 'Loading preview…') ||\n\t\t(!csfModule && 'Module could not be loaded.') ||\n\t\t(!primaryExport && 'No story export found.')\n\n\tif (message)
|
|
1
|
+
{"version":3,"file":"PrimaryPreview.mjs","names":[],"sources":["../../src/components/PrimaryPreview.tsx"],"sourcesContent":["import React, { useState } from 'react'\nimport { useDynamicStory } from '../hooks/useDynamicStory'\nimport type { StoryInfo } from '../types'\nimport { FullParityStory } from './FullParityStory'\nimport { LoadingRipples } from './icons/LoadingRipples'\n\ninterface PropsForPrimaryPreview {\n\tstoryInfo: StoryInfo\n}\n\nexport function PrimaryPreview({ storyInfo }: PropsForPrimaryPreview) {\n\tconst [hasLoaded, setHasLoaded] = useState(false)\n\tconst { csfModule, primaryExport, primaryId, error, isLoading } =\n\t\tuseDynamicStory(storyInfo)\n\tconst message =\n\t\terror ||\n\t\t(isLoading && 'Loading preview…') ||\n\t\t(!csfModule && 'Module could not be loaded.') ||\n\t\t(!primaryExport && 'No story export found.')\n\n\t// `pre-wrap` preserves the line breaks in multi-line diagnostics like the\n\t// `buildMissingStoryModuleError` output (lookup key, key count, sample keys).\n\t// Single-line statuses (\"Loading preview…\", \"Module could not be loaded.\")\n\t// render identically either way.\n\tif (message)\n\t\treturn (\n\t\t\t<p style={{ opacity: 0.7, whiteSpace: 'pre-wrap' }}>{message}</p>\n\t\t)\n\n\treturn (\n\t\t<div className=\"grid grid-cols-1 grid-rows-1\">\n\t\t\t{!hasLoaded && (\n\t\t\t\t<div className=\"col-start-1 row-start-1 grid place-items-center\">\n\t\t\t\t\t<LoadingRipples />\n\t\t\t\t</div>\n\t\t\t)}\n\t\t\t<div className=\"col-start-1 row-start-1\">\n\t\t\t\t<FullParityStory\n\t\t\t\t\tstoryId={primaryId!}\n\t\t\t\t\targs={primaryExport.args}\n\t\t\t\t\tonLoad={() => setHasLoaded(true)}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n"],"mappings":";;;;;;AAUA,SAAgB,eAAe,EAAE,aAAqC;CACrE,MAAM,CAAC,WAAW,gBAAgB,SAAS,MAAM;CACjD,MAAM,EAAE,WAAW,eAAe,WAAW,OAAO,cACnD,gBAAgB,UAAU;CAC3B,MAAM,UACL,SACC,aAAa,sBACb,CAAC,aAAa,iCACd,CAAC,iBAAiB;AAMpB,KAAI,QACH,QACC,oCAAC,OAAE,OAAO;EAAE,SAAS;EAAK,YAAY;EAAY,IAAG,QAAY;AAGnE,QACC,oCAAC,SAAI,WAAU,kCACb,CAAC,aACD,oCAAC,SAAI,WAAU,qDACd,oCAAC,qBAAiB,CACb,EAEP,oCAAC,SAAI,WAAU,6BACd,oCAAC;EACA,SAAS;EACT,MAAM,cAAc;EACpB,cAAc,aAAa,KAAK;GAC/B,CACG,CACD"}
|
|
@@ -4,14 +4,36 @@ let react = require("react");
|
|
|
4
4
|
react = require_rolldown_runtime.__toESM(react);
|
|
5
5
|
|
|
6
6
|
//#region src/hooks/useDynamicStory.ts
|
|
7
|
+
function normalizeStoryPath(p) {
|
|
8
|
+
return "/" + p.replace(/^[./]+/, "").replace(/\\/g, "/");
|
|
9
|
+
}
|
|
7
10
|
function findStoryImporter(storiesGlob, storyFilePath) {
|
|
8
11
|
if (!storyFilePath) {
|
|
9
12
|
return null;
|
|
10
13
|
}
|
|
11
|
-
const
|
|
12
|
-
const wanted = norm(storyFilePath);
|
|
14
|
+
const wanted = normalizeStoryPath(storyFilePath);
|
|
13
15
|
return storiesGlob[wanted] || Object.entries(storiesGlob).find(([k]) => k.endsWith(wanted))?.[1];
|
|
14
16
|
}
|
|
17
|
+
function buildMissingStoryModuleError(storiesGlob, storyInfo) {
|
|
18
|
+
const keys = Object.keys(storiesGlob);
|
|
19
|
+
const rawPath = storyInfo.storyFilePath ?? "(no storyFilePath in graph entry)";
|
|
20
|
+
const normalised = storyInfo.storyFilePath ? normalizeStoryPath(storyInfo.storyFilePath) : "(n/a)";
|
|
21
|
+
const lines = [
|
|
22
|
+
`No story module found at this path: ${rawPath}`,
|
|
23
|
+
` Normalised lookup key: ${normalised}`,
|
|
24
|
+
` storyModules key count: ${keys.length}`
|
|
25
|
+
];
|
|
26
|
+
if (keys.length === 0) {
|
|
27
|
+
lines.push(" Hint: your `import.meta.glob(...)` pattern matched zero files. If it uses", " the extglob syntax `@(a|b|c)` you are likely on Vite 8 (tinyglobby),", " which does not support extglob — switch to brace expansion", " `{a,b,c}` instead. Example:", " import.meta.glob('/src/**/*.stories.{tsx,ts,jsx,js,svelte}', { eager: false })");
|
|
28
|
+
} else {
|
|
29
|
+
const sample = keys.slice(0, 10);
|
|
30
|
+
lines.push(" Sample storyModules keys (first 10):", ...sample.map((k) => ` - ${k}`));
|
|
31
|
+
if (keys.length > sample.length) {
|
|
32
|
+
lines.push(` …and ${keys.length - sample.length} more`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return lines.join("\n");
|
|
36
|
+
}
|
|
15
37
|
function useDynamicStory(storyInfo) {
|
|
16
38
|
const { dependencyPreviews } = require_useStoryParams.useStoryParams();
|
|
17
39
|
const modules = dependencyPreviews.storyModules;
|
|
@@ -22,21 +44,27 @@ function useDynamicStory(storyInfo) {
|
|
|
22
44
|
(0, react.useEffect)(() => {
|
|
23
45
|
let alive = true;
|
|
24
46
|
setIsLoading(true);
|
|
47
|
+
setErr(null);
|
|
48
|
+
setCsfModule(null);
|
|
25
49
|
(async () => {
|
|
26
50
|
try {
|
|
27
|
-
if (!importer) throw new Error(
|
|
51
|
+
if (!importer) throw new Error(buildMissingStoryModuleError(modules, storyInfo));
|
|
28
52
|
const mod = await importer();
|
|
29
53
|
if (alive) setCsfModule(mod);
|
|
30
54
|
} catch (e) {
|
|
31
55
|
if (alive) setErr(e?.message || String(e));
|
|
32
56
|
} finally {
|
|
33
|
-
setIsLoading(false);
|
|
57
|
+
if (alive) setIsLoading(false);
|
|
34
58
|
}
|
|
35
59
|
})();
|
|
36
60
|
return () => {
|
|
37
61
|
alive = false;
|
|
38
62
|
};
|
|
39
|
-
}, [
|
|
63
|
+
}, [
|
|
64
|
+
importer,
|
|
65
|
+
modules,
|
|
66
|
+
storyInfo
|
|
67
|
+
]);
|
|
40
68
|
const primaryExport = getPrimaryStoryExport(csfModule, storyInfo);
|
|
41
69
|
const Component = csfModule?.default?.component;
|
|
42
70
|
const primaryId = storyInfo.storyId?.replace(/--docs$/, getPrimaryId(csfModule));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useDynamicStory.cjs","names":["useStoryParams","e: any","order: string[]"],"sources":["../../src/hooks/useDynamicStory.ts"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react'\nimport type { CsfModule, StoryInfo, StoryModules } from '../types'\nimport { useStoryParams } from './useStoryParams'\n\n// grab all stories; Vite will code-split them\n\
|
|
1
|
+
{"version":3,"file":"useDynamicStory.cjs","names":["useStoryParams","e: any","order: string[]"],"sources":["../../src/hooks/useDynamicStory.ts"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react'\nimport type { CsfModule, StoryInfo, StoryModules } from '../types'\nimport { useStoryParams } from './useStoryParams'\n\n// grab all stories; Vite will code-split them\n\n// Normalise a graph-JSON `storyFilePath` to Vite’s absolute-style glob key shape\n// (leading slash, forward slashes only, no leading `./` segments).\nfunction normalizeStoryPath(p: string) {\n\treturn '/' + p.replace(/^[./]+/, '').replace(/\\\\/g, '/')\n}\n\nfunction findStoryImporter(\n\tstoriesGlob: StoryModules,\n\tstoryFilePath: string | undefined | null,\n) {\n\tif (!storyFilePath) {\n\t\treturn null\n\t}\n\tconst wanted = normalizeStoryPath(storyFilePath)\n\t// exact match first, else suffix match as a fallback\n\treturn (\n\t\tstoriesGlob[wanted] ||\n\t\tObject.entries(storiesGlob).find(([k]) => k.endsWith(wanted))?.[1]\n\t)\n}\n\n// Compose an actionable error for the \"no story module matched\" failure mode. The\n// common cause — and the reason this helper exists — is the wizard-generated preview\n// shipping the extglob syntax `@(a|b|c)` which silently returns zero matches under\n// Vite 8 (tinyglobby). When the glob is empty, point the user at that fix; when the\n// glob has keys but none match, show a sample so the path-format mismatch is visible.\nfunction buildMissingStoryModuleError(\n\tstoriesGlob: StoryModules,\n\tstoryInfo: StoryInfo,\n): string {\n\tconst keys = Object.keys(storiesGlob)\n\tconst rawPath = storyInfo.storyFilePath ?? '(no storyFilePath in graph entry)'\n\tconst normalised = storyInfo.storyFilePath\n\t\t? normalizeStoryPath(storyInfo.storyFilePath)\n\t\t: '(n/a)'\n\tconst lines = [\n\t\t`No story module found at this path: ${rawPath}`,\n\t\t` Normalised lookup key: ${normalised}`,\n\t\t` storyModules key count: ${keys.length}`,\n\t]\n\tif (keys.length === 0) {\n\t\tlines.push(\n\t\t\t' Hint: your `import.meta.glob(...)` pattern matched zero files. If it uses',\n\t\t\t' the extglob syntax `@(a|b|c)` you are likely on Vite 8 (tinyglobby),',\n\t\t\t' which does not support extglob — switch to brace expansion',\n\t\t\t' `{a,b,c}` instead. Example:',\n\t\t\t\" import.meta.glob('/src/**/*.stories.{tsx,ts,jsx,js,svelte}', { eager: false })\",\n\t\t)\n\t} else {\n\t\tconst sample = keys.slice(0, 10)\n\t\tlines.push(\n\t\t\t' Sample storyModules keys (first 10):',\n\t\t\t...sample.map((k) => ` - ${k}`),\n\t\t)\n\t\tif (keys.length > sample.length) {\n\t\t\tlines.push(` …and ${keys.length - sample.length} more`)\n\t\t}\n\t}\n\treturn lines.join('\\n')\n}\n\nexport function useDynamicStory(storyInfo: StoryInfo) {\n\tconst { dependencyPreviews } = useStoryParams()\n\tconst modules = dependencyPreviews.storyModules\n\tconst importer = useMemo(\n\t\t() => findStoryImporter(modules, storyInfo.storyFilePath),\n\t\t[modules, storyInfo.storyFilePath],\n\t)\n\tconst [csfModule, setCsfModule] = useState<CsfModule | null>(null)\n\tconst [err, setErr] = useState<string | null>(null)\n\tconst [isLoading, setIsLoading] = useState(true)\n\n\tuseEffect(() => {\n\t\tlet alive = true\n\t\t// Reset before kicking off the new load: PrimaryPreview checks `error` first,\n\t\t// so a stale error from a previous story would keep rendering even after a\n\t\t// successful import for the new one. Same logic for `csfModule` — clearing it\n\t\t// makes the loading state visible while the new module resolves.\n\t\tsetIsLoading(true)\n\t\tsetErr(null)\n\t\tsetCsfModule(null)\n\t\t;(async () => {\n\t\t\ttry {\n\t\t\t\tif (!importer)\n\t\t\t\t\tthrow new Error(buildMissingStoryModuleError(modules, storyInfo))\n\t\t\t\tconst mod = await importer()\n\t\t\t\tif (alive) setCsfModule(mod as CsfModule)\n\t\t\t} catch (e: any) {\n\t\t\t\tif (alive) setErr(e?.message || String(e))\n\t\t\t} finally {\n\t\t\t\tif (alive) setIsLoading(false)\n\t\t\t}\n\t\t})()\n\t\treturn () => {\n\t\t\talive = false\n\t\t}\n\t}, [importer, modules, storyInfo])\n\n\tconst primaryExport = getPrimaryStoryExport(csfModule, storyInfo)\n\tconst Component = csfModule?.default?.component\n\tconst primaryId = storyInfo.storyId?.replace(\n\t\t/--docs$/,\n\t\tgetPrimaryId(csfModule),\n\t)\n\n\treturn {\n\t\tisLoading,\n\t\tprimaryExport,\n\t\tprimaryId,\n\t\tcsfModule,\n\t\tComponent,\n\t\terror: err,\n\t}\n}\n\nfunction getPrimaryId(csfModule: CsfModule | undefined | null) {\n\tif (!csfModule) return '--default'\n\tif ('Default' in csfModule) return '--default'\n\tif ('Primary' in csfModule) return '--primary'\n\treturn (\n\t\t'--' + slugifyExportName(csfModule?.__namedExportsOrder?.[0] || 'default')\n\t)\n}\n\n// Kebab-case like Storybook: \"ErrorStrings\" -> \"error-strings\"\nfunction slugifyExportName(name: string) {\n\treturn (\n\t\tname\n\t\t\t// split camelCase / PascalCase boundaries\n\t\t\t.replace(/([a-z0-9])([A-Z])/g, '$1-$2')\n\t\t\t.replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2') // ABCDef -> ABC-Def\n\t\t\t// underscores/spaces -> dashes\n\t\t\t.replace(/[_\\s]+/g, '-')\n\t\t\t// strip anything not alnum or dash\n\t\t\t.replace(/[^a-zA-Z0-9-]/g, '-')\n\t\t\t// lowercase + collapse/trim dashes\n\t\t\t.toLowerCase()\n\t\t\t.replace(/--+/g, '-')\n\t\t\t.replace(/^-+|-+$/g, '')\n\t)\n}\n\n/** Choose a sensible story:\n * 1) If the module has `Primary`, use it.\n * 2) If we have a docs/primary id, try to match the export name in the id.\n * 3) Else fall back to the first named export.\n */\nfunction getPrimaryStoryExport(mod: any, info?: StoryInfo) {\n\tif (!mod) return null\n\n\t// 1) Prefer \"Default\" or \"Primary\" if present\n\tif ('Default' in mod) return mod.Default\n\tif ('Primary' in mod) return mod.Primary\n\n\t// 2) If we have a storyId like \"…--errorstrings\", try to match export name\n\tif (info?.storyId && !info.storyId.endsWith('--docs')) {\n\t\tconst exportName = info.storyId.split('--').pop() // e.g. \"errorstrings\"\n\t\tconst match = Object.keys(mod).find(\n\t\t\t(k) => k !== 'default' && k.toLowerCase() === exportName,\n\t\t)\n\t\tif (match) return (mod as any)[match]\n\t}\n\n\t// 3) Fall back to first named export\n\tconst order: string[] = mod.__namedExportsOrder || []\n\tconst first = order.find((k) => k !== 'default')\n\tif (first && first in mod) return (mod as any)[first]\n\n\t// Last resort: any non-default function export\n\tconst anyKey = Object.keys(mod).find(\n\t\t(k) => k !== 'default' && typeof mod[k] === 'function',\n\t)\n\treturn anyKey ? (mod as any)[anyKey] : null\n}\n"],"mappings":";;;;;;AAQA,SAAS,mBAAmB,GAAW;AACtC,QAAO,MAAM,EAAE,QAAQ,UAAU,GAAG,CAAC,QAAQ,OAAO,IAAI;;AAGzD,SAAS,kBACR,aACA,eACC;AACD,KAAI,CAAC,eAAe;AACnB,SAAO;;CAER,MAAM,SAAS,mBAAmB,cAAc;AAEhD,QACC,YAAY,WACZ,OAAO,QAAQ,YAAY,CAAC,MAAM,CAAC,OAAO,EAAE,SAAS,OAAO,CAAC,GAAG;;AASlE,SAAS,6BACR,aACA,WACS;CACT,MAAM,OAAO,OAAO,KAAK,YAAY;CACrC,MAAM,UAAU,UAAU,iBAAiB;CAC3C,MAAM,aAAa,UAAU,gBAC1B,mBAAmB,UAAU,cAAc,GAC3C;CACH,MAAM,QAAQ;EACb,uCAAuC;EACvC,4BAA4B;EAC5B,6BAA6B,KAAK;EAClC;AACD,KAAI,KAAK,WAAW,GAAG;AACtB,QAAM,KACL,+EACA,gFACA,sEACA,uCACA,yFACA;QACK;EACN,MAAM,SAAS,KAAK,MAAM,GAAG,GAAG;AAChC,QAAM,KACL,0CACA,GAAG,OAAO,KAAK,MAAM,SAAS,IAAI,CAClC;AACD,MAAI,KAAK,SAAS,OAAO,QAAQ;AAChC,SAAM,KAAK,YAAY,KAAK,SAAS,OAAO,OAAO,OAAO;;;AAG5D,QAAO,MAAM,KAAK,KAAK;;AAGxB,SAAgB,gBAAgB,WAAsB;CACrD,MAAM,EAAE,uBAAuBA,uCAAgB;CAC/C,MAAM,UAAU,mBAAmB;CACnC,MAAM,oCACC,kBAAkB,SAAS,UAAU,cAAc,EACzD,CAAC,SAAS,UAAU,cAAc,CAClC;CACD,MAAM,CAAC,WAAW,oCAA2C,KAAK;CAClE,MAAM,CAAC,KAAK,8BAAkC,KAAK;CACnD,MAAM,CAAC,WAAW,oCAAyB,KAAK;AAEhD,4BAAgB;EACf,IAAI,QAAQ;AAKZ,eAAa,KAAK;AAClB,SAAO,KAAK;AACZ,eAAa,KAAK;AACjB,GAAC,YAAY;AACb,OAAI;AACH,QAAI,CAAC,SACJ,OAAM,IAAI,MAAM,6BAA6B,SAAS,UAAU,CAAC;IAClE,MAAM,MAAM,MAAM,UAAU;AAC5B,QAAI,MAAO,cAAa,IAAiB;YACjCC,GAAQ;AAChB,QAAI,MAAO,QAAO,GAAG,WAAW,OAAO,EAAE,CAAC;aACjC;AACT,QAAI,MAAO,cAAa,MAAM;;MAE5B;AACJ,eAAa;AACZ,WAAQ;;IAEP;EAAC;EAAU;EAAS;EAAU,CAAC;CAElC,MAAM,gBAAgB,sBAAsB,WAAW,UAAU;CACjE,MAAM,YAAY,WAAW,SAAS;CACtC,MAAM,YAAY,UAAU,SAAS,QACpC,WACA,aAAa,UAAU,CACvB;AAED,QAAO;EACN;EACA;EACA;EACA;EACA;EACA,OAAO;EACP;;AAGF,SAAS,aAAa,WAAyC;AAC9D,KAAI,CAAC,UAAW,QAAO;AACvB,KAAI,aAAa,UAAW,QAAO;AACnC,KAAI,aAAa,UAAW,QAAO;AACnC,QACC,OAAO,kBAAkB,WAAW,sBAAsB,MAAM,UAAU;;AAK5E,SAAS,kBAAkB,MAAc;AACxC,QACC,KAEE,QAAQ,sBAAsB,QAAQ,CACtC,QAAQ,yBAAyB,QAAQ,CAEzC,QAAQ,WAAW,IAAI,CAEvB,QAAQ,kBAAkB,IAAI,CAE9B,aAAa,CACb,QAAQ,QAAQ,IAAI,CACpB,QAAQ,YAAY,GAAG;;;;;;;AAS3B,SAAS,sBAAsB,KAAU,MAAkB;AAC1D,KAAI,CAAC,IAAK,QAAO;AAGjB,KAAI,aAAa,IAAK,QAAO,IAAI;AACjC,KAAI,aAAa,IAAK,QAAO,IAAI;AAGjC,KAAI,MAAM,WAAW,CAAC,KAAK,QAAQ,SAAS,SAAS,EAAE;EACtD,MAAM,aAAa,KAAK,QAAQ,MAAM,KAAK,CAAC,KAAK;EACjD,MAAM,QAAQ,OAAO,KAAK,IAAI,CAAC,MAC7B,MAAM,MAAM,aAAa,EAAE,aAAa,KAAK,WAC9C;AACD,MAAI,MAAO,QAAQ,IAAY;;CAIhC,MAAMC,QAAkB,IAAI,uBAAuB,EAAE;CACrD,MAAM,QAAQ,MAAM,MAAM,MAAM,MAAM,UAAU;AAChD,KAAI,SAAS,SAAS,IAAK,QAAQ,IAAY;CAG/C,MAAM,SAAS,OAAO,KAAK,IAAI,CAAC,MAC9B,MAAM,MAAM,aAAa,OAAO,IAAI,OAAO,WAC5C;AACD,QAAO,SAAU,IAAY,UAAU"}
|
|
@@ -2,14 +2,36 @@ import { useStoryParams } from "./useStoryParams.mjs";
|
|
|
2
2
|
import { useEffect, useMemo, useState } from "react";
|
|
3
3
|
|
|
4
4
|
//#region src/hooks/useDynamicStory.ts
|
|
5
|
+
function normalizeStoryPath(p) {
|
|
6
|
+
return "/" + p.replace(/^[./]+/, "").replace(/\\/g, "/");
|
|
7
|
+
}
|
|
5
8
|
function findStoryImporter(storiesGlob, storyFilePath) {
|
|
6
9
|
if (!storyFilePath) {
|
|
7
10
|
return null;
|
|
8
11
|
}
|
|
9
|
-
const
|
|
10
|
-
const wanted = norm(storyFilePath);
|
|
12
|
+
const wanted = normalizeStoryPath(storyFilePath);
|
|
11
13
|
return storiesGlob[wanted] || Object.entries(storiesGlob).find(([k]) => k.endsWith(wanted))?.[1];
|
|
12
14
|
}
|
|
15
|
+
function buildMissingStoryModuleError(storiesGlob, storyInfo) {
|
|
16
|
+
const keys = Object.keys(storiesGlob);
|
|
17
|
+
const rawPath = storyInfo.storyFilePath ?? "(no storyFilePath in graph entry)";
|
|
18
|
+
const normalised = storyInfo.storyFilePath ? normalizeStoryPath(storyInfo.storyFilePath) : "(n/a)";
|
|
19
|
+
const lines = [
|
|
20
|
+
`No story module found at this path: ${rawPath}`,
|
|
21
|
+
` Normalised lookup key: ${normalised}`,
|
|
22
|
+
` storyModules key count: ${keys.length}`
|
|
23
|
+
];
|
|
24
|
+
if (keys.length === 0) {
|
|
25
|
+
lines.push(" Hint: your `import.meta.glob(...)` pattern matched zero files. If it uses", " the extglob syntax `@(a|b|c)` you are likely on Vite 8 (tinyglobby),", " which does not support extglob — switch to brace expansion", " `{a,b,c}` instead. Example:", " import.meta.glob('/src/**/*.stories.{tsx,ts,jsx,js,svelte}', { eager: false })");
|
|
26
|
+
} else {
|
|
27
|
+
const sample = keys.slice(0, 10);
|
|
28
|
+
lines.push(" Sample storyModules keys (first 10):", ...sample.map((k) => ` - ${k}`));
|
|
29
|
+
if (keys.length > sample.length) {
|
|
30
|
+
lines.push(` …and ${keys.length - sample.length} more`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return lines.join("\n");
|
|
34
|
+
}
|
|
13
35
|
function useDynamicStory(storyInfo) {
|
|
14
36
|
const { dependencyPreviews } = useStoryParams();
|
|
15
37
|
const modules = dependencyPreviews.storyModules;
|
|
@@ -20,21 +42,27 @@ function useDynamicStory(storyInfo) {
|
|
|
20
42
|
useEffect(() => {
|
|
21
43
|
let alive = true;
|
|
22
44
|
setIsLoading(true);
|
|
45
|
+
setErr(null);
|
|
46
|
+
setCsfModule(null);
|
|
23
47
|
(async () => {
|
|
24
48
|
try {
|
|
25
|
-
if (!importer) throw new Error(
|
|
49
|
+
if (!importer) throw new Error(buildMissingStoryModuleError(modules, storyInfo));
|
|
26
50
|
const mod = await importer();
|
|
27
51
|
if (alive) setCsfModule(mod);
|
|
28
52
|
} catch (e) {
|
|
29
53
|
if (alive) setErr(e?.message || String(e));
|
|
30
54
|
} finally {
|
|
31
|
-
setIsLoading(false);
|
|
55
|
+
if (alive) setIsLoading(false);
|
|
32
56
|
}
|
|
33
57
|
})();
|
|
34
58
|
return () => {
|
|
35
59
|
alive = false;
|
|
36
60
|
};
|
|
37
|
-
}, [
|
|
61
|
+
}, [
|
|
62
|
+
importer,
|
|
63
|
+
modules,
|
|
64
|
+
storyInfo
|
|
65
|
+
]);
|
|
38
66
|
const primaryExport = getPrimaryStoryExport(csfModule, storyInfo);
|
|
39
67
|
const Component = csfModule?.default?.component;
|
|
40
68
|
const primaryId = storyInfo.storyId?.replace(/--docs$/, getPrimaryId(csfModule));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useDynamicStory.mjs","names":["e: any","order: string[]"],"sources":["../../src/hooks/useDynamicStory.ts"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react'\nimport type { CsfModule, StoryInfo, StoryModules } from '../types'\nimport { useStoryParams } from './useStoryParams'\n\n// grab all stories; Vite will code-split them\n\
|
|
1
|
+
{"version":3,"file":"useDynamicStory.mjs","names":["e: any","order: string[]"],"sources":["../../src/hooks/useDynamicStory.ts"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react'\nimport type { CsfModule, StoryInfo, StoryModules } from '../types'\nimport { useStoryParams } from './useStoryParams'\n\n// grab all stories; Vite will code-split them\n\n// Normalise a graph-JSON `storyFilePath` to Vite’s absolute-style glob key shape\n// (leading slash, forward slashes only, no leading `./` segments).\nfunction normalizeStoryPath(p: string) {\n\treturn '/' + p.replace(/^[./]+/, '').replace(/\\\\/g, '/')\n}\n\nfunction findStoryImporter(\n\tstoriesGlob: StoryModules,\n\tstoryFilePath: string | undefined | null,\n) {\n\tif (!storyFilePath) {\n\t\treturn null\n\t}\n\tconst wanted = normalizeStoryPath(storyFilePath)\n\t// exact match first, else suffix match as a fallback\n\treturn (\n\t\tstoriesGlob[wanted] ||\n\t\tObject.entries(storiesGlob).find(([k]) => k.endsWith(wanted))?.[1]\n\t)\n}\n\n// Compose an actionable error for the \"no story module matched\" failure mode. The\n// common cause — and the reason this helper exists — is the wizard-generated preview\n// shipping the extglob syntax `@(a|b|c)` which silently returns zero matches under\n// Vite 8 (tinyglobby). When the glob is empty, point the user at that fix; when the\n// glob has keys but none match, show a sample so the path-format mismatch is visible.\nfunction buildMissingStoryModuleError(\n\tstoriesGlob: StoryModules,\n\tstoryInfo: StoryInfo,\n): string {\n\tconst keys = Object.keys(storiesGlob)\n\tconst rawPath = storyInfo.storyFilePath ?? '(no storyFilePath in graph entry)'\n\tconst normalised = storyInfo.storyFilePath\n\t\t? normalizeStoryPath(storyInfo.storyFilePath)\n\t\t: '(n/a)'\n\tconst lines = [\n\t\t`No story module found at this path: ${rawPath}`,\n\t\t` Normalised lookup key: ${normalised}`,\n\t\t` storyModules key count: ${keys.length}`,\n\t]\n\tif (keys.length === 0) {\n\t\tlines.push(\n\t\t\t' Hint: your `import.meta.glob(...)` pattern matched zero files. If it uses',\n\t\t\t' the extglob syntax `@(a|b|c)` you are likely on Vite 8 (tinyglobby),',\n\t\t\t' which does not support extglob — switch to brace expansion',\n\t\t\t' `{a,b,c}` instead. Example:',\n\t\t\t\" import.meta.glob('/src/**/*.stories.{tsx,ts,jsx,js,svelte}', { eager: false })\",\n\t\t)\n\t} else {\n\t\tconst sample = keys.slice(0, 10)\n\t\tlines.push(\n\t\t\t' Sample storyModules keys (first 10):',\n\t\t\t...sample.map((k) => ` - ${k}`),\n\t\t)\n\t\tif (keys.length > sample.length) {\n\t\t\tlines.push(` …and ${keys.length - sample.length} more`)\n\t\t}\n\t}\n\treturn lines.join('\\n')\n}\n\nexport function useDynamicStory(storyInfo: StoryInfo) {\n\tconst { dependencyPreviews } = useStoryParams()\n\tconst modules = dependencyPreviews.storyModules\n\tconst importer = useMemo(\n\t\t() => findStoryImporter(modules, storyInfo.storyFilePath),\n\t\t[modules, storyInfo.storyFilePath],\n\t)\n\tconst [csfModule, setCsfModule] = useState<CsfModule | null>(null)\n\tconst [err, setErr] = useState<string | null>(null)\n\tconst [isLoading, setIsLoading] = useState(true)\n\n\tuseEffect(() => {\n\t\tlet alive = true\n\t\t// Reset before kicking off the new load: PrimaryPreview checks `error` first,\n\t\t// so a stale error from a previous story would keep rendering even after a\n\t\t// successful import for the new one. Same logic for `csfModule` — clearing it\n\t\t// makes the loading state visible while the new module resolves.\n\t\tsetIsLoading(true)\n\t\tsetErr(null)\n\t\tsetCsfModule(null)\n\t\t;(async () => {\n\t\t\ttry {\n\t\t\t\tif (!importer)\n\t\t\t\t\tthrow new Error(buildMissingStoryModuleError(modules, storyInfo))\n\t\t\t\tconst mod = await importer()\n\t\t\t\tif (alive) setCsfModule(mod as CsfModule)\n\t\t\t} catch (e: any) {\n\t\t\t\tif (alive) setErr(e?.message || String(e))\n\t\t\t} finally {\n\t\t\t\tif (alive) setIsLoading(false)\n\t\t\t}\n\t\t})()\n\t\treturn () => {\n\t\t\talive = false\n\t\t}\n\t}, [importer, modules, storyInfo])\n\n\tconst primaryExport = getPrimaryStoryExport(csfModule, storyInfo)\n\tconst Component = csfModule?.default?.component\n\tconst primaryId = storyInfo.storyId?.replace(\n\t\t/--docs$/,\n\t\tgetPrimaryId(csfModule),\n\t)\n\n\treturn {\n\t\tisLoading,\n\t\tprimaryExport,\n\t\tprimaryId,\n\t\tcsfModule,\n\t\tComponent,\n\t\terror: err,\n\t}\n}\n\nfunction getPrimaryId(csfModule: CsfModule | undefined | null) {\n\tif (!csfModule) return '--default'\n\tif ('Default' in csfModule) return '--default'\n\tif ('Primary' in csfModule) return '--primary'\n\treturn (\n\t\t'--' + slugifyExportName(csfModule?.__namedExportsOrder?.[0] || 'default')\n\t)\n}\n\n// Kebab-case like Storybook: \"ErrorStrings\" -> \"error-strings\"\nfunction slugifyExportName(name: string) {\n\treturn (\n\t\tname\n\t\t\t// split camelCase / PascalCase boundaries\n\t\t\t.replace(/([a-z0-9])([A-Z])/g, '$1-$2')\n\t\t\t.replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2') // ABCDef -> ABC-Def\n\t\t\t// underscores/spaces -> dashes\n\t\t\t.replace(/[_\\s]+/g, '-')\n\t\t\t// strip anything not alnum or dash\n\t\t\t.replace(/[^a-zA-Z0-9-]/g, '-')\n\t\t\t// lowercase + collapse/trim dashes\n\t\t\t.toLowerCase()\n\t\t\t.replace(/--+/g, '-')\n\t\t\t.replace(/^-+|-+$/g, '')\n\t)\n}\n\n/** Choose a sensible story:\n * 1) If the module has `Primary`, use it.\n * 2) If we have a docs/primary id, try to match the export name in the id.\n * 3) Else fall back to the first named export.\n */\nfunction getPrimaryStoryExport(mod: any, info?: StoryInfo) {\n\tif (!mod) return null\n\n\t// 1) Prefer \"Default\" or \"Primary\" if present\n\tif ('Default' in mod) return mod.Default\n\tif ('Primary' in mod) return mod.Primary\n\n\t// 2) If we have a storyId like \"…--errorstrings\", try to match export name\n\tif (info?.storyId && !info.storyId.endsWith('--docs')) {\n\t\tconst exportName = info.storyId.split('--').pop() // e.g. \"errorstrings\"\n\t\tconst match = Object.keys(mod).find(\n\t\t\t(k) => k !== 'default' && k.toLowerCase() === exportName,\n\t\t)\n\t\tif (match) return (mod as any)[match]\n\t}\n\n\t// 3) Fall back to first named export\n\tconst order: string[] = mod.__namedExportsOrder || []\n\tconst first = order.find((k) => k !== 'default')\n\tif (first && first in mod) return (mod as any)[first]\n\n\t// Last resort: any non-default function export\n\tconst anyKey = Object.keys(mod).find(\n\t\t(k) => k !== 'default' && typeof mod[k] === 'function',\n\t)\n\treturn anyKey ? (mod as any)[anyKey] : null\n}\n"],"mappings":"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"}
|
package/dist/types.d.cts
CHANGED
|
@@ -50,7 +50,7 @@ type DependencyPreviewStorybookParameters = Omit<StoryParameters, '__filePath'>
|
|
|
50
50
|
*
|
|
51
51
|
* Use the following as the value for this property:
|
|
52
52
|
* ```ts
|
|
53
|
-
* import.meta.glob('/src/**\/*.stories
|
|
53
|
+
* import.meta.glob('/src/**\/*.stories.{tsx,ts,jsx,js,svelte}', { eager: false });
|
|
54
54
|
* ``` */
|
|
55
55
|
storyModules: StoryModules;
|
|
56
56
|
/**
|
package/dist/types.d.mts
CHANGED
|
@@ -50,7 +50,7 @@ type DependencyPreviewStorybookParameters = Omit<StoryParameters, '__filePath'>
|
|
|
50
50
|
*
|
|
51
51
|
* Use the following as the value for this property:
|
|
52
52
|
* ```ts
|
|
53
|
-
* import.meta.glob('/src/**\/*.stories
|
|
53
|
+
* import.meta.glob('/src/**\/*.stories.{tsx,ts,jsx,js,svelte}', { eager: false });
|
|
54
54
|
* ``` */
|
|
55
55
|
storyModules: StoryModules;
|
|
56
56
|
/**
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "storybook-addon-dependency-previews",
|
|
3
3
|
"description": "A Storybook 10 addon to automatically generate a visual dependency tree for your components.",
|
|
4
|
-
"version": "0.5.
|
|
4
|
+
"version": "0.5.2",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"homepage": "https://dependency-previews-storybook.netlify.app/?path=/docs/04-templates-home-template--docs",
|