diff --git a/lib/cli/package.json b/lib/cli/package.json index a1b1aebca2de..e1e145797252 100644 --- a/lib/cli/package.json +++ b/lib/cli/package.json @@ -63,6 +63,7 @@ "update-notifier": "^4.0.0" }, "devDependencies": { + "@storybook/client-api": "6.0.0-rc.12", "@types/cross-spawn": "^6.0.1", "@types/inquirer": "^6.5.0", "@types/puppeteer-core": "^2.0.0", diff --git a/lib/cli/src/extract.ts b/lib/cli/src/extract.ts index b8b263df55ed..75182dd1290a 100644 --- a/lib/cli/src/extract.ts +++ b/lib/cli/src/extract.ts @@ -17,28 +17,7 @@ const read = async (url: string) => { const data = JSON.parse( await page.evaluate(async () => { // eslint-disable-next-line no-undef - const d = window.__STORYBOOK_STORY_STORE__.extract(); - - const result = Object.entries(d).reduce( - (acc, [k, v]: [string, any]) => ({ - ...acc, - [k]: { - ...v, - parameters: { - globals: v.parameters.globals, - globalTypes: v.parameters.globalTypes, - options: v.parameters.options, - args: v.parameters.args, - argTypes: v.parameters.argTypes, - framework: v.parameters.framework, - fileName: v.parameters.fileName, - docsOnly: v.parameters.docsOnly, - }, - }, - }), - {} - ); - return JSON.stringify(result, null, 2); + return JSON.stringify(window.__STORYBOOK_STORY_STORE__.getStoriesJsonData(), null, 2); }) ); @@ -95,9 +74,9 @@ export async function extract(input: string, targetPath: string) { if (input && targetPath) { const [location, exit] = await useLocation(input); - const stories = await read(location); + const data = await read(location); - await writeFile(targetPath, JSON.stringify({ stories }, null, 2)); + await writeFile(targetPath, JSON.stringify(data, null, 2)); await exit(); } else { diff --git a/lib/client-api/src/story_store.test.ts b/lib/client-api/src/story_store.test.ts index 614e7050cdc0..b4cb59b4cdf8 100644 --- a/lib/client-api/src/story_store.test.ts +++ b/lib/client-api/src/story_store.test.ts @@ -107,6 +107,33 @@ describe('preview.story_store', () => { }); }); + describe('getStoriesJsonData', () => { + it('produces stories objects with normalized metadata', () => { + const store = new StoryStore({ channel }); + + store.addGlobalMetadata({ parameters: { global: 'global' }, decorators: [] }); + + store.addKindMetadata('a', { parameters: { kind: 'kind' }, decorators: [] }); + + addStoryToStore(store, 'a', '1', () => 0, { story: 'story' }); + + const { v, globalParameters, kindParameters, stories } = store.getStoriesJsonData(); + + expect(v).toBe(2); + expect(globalParameters).toEqual({}); + expect(kindParameters).toEqual({ a: {} }); + expect(kindParameters.a).toEqual({}); + + expect(Object.keys(stories)).toEqual(['a--1']); + expect(stories['a--1']).toMatchObject({ + id: 'a--1', + kind: 'a', + name: '1', + parameters: { __isArgsStory: false }, + }); + }); + }); + describe('getRawStory', () => { it('produces a story with inherited decorators applied', () => { const store = new StoryStore({ channel }); diff --git a/lib/client-api/src/story_store.ts b/lib/client-api/src/story_store.ts index e50b413536f3..f1b8d1f96441 100644 --- a/lib/client-api/src/story_store.ts +++ b/lib/client-api/src/story_store.ts @@ -3,6 +3,7 @@ import memoize from 'memoizerific'; import dedent from 'ts-dedent'; import stable from 'stable'; import mapValues from 'lodash/mapValues'; +import pick from 'lodash/pick'; import store from 'store2'; import { Channel } from '@storybook/channels'; @@ -574,6 +575,21 @@ export default class StoryStore { }; }; + getStoriesJsonData = () => { + const value = this.getDataForManager(); + const allowed = ['fileName', 'docsOnly', 'framework', '__id', '__isArgsStory']; + + return { + v: 2, + globalParameters: pick(value.globalParameters, allowed), + kindParameters: mapValues(value.kindParameters, (v) => pick(v, allowed)), + stories: mapValues(value.stories, (v: any) => ({ + ...pick(v, ['id', 'name', 'kind', 'story']), + parameters: pick(v.parameters, allowed), + })), + }; + }; + pushToManager = () => { if (this._channel) { // send to the parent frame. @@ -603,7 +619,7 @@ export default class StoryStore { this.getStoriesForKind(kind).map((story) => this.cleanHooks(story.id)); } - // This API is a reimplementation of Storybook's original getStorybook() API. + // This API is a re-implementation of Storybook's original getStorybook() API. // As such it may not behave *exactly* the same, but aims to. Some notes: // - It is *NOT* sorted by the user's sort function, but remains sorted in "insertion order" // - It does not include docs-only stories