@storybook/core-server 7.0.0-alpha.11 → 7.0.0-alpha.16

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.
@@ -106,7 +106,7 @@ async function buildDevStandalone(options) {
106
106
 
107
107
  _nodeLogger.logger.info('=> Loading presets');
108
108
 
109
- var presets = (0, _coreCommon.loadAllPresets)(_objectSpread({
109
+ var presets = await (0, _coreCommon.loadAllPresets)(_objectSpread({
110
110
  corePresets: corePresets,
111
111
  overridePresets: []
112
112
  }, options));
@@ -118,7 +118,7 @@ async function buildDevStandalone(options) {
118
118
  previewBuilder = _await$getBuilders2[0],
119
119
  managerBuilder = _await$getBuilders2[1];
120
120
 
121
- presets = (0, _coreCommon.loadAllPresets)(_objectSpread({
121
+ presets = await (0, _coreCommon.loadAllPresets)(_objectSpread({
122
122
  corePresets: [require.resolve('./presets/common-preset'), ...managerBuilder.corePresets, ...previewBuilder.corePresets, ...corePresets, require.resolve('./presets/babel-cache-preset')],
123
123
  overridePresets: previewBuilder.overridePresets
124
124
  }, options));
@@ -147,10 +147,8 @@ async function buildDevStandalone(options) {
147
147
  }
148
148
 
149
149
  if (options.smokeTest) {
150
- var warnings = []; // @ts-ignore
151
-
152
- warnings.push(...(managerStats && managerStats.toJson().warnings || [])); // @ts-ignore
153
-
150
+ var warnings = [];
151
+ warnings.push(...(managerStats && managerStats.toJson().warnings || []));
154
152
  warnings.push(...(managerStats && previewStats.toJson().warnings || []));
155
153
  var problems = warnings.filter(function (warning) {
156
154
  return !warning.message.includes(`export 'useInsertionEffect'`);
@@ -98,7 +98,7 @@ async function buildStaticStandalone(options) {
98
98
 
99
99
  _nodeLogger.logger.info('=> Loading presets');
100
100
 
101
- var presets = (0, _coreCommon.loadAllPresets)(_objectSpread({
101
+ var presets = await (0, _coreCommon.loadAllPresets)(_objectSpread({
102
102
  corePresets: [require.resolve('./presets/common-preset'), ...corePresets],
103
103
  overridePresets: []
104
104
  }, options));
@@ -110,18 +110,19 @@ async function buildStaticStandalone(options) {
110
110
  previewBuilder = _await$getBuilders2[0],
111
111
  managerBuilder = _await$getBuilders2[1];
112
112
 
113
- presets = (0, _coreCommon.loadAllPresets)(_objectSpread({
113
+ presets = await (0, _coreCommon.loadAllPresets)(_objectSpread({
114
114
  corePresets: [require.resolve('./presets/common-preset'), ...(managerBuilder.corePresets || []), ...(previewBuilder.corePresets || []), ...corePresets, require.resolve('./presets/babel-cache-preset')],
115
115
  overridePresets: previewBuilder.overridePresets || []
116
116
  }, options));
117
117
 
118
- var _await$Promise$all = await Promise.all([presets.apply('features'), presets.apply('core'), presets.apply('staticDirs'), presets.apply('storyIndexers', []), presets.apply('stories')]),
119
- _await$Promise$all2 = _slicedToArray(_await$Promise$all, 5),
118
+ var _await$Promise$all = await Promise.all([presets.apply('features'), presets.apply('core'), presets.apply('staticDirs'), presets.apply('storyIndexers', []), presets.apply('stories'), presets.apply('docs', {})]),
119
+ _await$Promise$all2 = _slicedToArray(_await$Promise$all, 6),
120
120
  features = _await$Promise$all2[0],
121
121
  core = _await$Promise$all2[1],
122
122
  staticDirs = _await$Promise$all2[2],
123
123
  storyIndexers = _await$Promise$all2[3],
124
- stories = _await$Promise$all2[4];
124
+ stories = _await$Promise$all2[4],
125
+ docsOptions = _await$Promise$all2[5];
125
126
 
126
127
  var fullOptions = _objectSpread(_objectSpread({}, options), {}, {
127
128
  presets: presets,
@@ -166,6 +167,7 @@ async function buildStaticStandalone(options) {
166
167
  var normalizedStories = (0, _coreCommon.normalizeStories)(stories, directories);
167
168
  var generator = new _StoryIndexGenerator.StoryIndexGenerator(normalizedStories, _objectSpread(_objectSpread({}, directories), {}, {
168
169
  storyIndexers: storyIndexers,
170
+ docs: docsOptions,
169
171
  storiesV2Compatibility: !(features !== null && features !== void 0 && features.breakingChangesV7) && !(features !== null && features !== void 0 && features.storyStoreV7),
170
172
  storyStoreV7: !!(features !== null && features !== void 0 && features.storyStoreV7)
171
173
  }));
@@ -87,8 +87,10 @@ async function storybookDevServer(options) {
87
87
  };
88
88
  var normalizedStories = (0, _coreCommon.normalizeStories)(await options.presets.apply('stories'), directories);
89
89
  var storyIndexers = await options.presets.apply('storyIndexers', []);
90
+ var docsOptions = await options.presets.apply('docs', {});
90
91
  var generator = new _StoryIndexGenerator.StoryIndexGenerator(normalizedStories, _objectSpread(_objectSpread({}, directories), {}, {
91
92
  storyIndexers: storyIndexers,
93
+ docs: docsOptions,
92
94
  workingDir: workingDir,
93
95
  storiesV2Compatibility: !(features !== null && features !== void 0 && features.breakingChangesV7) && !(features !== null && features !== void 0 && features.storyStoreV7),
94
96
  storyStoreV7: features === null || features === void 0 ? void 0 : features.storyStoreV7
@@ -56,6 +56,25 @@ function _defineProperty(obj, key, value) { if (key in obj) { Object.definePrope
56
56
  var makeAbsolute = function (otherImport, normalizedPath, workingDir) {
57
57
  return otherImport.startsWith('.') ? (0, _slash.default)(_path.default.resolve(workingDir, (0, _coreCommon.normalizeStoryPath)(_path.default.join(_path.default.dirname(normalizedPath), otherImport)))) : otherImport;
58
58
  };
59
+ /**
60
+ * The StoryIndexGenerator extracts stories and docs entries for each file matching
61
+ * (one or more) stories "specifiers", as defined in main.js.
62
+ *
63
+ * The output is a set of entries (see above for the types).
64
+ *
65
+ * Each file is treated as a stories or a (modern) docs file.
66
+ *
67
+ * A stories file is indexed by an indexer (passed in), which produces a list of stories.
68
+ * - If the stories have the `parameters.docsOnly` setting, they are disregarded.
69
+ * - If the indexer is a "docs template" indexer, OR docsPage is enabled,
70
+ * a templated docs entry is added pointing to the story file.
71
+ *
72
+ * A (modern) docs file is indexed, a standalone docs entry is added.
73
+ *
74
+ * The entries are "uniq"-ed and sorted. Stories entries are preferred to docs entries and
75
+ * standalone docs entries are preferred to templates (with warnings).
76
+ */
77
+
59
78
 
60
79
  class StoryIndexGenerator {
61
80
  // An internal cache mapping specifiers to a set of path=><set of stories>
@@ -103,14 +122,15 @@ class StoryIndexGenerator {
103
122
  */
104
123
 
105
124
 
106
- async updateExtracted(updater) {
125
+ async updateExtracted(updater, overwrite = false) {
107
126
  var _this2 = this;
108
127
 
109
128
  await Promise.all(this.specifiers.map(async function (specifier) {
110
129
  var entry = _this2.specifierToCache.get(specifier);
111
130
 
112
131
  return Promise.all(Object.keys(entry).map(async function (absolutePath) {
113
- entry[absolutePath] = entry[absolutePath] || (await updater(specifier, absolutePath));
132
+ if (entry[absolutePath] && !overwrite) return;
133
+ entry[absolutePath] = await updater(specifier, absolutePath, entry[absolutePath]);
114
134
  }));
115
135
  }));
116
136
  }
@@ -129,9 +149,13 @@ class StoryIndexGenerator {
129
149
  await this.updateExtracted(async function (specifier, absolutePath) {
130
150
  return _this3.isDocsMdx(absolutePath) ? false : _this3.extractStories(specifier, absolutePath);
131
151
  });
132
- await this.updateExtracted(async function (specifier, absolutePath) {
133
- return _this3.extractDocs(specifier, absolutePath);
134
- });
152
+
153
+ if (this.options.docs.enabled) {
154
+ await this.updateExtracted(async function (specifier, absolutePath) {
155
+ return _this3.extractDocs(specifier, absolutePath);
156
+ });
157
+ }
158
+
135
159
  return this.specifiers.flatMap(function (specifier) {
136
160
  var cache = _this3.specifierToCache.get(specifier);
137
161
 
@@ -172,6 +196,79 @@ class StoryIndexGenerator {
172
196
  return dependencies;
173
197
  }
174
198
 
199
+ async extractStories(specifier, absolutePath) {
200
+ var relativePath = _path.default.relative(this.options.workingDir, absolutePath);
201
+
202
+ var entries = [];
203
+
204
+ try {
205
+ var importPath = (0, _slash.default)((0, _coreCommon.normalizeStoryPath)(relativePath));
206
+
207
+ var makeTitle = function (userTitle) {
208
+ return (0, _store.userOrAutoTitleFromSpecifier)(importPath, specifier, userTitle);
209
+ };
210
+
211
+ var storyIndexer = this.options.storyIndexers.find(function (indexer) {
212
+ return indexer.test.exec(absolutePath);
213
+ });
214
+
215
+ if (!storyIndexer) {
216
+ throw new Error(`No matching story indexer found for ${absolutePath}`);
217
+ }
218
+
219
+ var csf = await storyIndexer.indexer(absolutePath, {
220
+ makeTitle: makeTitle
221
+ });
222
+ csf.stories.forEach(function ({
223
+ id: id,
224
+ name: name,
225
+ parameters: parameters
226
+ }) {
227
+ if (!(parameters !== null && parameters !== void 0 && parameters.docsOnly)) {
228
+ entries.push({
229
+ id: id,
230
+ title: csf.meta.title,
231
+ name: name,
232
+ importPath: importPath,
233
+ type: 'story'
234
+ });
235
+ }
236
+ });
237
+
238
+ if (this.options.docs.enabled) {
239
+ // We always add a template for *.stories.mdx, but only if docs page is enabled for
240
+ // regular CSF files
241
+ if (storyIndexer.addDocsTemplate || this.options.docs.docsPage) {
242
+ var name = this.options.docs.defaultName;
243
+ var id = (0, _csf.toId)(csf.meta.title, name);
244
+ entries.unshift({
245
+ id: id,
246
+ title: csf.meta.title,
247
+ name: name,
248
+ importPath: importPath,
249
+ type: 'docs',
250
+ storiesImports: [],
251
+ standalone: false
252
+ });
253
+ }
254
+ }
255
+ } catch (err) {
256
+ if (err.name === 'NoMetaError') {
257
+ _nodeLogger.logger.info(`💡 Skipping ${relativePath}: ${err}`);
258
+ } else {
259
+ _nodeLogger.logger.warn(`🚨 Extraction error on ${relativePath}: ${err}`);
260
+
261
+ throw err;
262
+ }
263
+ }
264
+
265
+ return {
266
+ entries: entries,
267
+ type: 'stories',
268
+ dependents: []
269
+ };
270
+ }
271
+
175
272
  async extractDocs(specifier, absolutePath) {
176
273
  var _this4 = this;
177
274
 
@@ -193,9 +290,10 @@ class StoryIndexGenerator {
193
290
  var _await$require = await require('@storybook/docs-mdx'),
194
291
  analyze = _await$require.analyze;
195
292
 
196
- var content = await _fsExtra.default.readFile(absolutePath, 'utf8'); // { title?, of?, imports? }
293
+ var content = await _fsExtra.default.readFile(absolutePath, 'utf8');
294
+ var result = analyze(content); // Templates are not indexed
197
295
 
198
- var result = analyze(content);
296
+ if (result.isTemplate) return false;
199
297
  var absoluteImports = result.imports.map(function (p) {
200
298
  return makeAbsolute(p, normalizedPath, _this4.options.workingDir);
201
299
  }); // Go through the cache and collect all of the cache entries that this docs file depends on.
@@ -225,8 +323,8 @@ class StoryIndexGenerator {
225
323
  dependencies.forEach(function (dep) {
226
324
  dep.dependents.push(absolutePath);
227
325
  });
228
- var title = (0, _store.userOrAutoTitleFromSpecifier)(importPath, specifier, result.title || ofTitle);
229
- var name = 'docs';
326
+ var title = ofTitle || (0, _store.userOrAutoTitleFromSpecifier)(importPath, specifier, result.title);
327
+ var name = result.name || this.options.docs.defaultName;
230
328
  var id = (0, _csf.toId)(title, name);
231
329
  var docsEntry = {
232
330
  id: id,
@@ -236,7 +334,8 @@ class StoryIndexGenerator {
236
334
  storiesImports: dependencies.map(function (dep) {
237
335
  return dep.entries[0].importPath;
238
336
  }),
239
- type: 'docs'
337
+ type: 'docs',
338
+ standalone: true
240
339
  };
241
340
  return docsEntry;
242
341
  } catch (err) {
@@ -246,74 +345,62 @@ class StoryIndexGenerator {
246
345
  }
247
346
  }
248
347
 
249
- async index(filePath, options) {
250
- var storyIndexer = this.options.storyIndexers.find(function (indexer) {
251
- return indexer.test.exec(filePath);
252
- });
348
+ chooseDuplicate(firstEntry, secondEntry) {
349
+ var firstIsBetter = true;
253
350
 
254
- if (!storyIndexer) {
255
- throw new Error(`No matching story indexer found for ${filePath}`);
351
+ if (secondEntry.type === 'story') {
352
+ firstIsBetter = false;
353
+ } else if (secondEntry.standalone && firstEntry.type === 'docs' && !firstEntry.standalone) {
354
+ firstIsBetter = false;
256
355
  }
257
356
 
258
- return storyIndexer.indexer(filePath, options);
259
- }
260
-
261
- async extractStories(specifier, absolutePath) {
262
- var relativePath = _path.default.relative(this.options.workingDir, absolutePath);
263
-
264
- var entries = [];
357
+ var betterEntry = firstIsBetter ? firstEntry : secondEntry;
358
+ var worseEntry = firstIsBetter ? secondEntry : firstEntry;
359
+ var changeDocsName = 'Use `<Meta of={} name="Other Name">` to distinguish them.'; // This shouldn't be possible, but double check and use for typing
265
360
 
266
- try {
267
- var importPath = (0, _slash.default)((0, _coreCommon.normalizeStoryPath)(relativePath));
361
+ if (worseEntry.type === 'story') throw new Error(`Duplicate stories with id: ${firstEntry.id}`);
268
362
 
269
- var makeTitle = function (userTitle) {
270
- return (0, _store.userOrAutoTitleFromSpecifier)(importPath, specifier, userTitle);
271
- };
363
+ if (betterEntry.type === 'story') {
364
+ var worseDescriptor = worseEntry.standalone ? `component docs page` : `automatically generated docs page`;
272
365
 
273
- var csf = await this.index(absolutePath, {
274
- makeTitle: makeTitle
275
- });
276
- csf.stories.forEach(function ({
277
- id: id,
278
- name: name,
279
- parameters: parameters
280
- }) {
281
- var base = {
282
- id: id,
283
- title: csf.meta.title,
284
- name: name,
285
- importPath: importPath
286
- };
287
- var entry = parameters !== null && parameters !== void 0 && parameters.docsOnly ? _objectSpread(_objectSpread({}, base), {}, {
288
- type: 'docs',
289
- storiesImports: [],
290
- legacy: true
291
- }) : _objectSpread(_objectSpread({}, base), {}, {
292
- type: 'story'
293
- });
294
- entries.push(entry);
295
- });
296
- } catch (err) {
297
- if (err.name === 'NoMetaError') {
298
- _nodeLogger.logger.info(`💡 Skipping ${relativePath}: ${err}`);
366
+ if (betterEntry.name === this.options.docs.defaultName) {
367
+ _nodeLogger.logger.warn(`🚨 You have a story for ${betterEntry.title} with the same name as your default docs entry name (${betterEntry.name}), so the docs page is being dropped. Consider changing the story name.`);
299
368
  } else {
300
- _nodeLogger.logger.warn(`🚨 Extraction error on ${relativePath}: ${err}`);
301
-
302
- throw err;
369
+ _nodeLogger.logger.warn(`🚨 You have a story for ${betterEntry.title} with the same name as your ${worseDescriptor} (${worseEntry.name}), so the docs page is being dropped. ${changeDocsName}`);
303
370
  }
371
+ } else if (betterEntry.standalone) {
372
+ // Both entries are standalone but pointing at the same place
373
+ if (worseEntry.standalone) {
374
+ _nodeLogger.logger.warn(`🚨 You have two component docs pages with the same name ${betterEntry.title}:${betterEntry.name}. ${changeDocsName}`);
375
+ } // If one entry is standalone (i.e. .mdx of={}) we are OK with it overriding a template
376
+ // - docs page templates, this is totally fine and expected
377
+ // - not sure if it is even possible to have a .mdx of={} pointing at a stories.mdx file
378
+
379
+ } else {
380
+ // If both entries are templates (e.g. you have two CSF files with the same title), then
381
+ // we need to merge the entries. We'll use the the first one's name and importPath,
382
+ // but ensure we include both as storiesImports so they are both loaded before rendering
383
+ // the story (for the <Stories> block & friends)
384
+ return _objectSpread(_objectSpread({}, betterEntry), {}, {
385
+ storiesImports: [...betterEntry.storiesImports, worseEntry.importPath, ...worseEntry.storiesImports]
386
+ });
304
387
  }
305
388
 
306
- return {
307
- entries: entries,
308
- type: 'stories',
309
- dependents: []
310
- };
389
+ return betterEntry;
311
390
  }
312
391
 
313
392
  async sortStories(storiesList) {
393
+ var _this5 = this;
394
+
314
395
  var entries = {};
315
396
  storiesList.forEach(function (entry) {
316
- entries[entry.id] = entry;
397
+ var existing = entries[entry.id];
398
+
399
+ if (existing) {
400
+ entries[entry.id] = _this5.chooseDuplicate(existing, entry);
401
+ } else {
402
+ entries[entry.id] = entry;
403
+ }
317
404
  });
318
405
  var sortableStories = Object.values(entries); // Skip sorting if we're in v6 mode because we don't have
319
406
  // all the info we need here
@@ -371,7 +458,7 @@ class StoryIndexGenerator {
371
458
  }
372
459
 
373
460
  invalidate(specifier, importPath, removed) {
374
- var _this5 = this;
461
+ var _this6 = this;
375
462
 
376
463
  var absolutePath = (0, _slash.default)(_path.default.resolve(this.options.workingDir, importPath));
377
464
  var cache = this.specifierToCache.get(specifier);
@@ -402,7 +489,7 @@ class StoryIndexGenerator {
402
489
  if (removed) {
403
490
  if (cacheEntry && cacheEntry.type === 'docs') {
404
491
  var absoluteImports = cacheEntry.storiesImports.map(function (p) {
405
- return _path.default.resolve(_this5.options.workingDir, p);
492
+ return _path.default.resolve(_this6.options.workingDir, p);
406
493
  });
407
494
  var dependencies = this.findDependencies(absoluteImports);
408
495
  dependencies.forEach(function (dep) {
@@ -419,10 +506,10 @@ class StoryIndexGenerator {
419
506
  }
420
507
 
421
508
  async getStorySortParameter() {
422
- var _this6 = this;
509
+ var _this7 = this;
423
510
 
424
511
  var previewFile = ['js', 'jsx', 'ts', 'tsx'].map(function (ext) {
425
- return _path.default.join(_this6.options.configDir, `preview.${ext}`);
512
+ return _path.default.join(_this7.options.configDir, `preview.${ext}`);
426
513
  }).find(function (fname) {
427
514
  return _fsExtra.default.existsSync(fname);
428
515
  });
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = exports.StoryOne = void 0;
7
+ var component = {};
8
+ var _default = {
9
+ component: component,
10
+ title: 'duplicate/A'
11
+ };
12
+ exports.default = _default;
13
+ var StoryOne = {};
14
+ exports.StoryOne = StoryOne;
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = exports.StoryTwo = void 0;
7
+ var component = {};
8
+ var _default = {
9
+ component: component,
10
+ title: 'duplicate/A'
11
+ };
12
+ exports.default = _default;
13
+ var StoryTwo = {};
14
+ exports.StoryTwo = StoryTwo;
@@ -82,7 +82,7 @@ export async function buildDevStandalone(options) {
82
82
  }
83
83
 
84
84
  logger.info('=> Loading presets');
85
- var presets = loadAllPresets(_objectSpread({
85
+ var presets = await loadAllPresets(_objectSpread({
86
86
  corePresets: corePresets,
87
87
  overridePresets: []
88
88
  }, options));
@@ -94,7 +94,7 @@ export async function buildDevStandalone(options) {
94
94
  previewBuilder = _await$getBuilders2[0],
95
95
  managerBuilder = _await$getBuilders2[1];
96
96
 
97
- presets = loadAllPresets(_objectSpread({
97
+ presets = await loadAllPresets(_objectSpread({
98
98
  corePresets: [require.resolve('./presets/common-preset'), ...managerBuilder.corePresets, ...previewBuilder.corePresets, ...corePresets, require.resolve('./presets/babel-cache-preset')],
99
99
  overridePresets: previewBuilder.overridePresets
100
100
  }, options));
@@ -123,10 +123,8 @@ export async function buildDevStandalone(options) {
123
123
  }
124
124
 
125
125
  if (options.smokeTest) {
126
- var warnings = []; // @ts-ignore
127
-
128
- warnings.push(...(managerStats && managerStats.toJson().warnings || [])); // @ts-ignore
129
-
126
+ var warnings = [];
127
+ warnings.push(...(managerStats && managerStats.toJson().warnings || []));
130
128
  warnings.push(...(managerStats && previewStats.toJson().warnings || []));
131
129
  var problems = warnings.filter(function (warning) {
132
130
  return !warning.message.includes(`export 'useInsertionEffect'`);
@@ -72,7 +72,7 @@ export async function buildStaticStandalone(options) {
72
72
  }
73
73
 
74
74
  logger.info('=> Loading presets');
75
- var presets = loadAllPresets(_objectSpread({
75
+ var presets = await loadAllPresets(_objectSpread({
76
76
  corePresets: [require.resolve('./presets/common-preset'), ...corePresets],
77
77
  overridePresets: []
78
78
  }, options));
@@ -84,18 +84,19 @@ export async function buildStaticStandalone(options) {
84
84
  previewBuilder = _await$getBuilders2[0],
85
85
  managerBuilder = _await$getBuilders2[1];
86
86
 
87
- presets = loadAllPresets(_objectSpread({
87
+ presets = await loadAllPresets(_objectSpread({
88
88
  corePresets: [require.resolve('./presets/common-preset'), ...(managerBuilder.corePresets || []), ...(previewBuilder.corePresets || []), ...corePresets, require.resolve('./presets/babel-cache-preset')],
89
89
  overridePresets: previewBuilder.overridePresets || []
90
90
  }, options));
91
91
 
92
- var _await$Promise$all = await Promise.all([presets.apply('features'), presets.apply('core'), presets.apply('staticDirs'), presets.apply('storyIndexers', []), presets.apply('stories')]),
93
- _await$Promise$all2 = _slicedToArray(_await$Promise$all, 5),
92
+ var _await$Promise$all = await Promise.all([presets.apply('features'), presets.apply('core'), presets.apply('staticDirs'), presets.apply('storyIndexers', []), presets.apply('stories'), presets.apply('docs', {})]),
93
+ _await$Promise$all2 = _slicedToArray(_await$Promise$all, 6),
94
94
  features = _await$Promise$all2[0],
95
95
  core = _await$Promise$all2[1],
96
96
  staticDirs = _await$Promise$all2[2],
97
97
  storyIndexers = _await$Promise$all2[3],
98
- stories = _await$Promise$all2[4];
98
+ stories = _await$Promise$all2[4],
99
+ docsOptions = _await$Promise$all2[5];
99
100
 
100
101
  var fullOptions = _objectSpread(_objectSpread({}, options), {}, {
101
102
  presets: presets,
@@ -140,6 +141,7 @@ export async function buildStaticStandalone(options) {
140
141
  var normalizedStories = normalizeStories(stories, directories);
141
142
  var generator = new StoryIndexGenerator(normalizedStories, _objectSpread(_objectSpread({}, directories), {}, {
142
143
  storyIndexers: storyIndexers,
144
+ docs: docsOptions,
143
145
  storiesV2Compatibility: !(features !== null && features !== void 0 && features.breakingChangesV7) && !(features !== null && features !== void 0 && features.storyStoreV7),
144
146
  storyStoreV7: !!(features !== null && features !== void 0 && features.storyStoreV7)
145
147
  }));
@@ -54,8 +54,10 @@ export async function storybookDevServer(options) {
54
54
  };
55
55
  var normalizedStories = normalizeStories(await options.presets.apply('stories'), directories);
56
56
  var storyIndexers = await options.presets.apply('storyIndexers', []);
57
+ var docsOptions = await options.presets.apply('docs', {});
57
58
  var generator = new StoryIndexGenerator(normalizedStories, _objectSpread(_objectSpread({}, directories), {}, {
58
59
  storyIndexers: storyIndexers,
60
+ docs: docsOptions,
59
61
  workingDir: workingDir,
60
62
  storiesV2Compatibility: !(features !== null && features !== void 0 && features.breakingChangesV7) && !(features !== null && features !== void 0 && features.storyStoreV7),
61
63
  storyStoreV7: features === null || features === void 0 ? void 0 : features.storyStoreV7
@@ -30,10 +30,30 @@ import { normalizeStoryPath } from '@storybook/core-common';
30
30
  import { logger } from '@storybook/node-logger';
31
31
  import { getStorySortParameter } from '@storybook/csf-tools';
32
32
  import { toId } from '@storybook/csf';
33
+ /** A .mdx file will produce a "standalone" docs entry */
33
34
 
34
35
  var makeAbsolute = function (otherImport, normalizedPath, workingDir) {
35
36
  return otherImport.startsWith('.') ? slash(path.resolve(workingDir, normalizeStoryPath(path.join(path.dirname(normalizedPath), otherImport)))) : otherImport;
36
37
  };
38
+ /**
39
+ * The StoryIndexGenerator extracts stories and docs entries for each file matching
40
+ * (one or more) stories "specifiers", as defined in main.js.
41
+ *
42
+ * The output is a set of entries (see above for the types).
43
+ *
44
+ * Each file is treated as a stories or a (modern) docs file.
45
+ *
46
+ * A stories file is indexed by an indexer (passed in), which produces a list of stories.
47
+ * - If the stories have the `parameters.docsOnly` setting, they are disregarded.
48
+ * - If the indexer is a "docs template" indexer, OR docsPage is enabled,
49
+ * a templated docs entry is added pointing to the story file.
50
+ *
51
+ * A (modern) docs file is indexed, a standalone docs entry is added.
52
+ *
53
+ * The entries are "uniq"-ed and sorted. Stories entries are preferred to docs entries and
54
+ * standalone docs entries are preferred to templates (with warnings).
55
+ */
56
+
37
57
 
38
58
  export class StoryIndexGenerator {
39
59
  // An internal cache mapping specifiers to a set of path=><set of stories>
@@ -79,14 +99,15 @@ export class StoryIndexGenerator {
79
99
  */
80
100
 
81
101
 
82
- async updateExtracted(updater) {
102
+ async updateExtracted(updater, overwrite = false) {
83
103
  var _this2 = this;
84
104
 
85
105
  await Promise.all(this.specifiers.map(async function (specifier) {
86
106
  var entry = _this2.specifierToCache.get(specifier);
87
107
 
88
108
  return Promise.all(Object.keys(entry).map(async function (absolutePath) {
89
- entry[absolutePath] = entry[absolutePath] || (await updater(specifier, absolutePath));
109
+ if (entry[absolutePath] && !overwrite) return;
110
+ entry[absolutePath] = await updater(specifier, absolutePath, entry[absolutePath]);
90
111
  }));
91
112
  }));
92
113
  }
@@ -105,9 +126,13 @@ export class StoryIndexGenerator {
105
126
  await this.updateExtracted(async function (specifier, absolutePath) {
106
127
  return _this3.isDocsMdx(absolutePath) ? false : _this3.extractStories(specifier, absolutePath);
107
128
  });
108
- await this.updateExtracted(async function (specifier, absolutePath) {
109
- return _this3.extractDocs(specifier, absolutePath);
110
- });
129
+
130
+ if (this.options.docs.enabled) {
131
+ await this.updateExtracted(async function (specifier, absolutePath) {
132
+ return _this3.extractDocs(specifier, absolutePath);
133
+ });
134
+ }
135
+
111
136
  return this.specifiers.flatMap(function (specifier) {
112
137
  var cache = _this3.specifierToCache.get(specifier);
113
138
 
@@ -148,6 +173,77 @@ export class StoryIndexGenerator {
148
173
  return dependencies;
149
174
  }
150
175
 
176
+ async extractStories(specifier, absolutePath) {
177
+ var relativePath = path.relative(this.options.workingDir, absolutePath);
178
+ var entries = [];
179
+
180
+ try {
181
+ var importPath = slash(normalizeStoryPath(relativePath));
182
+
183
+ var makeTitle = function (userTitle) {
184
+ return userOrAutoTitleFromSpecifier(importPath, specifier, userTitle);
185
+ };
186
+
187
+ var storyIndexer = this.options.storyIndexers.find(function (indexer) {
188
+ return indexer.test.exec(absolutePath);
189
+ });
190
+
191
+ if (!storyIndexer) {
192
+ throw new Error(`No matching story indexer found for ${absolutePath}`);
193
+ }
194
+
195
+ var csf = await storyIndexer.indexer(absolutePath, {
196
+ makeTitle: makeTitle
197
+ });
198
+ csf.stories.forEach(function ({
199
+ id: id,
200
+ name: name,
201
+ parameters: parameters
202
+ }) {
203
+ if (!(parameters !== null && parameters !== void 0 && parameters.docsOnly)) {
204
+ entries.push({
205
+ id: id,
206
+ title: csf.meta.title,
207
+ name: name,
208
+ importPath: importPath,
209
+ type: 'story'
210
+ });
211
+ }
212
+ });
213
+
214
+ if (this.options.docs.enabled) {
215
+ // We always add a template for *.stories.mdx, but only if docs page is enabled for
216
+ // regular CSF files
217
+ if (storyIndexer.addDocsTemplate || this.options.docs.docsPage) {
218
+ var name = this.options.docs.defaultName;
219
+ var id = toId(csf.meta.title, name);
220
+ entries.unshift({
221
+ id: id,
222
+ title: csf.meta.title,
223
+ name: name,
224
+ importPath: importPath,
225
+ type: 'docs',
226
+ storiesImports: [],
227
+ standalone: false
228
+ });
229
+ }
230
+ }
231
+ } catch (err) {
232
+ if (err.name === 'NoMetaError') {
233
+ logger.info(`💡 Skipping ${relativePath}: ${err}`);
234
+ } else {
235
+ logger.warn(`🚨 Extraction error on ${relativePath}: ${err}`);
236
+ throw err;
237
+ }
238
+ }
239
+
240
+ return {
241
+ entries: entries,
242
+ type: 'stories',
243
+ dependents: []
244
+ };
245
+ }
246
+
151
247
  async extractDocs(specifier, absolutePath) {
152
248
  var _this4 = this;
153
249
 
@@ -169,9 +265,10 @@ export class StoryIndexGenerator {
169
265
  var _await$require = await require('@storybook/docs-mdx'),
170
266
  analyze = _await$require.analyze;
171
267
 
172
- var content = await fs.readFile(absolutePath, 'utf8'); // { title?, of?, imports? }
268
+ var content = await fs.readFile(absolutePath, 'utf8');
269
+ var result = analyze(content); // Templates are not indexed
173
270
 
174
- var result = analyze(content);
271
+ if (result.isTemplate) return false;
175
272
  var absoluteImports = result.imports.map(function (p) {
176
273
  return makeAbsolute(p, normalizedPath, _this4.options.workingDir);
177
274
  }); // Go through the cache and collect all of the cache entries that this docs file depends on.
@@ -201,8 +298,8 @@ export class StoryIndexGenerator {
201
298
  dependencies.forEach(function (dep) {
202
299
  dep.dependents.push(absolutePath);
203
300
  });
204
- var title = userOrAutoTitleFromSpecifier(importPath, specifier, result.title || ofTitle);
205
- var name = 'docs';
301
+ var title = ofTitle || userOrAutoTitleFromSpecifier(importPath, specifier, result.title);
302
+ var name = result.name || this.options.docs.defaultName;
206
303
  var id = toId(title, name);
207
304
  var docsEntry = {
208
305
  id: id,
@@ -212,7 +309,8 @@ export class StoryIndexGenerator {
212
309
  storiesImports: dependencies.map(function (dep) {
213
310
  return dep.entries[0].importPath;
214
311
  }),
215
- type: 'docs'
312
+ type: 'docs',
313
+ standalone: true
216
314
  };
217
315
  return docsEntry;
218
316
  } catch (err) {
@@ -221,72 +319,62 @@ export class StoryIndexGenerator {
221
319
  }
222
320
  }
223
321
 
224
- async index(filePath, options) {
225
- var storyIndexer = this.options.storyIndexers.find(function (indexer) {
226
- return indexer.test.exec(filePath);
227
- });
322
+ chooseDuplicate(firstEntry, secondEntry) {
323
+ var firstIsBetter = true;
228
324
 
229
- if (!storyIndexer) {
230
- throw new Error(`No matching story indexer found for ${filePath}`);
325
+ if (secondEntry.type === 'story') {
326
+ firstIsBetter = false;
327
+ } else if (secondEntry.standalone && firstEntry.type === 'docs' && !firstEntry.standalone) {
328
+ firstIsBetter = false;
231
329
  }
232
330
 
233
- return storyIndexer.indexer(filePath, options);
234
- }
331
+ var betterEntry = firstIsBetter ? firstEntry : secondEntry;
332
+ var worseEntry = firstIsBetter ? secondEntry : firstEntry;
333
+ var changeDocsName = 'Use `<Meta of={} name="Other Name">` to distinguish them.'; // This shouldn't be possible, but double check and use for typing
235
334
 
236
- async extractStories(specifier, absolutePath) {
237
- var relativePath = path.relative(this.options.workingDir, absolutePath);
238
- var entries = [];
335
+ if (worseEntry.type === 'story') throw new Error(`Duplicate stories with id: ${firstEntry.id}`);
239
336
 
240
- try {
241
- var importPath = slash(normalizeStoryPath(relativePath));
337
+ if (betterEntry.type === 'story') {
338
+ var worseDescriptor = worseEntry.standalone ? `component docs page` : `automatically generated docs page`;
242
339
 
243
- var makeTitle = function (userTitle) {
244
- return userOrAutoTitleFromSpecifier(importPath, specifier, userTitle);
245
- };
246
-
247
- var csf = await this.index(absolutePath, {
248
- makeTitle: makeTitle
249
- });
250
- csf.stories.forEach(function ({
251
- id: id,
252
- name: name,
253
- parameters: parameters
254
- }) {
255
- var base = {
256
- id: id,
257
- title: csf.meta.title,
258
- name: name,
259
- importPath: importPath
260
- };
261
- var entry = parameters !== null && parameters !== void 0 && parameters.docsOnly ? _objectSpread(_objectSpread({}, base), {}, {
262
- type: 'docs',
263
- storiesImports: [],
264
- legacy: true
265
- }) : _objectSpread(_objectSpread({}, base), {}, {
266
- type: 'story'
267
- });
268
- entries.push(entry);
269
- });
270
- } catch (err) {
271
- if (err.name === 'NoMetaError') {
272
- logger.info(`💡 Skipping ${relativePath}: ${err}`);
340
+ if (betterEntry.name === this.options.docs.defaultName) {
341
+ logger.warn(`🚨 You have a story for ${betterEntry.title} with the same name as your default docs entry name (${betterEntry.name}), so the docs page is being dropped. Consider changing the story name.`);
273
342
  } else {
274
- logger.warn(`🚨 Extraction error on ${relativePath}: ${err}`);
275
- throw err;
343
+ logger.warn(`🚨 You have a story for ${betterEntry.title} with the same name as your ${worseDescriptor} (${worseEntry.name}), so the docs page is being dropped. ${changeDocsName}`);
276
344
  }
345
+ } else if (betterEntry.standalone) {
346
+ // Both entries are standalone but pointing at the same place
347
+ if (worseEntry.standalone) {
348
+ logger.warn(`🚨 You have two component docs pages with the same name ${betterEntry.title}:${betterEntry.name}. ${changeDocsName}`);
349
+ } // If one entry is standalone (i.e. .mdx of={}) we are OK with it overriding a template
350
+ // - docs page templates, this is totally fine and expected
351
+ // - not sure if it is even possible to have a .mdx of={} pointing at a stories.mdx file
352
+
353
+ } else {
354
+ // If both entries are templates (e.g. you have two CSF files with the same title), then
355
+ // we need to merge the entries. We'll use the the first one's name and importPath,
356
+ // but ensure we include both as storiesImports so they are both loaded before rendering
357
+ // the story (for the <Stories> block & friends)
358
+ return _objectSpread(_objectSpread({}, betterEntry), {}, {
359
+ storiesImports: [...betterEntry.storiesImports, worseEntry.importPath, ...worseEntry.storiesImports]
360
+ });
277
361
  }
278
362
 
279
- return {
280
- entries: entries,
281
- type: 'stories',
282
- dependents: []
283
- };
363
+ return betterEntry;
284
364
  }
285
365
 
286
366
  async sortStories(storiesList) {
367
+ var _this5 = this;
368
+
287
369
  var entries = {};
288
370
  storiesList.forEach(function (entry) {
289
- entries[entry.id] = entry;
371
+ var existing = entries[entry.id];
372
+
373
+ if (existing) {
374
+ entries[entry.id] = _this5.chooseDuplicate(existing, entry);
375
+ } else {
376
+ entries[entry.id] = entry;
377
+ }
290
378
  });
291
379
  var sortableStories = Object.values(entries); // Skip sorting if we're in v6 mode because we don't have
292
380
  // all the info we need here
@@ -344,7 +432,7 @@ export class StoryIndexGenerator {
344
432
  }
345
433
 
346
434
  invalidate(specifier, importPath, removed) {
347
- var _this5 = this;
435
+ var _this6 = this;
348
436
 
349
437
  var absolutePath = slash(path.resolve(this.options.workingDir, importPath));
350
438
  var cache = this.specifierToCache.get(specifier);
@@ -375,7 +463,7 @@ export class StoryIndexGenerator {
375
463
  if (removed) {
376
464
  if (cacheEntry && cacheEntry.type === 'docs') {
377
465
  var absoluteImports = cacheEntry.storiesImports.map(function (p) {
378
- return path.resolve(_this5.options.workingDir, p);
466
+ return path.resolve(_this6.options.workingDir, p);
379
467
  });
380
468
  var dependencies = this.findDependencies(absoluteImports);
381
469
  dependencies.forEach(function (dep) {
@@ -392,10 +480,10 @@ export class StoryIndexGenerator {
392
480
  }
393
481
 
394
482
  async getStorySortParameter() {
395
- var _this6 = this;
483
+ var _this7 = this;
396
484
 
397
485
  var previewFile = ['js', 'jsx', 'ts', 'tsx'].map(function (ext) {
398
- return path.join(_this6.options.configDir, `preview.${ext}`);
486
+ return path.join(_this7.options.configDir, `preview.${ext}`);
399
487
  }).find(function (fname) {
400
488
  return fs.existsSync(fname);
401
489
  });
@@ -0,0 +1,6 @@
1
+ var component = {};
2
+ export default {
3
+ component: component,
4
+ title: 'duplicate/A'
5
+ };
6
+ export var StoryOne = {};
@@ -0,0 +1,6 @@
1
+ var component = {};
2
+ export default {
3
+ component: component,
4
+ title: 'duplicate/A'
5
+ };
6
+ export var StoryTwo = {};
@@ -1,15 +1,15 @@
1
1
  import { Router } from 'express';
2
- import type { Options } from '@storybook/core-common';
2
+ import { Options } from '@storybook/core-common';
3
3
  export declare const router: Router;
4
4
  export declare const DEBOUNCE = 100;
5
5
  export declare function storybookDevServer(options: Options): Promise<{
6
6
  previewResult: void | {
7
- stats: unknown;
7
+ stats?: import("@storybook/core-common").Stats;
8
8
  totalTime: [number, number];
9
9
  bail: (e?: Error) => Promise<void>;
10
10
  };
11
11
  managerResult: void | {
12
- stats: unknown;
12
+ stats?: import("@storybook/core-common").Stats;
13
13
  totalTime: [number, number];
14
14
  bail: (e?: Error) => Promise<void>;
15
15
  };
@@ -1,12 +1,32 @@
1
- import type { Path, StoryIndex, IndexEntry, DocsIndexEntry } from '@storybook/store';
2
- import type { StoryIndexer, IndexerOptions, NormalizedStoriesSpecifier } from '@storybook/core-common';
3
- declare type DocsCacheEntry = DocsIndexEntry;
1
+ import type { Path, StoryIndex, IndexEntry, StoryIndexEntry, StandaloneDocsIndexEntry, TemplateDocsIndexEntry } from '@storybook/store';
2
+ import type { StoryIndexer, NormalizedStoriesSpecifier, DocsOptions } from '@storybook/core-common';
3
+ /** A .mdx file will produce a "standalone" docs entry */
4
+ declare type DocsCacheEntry = StandaloneDocsIndexEntry;
5
+ /** A *.stories.* file will produce a list of stories and possibly a docs entry */
4
6
  declare type StoriesCacheEntry = {
5
- entries: IndexEntry[];
7
+ entries: (StoryIndexEntry | TemplateDocsIndexEntry)[];
6
8
  dependents: Path[];
7
9
  type: 'stories';
8
10
  };
9
11
  declare type CacheEntry = false | StoriesCacheEntry | DocsCacheEntry;
12
+ /**
13
+ * The StoryIndexGenerator extracts stories and docs entries for each file matching
14
+ * (one or more) stories "specifiers", as defined in main.js.
15
+ *
16
+ * The output is a set of entries (see above for the types).
17
+ *
18
+ * Each file is treated as a stories or a (modern) docs file.
19
+ *
20
+ * A stories file is indexed by an indexer (passed in), which produces a list of stories.
21
+ * - If the stories have the `parameters.docsOnly` setting, they are disregarded.
22
+ * - If the indexer is a "docs template" indexer, OR docsPage is enabled,
23
+ * a templated docs entry is added pointing to the story file.
24
+ *
25
+ * A (modern) docs file is indexed, a standalone docs entry is added.
26
+ *
27
+ * The entries are "uniq"-ed and sorted. Stories entries are preferred to docs entries and
28
+ * standalone docs entries are preferred to templates (with warnings).
29
+ */
10
30
  export declare class StoryIndexGenerator {
11
31
  readonly specifiers: NormalizedStoriesSpecifier[];
12
32
  readonly options: {
@@ -15,6 +35,7 @@ export declare class StoryIndexGenerator {
15
35
  storiesV2Compatibility: boolean;
16
36
  storyStoreV7: boolean;
17
37
  storyIndexers: StoryIndexer[];
38
+ docs: DocsOptions;
18
39
  };
19
40
  private specifierToCache;
20
41
  private lastIndex?;
@@ -24,18 +45,19 @@ export declare class StoryIndexGenerator {
24
45
  storiesV2Compatibility: boolean;
25
46
  storyStoreV7: boolean;
26
47
  storyIndexers: StoryIndexer[];
48
+ docs: DocsOptions;
27
49
  });
28
50
  initialize(): Promise<void>;
29
51
  /**
30
52
  * Run the updater function over all the empty cache entries
31
53
  */
32
- updateExtracted(updater: (specifier: NormalizedStoriesSpecifier, absolutePath: Path) => Promise<CacheEntry>): Promise<void>;
54
+ updateExtracted(updater: (specifier: NormalizedStoriesSpecifier, absolutePath: Path, existingEntry: CacheEntry) => Promise<CacheEntry>, overwrite?: boolean): Promise<void>;
33
55
  isDocsMdx(absolutePath: Path): boolean;
34
56
  ensureExtracted(): Promise<IndexEntry[]>;
35
57
  findDependencies(absoluteImports: Path[]): StoriesCacheEntry[];
36
- extractDocs(specifier: NormalizedStoriesSpecifier, absolutePath: Path): Promise<DocsIndexEntry>;
37
- index(filePath: string, options: IndexerOptions): Promise<import("@storybook/core-common").StoryIndex>;
38
58
  extractStories(specifier: NormalizedStoriesSpecifier, absolutePath: Path): Promise<StoriesCacheEntry>;
59
+ extractDocs(specifier: NormalizedStoriesSpecifier, absolutePath: Path): Promise<false | StandaloneDocsIndexEntry>;
60
+ chooseDuplicate(firstEntry: IndexEntry, secondEntry: IndexEntry): IndexEntry;
39
61
  sortStories(storiesList: IndexEntry[]): Promise<Record<string, IndexEntry>>;
40
62
  getIndex(): Promise<StoryIndex>;
41
63
  invalidate(specifier: NormalizedStoriesSpecifier, importPath: Path, removed: boolean): void;
@@ -1,2 +1,2 @@
1
1
  import type { Options, Builder } from '@storybook/core-common';
2
- export declare function getBuilders({ presets, configDir, }: Options): Promise<Builder<unknown, unknown>[]>;
2
+ export declare function getBuilders({ presets, configDir, }: Options): Promise<Builder<unknown>[]>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@storybook/core-server",
3
- "version": "7.0.0-alpha.11",
3
+ "version": "7.0.0-alpha.16",
4
4
  "description": "Storybook framework-agnostic API",
5
5
  "keywords": [
6
6
  "storybook"
@@ -35,17 +35,17 @@
35
35
  "dependencies": {
36
36
  "@aw-web-design/x-default-browser": "1.4.88",
37
37
  "@discoveryjs/json-ext": "^0.5.3",
38
- "@storybook/builder-manager": "7.0.0-alpha.11",
39
- "@storybook/core-client": "7.0.0-alpha.11",
40
- "@storybook/core-common": "7.0.0-alpha.11",
41
- "@storybook/core-events": "7.0.0-alpha.11",
38
+ "@storybook/builder-manager": "7.0.0-alpha.16",
39
+ "@storybook/core-client": "7.0.0-alpha.16",
40
+ "@storybook/core-common": "7.0.0-alpha.16",
41
+ "@storybook/core-events": "7.0.0-alpha.16",
42
42
  "@storybook/csf": "0.0.2--canary.4566f4d.1",
43
- "@storybook/csf-tools": "7.0.0-alpha.11",
44
- "@storybook/docs-mdx": "0.0.1-canary.1.4bea5cc.0",
45
- "@storybook/node-logger": "7.0.0-alpha.11",
43
+ "@storybook/csf-tools": "7.0.0-alpha.16",
44
+ "@storybook/docs-mdx": "0.0.1-canary.12433cf.0",
45
+ "@storybook/node-logger": "7.0.0-alpha.16",
46
46
  "@storybook/semver": "^7.3.2",
47
- "@storybook/store": "7.0.0-alpha.11",
48
- "@storybook/telemetry": "7.0.0-alpha.11",
47
+ "@storybook/store": "7.0.0-alpha.16",
48
+ "@storybook/telemetry": "7.0.0-alpha.16",
49
49
  "@types/node": "^14.0.10 || ^16.0.0",
50
50
  "@types/node-fetch": "^2.5.7",
51
51
  "@types/pretty-hrtime": "^1.0.0",
@@ -78,7 +78,7 @@
78
78
  "ws": "^8.2.3"
79
79
  },
80
80
  "devDependencies": {
81
- "@storybook/builder-webpack5": "7.0.0-alpha.11",
81
+ "@storybook/builder-webpack5": "7.0.0-alpha.16",
82
82
  "@types/compression": "^1.7.0",
83
83
  "@types/ip": "^1.1.0",
84
84
  "@types/serve-favicon": "^2.5.2",
@@ -101,5 +101,5 @@
101
101
  "publishConfig": {
102
102
  "access": "public"
103
103
  },
104
- "gitHead": "688d338903e84a7e83cb104472e868e734399f65"
104
+ "gitHead": "df30e7db2b251418af106345e5722477f057ec36"
105
105
  }