mirror of
https://github.com/storybookjs/storybook.git
synced 2025-04-08 11:11:53 +08:00
Merge branch 'next' into docs_adds_doctor
This commit is contained in:
commit
6376e9d809
4
.git-blame-ignore-revs
Normal file
4
.git-blame-ignore-revs
Normal file
@ -0,0 +1,4 @@
|
||||
34e364a0ca1d93555d36a7367d78e8e229493de8
|
||||
c0896915fb7fb9a8dd416b9aebca17abd909d1c1
|
||||
a41c227037e7e7249b8b376f838f4f8bcc3e3e59
|
||||
13c46e6c0b7f3dd8cf4ba42d1cfd6714f4777d54
|
@ -1,3 +1,8 @@
|
||||
## 7.6.8
|
||||
|
||||
- Addon-actions: Fix module resolution for react-native - [#25296](https://github.com/storybookjs/storybook/pull/25296), thanks [@dannyhw](https://github.com/dannyhw)!
|
||||
- Storysource: Fix import error - [#25391](https://github.com/storybookjs/storybook/pull/25391), thanks [@unional](https://github.com/unional)!
|
||||
|
||||
## 7.6.7
|
||||
|
||||
- Core: Skip no-framework error when ignorePreview=true - [#25286](https://github.com/storybookjs/storybook/pull/25286), thanks [@ndelangen](https://github.com/ndelangen)!
|
||||
|
18
MIGRATION.md
18
MIGRATION.md
@ -59,6 +59,8 @@
|
||||
- [Description Doc block properties](#description-doc-block-properties)
|
||||
- [Story Doc block properties](#story-doc-block-properties)
|
||||
- [Manager API expandAll and collapseAll methods](#manager-api-expandall-and-collapseall-methods)
|
||||
- [Source Doc block properties](#source-doc-block-properties)
|
||||
- [Canvas Doc block properties](#canvas-doc-block-properties)
|
||||
- [`Primary` Doc block properties](#primary-doc-block-properties)
|
||||
- [`createChannel` from `@storybook/postmessage` and `@storybook/channel-websocket`](#createchannel-from-storybookpostmessage-and--storybookchannel-websocket)
|
||||
- [From version 7.5.0 to 7.6.0](#from-version-750-to-760)
|
||||
@ -950,6 +952,22 @@ api.collapseAll() // becomes api.emit(STORIES_COLLAPSE_ALL)
|
||||
api.expandAll() // becomes api.emit(STORIES_EXPAND_ALL)
|
||||
```
|
||||
|
||||
#### Source Doc block properties
|
||||
|
||||
`id` and `ids` are now removed in favor of the `of` property. [More info](#doc-blocks).
|
||||
|
||||
#### Canvas Doc block properties
|
||||
|
||||
The following properties were removed from the Canvas Doc block:
|
||||
|
||||
- children
|
||||
- isColumn
|
||||
- columns
|
||||
- withSource
|
||||
- mdxSource
|
||||
|
||||
[More info](#doc-blocks).
|
||||
|
||||
#### `Primary` Doc block properties
|
||||
|
||||
The `name` prop is now removed in favor of the `of` property. [More info](#doc-blocks).
|
||||
|
@ -27,6 +27,7 @@ module.exports = {
|
||||
'jest/no-standalone-expect': 'off',
|
||||
'jest/no-done-callback': 'off',
|
||||
'jest/no-deprecated-functions': 'off',
|
||||
'jest/valid-expect': 'off',
|
||||
|
||||
'eslint-comments/disable-enable-pair': ['error', { allowWholeFile: true }],
|
||||
'eslint-comments/no-unused-disable': 'error',
|
||||
|
@ -1,8 +1,8 @@
|
||||
diff --git a/dist/index.js b/dist/index.js
|
||||
index 5a61947ad50426d27390b4e82533179323ad3ba1..32bfc45909b645cb31cec2e204c8baa23f21fdd2 100644
|
||||
index 974d6b26f626024fc9904908100c9ecaa54f43e1..5be2d35267e7f0525c6588758dbebe72599f88a9 100644
|
||||
--- a/dist/index.js
|
||||
+++ b/dist/index.js
|
||||
@@ -6,23 +6,29 @@ import { processError } from '@vitest/utils/error';
|
||||
@@ -6,31 +6,37 @@ import { processError } from '@vitest/utils/error';
|
||||
import { util } from 'chai';
|
||||
|
||||
const MATCHERS_OBJECT = Symbol.for("matchers-object");
|
||||
@ -11,15 +11,19 @@ index 5a61947ad50426d27390b4e82533179323ad3ba1..32bfc45909b645cb31cec2e204c8baa2
|
||||
+// Otherwise, vitest will override global jest matchers, and crash.
|
||||
+const JEST_MATCHERS_OBJECT = Symbol.for("$$jest-matchers-object-storybook");
|
||||
const GLOBAL_EXPECT = Symbol.for("expect-global");
|
||||
const ASYMMETRIC_MATCHERS_OBJECT = Symbol.for("asymmetric-matchers-object");
|
||||
|
||||
if (!Object.prototype.hasOwnProperty.call(globalThis, MATCHERS_OBJECT)) {
|
||||
const globalState = /* @__PURE__ */ new WeakMap();
|
||||
- const matchers = /* @__PURE__ */ Object.create(null);
|
||||
const assymetricMatchers = /* @__PURE__ */ Object.create(null);
|
||||
Object.defineProperty(globalThis, MATCHERS_OBJECT, {
|
||||
get: () => globalState
|
||||
});
|
||||
+ Object.defineProperty(globalThis, ASYMMETRIC_MATCHERS_OBJECT, {
|
||||
+ get: () => assymetricMatchers
|
||||
+ });
|
||||
+}
|
||||
+
|
||||
+if (!Object.prototype.hasOwnProperty.call(globalThis, JEST_MATCHERS_OBJECT)) {
|
||||
+ const matchers = /* @__PURE__ */ Object.create(null);
|
||||
Object.defineProperty(globalThis, JEST_MATCHERS_OBJECT, {
|
||||
@ -30,8 +34,15 @@ index 5a61947ad50426d27390b4e82533179323ad3ba1..32bfc45909b645cb31cec2e204c8baa2
|
||||
matchers
|
||||
})
|
||||
});
|
||||
- Object.defineProperty(globalThis, ASYMMETRIC_MATCHERS_OBJECT, {
|
||||
- get: () => assymetricMatchers
|
||||
- });
|
||||
}
|
||||
+
|
||||
function getState(expect) {
|
||||
return globalThis[MATCHERS_OBJECT].get(expect);
|
||||
}
|
||||
+
|
||||
function setState(state, expect) {
|
||||
const map = globalThis[MATCHERS_OBJECT];
|
||||
const current = map.get(expect) || {};
|
@ -40,6 +40,7 @@
|
||||
},
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.mjs",
|
||||
"react-native": "dist/index.mjs",
|
||||
"types": "dist/index.d.ts",
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
|
@ -37,40 +37,24 @@ const createBackgroundSelectorItem = memoize(1000)(
|
||||
})
|
||||
);
|
||||
|
||||
const getDisplayedItems = memoize(10)(
|
||||
(
|
||||
const getDisplayedItems = memoize(10)((
|
||||
backgrounds: Background[],
|
||||
selectedBackgroundColor: string | null,
|
||||
change: (arg: { selected: string; name: string }) => void
|
||||
) => {
|
||||
const backgroundSelectorItems = backgrounds.map(({ name, value }) =>
|
||||
createBackgroundSelectorItem(
|
||||
null,
|
||||
name,
|
||||
value,
|
||||
true,
|
||||
change,
|
||||
value === selectedBackgroundColor
|
||||
)
|
||||
createBackgroundSelectorItem(null, name, value, true, change, value === selectedBackgroundColor)
|
||||
);
|
||||
|
||||
if (selectedBackgroundColor !== 'transparent') {
|
||||
return [
|
||||
createBackgroundSelectorItem(
|
||||
'reset',
|
||||
'Clear background',
|
||||
'transparent',
|
||||
null,
|
||||
change,
|
||||
false
|
||||
),
|
||||
createBackgroundSelectorItem('reset', 'Clear background', 'transparent', null, change, false),
|
||||
...backgroundSelectorItems,
|
||||
];
|
||||
}
|
||||
|
||||
return backgroundSelectorItems;
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
const DEFAULT_BACKGROUNDS_CONFIG: BackgroundsParameter = {
|
||||
default: null,
|
||||
|
@ -1,8 +1,27 @@
|
||||
import type { PreparedStory } from '@storybook/types';
|
||||
import { global } from '@storybook/global';
|
||||
|
||||
const excludeTags = Object.entries(global.TAGS_OPTIONS ?? {}).reduce((acc, entry) => {
|
||||
const [tag, option] = entry;
|
||||
if ((option as any).excludeFromDocsStories) {
|
||||
acc[tag] = true;
|
||||
}
|
||||
return acc;
|
||||
}, {} as Record<string, boolean>);
|
||||
|
||||
export const parameters: any = {
|
||||
docs: {
|
||||
renderer: async () => {
|
||||
const { DocsRenderer } = (await import('./DocsRenderer')) as any;
|
||||
return new DocsRenderer();
|
||||
},
|
||||
stories: {
|
||||
filter: (story: PreparedStory) => {
|
||||
const tags = story.tags || [];
|
||||
return (
|
||||
tags.filter((tag) => excludeTags[tag]).length === 0 && !story.parameters.docs?.disable
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
2
code/addons/docs/src/typings.d.ts
vendored
2
code/addons/docs/src/typings.d.ts
vendored
@ -11,3 +11,5 @@ declare module 'sveltedoc-parser' {
|
||||
declare var FEATURES: import('@storybook/types').StorybookConfigRaw['features'];
|
||||
|
||||
declare var LOGLEVEL: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'silent' | undefined;
|
||||
|
||||
declare var TAGS_OPTIONS: import('@storybook/types').TagsOptions;
|
||||
|
@ -35,8 +35,12 @@ const responsiveViewport: ViewportItem = {
|
||||
|
||||
const baseViewports: ViewportItem[] = [responsiveViewport];
|
||||
|
||||
const toLinks = memoize(50)(
|
||||
(list: ViewportItem[], active: LinkBase, updateGlobals, close): Link[] => {
|
||||
const toLinks = memoize(50)((
|
||||
list: ViewportItem[],
|
||||
active: LinkBase,
|
||||
updateGlobals,
|
||||
close
|
||||
): Link[] => {
|
||||
return list
|
||||
.filter((i) => i.id !== responsiveViewport.id || active.id !== i.id)
|
||||
.map((i) => {
|
||||
@ -48,8 +52,7 @@ const toLinks = memoize(50)(
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
interface LinkBase {
|
||||
id: string;
|
||||
|
@ -138,6 +138,7 @@ const starter: StarterFunction = async function* starterGeneratorFn({
|
||||
title,
|
||||
logLevel,
|
||||
docsOptions,
|
||||
tagsOptions,
|
||||
} = await getData(options);
|
||||
|
||||
yield;
|
||||
@ -175,6 +176,7 @@ const starter: StarterFunction = async function* starterGeneratorFn({
|
||||
refs,
|
||||
logLevel,
|
||||
docsOptions,
|
||||
tagsOptions,
|
||||
options
|
||||
);
|
||||
|
||||
@ -222,6 +224,7 @@ const builder: BuilderFunction = async function* builderGeneratorFn({ startTime,
|
||||
title,
|
||||
logLevel,
|
||||
docsOptions,
|
||||
tagsOptions,
|
||||
} = await getData(options);
|
||||
yield;
|
||||
|
||||
@ -262,6 +265,7 @@ const builder: BuilderFunction = async function* builderGeneratorFn({ startTime,
|
||||
refs,
|
||||
logLevel,
|
||||
docsOptions,
|
||||
tagsOptions,
|
||||
options
|
||||
);
|
||||
|
||||
|
@ -14,6 +14,7 @@ export const getData = async (options: Options) => {
|
||||
const logLevel = options.presets.apply<string>('logLevel');
|
||||
const title = options.presets.apply<string>('title');
|
||||
const docsOptions = options.presets.apply('docs', {});
|
||||
const tagsOptions = options.presets.apply('tags', {});
|
||||
const template = readTemplate('template.ejs');
|
||||
const customHead = options.presets.apply<string>('managerHead');
|
||||
|
||||
@ -35,5 +36,6 @@ export const getData = async (options: Options) => {
|
||||
config,
|
||||
logLevel,
|
||||
favicon,
|
||||
tagsOptions,
|
||||
};
|
||||
};
|
||||
|
@ -3,7 +3,7 @@ import fs from 'fs-extra';
|
||||
|
||||
import { render } from 'ejs';
|
||||
|
||||
import type { DocsOptions, Options, Ref } from '@storybook/types';
|
||||
import type { DocsOptions, TagsOptions, Options, Ref } from '@storybook/types';
|
||||
|
||||
export const getTemplatePath = async (template: string) => {
|
||||
return join(
|
||||
@ -34,6 +34,7 @@ export const renderHTML = async (
|
||||
refs: Promise<Record<string, Ref>>,
|
||||
logLevel: Promise<string>,
|
||||
docsOptions: Promise<DocsOptions>,
|
||||
tagsOptions: Promise<TagsOptions>,
|
||||
{ versionCheck, previewUrl, configType, ignorePreview }: Options
|
||||
) => {
|
||||
const titleRef = await title;
|
||||
@ -52,6 +53,7 @@ export const renderHTML = async (
|
||||
// These two need to be double stringified because the UI expects a string
|
||||
VERSIONCHECK: JSON.stringify(JSON.stringify(versionCheck), null, 2),
|
||||
PREVIEW_URL: JSON.stringify(previewUrl, null, 2), // global preview URL
|
||||
TAGS_OPTIONS: JSON.stringify(await tagsOptions, null, 2),
|
||||
},
|
||||
head: (await customHead) || '',
|
||||
ignorePreview,
|
||||
|
@ -21,6 +21,7 @@
|
||||
window.FEATURES = '[FEATURES HERE]';
|
||||
window.STORIES = '[STORIES HERE]';
|
||||
window.DOCS_OPTIONS = '[DOCS_OPTIONS HERE]';
|
||||
window.TAGS_OPTIONS = '[TAGS_OPTIONS HERE]';
|
||||
|
||||
('OTHER_GLOBLALS HERE');
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { normalizeStories } from '@storybook/core-common';
|
||||
import type { DocsOptions, Options } from '@storybook/types';
|
||||
import type { DocsOptions, TagsOptions, Options } from '@storybook/types';
|
||||
|
||||
export type PreviewHtml = string | undefined;
|
||||
|
||||
@ -11,6 +11,7 @@ export async function transformIframeHtml(html: string, options: Options) {
|
||||
const bodyHtmlSnippet = await presets.apply<PreviewHtml>('previewBody');
|
||||
const logLevel = await presets.apply('logLevel', undefined);
|
||||
const docsOptions = await presets.apply<DocsOptions>('docs');
|
||||
const tagsOptions = await presets.apply<TagsOptions>('tags');
|
||||
|
||||
const coreOptions = await presets.apply('core');
|
||||
const stories = normalizeStories(await options.presets.apply('stories', [], options), {
|
||||
@ -42,6 +43,7 @@ export async function transformIframeHtml(html: string, options: Options) {
|
||||
.replace(`'[FEATURES HERE]'`, JSON.stringify(features || {}))
|
||||
.replace(`'[STORIES HERE]'`, JSON.stringify(stories || {}))
|
||||
.replace(`'[DOCS_OPTIONS HERE]'`, JSON.stringify(docsOptions || {}))
|
||||
.replace(`'[TAGS_OPTIONS HERE]'`, JSON.stringify(tagsOptions || {}))
|
||||
.replace('<!-- [HEAD HTML SNIPPET HERE] -->', headHtmlSnippet || '')
|
||||
.replace('<!-- [BODY HTML SNIPPET HERE] -->', bodyHtmlSnippet || '');
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ const dummyOptions: Options = {
|
||||
builder: {},
|
||||
},
|
||||
options: {},
|
||||
}[key]),
|
||||
})[key],
|
||||
} as Presets,
|
||||
presetsList: [],
|
||||
};
|
||||
|
@ -82,6 +82,7 @@ export default async (
|
||||
nonNormalizedStories,
|
||||
modulesCount = 1000,
|
||||
build,
|
||||
tagsOptions,
|
||||
] = await Promise.all([
|
||||
presets.apply('core'),
|
||||
presets.apply('frameworkOptions'),
|
||||
@ -95,6 +96,7 @@ export default async (
|
||||
presets.apply('stories', []),
|
||||
options.cache?.get('modulesCount').catch(() => {}),
|
||||
options.presets.apply('build'),
|
||||
presets.apply('tags', {}),
|
||||
]);
|
||||
|
||||
const stories = normalizeStories(nonNormalizedStories, {
|
||||
@ -129,9 +131,8 @@ export default async (
|
||||
externals['@storybook/blocks'] = '__STORYBOOK_BLOCKS_EMPTY_MODULE__';
|
||||
}
|
||||
|
||||
const { virtualModules: virtualModuleMapping, entries: dynamicEntries } = await getVirtualModules(
|
||||
options
|
||||
);
|
||||
const { virtualModules: virtualModuleMapping, entries: dynamicEntries } =
|
||||
await getVirtualModules(options);
|
||||
|
||||
return {
|
||||
name: 'preview',
|
||||
@ -185,6 +186,7 @@ export default async (
|
||||
importPathMatcher: specifier.importPathMatcher.source,
|
||||
})),
|
||||
DOCS_OPTIONS: docsOptions,
|
||||
TAGS_OPTIONS: tagsOptions,
|
||||
...(build?.test?.disableBlocks ? { __STORYBOOK_BLOCKS_EMPTY_MODULE__: {} } : {}),
|
||||
},
|
||||
headHtmlSnippet,
|
||||
@ -215,6 +217,7 @@ export default async (
|
||||
rules: [
|
||||
{
|
||||
test: /\.stories\.([tj])sx?$|(stories|story)\.mdx$/,
|
||||
exclude: /node_modules/,
|
||||
enforce: 'post',
|
||||
use: [
|
||||
{
|
||||
|
62
code/e2e-tests/tags.spec.ts
Normal file
62
code/e2e-tests/tags.spec.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { SbPage } from './util';
|
||||
|
||||
const storybookUrl = process.env.STORYBOOK_URL || 'http://localhost:8001';
|
||||
|
||||
test.describe('tags', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto(storybookUrl);
|
||||
await new SbPage(page).waitUntilLoaded();
|
||||
});
|
||||
|
||||
test('should correctly filter dev-only, docs-only, test-only stories', async ({ page }) => {
|
||||
const sbPage = new SbPage(page);
|
||||
|
||||
await sbPage.navigateToStory('lib/preview-api/tags', 'docs');
|
||||
|
||||
// Sidebar should include dev-only and exclude docs-only and test-only
|
||||
const devOnlyEntry = await page.locator('#lib-preview-api-tags--dev-only').all();
|
||||
expect(devOnlyEntry.length).toBe(1);
|
||||
|
||||
const docsOnlyEntry = await page.locator('#lib-preview-api-tags--docs-only').all();
|
||||
expect(docsOnlyEntry.length).toBe(0);
|
||||
|
||||
const testOnlyEntry = await page.locator('#lib-preview-api-tags--test-only').all();
|
||||
expect(testOnlyEntry.length).toBe(0);
|
||||
|
||||
// Autodocs should include docs-only and exclude dev-only and test-only
|
||||
const root = sbPage.previewRoot();
|
||||
|
||||
const devOnlyAnchor = await root.locator('#anchor--lib-preview-api-tags--dev-only').all();
|
||||
expect(devOnlyAnchor.length).toBe(0);
|
||||
|
||||
const docsOnlyAnchor = await root.locator('#anchor--lib-preview-api-tags--docs-only').all();
|
||||
expect(docsOnlyAnchor.length).toBe(1);
|
||||
|
||||
const testOnlyAnchor = await root.locator('#anchor--lib-preview-api-tags--test-only').all();
|
||||
expect(testOnlyAnchor.length).toBe(0);
|
||||
});
|
||||
|
||||
test('should correctly filter out test-only autodocs pages', async ({ page }) => {
|
||||
const sbPage = new SbPage(page);
|
||||
|
||||
await sbPage.selectToolbar('#lib-preview-api');
|
||||
|
||||
// Sidebar should exclude test-only stories and their docs
|
||||
const componentEntry = await page.locator('#lib-preview-api-test-only-tag').all();
|
||||
expect(componentEntry.length).toBe(0);
|
||||
|
||||
// Even though test-only autodocs not sidebar, it is still in the preview
|
||||
// Even though the test-only story is filtered out of the stories, it is still the primary story (should it be?)
|
||||
await sbPage.deepLinkToStory(storybookUrl, 'lib/preview-api/test-only-tag', 'docs');
|
||||
await sbPage.waitUntilLoaded();
|
||||
const docsButton = await sbPage.previewRoot().locator('button', { hasText: 'Button' });
|
||||
await expect(docsButton).toBeVisible();
|
||||
|
||||
// Even though test-only story not sidebar, it is still in the preview
|
||||
await sbPage.deepLinkToStory(storybookUrl, 'lib/preview-api/test-only-tag', 'default');
|
||||
await sbPage.waitUntilLoaded();
|
||||
const storyButton = await sbPage.previewRoot().locator('button', { hasText: 'Button' });
|
||||
await expect(storyButton).toBeVisible();
|
||||
});
|
||||
});
|
@ -33,6 +33,9 @@ export class SbPage {
|
||||
const storyLinkId = `${titleId}--${storyId}`;
|
||||
const viewMode = name === 'docs' ? 'docs' : 'story';
|
||||
await this.page.goto(`${baseURL}/?path=/${viewMode}/${storyLinkId}`);
|
||||
|
||||
await this.page.waitForURL((url) => url.search.includes(`path=/${viewMode}/${storyLinkId}`));
|
||||
await this.previewRoot();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -172,7 +172,7 @@ const buildTemplate = (
|
||||
const firstSelector = selector.split(',')[0];
|
||||
const templateReplacers: [
|
||||
string | RegExp,
|
||||
string | ((substring: string, ...args: any[]) => string)
|
||||
string | ((substring: string, ...args: any[]) => string),
|
||||
][] = [
|
||||
[/(^.*?)(?=[,])/, '$1'],
|
||||
[/(^\..+)/, 'div$1'],
|
||||
|
@ -41,7 +41,10 @@ export class PropertyExtractor implements NgModuleMetadata {
|
||||
applicationProviders?: Array<Provider | ReturnType<typeof importProvidersFrom>>;
|
||||
/* eslint-enable @typescript-eslint/lines-between-class-members */
|
||||
|
||||
constructor(private metadata: NgModuleMetadata, private component?: any) {
|
||||
constructor(
|
||||
private metadata: NgModuleMetadata,
|
||||
private component?: any
|
||||
) {
|
||||
this.init();
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,10 @@ export class AttributeSelectorComponent {
|
||||
|
||||
selectors!: string;
|
||||
|
||||
constructor(public el: ElementRef, private resolver: ComponentFactoryResolver) {
|
||||
constructor(
|
||||
public el: ElementRef,
|
||||
private resolver: ComponentFactoryResolver
|
||||
) {
|
||||
const factory = this.resolver.resolveComponentFactory(AttributeSelectorComponent);
|
||||
this.selectors = factory.selector;
|
||||
this.generatedTemplate = el.nativeElement.outerHTML;
|
||||
|
@ -11,7 +11,10 @@ export class ClassSelectorComponent {
|
||||
|
||||
selectors!: string;
|
||||
|
||||
constructor(public el: ElementRef, private resolver: ComponentFactoryResolver) {
|
||||
constructor(
|
||||
public el: ElementRef,
|
||||
private resolver: ComponentFactoryResolver
|
||||
) {
|
||||
const factory = this.resolver.resolveComponentFactory(ClassSelectorComponent);
|
||||
this.selectors = factory.selector;
|
||||
this.generatedTemplate = el.nativeElement.outerHTML;
|
||||
|
@ -11,7 +11,10 @@ export class MultipleSelectorComponent {
|
||||
|
||||
selectors!: string;
|
||||
|
||||
constructor(public el: ElementRef, private resolver: ComponentFactoryResolver) {
|
||||
constructor(
|
||||
public el: ElementRef,
|
||||
private resolver: ComponentFactoryResolver
|
||||
) {
|
||||
const factory = this.resolver.resolveComponentFactory(MultipleClassSelectorComponent);
|
||||
this.selectors = factory.selector;
|
||||
this.generatedTemplate = el.nativeElement.outerHTML;
|
||||
@ -29,7 +32,10 @@ export class MultipleClassSelectorComponent {
|
||||
|
||||
selectors!: string;
|
||||
|
||||
constructor(public el: ElementRef, private resolver: ComponentFactoryResolver) {
|
||||
constructor(
|
||||
public el: ElementRef,
|
||||
private resolver: ComponentFactoryResolver
|
||||
) {
|
||||
const factory = this.resolver.resolveComponentFactory(MultipleClassSelectorComponent);
|
||||
this.selectors = factory.selector;
|
||||
this.generatedTemplate = el.nativeElement.outerHTML;
|
||||
|
@ -99,7 +99,7 @@ npx storybook@latest init
|
||||
This framework is designed to work with Storybook 7. If you’re not already using v7, upgrade with this command:
|
||||
|
||||
```bash
|
||||
npx storybook@latest upgrade --prerelease
|
||||
npx storybook@latest upgrade
|
||||
```
|
||||
|
||||
#### Automatic migration
|
||||
|
@ -22,7 +22,7 @@ npx storybook@latest init
|
||||
This framework is designed to work with Storybook 7. If you’re not already using v7, upgrade with this command:
|
||||
|
||||
```bash
|
||||
npx storybook@latest upgrade --prerelease
|
||||
npx storybook@latest upgrade
|
||||
```
|
||||
|
||||
#### Manual migration
|
||||
|
@ -17,7 +17,6 @@ Check out our [Frameworks API](https://storybook.js.org/blog/framework-api/) ann
|
||||
- [Mocking links](#mocking-links)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [Error: `ERR! SyntaxError: Identifier '__esbuild_register_import_meta_url__' has already been declared` when starting Storybook](#error-err-syntaxerror-identifier-__esbuild_register_import_meta_url__-has-already-been-declared-when-starting-storybook)
|
||||
- [Error: `Cannot read properties of undefined (reading 'disable_scroll_handling')` in preview](#error-cannot-read-properties-of-undefined-reading-disable_scroll_handling-in-preview)
|
||||
- [Acknowledgements](#acknowledgements)
|
||||
|
||||
## Supported features
|
||||
@ -64,7 +63,7 @@ npx storybook@latest init
|
||||
This framework is designed to work with Storybook 7. If you’re not already using v7, upgrade with this command:
|
||||
|
||||
```bash
|
||||
npx storybook@latest upgrade --prerelease
|
||||
npx storybook@latest upgrade
|
||||
```
|
||||
|
||||
#### Automatic migration
|
||||
|
@ -85,7 +85,7 @@
|
||||
"jscodeshift": "^0.15.1",
|
||||
"leven": "^3.1.0",
|
||||
"ora": "^5.4.1",
|
||||
"prettier": "^2.8.0",
|
||||
"prettier": "^3.1.1",
|
||||
"prompts": "^2.4.0",
|
||||
"read-pkg-up": "^7.0.1",
|
||||
"semver": "^7.3.7",
|
||||
|
@ -104,7 +104,7 @@ describe('is not Nx project', () => {
|
||||
() =>
|
||||
({
|
||||
hasStorybookBuilder: true,
|
||||
} as any)
|
||||
}) as any
|
||||
);
|
||||
});
|
||||
|
||||
@ -129,7 +129,7 @@ describe('is not Nx project', () => {
|
||||
project1: { root: 'project1', architect: {} },
|
||||
},
|
||||
rootProject: 'project1',
|
||||
} as any)
|
||||
}) as any
|
||||
);
|
||||
});
|
||||
|
||||
@ -155,7 +155,7 @@ describe('is not Nx project', () => {
|
||||
project2: { root: 'project2', architect: {} },
|
||||
},
|
||||
rootProject: null,
|
||||
} as any)
|
||||
}) as any
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -107,7 +107,7 @@ describe('is not Nx project', () => {
|
||||
() =>
|
||||
({
|
||||
hasStorybookBuilder: true,
|
||||
} as any)
|
||||
}) as any
|
||||
);
|
||||
});
|
||||
|
||||
@ -133,7 +133,7 @@ describe('is not Nx project', () => {
|
||||
project2: { root: 'project2', architect: {} },
|
||||
},
|
||||
rootProject: null,
|
||||
} as any)
|
||||
}) as any
|
||||
);
|
||||
});
|
||||
|
||||
@ -158,7 +158,7 @@ describe('is not Nx project', () => {
|
||||
project1: { root: 'project1', architect: {} },
|
||||
},
|
||||
rootProject: 'project1',
|
||||
} as any)
|
||||
}) as any
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -6,6 +6,7 @@ import { writeConfig } from '@storybook/csf-tools';
|
||||
import type { Fix } from '../types';
|
||||
import type { PackageJson } from '../../js-package-manager';
|
||||
import { updateMainConfig } from '../helpers/mainConfigFile';
|
||||
import { getStorybookVersionSpecifier } from '../../helpers';
|
||||
|
||||
const logger = console;
|
||||
|
||||
@ -68,8 +69,11 @@ export const builderVite: Fix<BuilderViteOptions> = {
|
||||
|
||||
logger.info(`✅ Adding '@storybook/builder-vite' as dev dependency`);
|
||||
if (!dryRun) {
|
||||
const versionToInstall = getStorybookVersionSpecifier(
|
||||
await packageManager.retrievePackageJson()
|
||||
);
|
||||
await packageManager.addDependencies({ installAsDevDependencies: true }, [
|
||||
'@storybook/builder-vite',
|
||||
`@storybook/builder-vite@${versionToInstall}`,
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,8 @@ const logger = console;
|
||||
* which could actually be a custom script even though the name matches the legacy binary name
|
||||
*/
|
||||
export const getStorybookScripts = (allScripts: NonNullable<PackageJson['scripts']>) => {
|
||||
return Object.keys(allScripts).reduce((acc, key) => {
|
||||
return Object.keys(allScripts).reduce(
|
||||
(acc, key) => {
|
||||
const currentScript = allScripts[key];
|
||||
if (currentScript == null) {
|
||||
return acc;
|
||||
@ -62,7 +63,9 @@ export const getStorybookScripts = (allScripts: NonNullable<PackageJson['scripts
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {} as Record<string, { before: string; after: string }>);
|
||||
},
|
||||
{} as Record<string, { before: string; after: string }>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -129,10 +132,13 @@ export const sbScripts: Fix<SbScriptsRunOptions> = {
|
||||
logger.info(`✅ Updating scripts in package.json`);
|
||||
logger.log();
|
||||
if (!dryRun) {
|
||||
const newScripts = Object.keys(storybookScripts).reduce((acc, scriptKey) => {
|
||||
const newScripts = Object.keys(storybookScripts).reduce(
|
||||
(acc, scriptKey) => {
|
||||
acc[scriptKey] = storybookScripts[scriptKey].after;
|
||||
return acc;
|
||||
}, {} as Record<string, string>);
|
||||
},
|
||||
{} as Record<string, string>
|
||||
);
|
||||
|
||||
logger.log();
|
||||
|
||||
|
@ -20,9 +20,11 @@ export const checkWebpack5Builder = async ({
|
||||
|
||||
To upgrade to the latest stable release, run this from your project directory:
|
||||
|
||||
${chalk.cyan('npx storybook upgrade')}
|
||||
${chalk.cyan('npx storybook@latest upgrade')}
|
||||
|
||||
Add the ${chalk.cyan('--prerelease')} flag to get the latest prerelease.
|
||||
To upgrade to the latest pre-release, run this from your project directory:
|
||||
|
||||
${chalk.cyan('npx storybook@next upgrade')}
|
||||
`.trim()
|
||||
);
|
||||
return null;
|
||||
|
@ -107,12 +107,10 @@ export const detectBuilderInfo = async ({
|
||||
|
||||
// if builder is still not detected, rely on package dependencies
|
||||
if (!builderOrFrameworkName) {
|
||||
const storybookBuilderViteVersion = await packageManager.getPackageVersion(
|
||||
'@storybook/builder-vite'
|
||||
);
|
||||
const storybookBuilderVite2Version = await packageManager.getPackageVersion(
|
||||
'storybook-builder-vite'
|
||||
);
|
||||
const storybookBuilderViteVersion =
|
||||
await packageManager.getPackageVersion('@storybook/builder-vite');
|
||||
const storybookBuilderVite2Version =
|
||||
await packageManager.getPackageVersion('storybook-builder-vite');
|
||||
const storybookBuilderWebpack5Version = await packageManager.getPackageVersion(
|
||||
'@storybook/builder-webpack5'
|
||||
);
|
||||
|
@ -185,9 +185,8 @@ export async function detectLanguage(packageManager: JsPackageManager) {
|
||||
'@typescript-eslint/parser'
|
||||
);
|
||||
|
||||
const eslintPluginStorybookVersion = await packageManager.getPackageVersion(
|
||||
'eslint-plugin-storybook'
|
||||
);
|
||||
const eslintPluginStorybookVersion =
|
||||
await packageManager.getPackageVersion('eslint-plugin-storybook');
|
||||
|
||||
if (isTypescriptDirectDependency && typescriptVersion) {
|
||||
if (
|
||||
|
@ -52,7 +52,7 @@ export const getIncompatibleAddons = async (
|
||||
({
|
||||
name: addon,
|
||||
version: await packageManager.getPackageVersion(addon),
|
||||
} as { name: keyof typeof incompatibleList; version: string })
|
||||
}) as { name: keyof typeof incompatibleList; version: string }
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -76,15 +76,13 @@ command('remove <addon>')
|
||||
.action((addonName: string, options: any) => remove(addonName, options));
|
||||
|
||||
command('upgrade')
|
||||
.description('Upgrade your Storybook packages to the latest')
|
||||
.description(`Upgrade your Storybook packages to v${versions.storybook}`)
|
||||
.option(
|
||||
'--package-manager <npm|pnpm|yarn1|yarn2>',
|
||||
'Force package manager for installing dependencies'
|
||||
)
|
||||
.option('-y --yes', 'Skip prompting the user')
|
||||
.option('-n --dry-run', 'Only check for upgrades, do not install')
|
||||
.option('-t --tag <tag>', 'Upgrade to a certain npm dist-tag (e.g. next, prerelease)')
|
||||
.option('-p --prerelease', 'Upgrade to the pre-release packages')
|
||||
.option('-s --skip-check', 'Skip postinstall version and automigration checks')
|
||||
.option('-c, --config-dir <dir-name>', 'Directory where to load Storybook configurations from')
|
||||
.action(async (options: UpgradeOptions) => upgrade(options).catch(() => process.exit(1)));
|
||||
@ -143,10 +141,9 @@ command('sandbox [filterValue]')
|
||||
.alias('repro') // for backwards compatibility
|
||||
.description('Create a sandbox from a set of possible templates')
|
||||
.option('-o --output <outDir>', 'Define an output directory')
|
||||
.option('-b --branch <branch>', 'Define the branch to download from', 'next')
|
||||
.option('--no-init', 'Whether to download a template without an initialized Storybook', false)
|
||||
.action((filterValue, options) =>
|
||||
sandbox({ filterValue, ...options }).catch((e) => {
|
||||
sandbox({ filterValue, ...options }, pkg).catch((e) => {
|
||||
logger.error(e);
|
||||
process.exit(1);
|
||||
})
|
||||
|
@ -300,9 +300,8 @@ export async function baseGenerator(
|
||||
|
||||
try {
|
||||
if (process.env.CI !== 'true') {
|
||||
const { hasEslint, isStorybookPluginInstalled, eslintConfigFile } = await extractEslintInfo(
|
||||
packageManager
|
||||
);
|
||||
const { hasEslint, isStorybookPluginInstalled, eslintConfigFile } =
|
||||
await extractEslintInfo(packageManager);
|
||||
|
||||
if (hasEslint && !isStorybookPluginInstalled) {
|
||||
versionedPackages.push('eslint-plugin-storybook');
|
||||
|
@ -105,8 +105,8 @@ export async function configureMain({
|
||||
|
||||
try {
|
||||
const prettier = (await import('prettier')).default;
|
||||
mainJsContents = prettier.format(dedent(mainJsContents), {
|
||||
...prettier.resolveConfig.sync(process.cwd()),
|
||||
mainJsContents = await prettier.format(dedent(mainJsContents), {
|
||||
...(await prettier.resolveConfig(mainPath)),
|
||||
filepath: mainPath,
|
||||
});
|
||||
} catch {
|
||||
@ -168,8 +168,8 @@ export async function configurePreview(options: ConfigurePreviewOptions) {
|
||||
|
||||
try {
|
||||
const prettier = (await import('prettier')).default;
|
||||
preview = prettier.format(preview, {
|
||||
...prettier.resolveConfig.sync(process.cwd()),
|
||||
preview = await prettier.format(preview, {
|
||||
...(await prettier.resolveConfig(previewPath)),
|
||||
filepath: previewPath,
|
||||
});
|
||||
} catch {
|
||||
|
@ -224,7 +224,7 @@ const projectTypeInquirer = async (
|
||||
process.exit(0);
|
||||
};
|
||||
|
||||
async function doInitiate(
|
||||
export async function doInitiate(
|
||||
options: CommandOptions,
|
||||
pkg: PackageJson
|
||||
): Promise<
|
||||
|
@ -134,6 +134,8 @@ export abstract class JsPackageManager {
|
||||
|
||||
done = commandLog('Installing dependencies');
|
||||
|
||||
logger.log();
|
||||
|
||||
try {
|
||||
await this.runInstall();
|
||||
done();
|
||||
|
@ -192,7 +192,7 @@ export class Yarn1Proxy extends JsPackageManager {
|
||||
const existingVersions: Record<string, string[]> = {};
|
||||
const duplicatedDependencies: Record<string, string[]> = {};
|
||||
|
||||
const recurse = (tree: typeof trees[0]) => {
|
||||
const recurse = (tree: (typeof trees)[0]) => {
|
||||
const { children } = tree;
|
||||
const { name, value } = parsePackageData(tree.name);
|
||||
if (!name || !name.includes('storybook')) return;
|
||||
|
@ -7,8 +7,13 @@ import { downloadTemplate } from 'giget';
|
||||
|
||||
import { existsSync, readdir } from 'fs-extra';
|
||||
import invariant from 'tiny-invariant';
|
||||
import { lt, prerelease } from 'semver';
|
||||
import type { Template, TemplateKey } from './sandbox-templates';
|
||||
import { allTemplates as TEMPLATES } from './sandbox-templates';
|
||||
import type { PackageJson, PackageManagerName } from './js-package-manager';
|
||||
import { JsPackageManagerFactory } from './js-package-manager';
|
||||
import versions from './versions';
|
||||
import { doInitiate } from './initiate';
|
||||
|
||||
const logger = console;
|
||||
|
||||
@ -17,20 +22,60 @@ interface SandboxOptions {
|
||||
output?: string;
|
||||
branch?: string;
|
||||
init?: boolean;
|
||||
packageManager: PackageManagerName;
|
||||
}
|
||||
type Choice = keyof typeof TEMPLATES;
|
||||
|
||||
const toChoices = (c: Choice): prompts.Choice => ({ title: TEMPLATES[c].name, value: c });
|
||||
|
||||
export const sandbox = async ({
|
||||
output: outputDirectory,
|
||||
filterValue,
|
||||
branch,
|
||||
init,
|
||||
}: SandboxOptions) => {
|
||||
export const sandbox = async (
|
||||
{ output: outputDirectory, filterValue, init, ...options }: SandboxOptions,
|
||||
pkg: PackageJson
|
||||
) => {
|
||||
// Either get a direct match when users pass a template id, or filter through all templates
|
||||
let selectedConfig: Template | undefined = TEMPLATES[filterValue as TemplateKey];
|
||||
let selectedTemplate: Choice | null = selectedConfig ? (filterValue as TemplateKey) : null;
|
||||
let templateId: Choice | null = selectedConfig ? (filterValue as TemplateKey) : null;
|
||||
|
||||
const { packageManager: pkgMgr } = options;
|
||||
|
||||
const packageManager = JsPackageManagerFactory.getPackageManager({
|
||||
force: pkgMgr,
|
||||
});
|
||||
const latestVersion = await packageManager.latestVersion('@storybook/cli');
|
||||
const nextVersion = await packageManager.latestVersion('@storybook/cli@next');
|
||||
const currentVersion = versions['@storybook/cli'];
|
||||
const isPrerelease = prerelease(currentVersion);
|
||||
const isOutdated = lt(currentVersion, isPrerelease ? nextVersion : latestVersion);
|
||||
const borderColor = isOutdated ? '#FC521F' : '#F1618C';
|
||||
|
||||
const downloadType = !isOutdated && init ? 'after-storybook' : 'before-storybook';
|
||||
const branch = isPrerelease ? 'next' : 'main';
|
||||
|
||||
const messages = {
|
||||
welcome: `Creating a Storybook ${chalk.bold(currentVersion)} sandbox..`,
|
||||
notLatest: chalk.red(dedent`
|
||||
This version is behind the latest release, which is: ${chalk.bold(latestVersion)}!
|
||||
You likely ran the init command through npx, which can use a locally cached version, to get the latest please run:
|
||||
${chalk.bold('npx storybook@latest sandbox')}
|
||||
|
||||
You may want to CTRL+C to stop, and run with the latest version instead.
|
||||
`),
|
||||
longInitTime: chalk.yellow(
|
||||
'The creation of the sandbox will take longer, because we will need to run init.'
|
||||
),
|
||||
prerelease: chalk.yellow('This is a pre-release version.'),
|
||||
};
|
||||
|
||||
logger.log(
|
||||
boxen(
|
||||
[messages.welcome]
|
||||
.concat(isOutdated && !isPrerelease ? [messages.notLatest] : [])
|
||||
.concat(init && (isOutdated || isPrerelease) ? [messages.longInitTime] : [])
|
||||
.concat(isPrerelease ? [messages.prerelease] : [])
|
||||
.join('\n'),
|
||||
{ borderStyle: 'round', padding: 1, borderColor }
|
||||
)
|
||||
);
|
||||
|
||||
if (!selectedConfig) {
|
||||
const filterRegex = new RegExp(`^${filterValue || ''}`, 'i');
|
||||
@ -79,7 +124,7 @@ export const sandbox = async ({
|
||||
}
|
||||
|
||||
if (choices.length === 1) {
|
||||
[selectedTemplate] = choices;
|
||||
[templateId] = choices;
|
||||
} else {
|
||||
logger.info(
|
||||
boxen(
|
||||
@ -97,16 +142,16 @@ export const sandbox = async ({
|
||||
)
|
||||
);
|
||||
|
||||
selectedTemplate = await promptSelectedTemplate(choices);
|
||||
templateId = await promptSelectedTemplate(choices);
|
||||
}
|
||||
|
||||
const hasSelectedTemplate = !!(selectedTemplate ?? null);
|
||||
const hasSelectedTemplate = !!(templateId ?? null);
|
||||
if (!hasSelectedTemplate) {
|
||||
logger.error('Somehow we got no templates. Please rerun this command!');
|
||||
return;
|
||||
}
|
||||
|
||||
selectedConfig = selectedTemplate ? TEMPLATES[selectedTemplate] : undefined;
|
||||
selectedConfig = templateId ? TEMPLATES[templateId] : undefined;
|
||||
|
||||
if (!selectedConfig) {
|
||||
throw new Error('🚨 Sandbox: please specify a valid template type');
|
||||
@ -114,7 +159,7 @@ export const sandbox = async ({
|
||||
}
|
||||
|
||||
let selectedDirectory = outputDirectory;
|
||||
const outputDirectoryName = outputDirectory || selectedTemplate;
|
||||
const outputDirectoryName = outputDirectory || templateId;
|
||||
if (selectedDirectory && existsSync(`${selectedDirectory}`)) {
|
||||
logger.info(`⚠️ ${selectedDirectory} already exists! Overwriting...`);
|
||||
}
|
||||
@ -149,22 +194,35 @@ export const sandbox = async ({
|
||||
|
||||
logger.info(`🏃 Adding ${selectedConfig.name} into ${templateDestination}`);
|
||||
|
||||
logger.log('📦 Downloading sandbox template...');
|
||||
logger.log(`📦 Downloading sandbox template (${chalk.bold(downloadType)})...`);
|
||||
try {
|
||||
const templateType = init ? 'after-storybook' : 'before-storybook';
|
||||
// Download the sandbox based on subfolder "after-storybook" and selected branch
|
||||
const gitPath = `github:storybookjs/sandboxes/${selectedTemplate}/${templateType}#${branch}`;
|
||||
const gitPath = `github:storybookjs/sandboxes/${templateId}/${downloadType}#${branch}`;
|
||||
await downloadTemplate(gitPath, {
|
||||
force: true,
|
||||
dir: templateDestination,
|
||||
});
|
||||
// throw an error if templateDestination is an empty directory using fs-extra
|
||||
if ((await readdir(templateDestination)).length === 0) {
|
||||
throw new Error(
|
||||
dedent`Template downloaded from ${chalk.blue(gitPath)} is empty.
|
||||
Are you use it exists? Or did you want to set ${chalk.yellow(
|
||||
selectedTemplate
|
||||
)} to inDevelopment first?`
|
||||
const selected = chalk.yellow(templateId);
|
||||
throw new Error(dedent`
|
||||
Template downloaded from ${chalk.blue(gitPath)} is empty.
|
||||
Are you use it exists? Or did you want to set ${selected} to inDevelopment first?
|
||||
`);
|
||||
}
|
||||
|
||||
// when user wanted an sandbox that has been initiated, but force-downloaded the before-storybook directory
|
||||
// then we need to initiate the sandbox
|
||||
// this is to ensure we DO get the latest version of the template (output of the generator), but we initialize using the version of storybook that the CLI is.
|
||||
// we warned the user about the fact they are running an old version of storybook
|
||||
// we warned the user the sandbox step would take longer
|
||||
if ((isOutdated || isPrerelease) && init) {
|
||||
// we run doInitiate, instead of initiate, to avoid sending this init event to telemetry, because it's not a real world project
|
||||
await doInitiate(
|
||||
{
|
||||
...options,
|
||||
},
|
||||
pkg
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
@ -173,7 +231,10 @@ export const sandbox = async ({
|
||||
}
|
||||
|
||||
const initMessage = init
|
||||
? chalk.yellow(`yarn install\nyarn storybook`)
|
||||
? chalk.yellow(dedent`
|
||||
yarn install
|
||||
yarn storybook
|
||||
`)
|
||||
: `Recreate your setup, then ${chalk.yellow(`npx storybook@latest init`)}`;
|
||||
|
||||
logger.info(
|
||||
|
@ -1,5 +1,22 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { addExtraFlags, addNxPackagesToReject, getStorybookVersion } from './upgrade';
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { getStorybookCoreVersion } from '@storybook/telemetry';
|
||||
import {
|
||||
UpgradeStorybookToLowerVersionError,
|
||||
UpgradeStorybookToSameVersionError,
|
||||
} from '@storybook/core-events/server-errors';
|
||||
import { doUpgrade, getStorybookVersion } from './upgrade';
|
||||
import type versions from './versions';
|
||||
|
||||
vi.mock('@storybook/telemetry');
|
||||
vi.mock('./versions', async (importOriginal) => {
|
||||
const originalVersions = ((await importOriginal()) as { default: typeof versions }).default;
|
||||
return {
|
||||
default: Object.keys(originalVersions).reduce((acc, key) => {
|
||||
acc[key] = '8.0.0';
|
||||
return acc;
|
||||
}, {} as Record<string, string>),
|
||||
};
|
||||
});
|
||||
|
||||
describe.each([
|
||||
['│ │ │ ├── @babel/code-frame@7.10.3 deduped', null],
|
||||
@ -22,68 +39,15 @@ describe.each([
|
||||
});
|
||||
});
|
||||
|
||||
describe('extra flags', () => {
|
||||
const extraFlags = {
|
||||
'react-scripts@<5': ['--foo'],
|
||||
};
|
||||
const devDependencies = {};
|
||||
it('package matches constraints', () => {
|
||||
expect(
|
||||
addExtraFlags(extraFlags, [], { dependencies: { 'react-scripts': '4' }, devDependencies })
|
||||
).toEqual(['--foo']);
|
||||
});
|
||||
it('package prerelease matches constraints', () => {
|
||||
expect(
|
||||
addExtraFlags(extraFlags, [], {
|
||||
dependencies: { 'react-scripts': '4.0.0-alpha.0' },
|
||||
devDependencies,
|
||||
})
|
||||
).toEqual(['--foo']);
|
||||
});
|
||||
it('package not matches constraints', () => {
|
||||
expect(
|
||||
addExtraFlags(extraFlags, [], {
|
||||
dependencies: { 'react-scripts': '5.0.0-alpha.0' },
|
||||
devDependencies,
|
||||
})
|
||||
).toEqual([]);
|
||||
});
|
||||
it('no package not matches constraints', () => {
|
||||
expect(
|
||||
addExtraFlags(extraFlags, [], {
|
||||
dependencies: {},
|
||||
devDependencies,
|
||||
})
|
||||
).toEqual([]);
|
||||
});
|
||||
});
|
||||
describe('Upgrade errors', () => {
|
||||
it('should throw an error when upgrading to a lower version number', async () => {
|
||||
vi.mocked(getStorybookCoreVersion).mockResolvedValue('8.1.0');
|
||||
|
||||
describe('addNxPackagesToReject', () => {
|
||||
it('reject exists and is in regex pattern', () => {
|
||||
const flags = ['--reject', '/preset-create-react-app/', '--some-flag', 'hello'];
|
||||
expect(addNxPackagesToReject(flags)).toMatchObject([
|
||||
'--reject',
|
||||
'"/(preset-create-react-app|@nrwl/storybook|@nx/storybook)/"',
|
||||
'--some-flag',
|
||||
'hello',
|
||||
]);
|
||||
await expect(doUpgrade({} as any)).rejects.toThrowError(UpgradeStorybookToLowerVersionError);
|
||||
});
|
||||
it('reject exists and is in unknown pattern', () => {
|
||||
const flags = ['--some-flag', 'hello', '--reject', '@storybook/preset-create-react-app'];
|
||||
expect(addNxPackagesToReject(flags)).toMatchObject([
|
||||
'--some-flag',
|
||||
'hello',
|
||||
'--reject',
|
||||
'@storybook/preset-create-react-app,@nrwl/storybook,@nx/storybook',
|
||||
]);
|
||||
});
|
||||
it('reject does not exist', () => {
|
||||
const flags = ['--some-flag', 'hello'];
|
||||
expect(addNxPackagesToReject(flags)).toMatchObject([
|
||||
'--some-flag',
|
||||
'hello',
|
||||
'--reject',
|
||||
'@nrwl/storybook,@nx/storybook',
|
||||
]);
|
||||
it('should throw an error when upgrading to the same version number', async () => {
|
||||
vi.mocked(getStorybookCoreVersion).mockResolvedValue('8.0.0');
|
||||
|
||||
await expect(doUpgrade({} as any)).rejects.toThrowError(UpgradeStorybookToSameVersionError);
|
||||
});
|
||||
});
|
||||
|
@ -1,18 +1,22 @@
|
||||
import { sync as spawnSync } from 'cross-spawn';
|
||||
import { telemetry, getStorybookCoreVersion } from '@storybook/telemetry';
|
||||
import semver from 'semver';
|
||||
import semver, { eq, lt, prerelease } from 'semver';
|
||||
import { logger } from '@storybook/node-logger';
|
||||
import { withTelemetry } from '@storybook/core-server';
|
||||
import {
|
||||
ConflictingVersionTagsError,
|
||||
UpgradeStorybookPackagesError,
|
||||
UpgradeStorybookToLowerVersionError,
|
||||
UpgradeStorybookToSameVersionError,
|
||||
} from '@storybook/core-events/server-errors';
|
||||
|
||||
import type { PackageJsonWithMaybeDeps, PackageManagerName } from './js-package-manager';
|
||||
import { getPackageDetails, JsPackageManagerFactory } from './js-package-manager';
|
||||
import chalk from 'chalk';
|
||||
import dedent from 'ts-dedent';
|
||||
import boxen from 'boxen';
|
||||
import type { PackageManagerName } from './js-package-manager';
|
||||
import { JsPackageManagerFactory } from './js-package-manager';
|
||||
import { coerceSemver, commandLog } from './helpers';
|
||||
import { automigrate } from './automigrate';
|
||||
import { isCorePackage } from './utils';
|
||||
import versions from './versions';
|
||||
|
||||
type Package = {
|
||||
package: string;
|
||||
@ -87,57 +91,7 @@ export const checkVersionConsistency = () => {
|
||||
});
|
||||
};
|
||||
|
||||
type ExtraFlags = Record<string, string[]>;
|
||||
const EXTRA_FLAGS: ExtraFlags = {
|
||||
'react-scripts@<5': ['--reject', '/preset-create-react-app/'],
|
||||
};
|
||||
|
||||
export const addExtraFlags = (
|
||||
extraFlags: ExtraFlags,
|
||||
flags: string[],
|
||||
{ dependencies, devDependencies }: PackageJsonWithMaybeDeps
|
||||
) => {
|
||||
return Object.entries(extraFlags).reduce(
|
||||
(acc, entry) => {
|
||||
const [pattern, extra] = entry;
|
||||
const [pkg, specifier] = getPackageDetails(pattern);
|
||||
const pkgVersion = dependencies?.[pkg] || devDependencies?.[pkg];
|
||||
|
||||
if (pkgVersion && specifier && semver.satisfies(coerceSemver(pkgVersion), specifier)) {
|
||||
return [...acc, ...extra];
|
||||
}
|
||||
|
||||
return acc;
|
||||
},
|
||||
[...flags]
|
||||
);
|
||||
};
|
||||
|
||||
export const addNxPackagesToReject = (flags: string[]) => {
|
||||
const newFlags = [...flags];
|
||||
const index = flags.indexOf('--reject');
|
||||
if (index > -1) {
|
||||
// Try to understand if it's in the format of a regex pattern
|
||||
if (newFlags[index + 1].endsWith('/') && newFlags[index + 1].startsWith('/')) {
|
||||
// Remove last and first slash so that I can add the parentheses
|
||||
newFlags[index + 1] = newFlags[index + 1].substring(1, newFlags[index + 1].length - 1);
|
||||
newFlags[index + 1] = `"/(${newFlags[index + 1]}|@nrwl/storybook|@nx/storybook)/"`;
|
||||
} else {
|
||||
// Adding the two packages as comma-separated values
|
||||
// If the existing rejects are in regex format, they will be ignored.
|
||||
// Maybe we need to find a more robust way to treat rejects?
|
||||
newFlags[index + 1] = `${newFlags[index + 1]},@nrwl/storybook,@nx/storybook`;
|
||||
}
|
||||
} else {
|
||||
newFlags.push('--reject');
|
||||
newFlags.push('@nrwl/storybook,@nx/storybook');
|
||||
}
|
||||
return newFlags;
|
||||
};
|
||||
|
||||
export interface UpgradeOptions {
|
||||
tag: string;
|
||||
prerelease: boolean;
|
||||
skipCheck: boolean;
|
||||
packageManager: PackageManagerName;
|
||||
dryRun: boolean;
|
||||
@ -147,8 +101,6 @@ export interface UpgradeOptions {
|
||||
}
|
||||
|
||||
export const doUpgrade = async ({
|
||||
tag,
|
||||
prerelease,
|
||||
skipCheck,
|
||||
packageManager: pkgMgr,
|
||||
dryRun,
|
||||
@ -158,66 +110,88 @@ export const doUpgrade = async ({
|
||||
}: UpgradeOptions) => {
|
||||
const packageManager = JsPackageManagerFactory.getPackageManager({ force: pkgMgr });
|
||||
|
||||
const currentVersion = versions['@storybook/cli'];
|
||||
const beforeVersion = await getStorybookCoreVersion();
|
||||
|
||||
commandLog(`Checking for latest versions of '@storybook/*' packages\n`);
|
||||
|
||||
if (tag && prerelease) {
|
||||
throw new ConflictingVersionTagsError();
|
||||
if (lt(currentVersion, beforeVersion)) {
|
||||
throw new UpgradeStorybookToLowerVersionError({ beforeVersion, currentVersion });
|
||||
}
|
||||
if (eq(currentVersion, beforeVersion)) {
|
||||
throw new UpgradeStorybookToSameVersionError({ beforeVersion });
|
||||
}
|
||||
|
||||
let target = 'latest';
|
||||
if (prerelease) {
|
||||
// '@next' is storybook's convention for the latest prerelease tag.
|
||||
// This used to be 'greatest', but that was not reliable and could pick canaries, etc.
|
||||
// and random releases of other packages with storybook in their name.
|
||||
target = '@next';
|
||||
} else if (tag) {
|
||||
target = `@${tag}`;
|
||||
const latestVersion = await packageManager.latestVersion('@storybook/cli');
|
||||
const isOutdated = lt(currentVersion, latestVersion);
|
||||
const isPrerelease = prerelease(currentVersion) !== null;
|
||||
|
||||
const borderColor = isOutdated ? '#FC521F' : '#F1618C';
|
||||
|
||||
const messages = {
|
||||
welcome: `Upgrading Storybook from version ${chalk.bold(beforeVersion)} to version ${chalk.bold(
|
||||
currentVersion
|
||||
)}..`,
|
||||
notLatest: chalk.red(dedent`
|
||||
This version is behind the latest release, which is: ${chalk.bold(latestVersion)}!
|
||||
You likely ran the upgrade command through npx, which can use a locally cached version, to upgrade to the latest version please run:
|
||||
${chalk.bold('npx storybook@latest upgrade')}
|
||||
|
||||
You may want to CTRL+C to stop, and run with the latest version instead.
|
||||
`),
|
||||
prelease: chalk.yellow('This is a pre-release version.'),
|
||||
};
|
||||
|
||||
logger.plain(
|
||||
boxen(
|
||||
[messages.welcome]
|
||||
.concat(isOutdated && !isPrerelease ? [messages.notLatest] : [])
|
||||
.concat(isPrerelease ? [messages.prelease] : [])
|
||||
.join('\n'),
|
||||
{ borderStyle: 'round', padding: 1, borderColor }
|
||||
)
|
||||
);
|
||||
|
||||
const packageJson = await packageManager.retrievePackageJson();
|
||||
|
||||
const toUpgradedDependencies = (deps: Record<string, any>) => {
|
||||
const monorepoDependencies = Object.keys(deps || {}).filter((dependency) => {
|
||||
// don't upgrade @storybook/preset-create-react-app if react-scripts is < v5
|
||||
if (dependency === '@storybook/preset-create-react-app') {
|
||||
const reactScriptsVersion =
|
||||
packageJson.dependencies['react-scripts'] ?? packageJson.devDependencies['react-scripts'];
|
||||
if (reactScriptsVersion && lt(coerceSemver(reactScriptsVersion), '5.0.0')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
let flags = [];
|
||||
if (!dryRun) flags.push('--upgrade');
|
||||
flags.push('--target');
|
||||
flags.push(target);
|
||||
flags = addExtraFlags(EXTRA_FLAGS, flags, await packageManager.retrievePackageJson());
|
||||
flags = addNxPackagesToReject(flags);
|
||||
// only upgrade packages that are in the monorepo
|
||||
return dependency in versions;
|
||||
}) as Array<keyof typeof versions>;
|
||||
return monorepoDependencies.map(
|
||||
(dependency) =>
|
||||
// add ^ modifier to the version if this is the latest and stable version
|
||||
// example output: @storybook/react@^8.0.0
|
||||
`${dependency}@${!isOutdated || isPrerelease ? '^' : ''}${versions[dependency]}`
|
||||
);
|
||||
};
|
||||
|
||||
const command = 'npx';
|
||||
const checkArgs = ['npm-check-updates@latest', '/storybook/', ...flags];
|
||||
const check = spawnSync(command, checkArgs, {
|
||||
stdio: 'pipe',
|
||||
shell: true,
|
||||
});
|
||||
|
||||
if (check.stderr && check.stderr.toString().includes('npm ERR')) {
|
||||
throw new UpgradeStorybookPackagesError({
|
||||
command,
|
||||
args: checkArgs,
|
||||
errorMessage: check.stderr.toString(),
|
||||
});
|
||||
}
|
||||
|
||||
logger.info(check.stdout.toString());
|
||||
|
||||
const checkSbArgs = ['npm-check-updates@latest', 'sb', ...flags];
|
||||
const checkSb = spawnSync(command, checkSbArgs, {
|
||||
stdio: 'pipe',
|
||||
shell: true,
|
||||
});
|
||||
logger.info(checkSb.stdout.toString());
|
||||
logger.info(checkSb.stderr.toString());
|
||||
|
||||
if (checkSb.stderr && checkSb.stderr.toString().includes('npm ERR')) {
|
||||
throw new UpgradeStorybookPackagesError({
|
||||
command,
|
||||
args: checkSbArgs,
|
||||
errorMessage: checkSb.stderr.toString(),
|
||||
});
|
||||
}
|
||||
const upgradedDependencies = toUpgradedDependencies(packageJson.dependencies);
|
||||
const upgradedDevDependencies = toUpgradedDependencies(packageJson.devDependencies);
|
||||
|
||||
if (!dryRun) {
|
||||
commandLog(`Installing upgrades`);
|
||||
commandLog(`Updating dependencies in ${chalk.cyan('package.json')}..`);
|
||||
logger.plain('');
|
||||
if (upgradedDependencies.length > 0) {
|
||||
await packageManager.addDependencies(
|
||||
{ installAsDevDependencies: false, skipInstall: true, packageJson },
|
||||
upgradedDependencies
|
||||
);
|
||||
}
|
||||
if (upgradedDevDependencies.length > 0) {
|
||||
await packageManager.addDependencies(
|
||||
{ installAsDevDependencies: true, skipInstall: true, packageJson },
|
||||
upgradedDevDependencies
|
||||
);
|
||||
}
|
||||
await packageManager.installDependencies();
|
||||
}
|
||||
|
||||
@ -234,8 +208,6 @@ export const doUpgrade = async ({
|
||||
automigrationPreCheckFailure: preCheckFailure || null,
|
||||
};
|
||||
telemetry('upgrade', {
|
||||
prerelease,
|
||||
tag,
|
||||
beforeVersion,
|
||||
afterVersion,
|
||||
...automigrationTelemetry,
|
||||
|
@ -64,7 +64,7 @@
|
||||
"globby": "^11.0.2",
|
||||
"jscodeshift": "^0.15.1",
|
||||
"lodash": "^4.17.21",
|
||||
"prettier": "^2.8.0",
|
||||
"prettier": "^3.1.1",
|
||||
"recast": "^0.23.1",
|
||||
"tiny-invariant": "^1.3.1"
|
||||
},
|
||||
|
@ -9,21 +9,25 @@ expect.addSnapshotSerializer({
|
||||
test: () => true,
|
||||
});
|
||||
|
||||
const jsTransform = (source: string) =>
|
||||
_transform({ source, path: 'Component.stories.js' }, {} as API, {}).trim();
|
||||
const tsTransform = (source: string) =>
|
||||
_transform({ source, path: 'Component.stories.ts' }, {} as API, { parser: 'tsx' }).trim();
|
||||
const jsTransform = async (source: string) =>
|
||||
(await _transform({ source, path: 'Component.stories.jsx' }, {} as API, {})).trim();
|
||||
const tsTransform = async (source: string) =>
|
||||
(
|
||||
await _transform({ source, path: 'Component.stories.tsx' }, {} as API, {
|
||||
parser: 'tsx',
|
||||
})
|
||||
).trim();
|
||||
|
||||
describe('csf-2-to-3', () => {
|
||||
describe('javascript', () => {
|
||||
it('should replace non-simple function exports with objects', () => {
|
||||
expect(
|
||||
it('should replace non-simple function exports with objects', async () => {
|
||||
await expect(
|
||||
jsTransform(dedent`
|
||||
export default { title: 'Cat' };
|
||||
export const A = () => <Cat />;
|
||||
export const B = (args) => <Button {...args} />;
|
||||
`)
|
||||
).toMatchInlineSnapshot(`
|
||||
).resolves.toMatchInlineSnapshot(`
|
||||
export default { title: 'Cat' };
|
||||
export const A = () => <Cat />;
|
||||
export const B = {
|
||||
@ -32,8 +36,8 @@ describe('csf-2-to-3', () => {
|
||||
`);
|
||||
});
|
||||
|
||||
it('should move annotations into story objects', () => {
|
||||
expect(
|
||||
it('should move annotations into story objects', async () => {
|
||||
await expect(
|
||||
jsTransform(dedent`
|
||||
export default { title: 'Cat' };
|
||||
|
||||
@ -42,7 +46,7 @@ describe('csf-2-to-3', () => {
|
||||
A.parameters = { bar: 2 };
|
||||
A.play = () => {};
|
||||
`)
|
||||
).toMatchInlineSnapshot(`
|
||||
).resolves.toMatchInlineSnapshot(`
|
||||
export default { title: 'Cat' };
|
||||
|
||||
export const A = {
|
||||
@ -54,8 +58,8 @@ describe('csf-2-to-3', () => {
|
||||
`);
|
||||
});
|
||||
|
||||
it('should ignore non-story exports, statements', () => {
|
||||
expect(
|
||||
it('should ignore non-story exports, statements', async () => {
|
||||
await expect(
|
||||
jsTransform(dedent`
|
||||
export default { title: 'components/Fruit', includeStories: ['A'] };
|
||||
|
||||
@ -65,7 +69,7 @@ describe('csf-2-to-3', () => {
|
||||
|
||||
const C = (args) => <Cherry {...args} />;
|
||||
`)
|
||||
).toMatchInlineSnapshot(`
|
||||
).resolves.toMatchInlineSnapshot(`
|
||||
export default { title: 'components/Fruit', includeStories: ['A'] };
|
||||
|
||||
export const A = {
|
||||
@ -78,28 +82,28 @@ describe('csf-2-to-3', () => {
|
||||
`);
|
||||
});
|
||||
|
||||
it('should do nothing when there is no meta', () => {
|
||||
expect(
|
||||
it('should do nothing when there is no meta', async () => {
|
||||
await expect(
|
||||
jsTransform(dedent`
|
||||
export const A = () => <Apple />;
|
||||
|
||||
export const B = (args) => <Banana {...args} />;
|
||||
`)
|
||||
).toMatchInlineSnapshot(`
|
||||
).resolves.toMatchInlineSnapshot(`
|
||||
export const A = () => <Apple />;
|
||||
|
||||
export const B = (args) => <Banana {...args} />;
|
||||
`);
|
||||
});
|
||||
|
||||
it('should remove implicit global render function (react)', () => {
|
||||
expect(
|
||||
it('should remove implicit global render function (react)', async () => {
|
||||
await expect(
|
||||
jsTransform(dedent`
|
||||
export default { title: 'Cat', component: Cat };
|
||||
export const A = (args) => <Cat {...args} />;
|
||||
export const B = (args) => <Banana {...args} />;
|
||||
`)
|
||||
).toMatchInlineSnapshot(`
|
||||
).resolves.toMatchInlineSnapshot(`
|
||||
export default { title: 'Cat', component: Cat };
|
||||
export const A = {};
|
||||
export const B = {
|
||||
@ -108,8 +112,8 @@ describe('csf-2-to-3', () => {
|
||||
`);
|
||||
});
|
||||
|
||||
it('should ignore object exports', () => {
|
||||
expect(
|
||||
it('should ignore object exports', async () => {
|
||||
await expect(
|
||||
jsTransform(dedent`
|
||||
export default { title: 'Cat', component: Cat };
|
||||
|
||||
@ -117,7 +121,7 @@ describe('csf-2-to-3', () => {
|
||||
render: (args) => <Cat {...args} />
|
||||
};
|
||||
`)
|
||||
).toMatchInlineSnapshot(`
|
||||
).resolves.toMatchInlineSnapshot(`
|
||||
export default { title: 'Cat', component: Cat };
|
||||
|
||||
export const A = {
|
||||
@ -126,15 +130,15 @@ describe('csf-2-to-3', () => {
|
||||
`);
|
||||
});
|
||||
|
||||
it('should hoist template.bind (if there is only one)', () => {
|
||||
expect(
|
||||
it('should hoist template.bind (if there is only one)', async () => {
|
||||
await expect(
|
||||
jsTransform(dedent`
|
||||
export default { title: 'Cat' };
|
||||
const Template = (args) => <Cat {...args} />;
|
||||
export const A = Template.bind({});
|
||||
A.args = { isPrimary: false };
|
||||
`)
|
||||
).toMatchInlineSnapshot(`
|
||||
).resolves.toMatchInlineSnapshot(`
|
||||
export default { title: 'Cat' };
|
||||
const Template = (args) => <Cat {...args} />;
|
||||
|
||||
@ -145,8 +149,8 @@ describe('csf-2-to-3', () => {
|
||||
`);
|
||||
});
|
||||
|
||||
it('should reuse the template when there are multiple Template.bind references but no component defined', () => {
|
||||
expect(
|
||||
it('should reuse the template when there are multiple Template.bind references but no component defined', async () => {
|
||||
await expect(
|
||||
jsTransform(dedent`
|
||||
export default { title: 'Cat' };
|
||||
const Template = (args) => <Cat {...args} />;
|
||||
@ -164,7 +168,7 @@ describe('csf-2-to-3', () => {
|
||||
export const D = Template.bind({});
|
||||
D.args = { bla: false };
|
||||
`)
|
||||
).toMatchInlineSnapshot(`
|
||||
).resolves.toMatchInlineSnapshot(`
|
||||
export default { title: 'Cat' };
|
||||
const Template = (args) => <Cat {...args} />;
|
||||
|
||||
@ -190,8 +194,8 @@ describe('csf-2-to-3', () => {
|
||||
`);
|
||||
});
|
||||
|
||||
it('should remove implicit global render for template.bind', () => {
|
||||
expect(
|
||||
it('should remove implicit global render for template.bind', async () => {
|
||||
await expect(
|
||||
jsTransform(dedent`
|
||||
export default { title: 'Cat', component: Cat };
|
||||
|
||||
@ -205,7 +209,7 @@ describe('csf-2-to-3', () => {
|
||||
export const B = Template2.bind({});
|
||||
B.args = { isPrimary: true };
|
||||
`)
|
||||
).toMatchInlineSnapshot(`
|
||||
).resolves.toMatchInlineSnapshot(`
|
||||
export default { title: 'Cat', component: Cat };
|
||||
|
||||
export const A = {
|
||||
@ -221,8 +225,8 @@ describe('csf-2-to-3', () => {
|
||||
`);
|
||||
});
|
||||
|
||||
it('should ignore no-arg stories without annotations', () => {
|
||||
expect(
|
||||
it('should ignore no-arg stories without annotations', async () => {
|
||||
await expect(
|
||||
jsTransform(dedent`
|
||||
export default { title: 'Cat', component: Cat };
|
||||
|
||||
@ -231,7 +235,7 @@ describe('csf-2-to-3', () => {
|
||||
export const C = () => <Cat name="fluffy" />;
|
||||
C.parameters = { foo: 2 };
|
||||
`)
|
||||
).toMatchInlineSnapshot(`
|
||||
).resolves.toMatchInlineSnapshot(`
|
||||
export default { title: 'Cat', component: Cat };
|
||||
|
||||
export const A = {};
|
||||
@ -244,8 +248,8 @@ describe('csf-2-to-3', () => {
|
||||
`);
|
||||
});
|
||||
|
||||
it('should work for v1-style annotations', () => {
|
||||
expect(
|
||||
it('should work for v1-style annotations', async () => {
|
||||
await expect(
|
||||
jsTransform(dedent`
|
||||
export default { title: 'Cat' };
|
||||
export const A = (args) => <Cat {...args} />;
|
||||
@ -253,7 +257,7 @@ describe('csf-2-to-3', () => {
|
||||
parameters: { foo: 2 }
|
||||
};
|
||||
`)
|
||||
).toMatchInlineSnapshot(`
|
||||
).resolves.toMatchInlineSnapshot(`
|
||||
export default { title: 'Cat' };
|
||||
|
||||
export const A = {
|
||||
@ -265,15 +269,15 @@ describe('csf-2-to-3', () => {
|
||||
});
|
||||
|
||||
describe('typescript', () => {
|
||||
it('should error with namespace imports', () => {
|
||||
expect.addSnapshotSerializer({
|
||||
it('should error with namespace imports', async () => {
|
||||
await expect.addSnapshotSerializer({
|
||||
serialize: (value) => {
|
||||
const stringVal = typeof value === 'string' ? value : value.toString();
|
||||
return stringVal.replace(ansiRegex(), '');
|
||||
},
|
||||
test: () => true,
|
||||
});
|
||||
expect(() =>
|
||||
await expect(() =>
|
||||
tsTransform(dedent`
|
||||
import * as SB from '@storybook/react';
|
||||
import { CatProps } from './Cat';
|
||||
@ -283,13 +287,13 @@ describe('csf-2-to-3', () => {
|
||||
|
||||
export const A: SB.StoryFn<CatProps> = () => <Cat />;
|
||||
`)
|
||||
).toThrowErrorMatchingInlineSnapshot(dedent`
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(dedent`
|
||||
Error: This codemod does not support namespace imports for a @storybook/react package.
|
||||
Replace the namespace import with named imports and try again.
|
||||
`);
|
||||
});
|
||||
it('should keep local names', () => {
|
||||
expect(
|
||||
it('should keep local names', async () => {
|
||||
await expect(
|
||||
tsTransform(dedent`
|
||||
import { Meta, StoryObj as CSF3, StoryFn as CSF2 } from '@storybook/react';
|
||||
import { CatProps } from './Cat';
|
||||
@ -308,7 +312,7 @@ describe('csf-2-to-3', () => {
|
||||
name: "Fluffy"
|
||||
};
|
||||
`)
|
||||
).toMatchInlineSnapshot(`
|
||||
).resolves.toMatchInlineSnapshot(`
|
||||
import { Meta, StoryObj as CSF3, StoryFn as CSF2 } from '@storybook/react';
|
||||
import { CatProps } from './Cat';
|
||||
|
||||
@ -329,8 +333,8 @@ describe('csf-2-to-3', () => {
|
||||
`);
|
||||
});
|
||||
|
||||
it('should replace function exports with objects and update type', () => {
|
||||
expect(
|
||||
it('should replace function exports with objects and update type', async () => {
|
||||
await expect(
|
||||
tsTransform(dedent`
|
||||
import { Story, StoryFn, ComponentStory, ComponentStoryObj } from '@storybook/react';
|
||||
|
||||
@ -367,7 +371,7 @@ describe('csf-2-to-3', () => {
|
||||
},
|
||||
};
|
||||
`)
|
||||
).toMatchInlineSnapshot(`
|
||||
).resolves.toMatchInlineSnapshot(`
|
||||
import { StoryObj, StoryFn } from '@storybook/react';
|
||||
|
||||
// some extra whitespace to test
|
||||
@ -411,8 +415,8 @@ describe('csf-2-to-3', () => {
|
||||
`);
|
||||
});
|
||||
|
||||
it('migrate Story type to StoryFn when used in an not exported Template function', () => {
|
||||
expect(
|
||||
it('migrate Story type to StoryFn when used in an not exported Template function', async () => {
|
||||
await expect(
|
||||
tsTransform(dedent`
|
||||
import { Story, Meta } from '@storybook/react'
|
||||
|
||||
@ -424,7 +428,7 @@ describe('csf-2-to-3', () => {
|
||||
|
||||
export const Default = Template.bind({})
|
||||
`)
|
||||
).toMatchInlineSnapshot(`
|
||||
).resolves.toMatchInlineSnapshot(`
|
||||
import { StoryFn, Meta } from '@storybook/react';
|
||||
|
||||
export default {
|
||||
|
@ -15,24 +15,23 @@ beforeEach(() => {
|
||||
fs.existsSync.mockImplementation(() => false);
|
||||
});
|
||||
|
||||
it('update import even when no stories can be extracted', () => {
|
||||
it('update import even when no stories can be extracted', async () => {
|
||||
const input = dedent`
|
||||
import { Heading } from '@storybook/addon-docs';
|
||||
|
||||
<Heading />
|
||||
`;
|
||||
|
||||
const mdx = jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
|
||||
const mdx = await jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
|
||||
|
||||
expect(mdx).toMatchInlineSnapshot(`
|
||||
import { Heading } from '@storybook/blocks';
|
||||
|
||||
<Heading />
|
||||
|
||||
`);
|
||||
});
|
||||
|
||||
it('drop invalid story nodes', () => {
|
||||
it('drop invalid story nodes', async () => {
|
||||
const input = dedent`
|
||||
import { Meta, Story } from '@storybook/addon-docs';
|
||||
|
||||
@ -43,7 +42,7 @@ it('drop invalid story nodes', () => {
|
||||
<Story name="Primary">Story</Story>
|
||||
`;
|
||||
|
||||
const mdx = jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
|
||||
const mdx = await jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
|
||||
|
||||
expect(mdx).toMatchInlineSnapshot(`
|
||||
import { Meta, Story } from '@storybook/blocks';
|
||||
@ -54,11 +53,10 @@ it('drop invalid story nodes', () => {
|
||||
|
||||
|
||||
<Story of={FoobarStories.Primary} />
|
||||
|
||||
`);
|
||||
});
|
||||
|
||||
it('convert story re-definition', () => {
|
||||
it('convert story re-definition', async () => {
|
||||
const input = dedent`
|
||||
import { Meta, Story } from '@storybook/addon-docs';
|
||||
import { Primary } from './Foobar.stories';
|
||||
@ -70,7 +68,7 @@ it('convert story re-definition', () => {
|
||||
|
||||
fs.existsSync.mockImplementation((path) => path === 'Foobar.stories.js');
|
||||
|
||||
const mdx = jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
|
||||
const mdx = await jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
|
||||
|
||||
expect(mdx).toMatchInlineSnapshot(`
|
||||
import { Meta, Story } from '@storybook/blocks';
|
||||
@ -80,7 +78,6 @@ it('convert story re-definition', () => {
|
||||
<Meta of={FoobarStories} />
|
||||
|
||||
<Story of={FoobarStories.Primary} />
|
||||
|
||||
`);
|
||||
const [csfFileName, csf] = fs.writeFileSync.mock.calls[0];
|
||||
expect(csfFileName).toMatchInlineSnapshot(`Foobar_.stories.js`);
|
||||
@ -92,11 +89,10 @@ it('convert story re-definition', () => {
|
||||
};
|
||||
|
||||
export { Primary };
|
||||
|
||||
`);
|
||||
});
|
||||
|
||||
it('Comment out story nodes with id', () => {
|
||||
it('Comment out story nodes with id', async () => {
|
||||
const input = dedent`
|
||||
import { Meta, Story } from '@storybook/addon-docs';
|
||||
|
||||
@ -105,7 +101,7 @@ it('Comment out story nodes with id', () => {
|
||||
<Story id="button--primary" />
|
||||
`;
|
||||
|
||||
const mdx = jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
|
||||
const mdx = await jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
|
||||
|
||||
expect(mdx).toMatchInlineSnapshot(`
|
||||
import { Meta, Story } from '@storybook/blocks';
|
||||
@ -116,11 +112,10 @@ it('Comment out story nodes with id', () => {
|
||||
{/* <Story id="button--primary" /> is deprecated, please migrate it to <Story of={referenceToStory} /> see: https://storybook.js.org/migration-guides/7.0 */}
|
||||
|
||||
<Story id="button--primary" />
|
||||
|
||||
`);
|
||||
});
|
||||
|
||||
it('convert correct story nodes', () => {
|
||||
it('convert correct story nodes', async () => {
|
||||
const input = dedent`
|
||||
import { Meta, Story } from '@storybook/addon-docs';
|
||||
|
||||
@ -129,7 +124,7 @@ it('convert correct story nodes', () => {
|
||||
<Story name="Primary">Story</Story>
|
||||
`;
|
||||
|
||||
const mdx = jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
|
||||
const mdx = await jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
|
||||
|
||||
expect(mdx).toMatchInlineSnapshot(`
|
||||
import { Meta, Story } from '@storybook/blocks';
|
||||
@ -138,7 +133,6 @@ it('convert correct story nodes', () => {
|
||||
<Meta of={FoobarStories} />
|
||||
|
||||
<Story of={FoobarStories.Primary} />
|
||||
|
||||
`);
|
||||
|
||||
const [, csf] = fs.writeFileSync.mock.calls[0];
|
||||
@ -151,11 +145,10 @@ it('convert correct story nodes', () => {
|
||||
render: () => 'Story',
|
||||
name: 'Primary',
|
||||
};
|
||||
|
||||
`);
|
||||
});
|
||||
|
||||
it('convert addon-docs imports', () => {
|
||||
it('convert addon-docs imports', async () => {
|
||||
const input = dedent`
|
||||
import { Meta } from '@storybook/addon-docs';
|
||||
import { Story } from '@storybook/addon-docs/blocks';
|
||||
@ -165,7 +158,7 @@ it('convert addon-docs imports', () => {
|
||||
<Story name="Primary">Story</Story>
|
||||
`;
|
||||
|
||||
const mdx = jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
|
||||
const mdx = await jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
|
||||
|
||||
expect(mdx).toMatchInlineSnapshot(`
|
||||
import { Meta } from '@storybook/blocks';
|
||||
@ -175,11 +168,10 @@ it('convert addon-docs imports', () => {
|
||||
<Meta of={FoobarStories} />
|
||||
|
||||
<Story of={FoobarStories.Primary} />
|
||||
|
||||
`);
|
||||
});
|
||||
|
||||
it('convert story nodes with spaces', () => {
|
||||
it('convert story nodes with spaces', async () => {
|
||||
const input = dedent`
|
||||
import { Meta, Story } from '@storybook/addon-docs';
|
||||
|
||||
@ -188,7 +180,7 @@ it('convert story nodes with spaces', () => {
|
||||
<Story name="Primary Space">Story</Story>
|
||||
`;
|
||||
|
||||
const mdx = jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
|
||||
const mdx = await jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
|
||||
|
||||
expect(mdx).toMatchInlineSnapshot(`
|
||||
import { Meta, Story } from '@storybook/blocks';
|
||||
@ -197,7 +189,6 @@ it('convert story nodes with spaces', () => {
|
||||
<Meta of={FoobarStories} />
|
||||
|
||||
<Story of={FoobarStories.PrimarySpace} />
|
||||
|
||||
`);
|
||||
|
||||
const [, csf] = fs.writeFileSync.mock.calls[0];
|
||||
@ -210,11 +201,10 @@ it('convert story nodes with spaces', () => {
|
||||
render: () => 'Story',
|
||||
name: 'Primary Space',
|
||||
};
|
||||
|
||||
`);
|
||||
});
|
||||
|
||||
it('extract esm into csf head code', () => {
|
||||
it('extract esm into csf head code', async () => {
|
||||
const input = dedent`
|
||||
import { Meta, Story } from '@storybook/addon-docs';
|
||||
import { Button } from './Button';
|
||||
@ -240,7 +230,7 @@ it('extract esm into csf head code', () => {
|
||||
</Story>
|
||||
`;
|
||||
|
||||
const mdx = jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
|
||||
const mdx = await jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
|
||||
|
||||
expect(mdx).toMatchInlineSnapshot(`
|
||||
import { Meta, Story } from '@storybook/blocks';
|
||||
@ -259,7 +249,6 @@ it('extract esm into csf head code', () => {
|
||||
<Story of={FoobarStories.Foo} />
|
||||
|
||||
<Story of={FoobarStories.Unchecked} />
|
||||
|
||||
`);
|
||||
|
||||
const [csfFileName, csf] = fs.writeFileSync.mock.calls[0];
|
||||
@ -288,11 +277,10 @@ it('extract esm into csf head code', () => {
|
||||
label: 'Unchecked',
|
||||
},
|
||||
};
|
||||
|
||||
`);
|
||||
});
|
||||
|
||||
it('extract all meta parameters', () => {
|
||||
it('extract all meta parameters', async () => {
|
||||
const input = dedent`
|
||||
import { Meta, Story } from '@storybook/addon-docs';
|
||||
|
||||
@ -303,7 +291,7 @@ it('extract all meta parameters', () => {
|
||||
<Story name="foo">bar</Story>
|
||||
`;
|
||||
|
||||
jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
|
||||
await jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
|
||||
|
||||
const [, csf] = fs.writeFileSync.mock.calls[0];
|
||||
|
||||
@ -326,11 +314,10 @@ it('extract all meta parameters', () => {
|
||||
render: () => 'bar',
|
||||
name: 'foo',
|
||||
};
|
||||
|
||||
`);
|
||||
});
|
||||
|
||||
it('extract all story attributes', () => {
|
||||
it('extract all story attributes', async () => {
|
||||
const input = dedent`
|
||||
import { Meta, Story } from '@storybook/addon-docs';
|
||||
import { Button } from './Button';
|
||||
@ -355,7 +342,7 @@ it('extract all story attributes', () => {
|
||||
|
||||
`;
|
||||
|
||||
jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
|
||||
await jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
|
||||
|
||||
const [, csf] = fs.writeFileSync.mock.calls[0];
|
||||
|
||||
@ -388,11 +375,10 @@ it('extract all story attributes', () => {
|
||||
render: Template.bind({}),
|
||||
name: 'Second',
|
||||
};
|
||||
|
||||
`);
|
||||
});
|
||||
|
||||
it('duplicate story name', () => {
|
||||
it('duplicate story name', async () => {
|
||||
const input = dedent`
|
||||
import { Meta, Story } from '@storybook/addon-docs';
|
||||
import { Button } from './Button';
|
||||
@ -409,7 +395,7 @@ it('duplicate story name', () => {
|
||||
|
||||
`;
|
||||
|
||||
const mdx = jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
|
||||
const mdx = await jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
|
||||
const [, csf] = fs.writeFileSync.mock.calls[0];
|
||||
|
||||
expect(mdx).toMatchInlineSnapshot(`
|
||||
@ -444,11 +430,10 @@ it('duplicate story name', () => {
|
||||
render: Default.bind({}),
|
||||
name: 'Second',
|
||||
};
|
||||
|
||||
`);
|
||||
});
|
||||
|
||||
it('kebab case file name', () => {
|
||||
it('kebab case file name', async () => {
|
||||
const input = dedent`
|
||||
import { Meta, Story } from '@storybook/addon-docs';
|
||||
import { Kebab } from './my-component/some-kebab-case';
|
||||
@ -465,7 +450,7 @@ it('kebab case file name', () => {
|
||||
|
||||
`;
|
||||
|
||||
const mdx = jscodeshift({ source: input, path: 'some-kebab-case.stories.mdx' });
|
||||
const mdx = await jscodeshift({ source: input, path: 'some-kebab-case.stories.mdx' });
|
||||
|
||||
expect(mdx).toMatchInlineSnapshot(`
|
||||
import { Meta, Story } from '@storybook/blocks';
|
||||
@ -479,7 +464,6 @@ it('kebab case file name', () => {
|
||||
<Story of={SomeKebabCaseStories.MuchKebab} />
|
||||
|
||||
<Story of={SomeKebabCaseStories.ReallyMuchKebab} />
|
||||
|
||||
`);
|
||||
|
||||
const [, csf] = fs.writeFileSync.mock.calls[0];
|
||||
@ -502,11 +486,10 @@ it('kebab case file name', () => {
|
||||
render: Template.bind({}),
|
||||
name: 'Really-Much-Kebab',
|
||||
};
|
||||
|
||||
`);
|
||||
});
|
||||
|
||||
it('story child is jsx', () => {
|
||||
it('story child is jsx', async () => {
|
||||
const input = dedent`
|
||||
import { Canvas, Meta, Story } from '@storybook/addon-docs';
|
||||
import { Button } from './button';
|
||||
@ -518,7 +501,7 @@ it('story child is jsx', () => {
|
||||
</Story>
|
||||
`;
|
||||
|
||||
jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
|
||||
await jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
|
||||
|
||||
const [, csf] = fs.writeFileSync.mock.calls[0];
|
||||
|
||||
@ -535,11 +518,10 @@ it('story child is jsx', () => {
|
||||
|
||||
name: 'Primary',
|
||||
};
|
||||
|
||||
`);
|
||||
});
|
||||
|
||||
it('story child is CSF3', () => {
|
||||
it('story child is CSF3', async () => {
|
||||
const input = dedent`
|
||||
import { Story } from '@storybook/addon-docs';
|
||||
import { Button } from './button';
|
||||
@ -547,7 +529,7 @@ it('story child is CSF3', () => {
|
||||
<Story name="Primary" render={(args) => <Button {...args}></Button> } args={{label: 'Hello' }} />
|
||||
`;
|
||||
|
||||
jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
|
||||
await jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
|
||||
|
||||
const [, csf] = fs.writeFileSync.mock.calls[0];
|
||||
|
||||
@ -563,11 +545,10 @@ it('story child is CSF3', () => {
|
||||
label: 'Hello',
|
||||
},
|
||||
};
|
||||
|
||||
`);
|
||||
});
|
||||
|
||||
it('story child is arrow function', () => {
|
||||
it('story child is arrow function', async () => {
|
||||
const input = dedent`
|
||||
import { Canvas, Meta, Story } from '@storybook/addon-docs';
|
||||
import { Button } from './button';
|
||||
@ -577,7 +558,7 @@ it('story child is arrow function', () => {
|
||||
</Story>
|
||||
`;
|
||||
|
||||
jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
|
||||
await jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
|
||||
|
||||
const [, csf] = fs.writeFileSync.mock.calls[0];
|
||||
|
||||
@ -589,11 +570,10 @@ it('story child is arrow function', () => {
|
||||
render: (args) => <Button />,
|
||||
name: 'Primary',
|
||||
};
|
||||
|
||||
`);
|
||||
});
|
||||
|
||||
it('story child is identifier', () => {
|
||||
it('story child is identifier', async () => {
|
||||
const input = dedent`
|
||||
import { Canvas, Meta, Story } from '@storybook/addon-docs';
|
||||
import { Button } from './button';
|
||||
@ -603,7 +583,7 @@ it('story child is identifier', () => {
|
||||
</Story>
|
||||
`;
|
||||
|
||||
jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
|
||||
await jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
|
||||
|
||||
const [, csf] = fs.writeFileSync.mock.calls[0];
|
||||
|
||||
@ -615,7 +595,6 @@ it('story child is identifier', () => {
|
||||
render: Button,
|
||||
name: 'Primary',
|
||||
};
|
||||
|
||||
`);
|
||||
});
|
||||
|
||||
|
@ -9,13 +9,17 @@ expect.addSnapshotSerializer({
|
||||
test: () => true,
|
||||
});
|
||||
|
||||
const tsTransform = (source: string) =>
|
||||
_transform({ source, path: 'Component.stories.ts' }, {} as API, { parser: 'tsx' }).trim();
|
||||
const tsTransform = async (source: string) =>
|
||||
(
|
||||
await _transform({ source, path: 'Component.stories.tsx' }, {} as API, {
|
||||
parser: 'tsx',
|
||||
})
|
||||
).trim();
|
||||
|
||||
describe('upgrade-deprecated-types', () => {
|
||||
describe('typescript', () => {
|
||||
it('upgrade regular imports', () => {
|
||||
expect(
|
||||
it('upgrade regular imports', async () => {
|
||||
await expect(
|
||||
tsTransform(dedent`
|
||||
import { Story, ComponentMeta, Meta, ComponentStory, ComponentStoryObj, ComponentStoryFn } from '@storybook/react';
|
||||
import { Cat, CatProps } from './Cat';
|
||||
@ -34,11 +38,11 @@ describe('upgrade-deprecated-types', () => {
|
||||
};
|
||||
export const E: Story<CatProps> = (args) => <Cat {...args} />;
|
||||
`)
|
||||
).toMatchSnapshot();
|
||||
).resolves.toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('upgrade imports with local names', () => {
|
||||
expect(
|
||||
it('upgrade imports with local names', async () => {
|
||||
await expect(
|
||||
tsTransform(dedent`
|
||||
import { Story as Story_, ComponentMeta as ComponentMeta_, ComponentStory as Story__, ComponentStoryObj as ComponentStoryObj_, ComponentStoryFn as StoryFn_ } from '@storybook/react';
|
||||
import { Cat } from './Cat';
|
||||
@ -57,11 +61,11 @@ describe('upgrade-deprecated-types', () => {
|
||||
};
|
||||
export const E: Story_<CatProps> = (args) => <Cat {...args} />;
|
||||
`)
|
||||
).toMatchSnapshot();
|
||||
).resolves.toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('upgrade imports with conflicting local names', () => {
|
||||
expect.addSnapshotSerializer({
|
||||
it('upgrade imports with conflicting local names', async () => {
|
||||
await expect.addSnapshotSerializer({
|
||||
serialize: (value) => {
|
||||
const stringVal = typeof value === 'string' ? value : value.toString();
|
||||
return stringVal.replace(ansiRegex(), '');
|
||||
@ -69,7 +73,7 @@ describe('upgrade-deprecated-types', () => {
|
||||
test: () => true,
|
||||
});
|
||||
|
||||
expect(() =>
|
||||
await expect(() =>
|
||||
tsTransform(dedent`
|
||||
import { ComponentMeta as Meta, ComponentStory as StoryFn } from '@storybook/react';
|
||||
import { Cat } from './Cat';
|
||||
@ -80,11 +84,11 @@ describe('upgrade-deprecated-types', () => {
|
||||
export const A: StoryFn<typeof Cat> = (args) => <Cat {...args} />;
|
||||
|
||||
`)
|
||||
).toThrowErrorMatchingSnapshot();
|
||||
).rejects.toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
|
||||
it('upgrade namespaces', () => {
|
||||
expect(
|
||||
it('upgrade namespaces', async () => {
|
||||
await expect(
|
||||
tsTransform(dedent`
|
||||
import * as SB from '@storybook/react';
|
||||
import { Cat, CatProps } from './Cat';
|
||||
@ -104,7 +108,7 @@ describe('upgrade-deprecated-types', () => {
|
||||
export const E: SB.Story<CatProps> = (args) => <Cat {...args} />;
|
||||
|
||||
`)
|
||||
).toMatchSnapshot();
|
||||
).resolves.toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -110,7 +110,7 @@ function removeUnusedTemplates(csf: CsfFile) {
|
||||
});
|
||||
}
|
||||
|
||||
export default function transform(info: FileInfo, api: API, options: { parser?: string }) {
|
||||
export default async function transform(info: FileInfo, api: API, options: { parser?: string }) {
|
||||
const makeTitle = (userTitle?: string) => {
|
||||
return userTitle || 'FIXME';
|
||||
};
|
||||
@ -204,7 +204,7 @@ export default function transform(info: FileInfo, api: API, options: { parser?:
|
||||
let output = printCsf(csf).code;
|
||||
|
||||
try {
|
||||
const prettierConfig = prettier.resolveConfig.sync('.', { editorconfig: true }) || {
|
||||
const prettierConfig = (await prettier.resolveConfig(info.path)) ?? {
|
||||
printWidth: 100,
|
||||
tabWidth: 2,
|
||||
bracketSpacing: true,
|
||||
@ -212,9 +212,8 @@ export default function transform(info: FileInfo, api: API, options: { parser?:
|
||||
singleQuote: true,
|
||||
};
|
||||
|
||||
output = prettier.format(output, {
|
||||
output = await prettier.format(output, {
|
||||
...prettierConfig,
|
||||
// This will infer the parser from the filename.
|
||||
filepath: info.path,
|
||||
});
|
||||
} catch (e) {
|
||||
|
@ -24,7 +24,7 @@ import type { MdxFlowExpression } from 'mdast-util-mdx-expression';
|
||||
|
||||
const mdxProcessor = remark().use(remarkMdx) as ReturnType<typeof remark>;
|
||||
|
||||
export default function jscodeshift(info: FileInfo) {
|
||||
export default async function jscodeshift(info: FileInfo) {
|
||||
const parsed = path.parse(info.path);
|
||||
|
||||
let baseName = path.join(
|
||||
@ -37,7 +37,7 @@ export default function jscodeshift(info: FileInfo) {
|
||||
baseName += '_';
|
||||
}
|
||||
|
||||
const result = transform(info.source, path.basename(baseName));
|
||||
const result = await transform(info, path.basename(baseName));
|
||||
|
||||
const [mdx, csf] = result;
|
||||
|
||||
@ -48,8 +48,8 @@ export default function jscodeshift(info: FileInfo) {
|
||||
return mdx;
|
||||
}
|
||||
|
||||
export function transform(source: string, baseName: string): [string, string] {
|
||||
const root = mdxProcessor.parse(source);
|
||||
export async function transform(info: FileInfo, baseName: string): Promise<[string, string]> {
|
||||
const root = mdxProcessor.parse(info.source);
|
||||
const storyNamespaceName = nameToValidExport(`${baseName}Stories`);
|
||||
|
||||
const metaAttributes: Array<MdxJsxAttribute | MdxJsxExpressionAttribute> = [];
|
||||
@ -291,7 +291,7 @@ export function transform(source: string, baseName: string): [string, string] {
|
||||
const newMdx = mdxProcessor.stringify(root);
|
||||
let output = recast.print(file.path.node).code;
|
||||
|
||||
const prettierConfig = prettier.resolveConfig.sync('.', { editorconfig: true }) || {
|
||||
const prettierConfig = (await prettier.resolveConfig(`${info.path}.jsx`)) || {
|
||||
printWidth: 100,
|
||||
tabWidth: 2,
|
||||
bracketSpacing: true,
|
||||
@ -299,7 +299,10 @@ export function transform(source: string, baseName: string): [string, string] {
|
||||
singleQuote: true,
|
||||
};
|
||||
|
||||
output = prettier.format(output, { ...prettierConfig, filepath: `file.jsx` });
|
||||
output = await prettier.format(output.trim(), {
|
||||
...prettierConfig,
|
||||
filepath: `${info.path}.jsx`,
|
||||
});
|
||||
|
||||
return [newMdx, output];
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ import { sanitizeName, jscodeshiftToPrettierParser } from '../lib/utils';
|
||||
*
|
||||
* NOTES: only support chained storiesOf() calls
|
||||
*/
|
||||
export default function transformer(file, api, options) {
|
||||
export default async function transformer(file, api, options) {
|
||||
const LITERAL = ['ts', 'tsx'].includes(options.parser) ? 'StringLiteral' : 'Literal';
|
||||
|
||||
const j = api.jscodeshift;
|
||||
@ -262,7 +262,10 @@ export default function transformer(file, api, options) {
|
||||
return source;
|
||||
}
|
||||
|
||||
const prettierConfig = prettier.resolveConfig.sync('.', { editorconfig: true }) || {
|
||||
let output = source;
|
||||
|
||||
try {
|
||||
const prettierConfig = (await prettier.resolveConfig(file.path)) || {
|
||||
printWidth: 100,
|
||||
tabWidth: 2,
|
||||
bracketSpacing: true,
|
||||
@ -270,8 +273,13 @@ export default function transformer(file, api, options) {
|
||||
singleQuote: true,
|
||||
};
|
||||
|
||||
return prettier.format(source, {
|
||||
output = prettier.format(source, {
|
||||
...prettierConfig,
|
||||
parser: jscodeshiftToPrettierParser(options.parser),
|
||||
});
|
||||
} catch (e) {
|
||||
logger.warn(`Failed to format ${file.path} with prettier`);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ function migrateType(oldType: string) {
|
||||
return oldType.replace('Component', '');
|
||||
}
|
||||
|
||||
export default function transform(info: FileInfo, api: API, options: { parser?: string }) {
|
||||
export default async function transform(info: FileInfo, api: API, options: { parser?: string }) {
|
||||
// TODO what do I need to with the title?
|
||||
const csf = loadCsf(info.source, { makeTitle: (title) => title });
|
||||
const fileNode = csf._ast;
|
||||
@ -36,7 +36,7 @@ export default function transform(info: FileInfo, api: API, options: { parser?:
|
||||
let output = printCsf(csf).code;
|
||||
|
||||
try {
|
||||
const prettierConfig = prettier.resolveConfig.sync('.', { editorconfig: true }) || {
|
||||
const prettierConfig = (await prettier.resolveConfig(info.path)) || {
|
||||
printWidth: 100,
|
||||
tabWidth: 2,
|
||||
bracketSpacing: true,
|
||||
@ -44,7 +44,7 @@ export default function transform(info: FileInfo, api: API, options: { parser?:
|
||||
singleQuote: true,
|
||||
};
|
||||
|
||||
output = prettier.format(output, { ...prettierConfig, filepath: info.path });
|
||||
output = await prettier.format(output, { ...prettierConfig, filepath: info.path });
|
||||
} catch (e) {
|
||||
logger.log(`Failed applying prettier to ${info.path}.`);
|
||||
}
|
||||
|
@ -417,34 +417,55 @@ export class GenerateNewProjectOnInitError extends StorybookError {
|
||||
}
|
||||
}
|
||||
|
||||
export class ConflictingVersionTagsError extends StorybookError {
|
||||
export class UpgradeStorybookToLowerVersionError extends StorybookError {
|
||||
readonly category = Category.CLI_UPGRADE;
|
||||
|
||||
readonly code = 1;
|
||||
readonly code = 3;
|
||||
|
||||
template() {
|
||||
return 'Cannot set both --tag and --prerelease. Use --tag=next to get the latest prerelease.';
|
||||
}
|
||||
}
|
||||
|
||||
export class UpgradeStorybookPackagesError extends StorybookError {
|
||||
readonly category = Category.CLI_UPGRADE;
|
||||
|
||||
readonly code = 2;
|
||||
|
||||
constructor(public data: { command: string; args: string[]; errorMessage: string }) {
|
||||
constructor(public data: { beforeVersion: string; currentVersion: string }) {
|
||||
super();
|
||||
}
|
||||
|
||||
template() {
|
||||
return dedent`
|
||||
There was an error while trying to upgrade your Storybook dependencies.
|
||||
You are trying to upgrade Storybook to a lower version than the version currently installed. This is not supported.
|
||||
|
||||
Command:
|
||||
${this.data.command} ${this.data.args.join(' ')}
|
||||
Storybook version ${this.data.beforeVersion} was detected in your project, but you are trying to "upgrade" to version ${this.data.currentVersion}.
|
||||
|
||||
Error:
|
||||
${this.data.errorMessage}
|
||||
This usually happens when running the upgrade command without a version specifier, e.g. "npx storybook upgrade".
|
||||
This will cause npm to run the globally cached storybook binary, which might be an older version.
|
||||
|
||||
Instead you should always run the Storybook CLI with a version specifier to force npm to download the latest version:
|
||||
|
||||
"npx storybook@latest upgrade"
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
export class UpgradeStorybookToSameVersionError extends StorybookError {
|
||||
readonly category = Category.CLI_UPGRADE;
|
||||
|
||||
readonly code = 4;
|
||||
|
||||
constructor(public data: { beforeVersion: string }) {
|
||||
super();
|
||||
}
|
||||
|
||||
template() {
|
||||
return dedent`
|
||||
You are trying to upgrade Storybook to the same version that is currently installed in the project, version ${this.data.beforeVersion}. This is not supported.
|
||||
|
||||
This usually happens when running the upgrade command without a version specifier, e.g. "npx storybook upgrade".
|
||||
This will cause npm to run the globally cached storybook binary, which might be the same version that you already have.
|
||||
This also happens if you're running the Storybook CLI that is locally installed in your project.
|
||||
|
||||
If you intended to upgrade to the latest version, you should always run the Storybook CLI with a version specifier to force npm to download the latest version:
|
||||
|
||||
"npx storybook@latest upgrade"
|
||||
|
||||
If you intended to re-run automigrations, you should run the "automigrate" command directly instead:
|
||||
|
||||
"npx storybook@${this.data.beforeVersion} automigrate"
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@ -66,6 +66,7 @@
|
||||
"@storybook/docs-mdx": "3.0.0",
|
||||
"@storybook/global": "^5.0.0",
|
||||
"@storybook/manager": "workspace:*",
|
||||
"@storybook/manager-api": "workspace:*",
|
||||
"@storybook/node-logger": "workspace:*",
|
||||
"@storybook/preview-api": "workspace:*",
|
||||
"@storybook/telemetry": "workspace:*",
|
||||
@ -115,6 +116,7 @@
|
||||
"entries": [
|
||||
"./src/index.ts",
|
||||
"./src/presets/common-preset.ts",
|
||||
"./src/presets/common-manager.ts",
|
||||
"./src/presets/common-override-preset.ts"
|
||||
],
|
||||
"platform": "node"
|
||||
|
21
code/lib/core-server/src/presets/common-manager.ts
Normal file
21
code/lib/core-server/src/presets/common-manager.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { addons } from '@storybook/manager-api';
|
||||
import { global } from '@storybook/global';
|
||||
|
||||
const STATIC_FILTER = 'static-filter';
|
||||
|
||||
addons.register(STATIC_FILTER, (api) => {
|
||||
// FIXME: this ensures the filter is applied after the first render
|
||||
// to avoid a strange race condition in Webkit only.
|
||||
const excludeTags = Object.entries(global.TAGS_OPTIONS ?? {}).reduce((acc, entry) => {
|
||||
const [tag, option] = entry;
|
||||
if ((option as any).excludeFromSidebar) {
|
||||
acc[tag] = true;
|
||||
}
|
||||
return acc;
|
||||
}, {} as Record<string, boolean>);
|
||||
|
||||
api.experimental_setFilter(STATIC_FILTER, (item) => {
|
||||
const tags = item.tags || [];
|
||||
return tags.filter((tag) => excludeTags[tag]).length === 0;
|
||||
});
|
||||
});
|
@ -359,3 +359,19 @@ export const resolvedReact = async (existing: any) => {
|
||||
return existing;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set up `dev-only`, `docs-only`, `test-only` tags out of the box
|
||||
*/
|
||||
export const tags = async (existing: any) => {
|
||||
return {
|
||||
...existing,
|
||||
'dev-only': { excludeFromDocsStories: true },
|
||||
'docs-only': { excludeFromSidebar: true },
|
||||
'test-only': { excludeFromSidebar: true, excludeFromDocsStories: true },
|
||||
};
|
||||
};
|
||||
|
||||
export const managerEntries = async (existing: any, options: Options) => {
|
||||
return [require.resolve('./common-manager'), ...(existing || [])];
|
||||
};
|
||||
|
1
code/lib/core-server/src/typings.d.ts
vendored
1
code/lib/core-server/src/typings.d.ts
vendored
@ -7,3 +7,4 @@ declare module '@discoveryjs/json-ext';
|
||||
declare module 'watchpack';
|
||||
|
||||
declare var FEATURES: import('@storybook/types').StorybookConfigRaw['features'];
|
||||
declare var TAGS_OPTIONS: import('@storybook/types').TagsOptions;
|
||||
|
@ -318,7 +318,7 @@ export class StoryIndexGenerator {
|
||||
const name = this.options.docs.defaultName ?? 'Docs';
|
||||
const { metaId } = indexInputs[0];
|
||||
const { title } = entries[0];
|
||||
const tags = indexInputs[0].tags || [];
|
||||
const metaTags = indexInputs[0].metaTags || [];
|
||||
const id = toId(metaId ?? title, name);
|
||||
entries.unshift({
|
||||
id,
|
||||
@ -326,7 +326,7 @@ export class StoryIndexGenerator {
|
||||
name,
|
||||
importPath,
|
||||
type: 'docs',
|
||||
tags: [...tags, 'docs', ...(!hasAutodocsTag && !isStoriesMdx ? [AUTODOCS_TAG] : [])],
|
||||
tags: [...metaTags, 'docs', ...(!hasAutodocsTag && !isStoriesMdx ? [AUTODOCS_TAG] : [])],
|
||||
storiesImports: [],
|
||||
});
|
||||
}
|
||||
@ -432,6 +432,7 @@ export class StoryIndexGenerator {
|
||||
importPath,
|
||||
storiesImports: sortedDependencies.map((dep) => dep.entries[0].importPath),
|
||||
type: 'docs',
|
||||
// FIXME: update this to use the index entry's metaTags once we update this to run on `IndexInputs`
|
||||
tags: [...(result.tags || []), csfEntry ? 'attached-mdx' : 'unattached-mdx', 'docs'],
|
||||
};
|
||||
return docsEntry;
|
||||
@ -528,10 +529,13 @@ export class StoryIndexGenerator {
|
||||
const fileNameOrder = this.storyFileNames();
|
||||
sortStoriesV7(sortableStories, storySortParameter, fileNameOrder);
|
||||
|
||||
return sortableStories.reduce((acc, item) => {
|
||||
return sortableStories.reduce(
|
||||
(acc, item) => {
|
||||
acc[item.id] = item;
|
||||
return acc;
|
||||
}, {} as StoryIndex['entries']);
|
||||
},
|
||||
{} as StoryIndex['entries']
|
||||
);
|
||||
}
|
||||
|
||||
async getIndex() {
|
||||
|
@ -405,7 +405,6 @@ describe('docs entries from story extraction', () => {
|
||||
"name": "docs",
|
||||
"storiesImports": [],
|
||||
"tags": [
|
||||
"story-tag-from-indexer",
|
||||
"docs",
|
||||
"autodocs",
|
||||
],
|
||||
@ -466,8 +465,6 @@ describe('docs entries from story extraction', () => {
|
||||
"name": "docs",
|
||||
"storiesImports": [],
|
||||
"tags": [
|
||||
"autodocs",
|
||||
"story-tag-from-indexer",
|
||||
"docs",
|
||||
],
|
||||
"title": "A",
|
||||
@ -577,8 +574,6 @@ describe('docs entries from story extraction', () => {
|
||||
"name": "docs",
|
||||
"storiesImports": [],
|
||||
"tags": [
|
||||
"stories-mdx",
|
||||
"story-tag-from-indexer",
|
||||
"docs",
|
||||
],
|
||||
"title": "A",
|
||||
|
@ -6,8 +6,8 @@ export async function buildOrThrow<T>(callback: () => Promise<T>): Promise<T> {
|
||||
} catch (err: any) {
|
||||
const builderErrors = err.errors as { text: string }[];
|
||||
if (builderErrors) {
|
||||
const inconsistentVersionsError = builderErrors.find((er) =>
|
||||
er.text?.includes('No matching export')
|
||||
const inconsistentVersionsError = builderErrors.find(
|
||||
(er) => er.text?.includes('No matching export')
|
||||
);
|
||||
|
||||
if (inconsistentVersionsError) {
|
||||
|
@ -38,8 +38,7 @@ export function createUpdateMessage(updateInfo: VersionCheck, version: string):
|
||||
|
||||
try {
|
||||
const isPrerelease = semver.prerelease(updateInfo.data.latest.version);
|
||||
const suffix = isPrerelease ? '@next upgrade --prerelease' : '@latest upgrade';
|
||||
const upgradeCommand = `npx storybook${suffix}`;
|
||||
const upgradeCommand = `npx storybook@${isPrerelease ? 'next' : 'latest'} upgrade`;
|
||||
updateMessage =
|
||||
updateInfo.success && semver.lt(version, updateInfo.data.latest.version)
|
||||
? dedent`
|
||||
|
@ -79,7 +79,7 @@ describe('withTelemetry', () => {
|
||||
|
||||
it('does not send full error message when crash reports are disabled', async () => {
|
||||
vi.mocked(loadAllPresets).mockResolvedValueOnce({
|
||||
apply: async () => ({ enableCrashReports: false } as any),
|
||||
apply: async () => ({ enableCrashReports: false }) as any,
|
||||
});
|
||||
await expect(async () =>
|
||||
withTelemetry(
|
||||
@ -99,7 +99,7 @@ describe('withTelemetry', () => {
|
||||
|
||||
it('does send error message when crash reports are enabled', async () => {
|
||||
vi.mocked(loadAllPresets).mockResolvedValueOnce({
|
||||
apply: async () => ({ enableCrashReports: true } as any),
|
||||
apply: async () => ({ enableCrashReports: true }) as any,
|
||||
});
|
||||
|
||||
await expect(async () =>
|
||||
@ -120,7 +120,7 @@ describe('withTelemetry', () => {
|
||||
|
||||
it('does not send any error message when telemetry is disabled', async () => {
|
||||
vi.mocked(loadAllPresets).mockResolvedValueOnce({
|
||||
apply: async () => ({ disableTelemetry: true } as any),
|
||||
apply: async () => ({ disableTelemetry: true }) as any,
|
||||
});
|
||||
|
||||
await expect(async () =>
|
||||
@ -141,7 +141,7 @@ describe('withTelemetry', () => {
|
||||
|
||||
it('does send error messages when telemetry is disabled, but crash reports are enabled', async () => {
|
||||
vi.mocked(loadAllPresets).mockResolvedValueOnce({
|
||||
apply: async () => ({ disableTelemetry: true, enableCrashReports: true } as any),
|
||||
apply: async () => ({ disableTelemetry: true, enableCrashReports: true }) as any,
|
||||
});
|
||||
|
||||
await expect(async () =>
|
||||
@ -162,7 +162,7 @@ describe('withTelemetry', () => {
|
||||
|
||||
it('does not send full error messages when disabled crash reports are cached', async () => {
|
||||
vi.mocked(loadAllPresets).mockResolvedValueOnce({
|
||||
apply: async () => ({} as any),
|
||||
apply: async () => ({}) as any,
|
||||
});
|
||||
vi.mocked(cache.get).mockResolvedValueOnce(false);
|
||||
|
||||
@ -184,7 +184,7 @@ describe('withTelemetry', () => {
|
||||
|
||||
it('does send error messages when enabled crash reports are cached', async () => {
|
||||
vi.mocked(loadAllPresets).mockResolvedValueOnce({
|
||||
apply: async () => ({} as any),
|
||||
apply: async () => ({}) as any,
|
||||
});
|
||||
vi.mocked(cache.get).mockResolvedValueOnce(true);
|
||||
|
||||
@ -206,7 +206,7 @@ describe('withTelemetry', () => {
|
||||
|
||||
it('does not send full error messages when disabled crash reports are prompted', async () => {
|
||||
vi.mocked(loadAllPresets).mockResolvedValueOnce({
|
||||
apply: async () => ({} as any),
|
||||
apply: async () => ({}) as any,
|
||||
});
|
||||
vi.mocked(cache.get).mockResolvedValueOnce(undefined);
|
||||
vi.mocked(prompts).mockResolvedValueOnce({ enableCrashReports: false });
|
||||
@ -229,7 +229,7 @@ describe('withTelemetry', () => {
|
||||
|
||||
it('does send error messages when enabled crash reports are prompted', async () => {
|
||||
vi.mocked(loadAllPresets).mockResolvedValueOnce({
|
||||
apply: async () => ({} as any),
|
||||
apply: async () => ({}) as any,
|
||||
});
|
||||
vi.mocked(cache.get).mockResolvedValueOnce(undefined);
|
||||
vi.mocked(prompts).mockResolvedValueOnce({ enableCrashReports: true });
|
||||
@ -386,7 +386,7 @@ describe('getErrorLevel', () => {
|
||||
};
|
||||
|
||||
vi.mocked(loadAllPresets).mockResolvedValueOnce({
|
||||
apply: async () => ({ enableCrashReports: true } as any),
|
||||
apply: async () => ({ enableCrashReports: true }) as any,
|
||||
});
|
||||
vi.mocked(cache.get).mockResolvedValueOnce(false);
|
||||
|
||||
@ -405,7 +405,7 @@ describe('getErrorLevel', () => {
|
||||
};
|
||||
|
||||
vi.mocked(loadAllPresets).mockResolvedValueOnce({
|
||||
apply: async () => ({ enableCrashReports: false } as any),
|
||||
apply: async () => ({ enableCrashReports: false }) as any,
|
||||
});
|
||||
vi.mocked(cache.get).mockResolvedValueOnce(false);
|
||||
|
||||
@ -424,7 +424,7 @@ describe('getErrorLevel', () => {
|
||||
};
|
||||
|
||||
vi.mocked(loadAllPresets).mockResolvedValueOnce({
|
||||
apply: async () => ({ disableTelemetry: true } as any),
|
||||
apply: async () => ({ disableTelemetry: true }) as any,
|
||||
});
|
||||
vi.mocked(cache.get).mockResolvedValueOnce(false);
|
||||
|
||||
@ -444,7 +444,7 @@ describe('getErrorLevel', () => {
|
||||
|
||||
vi.mocked(cache.get).mockResolvedValueOnce(true);
|
||||
vi.mocked(loadAllPresets).mockResolvedValueOnce({
|
||||
apply: async () => ({} as any),
|
||||
apply: async () => ({}) as any,
|
||||
});
|
||||
|
||||
const errorLevel = await getErrorLevel(options);
|
||||
@ -462,7 +462,7 @@ describe('getErrorLevel', () => {
|
||||
};
|
||||
|
||||
vi.mocked(loadAllPresets).mockResolvedValueOnce({
|
||||
apply: async () => ({} as any),
|
||||
apply: async () => ({}) as any,
|
||||
});
|
||||
vi.mocked(cache.get).mockResolvedValueOnce(undefined);
|
||||
|
||||
|
@ -4,6 +4,20 @@ import { globToRegexp } from '@storybook/core-common';
|
||||
|
||||
import { importPipeline } from './importPipeline';
|
||||
|
||||
function adjustRegexToExcludeNodeModules(originalRegex: RegExp) {
|
||||
const originalRegexString = originalRegex.source;
|
||||
const startsWithCaret = originalRegexString.startsWith('^');
|
||||
const excludeNodeModulesPattern = startsWithCaret ? '(?!.*node_modules)' : '^(?!.*node_modules)';
|
||||
|
||||
// Combine the new exclusion pattern with the original regex
|
||||
const adjustedRegexString = startsWithCaret
|
||||
? `^${excludeNodeModulesPattern}${originalRegexString.substring(1)}`
|
||||
: excludeNodeModulesPattern + originalRegexString;
|
||||
|
||||
// Create and return the new regex
|
||||
return new RegExp(adjustedRegexString);
|
||||
}
|
||||
|
||||
export function webpackIncludeRegexp(specifier: NormalizedStoriesSpecifier) {
|
||||
const { directory, files } = specifier;
|
||||
|
||||
@ -17,7 +31,9 @@ export function webpackIncludeRegexp(specifier: NormalizedStoriesSpecifier) {
|
||||
const webpackIncludeGlob = ['.', '..'].includes(directory)
|
||||
? files
|
||||
: `${directoryWithoutLeadingDots}/${files}`;
|
||||
const webpackIncludeRegexpWithCaret = globToRegexp(webpackIncludeGlob);
|
||||
const webpackIncludeRegexpWithCaret = webpackIncludeGlob.includes('node_modules')
|
||||
? globToRegexp(webpackIncludeGlob)
|
||||
: adjustRegexToExcludeNodeModules(globToRegexp(webpackIncludeGlob));
|
||||
// picomatch is creating an exact match, but we are only matching the end of the filename
|
||||
return new RegExp(webpackIncludeRegexpWithCaret.source.replace(/^\^/, ''));
|
||||
}
|
||||
|
@ -5,7 +5,8 @@ import type { EnrichCsfOptions } from '@storybook/csf-tools';
|
||||
|
||||
export type CsfPluginOptions = EnrichCsfOptions;
|
||||
|
||||
const STORIES_REGEX = /\.(story|stories)\.[tj]sx?$/;
|
||||
// Ignore node_modules
|
||||
const STORIES_REGEX = /(?<!node_modules.*)\.(story|stories)\.[tj]sx?$/;
|
||||
|
||||
const logger = console;
|
||||
|
||||
|
@ -1098,6 +1098,8 @@ describe('CsfFile', () => {
|
||||
- component-tag
|
||||
- story-tag
|
||||
- play-fn
|
||||
metaTags: &ref_0
|
||||
- component-tag
|
||||
__id: component-id--a
|
||||
- type: story
|
||||
importPath: foo/bar.stories.js
|
||||
@ -1109,6 +1111,7 @@ describe('CsfFile', () => {
|
||||
- component-tag
|
||||
- story-tag
|
||||
- play-fn
|
||||
metaTags: *ref_0
|
||||
__id: component-id--b
|
||||
`);
|
||||
});
|
||||
@ -1138,6 +1141,8 @@ describe('CsfFile', () => {
|
||||
metaId: component-id
|
||||
tags:
|
||||
- component-tag
|
||||
metaTags:
|
||||
- component-tag
|
||||
__id: custom-story-id
|
||||
`);
|
||||
});
|
||||
@ -1169,6 +1174,11 @@ describe('CsfFile', () => {
|
||||
- inherit-tag-dup
|
||||
- story-tag
|
||||
- story-tag-dup
|
||||
metaTags:
|
||||
- component-tag
|
||||
- component-tag-dup
|
||||
- component-tag-dup
|
||||
- inherit-tag-dup
|
||||
__id: custom-foo-title--a
|
||||
`);
|
||||
});
|
||||
|
@ -100,11 +100,14 @@ const parseExportsOrder = (init: t.Expression) => {
|
||||
};
|
||||
|
||||
const sortExports = (exportByName: Record<string, any>, order: string[]) => {
|
||||
return order.reduce((acc, name) => {
|
||||
return order.reduce(
|
||||
(acc, name) => {
|
||||
const namedExport = exportByName[name];
|
||||
if (namedExport) acc[name] = namedExport;
|
||||
return acc;
|
||||
}, {} as Record<string, any>);
|
||||
},
|
||||
{} as Record<string, any>
|
||||
);
|
||||
};
|
||||
|
||||
export interface CsfOptions {
|
||||
@ -490,7 +493,8 @@ export class CsfFile {
|
||||
if (self._metaAnnotations.play) {
|
||||
self._meta.tags = [...(self._meta.tags || []), 'play-fn'];
|
||||
}
|
||||
self._stories = entries.reduce((acc, [key, story]) => {
|
||||
self._stories = entries.reduce(
|
||||
(acc, [key, story]) => {
|
||||
if (!isExportStory(key, self._meta as StaticMeta)) {
|
||||
return acc;
|
||||
}
|
||||
@ -518,7 +522,9 @@ export class CsfFile {
|
||||
acc[key].tags = [...(acc[key].tags || []), 'play-fn'];
|
||||
}
|
||||
return acc;
|
||||
}, {} as Record<string, StaticStory>);
|
||||
},
|
||||
{} as Record<string, StaticStory>
|
||||
);
|
||||
|
||||
Object.keys(self._storyExports).forEach((key) => {
|
||||
if (!isExportStory(key, self._meta as StaticMeta)) {
|
||||
@ -571,6 +577,7 @@ export class CsfFile {
|
||||
title: this.meta?.title,
|
||||
metaId: this.meta?.id,
|
||||
tags,
|
||||
metaTags: this.meta?.tags,
|
||||
__id: story.id,
|
||||
};
|
||||
});
|
||||
|
@ -53,6 +53,7 @@
|
||||
"lodash": "^4.17.21"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/preset-react": "^7.23.3",
|
||||
"babel-plugin-react-docgen": "4.2.1",
|
||||
"require-from-string": "^2.0.2",
|
||||
"typescript": "^5.3.2"
|
||||
|
@ -248,12 +248,15 @@ export class Instrumenter {
|
||||
|
||||
cleanup() {
|
||||
// Reset stories with retained state to their initial state, and drop the rest.
|
||||
this.state = Object.entries(this.state).reduce((acc, [storyId, state]) => {
|
||||
this.state = Object.entries(this.state).reduce(
|
||||
(acc, [storyId, state]) => {
|
||||
const retainedState = getRetainedState(state);
|
||||
if (!retainedState) return acc;
|
||||
acc[storyId] = Object.assign(getInitialState(), retainedState);
|
||||
return acc;
|
||||
}, {} as Record<StoryId, State>);
|
||||
},
|
||||
{} as Record<StoryId, State>
|
||||
);
|
||||
const payload: SyncPayload = { controlStates: controlsDisabled, logItems: [] };
|
||||
this.channel.emit(EVENTS.SYNC, payload);
|
||||
// @ts-expect-error (TS doesn't know about this global variable)
|
||||
|
@ -69,7 +69,7 @@ export class AddonStore {
|
||||
| Addon_Types
|
||||
| Addon_TypesEnum.experimental_PAGE
|
||||
| Addon_TypesEnum.experimental_SIDEBAR_BOTTOM
|
||||
| Addon_TypesEnum.experimental_SIDEBAR_TOP
|
||||
| Addon_TypesEnum.experimental_SIDEBAR_TOP,
|
||||
>(type: T): Addon_Collection<Addon_TypesMapping[T]> | any {
|
||||
if (!this.elements[type]) {
|
||||
this.elements[type] = {};
|
||||
|
@ -92,7 +92,8 @@ export const transformSetStoriesStoryDataToPreparedStoryIndex = (
|
||||
export const transformStoryIndexV2toV3 = (index: StoryIndexV2): StoryIndexV3 => {
|
||||
return {
|
||||
v: 3,
|
||||
stories: Object.values(index.stories).reduce((acc, entry) => {
|
||||
stories: Object.values(index.stories).reduce(
|
||||
(acc, entry) => {
|
||||
acc[entry.id] = {
|
||||
...entry,
|
||||
title: entry.kind,
|
||||
@ -101,7 +102,9 @@ export const transformStoryIndexV2toV3 = (index: StoryIndexV2): StoryIndexV3 =>
|
||||
};
|
||||
|
||||
return acc;
|
||||
}, {} as StoryIndexV3['stories']),
|
||||
},
|
||||
{} as StoryIndexV3['stories']
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
@ -109,7 +112,8 @@ export const transformStoryIndexV3toV4 = (index: StoryIndexV3): API_PreparedStor
|
||||
const countByTitle = countBy(Object.values(index.stories), 'title');
|
||||
return {
|
||||
v: 4,
|
||||
entries: Object.values(index.stories).reduce((acc, entry: any) => {
|
||||
entries: Object.values(index.stories).reduce(
|
||||
(acc, entry: any) => {
|
||||
let type: IndexEntry['type'] = 'story';
|
||||
if (
|
||||
entry.parameters?.docsOnly ||
|
||||
@ -129,7 +133,9 @@ export const transformStoryIndexV3toV4 = (index: StoryIndexV3): API_PreparedStor
|
||||
delete acc[entry.id].kind;
|
||||
|
||||
return acc;
|
||||
}, {} as API_PreparedStoryIndex['entries']),
|
||||
},
|
||||
{} as API_PreparedStoryIndex['entries']
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
@ -215,11 +221,6 @@ export const transformStoryIndexToStoriesHash = (
|
||||
startCollapsed: collapsedRoots.includes(id),
|
||||
// Note that this will later get appended to the previous list of children (see below)
|
||||
children: [childId],
|
||||
|
||||
// deprecated fields
|
||||
isRoot: true,
|
||||
isComponent: false,
|
||||
isLeaf: false,
|
||||
});
|
||||
// Usually the last path/name pair will be displayed as a component,
|
||||
// *unless* there are other stories that are more deeply nested under it
|
||||
@ -240,10 +241,6 @@ export const transformStoryIndexToStoriesHash = (
|
||||
...(childId && {
|
||||
children: [childId],
|
||||
}),
|
||||
// deprecated fields
|
||||
isRoot: false,
|
||||
isComponent: true,
|
||||
isLeaf: false,
|
||||
});
|
||||
} else {
|
||||
acc[id] = merge<API_GroupEntry>((acc[id] || {}) as API_GroupEntry, {
|
||||
@ -256,10 +253,6 @@ export const transformStoryIndexToStoriesHash = (
|
||||
...(childId && {
|
||||
children: [childId],
|
||||
}),
|
||||
// deprecated fields
|
||||
isRoot: false,
|
||||
isComponent: false,
|
||||
isLeaf: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -272,12 +265,6 @@ export const transformStoryIndexToStoriesHash = (
|
||||
parent: paths[paths.length - 1],
|
||||
renderLabel,
|
||||
prepared: !!item.parameters,
|
||||
|
||||
// deprecated fields
|
||||
kind: item.title,
|
||||
isRoot: false,
|
||||
isComponent: false,
|
||||
isLeaf: true,
|
||||
} as API_DocsEntry | API_StoryEntry;
|
||||
|
||||
return acc;
|
||||
|
@ -27,7 +27,7 @@ export interface SubAPI {
|
||||
| Addon_Types
|
||||
| Addon_TypesEnum.experimental_PAGE
|
||||
| Addon_TypesEnum.experimental_SIDEBAR_BOTTOM
|
||||
| Addon_TypesEnum.experimental_SIDEBAR_TOP = Addon_Types
|
||||
| Addon_TypesEnum.experimental_SIDEBAR_TOP = Addon_Types,
|
||||
>(
|
||||
type: T
|
||||
) => Addon_Collection<Addon_TypesMapping[T]>;
|
||||
|
@ -57,7 +57,8 @@ export const init: ModuleFn = ({ fullAPI, store, provider }) => {
|
||||
function getLatestWhatsNewPost(): Promise<WhatsNewData> {
|
||||
provider.channel?.emit(REQUEST_WHATS_NEW_DATA);
|
||||
|
||||
return new Promise((resolve) =>
|
||||
return new Promise(
|
||||
(resolve) =>
|
||||
provider.channel?.once(RESULT_WHATS_NEW_DATA, ({ data }: { data: WhatsNewData }) =>
|
||||
resolve(data)
|
||||
)
|
||||
|
@ -1448,9 +1448,6 @@ describe('stories API', () => {
|
||||
],
|
||||
"depth": 0,
|
||||
"id": "a",
|
||||
"isComponent": true,
|
||||
"isLeaf": false,
|
||||
"isRoot": false,
|
||||
"name": "a",
|
||||
"parent": undefined,
|
||||
"renderLabel": undefined,
|
||||
@ -1460,10 +1457,6 @@ describe('stories API', () => {
|
||||
"depth": 1,
|
||||
"id": "a--1",
|
||||
"importPath": "./a.ts",
|
||||
"isComponent": false,
|
||||
"isLeaf": true,
|
||||
"isRoot": false,
|
||||
"kind": "a",
|
||||
"name": "1",
|
||||
"parent": "a",
|
||||
"prepared": false,
|
||||
@ -1475,10 +1468,6 @@ describe('stories API', () => {
|
||||
"depth": 1,
|
||||
"id": "a--2",
|
||||
"importPath": "./a.ts",
|
||||
"isComponent": false,
|
||||
"isLeaf": true,
|
||||
"isRoot": false,
|
||||
"kind": "a",
|
||||
"name": "2",
|
||||
"parent": "a",
|
||||
"prepared": false,
|
||||
@ -1524,9 +1513,6 @@ describe('stories API', () => {
|
||||
],
|
||||
"depth": 0,
|
||||
"id": "a",
|
||||
"isComponent": true,
|
||||
"isLeaf": false,
|
||||
"isRoot": false,
|
||||
"name": "a",
|
||||
"parent": undefined,
|
||||
"renderLabel": undefined,
|
||||
@ -1536,10 +1522,6 @@ describe('stories API', () => {
|
||||
"depth": 1,
|
||||
"id": "a--1",
|
||||
"importPath": "./a.ts",
|
||||
"isComponent": false,
|
||||
"isLeaf": true,
|
||||
"isRoot": false,
|
||||
"kind": "a",
|
||||
"name": "1",
|
||||
"parent": "a",
|
||||
"prepared": false,
|
||||
@ -1572,9 +1554,6 @@ describe('stories API', () => {
|
||||
],
|
||||
"depth": 0,
|
||||
"id": "a",
|
||||
"isComponent": true,
|
||||
"isLeaf": false,
|
||||
"isRoot": false,
|
||||
"name": "a",
|
||||
"parent": undefined,
|
||||
"renderLabel": undefined,
|
||||
@ -1584,10 +1563,6 @@ describe('stories API', () => {
|
||||
"depth": 1,
|
||||
"id": "a--1",
|
||||
"importPath": "./a.ts",
|
||||
"isComponent": false,
|
||||
"isLeaf": true,
|
||||
"isRoot": false,
|
||||
"kind": "a",
|
||||
"name": "1",
|
||||
"parent": "a",
|
||||
"prepared": false,
|
||||
@ -1599,10 +1574,6 @@ describe('stories API', () => {
|
||||
"depth": 1,
|
||||
"id": "a--2",
|
||||
"importPath": "./a.ts",
|
||||
"isComponent": false,
|
||||
"isLeaf": true,
|
||||
"isRoot": false,
|
||||
"kind": "a",
|
||||
"name": "2",
|
||||
"parent": "a",
|
||||
"prepared": false,
|
||||
|
@ -157,7 +157,6 @@ describe('initModule', () => {
|
||||
type: 'story',
|
||||
args: { a: 1, b: 2 },
|
||||
initialArgs: { a: 1, b: 1 },
|
||||
isLeaf: true,
|
||||
}),
|
||||
}),
|
||||
});
|
||||
@ -194,7 +193,7 @@ describe('initModule', () => {
|
||||
state: { location: {} },
|
||||
navigate,
|
||||
fullAPI: Object.assign(fullAPI, {
|
||||
getCurrentStoryData: () => ({ type: 'story', args: { a: 1 }, isLeaf: true }),
|
||||
getCurrentStoryData: () => ({ type: 'story', args: { a: 1 } }),
|
||||
}),
|
||||
});
|
||||
|
||||
|
1
code/lib/manager-api/src/typings.d.ts
vendored
1
code/lib/manager-api/src/typings.d.ts
vendored
@ -3,6 +3,7 @@ declare var __STORYBOOK_ADDONS_MANAGER: any;
|
||||
|
||||
declare var CONFIG_TYPE: string;
|
||||
declare var FEATURES: import('@storybook/types').StorybookConfigRaw['features'];
|
||||
declare var TAGS_OPTIONS: import('@storybook/types').StorybookConfigRaw['tags'];
|
||||
declare var REFS: any;
|
||||
declare var VERSIONCHECK: any;
|
||||
declare var LOGLEVEL: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'silent' | undefined;
|
||||
|
@ -221,14 +221,14 @@ const invalidHooksError = () =>
|
||||
|
||||
function getHooksContextOrNull<
|
||||
TRenderer extends Renderer,
|
||||
TArgs extends Args = Args
|
||||
TArgs extends Args = Args,
|
||||
>(): HooksContext<TRenderer, TArgs> | null {
|
||||
return global.STORYBOOK_HOOKS_CONTEXT || null;
|
||||
}
|
||||
|
||||
function getHooksContextOrThrow<
|
||||
TRenderer extends Renderer,
|
||||
TArgs extends Args = Args
|
||||
TArgs extends Args = Args,
|
||||
>(): HooksContext<TRenderer, TArgs> {
|
||||
const hooks = getHooksContextOrNull<TRenderer, TArgs>();
|
||||
if (hooks == null) {
|
||||
@ -530,7 +530,7 @@ export function useChannel(eventMap: EventMap, deps: any[] = []) {
|
||||
*/
|
||||
export function useStoryContext<
|
||||
TRenderer extends Renderer,
|
||||
TArgs extends Args = Args
|
||||
TArgs extends Args = Args,
|
||||
>(): StoryContext<TRenderer> {
|
||||
const { currentContext } = getHooksContextOrThrow<TRenderer, TArgs>();
|
||||
if (currentContext == null) {
|
||||
@ -576,7 +576,7 @@ export function useParameter<S>(parameterKey: string, defaultValue?: S): S | und
|
||||
export function useArgs<TArgs extends Args = Args>(): [
|
||||
TArgs,
|
||||
(newArgs: Partial<TArgs>) => void,
|
||||
(argNames?: (keyof TArgs)[]) => void
|
||||
(argNames?: (keyof TArgs)[]) => void,
|
||||
] {
|
||||
const channel = addons.getChannel();
|
||||
const { id: storyId, args } = useStoryContext<Renderer, TArgs>();
|
||||
|
@ -57,7 +57,7 @@ export const importFn: Mocked<ModuleImportFn> = vi.fn(
|
||||
'./src/ComponentTwo.stories.js': componentTwoExports,
|
||||
'./src/Introduction.mdx': unattachedDocsExports,
|
||||
'./src/ExtraComponentOne.stories.js': extraComponentOneExports,
|
||||
}[path] || {})
|
||||
})[path] || {}
|
||||
);
|
||||
|
||||
export const docsRenderer = {
|
||||
|
@ -38,7 +38,7 @@ const ansiConverter = new AnsiToHtml({
|
||||
});
|
||||
|
||||
export class WebView implements View<HTMLElement> {
|
||||
private currentLayoutClass?: typeof layoutClassMap[keyof typeof layoutClassMap] | null;
|
||||
private currentLayoutClass?: (typeof layoutClassMap)[keyof typeof layoutClassMap] | null;
|
||||
|
||||
private testing = false;
|
||||
|
||||
|
@ -12,10 +12,13 @@ import { MissingStoryAfterHmrError } from '@storybook/core-events/preview-errors
|
||||
export type StorySpecifier = StoryId | { name: StoryName; title: ComponentTitle } | '*';
|
||||
|
||||
const getImportPathMap = memoize(1)((entries: StoryIndex['entries']) =>
|
||||
Object.values(entries).reduce((acc, entry) => {
|
||||
Object.values(entries).reduce(
|
||||
(acc, entry) => {
|
||||
acc[entry.importPath] = acc[entry.importPath] || entry;
|
||||
return acc;
|
||||
}, {} as Record<Path, IndexEntry>)
|
||||
},
|
||||
{} as Record<Path, IndexEntry>
|
||||
)
|
||||
);
|
||||
|
||||
export class StoryIndexStore {
|
||||
|
@ -175,10 +175,13 @@ export class StoryStore<TRenderer extends Renderer> {
|
||||
};
|
||||
|
||||
const list = await loadInBatches(importPaths);
|
||||
return list.reduce((acc, { importPath, csfFile }) => {
|
||||
return list.reduce(
|
||||
(acc, { importPath, csfFile }) => {
|
||||
acc[importPath] = csfFile;
|
||||
return acc;
|
||||
}, {} as Record<Path, CSFFile<TRenderer>>);
|
||||
},
|
||||
{} as Record<Path, CSFFile<TRenderer>>
|
||||
);
|
||||
}
|
||||
|
||||
async cacheAllCSFFiles(): Promise<void> {
|
||||
|
@ -228,7 +228,7 @@ function preparePartialAnnotations<TRenderer extends Renderer>(
|
||||
// eg. reactive proxies set by frameworks like SolidJS or Vue
|
||||
export function prepareContext<
|
||||
TRenderer extends Renderer,
|
||||
TContext extends Pick<StoryContextForLoaders<TRenderer>, 'args' | 'argTypes' | 'globals'>
|
||||
TContext extends Pick<StoryContextForLoaders<TRenderer>, 'args' | 'argTypes' | 'globals'>,
|
||||
>(
|
||||
context: TContext
|
||||
): TContext & Pick<StoryContextForLoaders<TRenderer>, 'allArgs' | 'argsByTarget' | 'unmappedArgs'> {
|
||||
|
1
code/lib/preview-api/src/typings.d.ts
vendored
1
code/lib/preview-api/src/typings.d.ts
vendored
@ -11,6 +11,7 @@ declare var FEATURES: import('@storybook/types').StorybookConfigRaw['features'];
|
||||
|
||||
declare var STORIES: any;
|
||||
declare var DOCS_OPTIONS: any;
|
||||
declare var TAGS_OPTIONS: import('@storybook/types').StorybookConfigRaw['tags'];
|
||||
|
||||
// To enable user code to detect if it is running in Storybook
|
||||
declare var IS_STORYBOOK: boolean;
|
||||
|
@ -5,7 +5,7 @@ import { expect } from '@storybook/jest';
|
||||
|
||||
export default {
|
||||
component: globalThis.Components.Pre,
|
||||
tags: ['component-one', 'component-two'],
|
||||
tags: ['component-one', 'component-two', 'autodocs'],
|
||||
decorators: [
|
||||
(storyFn: PartialStoryFn, context: StoryContext) => {
|
||||
return storyFn({
|
||||
@ -13,6 +13,7 @@ export default {
|
||||
});
|
||||
},
|
||||
],
|
||||
parameters: { chromatic: { disable: true } },
|
||||
};
|
||||
|
||||
export const Inheritance = {
|
||||
@ -23,4 +24,17 @@ export const Inheritance = {
|
||||
tags: ['story-one', 'story-two', 'story'],
|
||||
});
|
||||
},
|
||||
parameters: { chromatic: { disable: false } },
|
||||
};
|
||||
|
||||
export const DocsOnly = {
|
||||
tags: ['docs-only'],
|
||||
};
|
||||
|
||||
export const TestOnly = {
|
||||
tags: ['test-only'],
|
||||
};
|
||||
|
||||
export const DevOnly = {
|
||||
tags: ['dev-only'],
|
||||
};
|
||||
|
@ -0,0 +1,11 @@
|
||||
import { global as globalThis } from '@storybook/global';
|
||||
|
||||
export default {
|
||||
component: globalThis.Components.Button,
|
||||
tags: ['autodocs', 'test-only'],
|
||||
parameters: { chromatic: { disable: true } },
|
||||
};
|
||||
|
||||
export const Default = {
|
||||
args: { label: 'Button' },
|
||||
};
|
@ -14,8 +14,9 @@ export interface StoryData {
|
||||
|
||||
const splitPathRegex = /\/([^/]+)\/(?:(.*)_)?([^/]+)?/;
|
||||
|
||||
export const parsePath: (path: string | undefined) => StoryData = memoize(1000)(
|
||||
(path: string | undefined | null) => {
|
||||
export const parsePath: (path: string | undefined) => StoryData = memoize(1000)((
|
||||
path: string | undefined | null
|
||||
) => {
|
||||
const result: StoryData = {
|
||||
viewMode: undefined,
|
||||
storyId: undefined,
|
||||
@ -33,8 +34,7 @@ export const parsePath: (path: string | undefined) => StoryData = memoize(1000)(
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
interface Args {
|
||||
[key: string]: any;
|
||||
@ -157,8 +157,11 @@ export const stringifyQuery = (query: Query) =>
|
||||
|
||||
type Match = { path: string };
|
||||
|
||||
export const getMatch = memoize(1000)(
|
||||
(current: string, target: string | RegExp, startsWith = true): Match | null => {
|
||||
export const getMatch = memoize(1000)((
|
||||
current: string,
|
||||
target: string | RegExp,
|
||||
startsWith = true
|
||||
): Match | null => {
|
||||
if (startsWith) {
|
||||
if (typeof target !== 'string') {
|
||||
throw new Error('startsWith only works with string targets');
|
||||
@ -179,5 +182,4 @@ export const getMatch = memoize(1000)(
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
);
|
||||
});
|
||||
|
@ -49,7 +49,7 @@
|
||||
"@storybook/types": "workspace:*",
|
||||
"estraverse": "^5.2.0",
|
||||
"lodash": "^4.17.21",
|
||||
"prettier": "^2.8.0"
|
||||
"prettier": "^3.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.3.2"
|
||||
|
@ -1,4 +1,4 @@
|
||||
import parseFlow from 'prettier/parser-flow';
|
||||
import parseFlow from 'prettier/plugins/flow';
|
||||
|
||||
function parse(source) {
|
||||
return parseFlow.parsers.flow.parse(source);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import parseJs from 'prettier/parser-babel';
|
||||
import parseJs from 'prettier/plugins/babel';
|
||||
|
||||
function parse(source) {
|
||||
try {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import parseTs from 'prettier/parser-typescript';
|
||||
import parseTs from 'prettier/plugins/typescript';
|
||||
|
||||
function parse(source) {
|
||||
try {
|
||||
|
@ -50,9 +50,8 @@
|
||||
"@testing-library/dom": "^9.3.1",
|
||||
"@testing-library/jest-dom": "^6.1.3",
|
||||
"@testing-library/user-event": "14.3.0",
|
||||
"@types/chai": "^4",
|
||||
"@vitest/expect": "^0.34.2",
|
||||
"@vitest/spy": "^0.34.1",
|
||||
"@vitest/expect": "1.1.3",
|
||||
"@vitest/spy": "^1.1.3",
|
||||
"chai": "^4.3.7",
|
||||
"util": "^0.12.4"
|
||||
},
|
||||
|
@ -2,7 +2,7 @@ import {
|
||||
spyOn,
|
||||
isMockFunction,
|
||||
fn,
|
||||
spies as mocks,
|
||||
mocks,
|
||||
type MaybeMocked,
|
||||
type MaybeMockedDeep,
|
||||
type MaybePartiallyMocked,
|
||||
|
@ -84,8 +84,7 @@ export const createReset = memoize(1)(
|
||||
})
|
||||
);
|
||||
|
||||
export const createGlobal = memoize(1)(
|
||||
({
|
||||
export const createGlobal = memoize(1)(({
|
||||
color,
|
||||
background,
|
||||
typography,
|
||||
@ -109,5 +108,4 @@ export const createGlobal = memoize(1)(
|
||||
borderTop: `1px solid ${color.border}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
);
|
||||
});
|
||||
|
@ -172,7 +172,7 @@ export type Addon_BaseDecorators<StoryFnReturnType> = Array<
|
||||
export interface Addon_BaseAnnotations<
|
||||
TArgs,
|
||||
StoryFnReturnType,
|
||||
TRenderer extends Renderer = Renderer
|
||||
TRenderer extends Renderer = Renderer,
|
||||
> {
|
||||
/**
|
||||
* Dynamic data that are provided (and possibly updated by) Storybook and its addons.
|
||||
|
@ -9,81 +9,42 @@ export interface API_BaseEntry {
|
||||
name: string;
|
||||
refId?: string;
|
||||
renderLabel?: (item: API_BaseEntry) => any;
|
||||
|
||||
/** @deprecated */
|
||||
isRoot: boolean;
|
||||
/** @deprecated */
|
||||
isComponent: boolean;
|
||||
/** @deprecated */
|
||||
isLeaf: boolean;
|
||||
}
|
||||
|
||||
export interface API_RootEntry extends API_BaseEntry {
|
||||
type: 'root';
|
||||
startCollapsed?: boolean;
|
||||
children: StoryId[];
|
||||
|
||||
/** @deprecated */
|
||||
isRoot: true;
|
||||
/** @deprecated */
|
||||
isComponent: false;
|
||||
/** @deprecated */
|
||||
isLeaf: false;
|
||||
}
|
||||
|
||||
export interface API_GroupEntry extends API_BaseEntry {
|
||||
type: 'group';
|
||||
parent?: StoryId;
|
||||
children: StoryId[];
|
||||
|
||||
/** @deprecated */
|
||||
isRoot: false;
|
||||
/** @deprecated */
|
||||
isComponent: false;
|
||||
/** @deprecated */
|
||||
isLeaf: false;
|
||||
}
|
||||
|
||||
export interface API_ComponentEntry extends API_BaseEntry {
|
||||
type: 'component';
|
||||
parent?: StoryId;
|
||||
children: StoryId[];
|
||||
|
||||
/** @deprecated */
|
||||
isRoot: false;
|
||||
/** @deprecated */
|
||||
isComponent: true;
|
||||
/** @deprecated */
|
||||
isLeaf: false;
|
||||
}
|
||||
|
||||
export interface API_DocsEntry extends API_BaseEntry {
|
||||
type: 'docs';
|
||||
parent: StoryId;
|
||||
title: ComponentTitle;
|
||||
/** @deprecated */
|
||||
kind: ComponentTitle;
|
||||
importPath: Path;
|
||||
tags: Tag[];
|
||||
prepared: boolean;
|
||||
parameters?: {
|
||||
[parameterName: string]: any;
|
||||
};
|
||||
|
||||
/** @deprecated */
|
||||
isRoot: false;
|
||||
/** @deprecated */
|
||||
isComponent: false;
|
||||
/** @deprecated */
|
||||
isLeaf: true;
|
||||
}
|
||||
|
||||
export interface API_StoryEntry extends API_BaseEntry {
|
||||
type: 'story';
|
||||
parent: StoryId;
|
||||
title: ComponentTitle;
|
||||
/** @deprecated */
|
||||
kind: ComponentTitle;
|
||||
importPath: Path;
|
||||
tags: Tag[];
|
||||
prepared: boolean;
|
||||
@ -93,13 +54,6 @@ export interface API_StoryEntry extends API_BaseEntry {
|
||||
args?: Args;
|
||||
argTypes?: ArgTypes;
|
||||
initialArgs?: Args;
|
||||
|
||||
/** @deprecated */
|
||||
isRoot: false;
|
||||
/** @deprecated */
|
||||
isComponent: false;
|
||||
/** @deprecated */
|
||||
isLeaf: true;
|
||||
}
|
||||
|
||||
export type API_LeafEntry = API_DocsEntry | API_StoryEntry;
|
||||
@ -110,15 +64,6 @@ export type API_HashEntry =
|
||||
| API_DocsEntry
|
||||
| API_StoryEntry;
|
||||
|
||||
/** @deprecated */
|
||||
export type API_Root = API_RootEntry;
|
||||
|
||||
/** @deprecated */
|
||||
export type API_Group = API_GroupEntry | API_ComponentEntry;
|
||||
|
||||
/** @deprecated */
|
||||
export type API_Story = API_LeafEntry;
|
||||
|
||||
/**
|
||||
* The `IndexHash` is our manager-side representation of the `StoryIndex`.
|
||||
* We create entries in the hash not only for each story or docs entry, but
|
||||
|
@ -53,7 +53,7 @@ export type PartialArgsStoryFn<TRenderer extends Renderer = Renderer, TArgs = Ar
|
||||
*/
|
||||
export type ComposedStoryFn<
|
||||
TRenderer extends Renderer = Renderer,
|
||||
TArgs = Args
|
||||
TArgs = Args,
|
||||
> = PartialArgsStoryFn<TRenderer, TArgs> & {
|
||||
play: ComposedStoryPlayFn<TRenderer, TArgs>;
|
||||
args: TArgs;
|
||||
|
@ -326,6 +326,15 @@ export interface TestBuildConfig {
|
||||
test?: TestBuildFlags;
|
||||
}
|
||||
|
||||
type Tag = string;
|
||||
|
||||
export interface TagOptions {
|
||||
excludeFromSidebar: boolean;
|
||||
excludeFromDocsStories: boolean;
|
||||
}
|
||||
|
||||
export type TagsOptions = Record<Tag, Partial<TagOptions>>;
|
||||
|
||||
/**
|
||||
* The interface for Storybook configuration used internally in presets
|
||||
* The difference is that these values are the raw values, AKA, not wrapped with `PresetValue<>`
|
||||
@ -404,6 +413,8 @@ export interface StorybookConfigRaw {
|
||||
previewMainTemplate?: string;
|
||||
|
||||
managerHead?: string;
|
||||
|
||||
tags?: TagsOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -518,6 +529,11 @@ export interface StorybookConfig {
|
||||
* which is the existing head content, and return a modified string.
|
||||
*/
|
||||
managerHead?: PresetValue<StorybookConfigRaw['managerHead']>;
|
||||
|
||||
/**
|
||||
* Configure non-standard tag behaviors
|
||||
*/
|
||||
tags?: PresetValue<StorybookConfigRaw['tags']>;
|
||||
}
|
||||
|
||||
export type PresetValue<T> = T | ((config: T, options: Options) => T | Promise<T>);
|
||||
|
@ -32,7 +32,7 @@ export type ResolvedModuleExportType = 'component' | 'meta' | 'story';
|
||||
*/
|
||||
export type ResolvedModuleExportFromType<
|
||||
TType extends ResolvedModuleExportType,
|
||||
TRenderer extends Renderer = Renderer
|
||||
TRenderer extends Renderer = Renderer,
|
||||
> = TType extends 'component'
|
||||
? {
|
||||
type: 'component';
|
||||
|
@ -107,6 +107,8 @@ export type BaseIndexInput = {
|
||||
metaId?: MetaId;
|
||||
/** Tags for filtering entries in Storybook and its tools. */
|
||||
tags?: Tag[];
|
||||
/** Tags from the meta for filtering entries in Storybook and its tools. */
|
||||
metaTags?: Tag[];
|
||||
/**
|
||||
* The id of the entry, auto-generated from {@link title}/{@link metaId} and {@link exportName} if unspecified.
|
||||
* If specified, the story in the CSF file _must_ have a matching id set at `parameters.__id`, to be correctly matched.
|
||||
|
@ -80,9 +80,10 @@
|
||||
"@typescript-eslint/eslint-plugin": "^5.45.0",
|
||||
"@typescript-eslint/experimental-utils": "^5.45.0",
|
||||
"@typescript-eslint/parser": "^5.45.0",
|
||||
"@vitest/expect@^0.34.2": "patch:@vitest/expect@npm%3A0.34.5#./.yarn/patches/@vitest-expect-npm-0.34.5-8031508efe.patch",
|
||||
"@vitest/expect@1.1.3": "patch:@vitest/expect@npm%3A1.1.3#~/.yarn/patches/@vitest-expect-npm-1.1.3-2062bf533f.patch",
|
||||
"esbuild": "^0.18.0",
|
||||
"eslint": "^8.28.0",
|
||||
"eslint-plugin-prettier": "^5.1.2",
|
||||
"playwright": "1.36.0",
|
||||
"playwright-core": "1.36.0",
|
||||
"serialize-javascript": "^3.1.0",
|
||||
@ -199,6 +200,7 @@
|
||||
"eslint-import-resolver-typescript": "^3.5.2",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-local-rules": "portal:../scripts/eslint-plugin-local-rules",
|
||||
"eslint-plugin-prettier": "^5.1.2",
|
||||
"eslint-plugin-react": "^7.31.10",
|
||||
"eslint-plugin-storybook": "^0.6.15",
|
||||
"fs-extra": "^11.1.0",
|
||||
@ -211,7 +213,7 @@
|
||||
"mock-require": "^3.0.3",
|
||||
"node-gyp": "^9.3.1",
|
||||
"nx": "17.0.2",
|
||||
"prettier": "2.8.0",
|
||||
"prettier": "^3.1.1",
|
||||
"process": "^0.11.10",
|
||||
"raf": "^3.4.1",
|
||||
"react": "^18.2.0",
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user