0
# Story Processing
1
2
Story entry normalization and metadata generation for Storybook's story indexing system. These utilities handle glob patterns, directory mapping, story ID generation, and story metadata processing.
3
4
## Capabilities
5
6
### Story Entry Normalization
7
8
Convert and normalize story entry configurations from various formats into a standardized structure.
9
10
```typescript { .api }
11
/**
12
* Normalize array of story entries with configuration options
13
* @param entries - Array of story entry configurations
14
* @param options - Normalization options including working directory context
15
* @returns Array of normalized story entries
16
*/
17
function normalizeStories(
18
entries: StoriesEntry[],
19
options: NormalizeOptions
20
): NormalizedStoriesEntry[];
21
22
/**
23
* Normalize individual story entry configuration
24
* @param entry - Single story entry configuration
25
* @param options - Normalization options
26
* @returns Normalized story entry
27
*/
28
function normalizeStoriesEntry(
29
entry: StoriesEntry,
30
options: NormalizeOptions
31
): NormalizedStoriesEntry;
32
33
interface NormalizeOptions {
34
configDir: string;
35
workingDir?: string;
36
}
37
```
38
39
**Usage Examples:**
40
41
```typescript
42
import { normalizeStories } from "@storybook/core-common";
43
44
// Normalize story entries from main config
45
const stories = normalizeStories([
46
'./src/**/*.stories.@(js|jsx|ts|tsx)',
47
{
48
directory: '../shared/components',
49
files: '**/*.stories.@(js|jsx|ts|tsx)',
50
titlePrefix: 'Shared'
51
}
52
], {
53
configDir: '.storybook',
54
workingDir: process.cwd()
55
});
56
57
console.log(stories);
58
// [
59
// {
60
// directory: './src',
61
// files: '**/*.stories.@(js|jsx|ts|tsx)',
62
// importPathMatcher: /^\.\/src\/(.*)\.stories\.(js|jsx|ts|tsx)$/,
63
// normalizedPath: './src',
64
// titlePrefix: undefined
65
// },
66
// ...
67
// ]
68
```
69
70
### Story ID Generation
71
72
Generate unique story IDs from file paths and story exports.
73
74
```typescript { .api }
75
/**
76
* Generate unique story ID from story data and options
77
* @param data - Story identification data
78
* @param options - Story ID generation options
79
* @returns Generated story ID string
80
*/
81
function getStoryId(data: StoryIdData, options: GetStoryIdOptions): string;
82
83
interface StoryIdData {
84
title: string;
85
name: string;
86
story?: string;
87
}
88
89
interface GetStoryIdOptions {
90
directory: string;
91
importPath: string;
92
normalizedPath: string;
93
titlePrefix?: string;
94
}
95
```
96
97
**Usage Example:**
98
99
```typescript
100
import { getStoryId } from "@storybook/core-common";
101
102
// Generate story ID
103
const storyId = getStoryId(
104
{
105
title: 'Button',
106
name: 'Primary',
107
story: 'primary'
108
},
109
{
110
directory: './src/components',
111
importPath: './src/components/Button.stories.js',
112
normalizedPath: './src/components',
113
titlePrefix: 'UI'
114
}
115
);
116
117
console.log(storyId); // 'ui-button--primary'
118
```
119
120
### Story Title Generation
121
122
Generate story titles from file paths and component information.
123
124
```typescript { .api }
125
/**
126
* Generate story title from file path and component specifier
127
* @param options - Title generation options
128
* @returns Generated story title
129
*/
130
function getStoryTitle(options: {
131
specifier: {
132
title?: string;
133
component?: string;
134
};
135
filepath: string;
136
normalizedPath: string;
137
}): string;
138
```
139
140
**Usage Example:**
141
142
```typescript
143
import { getStoryTitle } from "@storybook/core-common";
144
145
// Generate title from component
146
const title = getStoryTitle({
147
specifier: { component: 'Button' },
148
filepath: './src/components/Button/Button.stories.js',
149
normalizedPath: './src/components'
150
});
151
152
console.log(title); // 'Button/Button'
153
154
// Generate title with explicit title
155
const explicitTitle = getStoryTitle({
156
specifier: { title: 'Design System/Button' },
157
filepath: './src/components/Button.stories.js',
158
normalizedPath: './src'
159
});
160
161
console.log(explicitTitle); // 'Design System/Button'
162
```
163
164
### Directory Path Utilities
165
166
Convert between different path representations and resolve working directory contexts.
167
168
```typescript { .api }
169
/**
170
* Convert config-relative paths to working-directory-relative paths
171
* @param options - Path conversion options
172
* @returns Working directory relative path
173
*/
174
function getDirectoryFromWorkingDir(options: {
175
configDir: string;
176
workingDir?: string;
177
directory: string;
178
}): string;
179
180
/**
181
* Ensure story paths start with './' or '../'
182
* @param filename - File path to normalize
183
* @returns Normalized path with proper prefix
184
*/
185
function normalizeStoryPath(filename: string): string;
186
```
187
188
**Usage Examples:**
189
190
```typescript
191
import {
192
getDirectoryFromWorkingDir,
193
normalizeStoryPath
194
} from "@storybook/core-common";
195
196
// Convert to working directory relative
197
const workingDirPath = getDirectoryFromWorkingDir({
198
configDir: '.storybook',
199
workingDir: '/project',
200
directory: '../shared/stories'
201
});
202
203
// Normalize story paths
204
const normalized = normalizeStoryPath('src/components/Button.stories.js');
205
console.log(normalized); // './src/components/Button.stories.js'
206
```
207
208
### Pattern Matching Utilities
209
210
Convert glob patterns to regular expressions for story matching.
211
212
```typescript { .api }
213
/**
214
* Convert glob pattern to RegExp with special handling for story patterns
215
* @param glob - Glob pattern string
216
* @returns Regular expression for pattern matching
217
*/
218
function globToRegexp(glob: string): RegExp;
219
```
220
221
**Usage Example:**
222
223
```typescript
224
import { globToRegexp } from "@storybook/core-common";
225
226
// Convert story glob to regex
227
const pattern = globToRegexp('**/*.stories.@(js|jsx|ts|tsx)');
228
console.log(pattern.test('Button.stories.js')); // true
229
console.log(pattern.test('Button.test.js')); // false
230
231
// Handle specific path patterns
232
const srcPattern = globToRegexp('./src/**/*.stories.*');
233
console.log(srcPattern.test('./src/Button.stories.js')); // true
234
```
235
236
## Story Entry Types
237
238
```typescript { .api }
239
/**
240
* Story entry configuration - string glob or detailed object
241
*/
242
type StoriesEntry = string | {
243
/** Directory containing stories */
244
directory: string;
245
/** File pattern within directory */
246
files: string;
247
/** Optional prefix for story titles */
248
titlePrefix?: string;
249
};
250
251
/**
252
* Normalized story entry with computed properties
253
*/
254
interface NormalizedStoriesEntry {
255
/** Base directory for stories */
256
directory: string;
257
/** File pattern for matching stories */
258
files: string;
259
/** RegExp for matching import paths */
260
importPathMatcher: RegExp;
261
/** Normalized directory path */
262
normalizedPath: string;
263
/** Title prefix for story organization */
264
titlePrefix?: string;
265
}
266
```
267
268
## Advanced Story Processing
269
270
### Custom Story Indexing
271
272
```typescript
273
import { normalizeStories, getStoryId } from "@storybook/core-common";
274
275
async function buildCustomStoryIndex(mainConfig: any) {
276
// Normalize all story entries
277
const normalizedEntries = normalizeStories(mainConfig.stories, {
278
configDir: '.storybook'
279
});
280
281
const storyIndex = {};
282
283
for (const entry of normalizedEntries) {
284
// Find all story files matching this entry
285
const storyFiles = await glob(entry.files, {
286
cwd: entry.directory
287
});
288
289
for (const file of storyFiles) {
290
// Extract stories from file
291
const stories = await extractStoriesFromFile(file);
292
293
for (const story of stories) {
294
const storyId = getStoryId(
295
{ title: story.title, name: story.name },
296
{
297
directory: entry.directory,
298
importPath: file,
299
normalizedPath: entry.normalizedPath,
300
titlePrefix: entry.titlePrefix
301
}
302
);
303
304
storyIndex[storyId] = {
305
id: storyId,
306
title: story.title,
307
name: story.name,
308
importPath: file
309
};
310
}
311
}
312
}
313
314
return storyIndex;
315
}
316
```
317
318
### Story Validation
319
320
```typescript
321
import { normalizeStories } from "@storybook/core-common";
322
323
function validateStoryConfiguration(stories: StoriesEntry[], configDir: string) {
324
try {
325
const normalized = normalizeStories(stories, { configDir });
326
327
// Check for valid patterns
328
for (const entry of normalized) {
329
if (!entry.files || !entry.directory) {
330
throw new Error(`Invalid story entry: ${JSON.stringify(entry)}`);
331
}
332
333
// Validate glob patterns
334
if (!entry.importPathMatcher) {
335
throw new Error(`Unable to create matcher for entry: ${entry.files}`);
336
}
337
}
338
339
return { valid: true, entries: normalized };
340
} catch (error) {
341
return { valid: false, error: error.message };
342
}
343
}
344
```
345
346
### Story Path Resolution
347
348
```typescript
349
import { normalizeStoryPath, getDirectoryFromWorkingDir } from "@storybook/core-common";
350
351
function resolveStoryPaths(configDir: string, stories: string[]) {
352
return stories.map(story => {
353
// Normalize the path format
354
const normalizedPath = normalizeStoryPath(story);
355
356
// Convert to working directory relative if needed
357
return getDirectoryFromWorkingDir({
358
configDir,
359
workingDir: process.cwd(),
360
directory: normalizedPath
361
});
362
});
363
}
364
365
// Usage
366
const resolvedPaths = resolveStoryPaths('.storybook', [
367
'src/**/*.stories.js',
368
'../shared/components/**/*.stories.tsx'
369
]);
370
```