From 71989a3c31f21d64f3428417b4cc48eaadf3e50d Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Fri, 5 Jul 2024 13:54:51 +0200 Subject: [PATCH 01/12] Proposal --- code/core/src/__tests/storybook-error.test.ts | 24 ++++---- code/core/src/preview-errors.ts | 54 ++++++++---------- code/core/src/storybook-error.ts | 56 ++++++++++++------- 3 files changed, 71 insertions(+), 63 deletions(-) diff --git a/code/core/src/__tests/storybook-error.test.ts b/code/core/src/__tests/storybook-error.test.ts index 8a0c5c81f2af..10803f21d062 100644 --- a/code/core/src/__tests/storybook-error.test.ts +++ b/code/core/src/__tests/storybook-error.test.ts @@ -3,12 +3,13 @@ import { StorybookError } from '../storybook-error'; describe('StorybookError', () => { class TestError extends StorybookError { - category = 'TEST_CATEGORY'; - - code = 123; - - template() { - return 'This is a test error.'; + constructor(documentation?: StorybookError['documentation']) { + super({ + category: 'TEST_CATEGORY', + code: 123, + template: 'This is a test error.', + documentation, + }); } } @@ -24,16 +25,14 @@ describe('StorybookError', () => { }); it('should generate the correct message with internal documentation link', () => { - const error = new TestError(); - error.documentation = true; + const error = new TestError(true); const expectedMessage = 'This is a test error.\n\nMore info: https://storybook.js.org/error/SB_TEST_CATEGORY_0123\n'; expect(error.message).toBe(expectedMessage); }); it('should generate the correct message with external documentation link', () => { - const error = new TestError(); - error.documentation = 'https://example.com/docs/test-error'; + const error = new TestError('https://example.com/docs/test-error'); expect(error.message).toMatchInlineSnapshot(` "This is a test error. @@ -43,11 +42,10 @@ describe('StorybookError', () => { }); it('should generate the correct message with multiple external documentation links', () => { - const error = new TestError(); - error.documentation = [ + const error = new TestError([ 'https://example.com/docs/first-error', 'https://example.com/docs/second-error', - ]; + ]); expect(error.message).toMatchInlineSnapshot(` "This is a test error. diff --git a/code/core/src/preview-errors.ts b/code/core/src/preview-errors.ts index cbce06278ff3..894a5d704f24 100644 --- a/code/core/src/preview-errors.ts +++ b/code/core/src/preview-errors.ts @@ -352,42 +352,34 @@ export class NextJsSharpError extends StorybookError { } export class NextjsRouterMocksNotAvailable extends StorybookError { - readonly category = Category.FRAMEWORK_NEXTJS; - - readonly code = 2; - constructor(public data: { importType: string }) { - super(); - } - - template() { - return dedent` - Tried to access router mocks from "${this.data.importType}" but they were not created yet. You might be running code in an unsupported environment. - `; + super({ + category: Category.FRAMEWORK_NEXTJS, + code: 2, + template: dedent` + Tried to access router mocks from "${data.importType}" but they were not created yet. You might be running code in an unsupported environment. + `, + }); } } export class UnknownArgTypesError extends StorybookError { - readonly category = Category.DOCS_TOOLS; - - readonly code = 1; - - readonly documentation = 'https://github.com/storybookjs/storybook/issues/26606'; - constructor(public data: { type: object; language: string }) { - super(); - } - - template() { - return dedent`There was a failure when generating detailed ArgTypes in ${ - this.data.language - } for: - - ${JSON.stringify(this.data.type, null, 2)} - - Storybook will fall back to use a generic type description instead. - - This type is either not supported or it is a bug in the docgen generation in Storybook. - If you think this is a bug, please detail it as much as possible in the Github issue.`; + super({ + category: Category.DOCS_TOOLS, + code: 1, + documentation: 'https://github.com/storybookjs/storybook/issues/26606', + template: dedent` + There was a failure when generating detailed ArgTypes in ${data.language} for: + ${JSON.stringify(data.type, null, 2)} + + Storybook will fall back to use a generic type description instead. + + This type is either not supported or it is a bug in the docgen generation in Storybook. + If you think this is a bug, please detail it as much as possible in the Github issue. + + More info: https://github.com/storybookjs/storybook/issues/26606 + `, + }); } } diff --git a/code/core/src/storybook-error.ts b/code/core/src/storybook-error.ts index 393b0f424f80..cc9d46ad802a 100644 --- a/code/core/src/storybook-error.ts +++ b/code/core/src/storybook-error.ts @@ -2,18 +2,12 @@ export abstract class StorybookError extends Error { /** * Category of the error. Used to classify the type of error, e.g., 'PREVIEW_API'. */ - abstract readonly category: string; + public readonly category: string; /** * Code representing the error. Used to uniquely identify the error, e.g., 1. */ - abstract readonly code: number; - - /** - * A properly written error message template for this error. - * @see https://github.com/storybookjs/storybook/blob/next/code/lib/core-events/src/errors/README.md#how-to-write-a-proper-error-message - */ - abstract template(): string; + public readonly code: number; /** * Data associated with the error. Used to provide additional information in the error message or to be passed to telemetry. @@ -26,7 +20,7 @@ export abstract class StorybookError extends Error { * - If a string, uses the provided URL for documentation (external or FAQ links). * - If `false` (default), no documentation link is added. */ - public documentation: boolean | string | string[] = false; + public documentation: boolean | string | string[]; /** * Flag used to easily determine if the error originates from Storybook. @@ -34,8 +28,7 @@ export abstract class StorybookError extends Error { readonly fromStorybook: true = true as const; get fullErrorCode() { - const paddedCode = String(this.code).padStart(4, '0'); - return `SB_${this.category}_${paddedCode}` as `SB_${this['category']}_${string}`; + return fullErrorCode({ code: this.code, category: this.category }); } /** @@ -47,20 +40,45 @@ export abstract class StorybookError extends Error { return `${this.fullErrorCode} (${errorName})`; } + constructor(props: { + category: string; + code: number; + template: string; + documentation?: boolean | string | string[]; + }) { + super(StorybookError.message(props)); + this.category = props.category; + this.documentation = props.documentation ?? false; + this.code = props.code; + } + /** * Generates the error message along with additional documentation link (if applicable). */ - get message() { + static message({ + documentation, + code, + category, + template, + }: ConstructorParameters[0]) { let page: string | undefined; - if (this.documentation === true) { - page = `https://storybook.js.org/error/${this.fullErrorCode}`; - } else if (typeof this.documentation === 'string') { - page = this.documentation; - } else if (Array.isArray(this.documentation)) { - page = `\n${this.documentation.map((doc) => `\t- ${doc}`).join('\n')}`; + if (documentation === true) { + page = `https://storybook.js.org/error/${fullErrorCode({ code, category })}`; + } else if (typeof documentation === 'string') { + page = documentation; + } else if (Array.isArray(documentation)) { + page = `\n${documentation.map((doc) => `\t- ${doc}`).join('\n')}`; } - return `${this.template()}${page != null ? `\n\nMore info: ${page}\n` : ''}`; + return `${template}${page != null ? `\n\nMore info: ${page}\n` : ''}`; } } + +function fullErrorCode({ + code, + category, +}: Pick): `SB_${typeof category}_${string}` { + const paddedCode = String(code).padStart(4, '0'); + return `SB_${category}_${paddedCode}`; +} From 0bd760a00e7260853c8bdef2adf79a6f22369d79 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 5 Jul 2024 18:02:25 +0200 Subject: [PATCH 02/12] migrate all errors to the new format --- code/core/src/__tests/preview-errors.test.ts | 1 - code/core/src/manager-errors.ts | 26 +- code/core/src/preview-errors.ts | 434 ++++++------ code/core/src/server-errors.ts | 683 ++++++++----------- code/core/src/storybook-error.ts | 30 +- 5 files changed, 525 insertions(+), 649 deletions(-) diff --git a/code/core/src/__tests/preview-errors.test.ts b/code/core/src/__tests/preview-errors.test.ts index bed672437ce9..ae573d0f4eaf 100644 --- a/code/core/src/__tests/preview-errors.test.ts +++ b/code/core/src/__tests/preview-errors.test.ts @@ -11,7 +11,6 @@ describe('UnknownFlowArgTypesError', () => { const typeError = new UnknownArgTypesError({ type, language: 'Typescript' }); expect(typeError.message).toMatchInlineSnapshot(` "There was a failure when generating detailed ArgTypes in Typescript for: - { "name": "signature", "raw": "SomeType['someProperty']" diff --git a/code/core/src/manager-errors.ts b/code/core/src/manager-errors.ts index a025b708e0a3..6c90caa5bb1f 100644 --- a/code/core/src/manager-errors.ts +++ b/code/core/src/manager-errors.ts @@ -21,30 +21,26 @@ export enum Category { } export class ProviderDoesNotExtendBaseProviderError extends StorybookError { - readonly category = Category.MANAGER_UI; - - readonly code = 1; - - template() { - return `The Provider passed into Storybook's UI is not extended from the base Provider. Please check your Provider implementation.`; + constructor() { + super({ + category: Category.MANAGER_UI, + code: 1, + template: `The Provider passed into Storybook's UI is not extended from the base Provider. Please check your Provider implementation.`, + }); } } export class UncaughtManagerError extends StorybookError { - readonly category = Category.MANAGER_UNCAUGHT; - - readonly code = 1; - constructor( public data: { error: Error; } ) { - super(data.error.message); + super({ + category: Category.MANAGER_UNCAUGHT, + code: 1, + template: data.error.message, + }); this.stack = data.error.stack; } - - template() { - return this.message; - } } diff --git a/code/core/src/preview-errors.ts b/code/core/src/preview-errors.ts index 894a5d704f24..c082f8fb1162 100644 --- a/code/core/src/preview-errors.ts +++ b/code/core/src/preview-errors.ts @@ -33,321 +33,279 @@ export enum Category { } export class MissingStoryAfterHmrError extends StorybookError { - readonly category = Category.PREVIEW_API; - - readonly code = 1; - constructor(public data: { storyId: string }) { - super(); - } - - template() { - return dedent` - Couldn't find story matching id '${this.data.storyId}' after HMR. - - Did you just rename a story? - - Did you remove it from your CSF file? - - Are you sure a story with the id '${this.data.storyId}' exists? - - Please check the values in the stories field of your main.js config and see if they would match your CSF File. - - Also check the browser console and terminal for potential error messages.`; + super({ + category: Category.PREVIEW_API, + code: 1, + template: dedent` + Couldn't find story matching id '${data.storyId}' after HMR. + - Did you just rename a story? + - Did you remove it from your CSF file? + - Are you sure a story with the id '${data.storyId}' exists? + - Please check the values in the stories field of your main.js config and see if they would match your CSF File. + - Also check the browser console and terminal for potential error messages.`, + }); } } export class ImplicitActionsDuringRendering extends StorybookError { - readonly category = Category.PREVIEW_API; - - readonly code = 2; - - readonly documentation = - 'https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#using-implicit-actions-during-rendering-is-deprecated-for-example-in-the-play-function'; - constructor(public data: { phase: string; name: string; deprecated: boolean }) { - super(); - } - - template() { - return dedent` - We detected that you use an implicit action arg while ${this.data.phase} of your story. - ${this.data.deprecated ? `\nThis is deprecated and won't work in Storybook 8 anymore.\n` : ``} - Please provide an explicit spy to your args like this: - import { fn } from '@storybook/test'; - ... - args: { - ${this.data.name}: fn() - } - `; + super({ + category: Category.PREVIEW_API, + code: 2, + documentation: + 'https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#using-implicit-actions-during-rendering-is-deprecated-for-example-in-the-play-function', + template: dedent` + We detected that you use an implicit action arg while ${data.phase} of your story. + ${data.deprecated ? `\nThis is deprecated and won't work in Storybook 8 anymore.\n` : ``} + Please provide an explicit spy to your args like this: + import { fn } from '@storybook/test'; + ... + args: { + ${data.name}: fn() + } + `, + }); } } export class CalledExtractOnStoreError extends StorybookError { - readonly category = Category.PREVIEW_API; - - readonly code = 3; - - template() { - return dedent` - Cannot call \`storyStore.extract()\` without calling \`storyStore.cacheAllCsfFiles()\` first. + constructor() { + super({ + category: Category.PREVIEW_API, + code: 3, + template: dedent` + Cannot call \`storyStore.extract()\` without calling \`storyStore.cacheAllCsfFiles()\` first. - You probably meant to call \`await preview.extract()\` which does the above for you.`; + You probably meant to call \`await preview.extract()\` which does the above for you.`, + }); } } export class MissingRenderToCanvasError extends StorybookError { - readonly category = Category.PREVIEW_API; - - readonly code = 4; - - readonly documentation = - 'https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#mainjs-framework-field'; - - template() { - return dedent` - Expected your framework's preset to export a \`renderToCanvas\` field. + constructor() { + super({ + category: Category.PREVIEW_API, + code: 4, + template: dedent` + Expected your framework's preset to export a \`renderToCanvas\` field. - Perhaps it needs to be upgraded for Storybook 6.4?`; + Perhaps it needs to be upgraded for Storybook 6.4?`, + documentation: + 'https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#mainjs-framework-field', + }); } } export class CalledPreviewMethodBeforeInitializationError extends StorybookError { - readonly category = Category.PREVIEW_API; - - readonly code = 5; - constructor(public data: { methodName: string }) { - super(); - } - - template() { - return dedent` - Called \`Preview.${this.data.methodName}()\` before initialization. - - The preview needs to load the story index before most methods can be called. If you want - to call \`${this.data.methodName}\`, try \`await preview.initializationPromise;\` first. - - If you didn't call the above code, then likely it was called by an addon that needs to - do the above.`; + super({ + category: Category.PREVIEW_API, + code: 5, + template: dedent` + Called \`Preview.${data.methodName}()\` before initialization. + + The preview needs to load the story index before most methods can be called. If you want + to call \`${data.methodName}\`, try \`await preview.initializationPromise;\` first. + + If you didn't call the above code, then likely it was called by an addon that needs to + do the above.`, + }); } } export class StoryIndexFetchError extends StorybookError { - readonly category = Category.PREVIEW_API; - - readonly code = 6; - constructor(public data: { text: string }) { - super(); - } - - template() { - return dedent` - Error fetching \`/index.json\`: - - ${this.data.text} + super({ + category: Category.PREVIEW_API, + code: 6, + template: dedent` + Error fetching \`/index.json\`: + + ${data.text} - If you are in development, this likely indicates a problem with your Storybook process, - check the terminal for errors. + If you are in development, this likely indicates a problem with your Storybook process, + check the terminal for errors. - If you are in a deployed Storybook, there may have been an issue deploying the full Storybook - build.`; + If you are in a deployed Storybook, there may have been an issue deploying the full Storybook + build.`, + }); } } export class MdxFileWithNoCsfReferencesError extends StorybookError { - readonly category = Category.PREVIEW_API; - - readonly code = 7; - constructor(public data: { storyId: string }) { - super(); - } - - template() { - return dedent` - Tried to render docs entry ${this.data.storyId} but it is a MDX file that has no CSF - references, or autodocs for a CSF file that some doesn't refer to itself. - - This likely is an internal error in Storybook's indexing, or you've attached the - \`attached-mdx\` tag to an MDX file that is not attached.`; + super({ + category: Category.PREVIEW_API, + code: 7, + template: dedent` + Tried to render docs entry ${data.storyId} but it is a MDX file that has no CSF + references, or autodocs for a CSF file that some doesn't refer to itself. + + This likely is an internal error in Storybook's indexing, or you've attached the + \`attached-mdx\` tag to an MDX file that is not attached.`, + }); } } export class EmptyIndexError extends StorybookError { - readonly category = Category.PREVIEW_API; - - readonly code = 8; - - template() { - return dedent` - Couldn't find any stories in your Storybook. + constructor() { + super({ + category: Category.PREVIEW_API, + code: 8, + template: dedent` + Couldn't find any stories in your Storybook. - Please check your stories field of your main.js config: does it match correctly? - - Also check the browser console and terminal for error messages.`; + - Also check the browser console and terminal for error messages.`, + }); } } export class NoStoryMatchError extends StorybookError { - readonly category = Category.PREVIEW_API; - - readonly code = 9; - constructor(public data: { storySpecifier: string }) { - super(); - } - - template() { - return dedent` - Couldn't find story matching '${this.data.storySpecifier}'. + super({ + category: Category.PREVIEW_API, + code: 9, + template: dedent` + Couldn't find story matching '${data.storySpecifier}'. - Are you sure a story with that id exists? - Please check your stories field of your main.js config. - - Also check the browser console and terminal for error messages.`; + - Also check the browser console and terminal for error messages.`, + }); } } export class MissingStoryFromCsfFileError extends StorybookError { - readonly category = Category.PREVIEW_API; - - readonly code = 10; - constructor(public data: { storyId: string }) { - super(); - } - - template() { - return dedent` - Couldn't find story matching id '${this.data.storyId}' after importing a CSF file. + super({ + category: Category.PREVIEW_API, + code: 10, + template: dedent` + Couldn't find story matching id '${data.storyId}' after importing a CSF file. - The file was indexed as if the story was there, but then after importing the file in the browser - we didn't find the story. Possible reasons: - - You are using a custom story indexer that is misbehaving. - - You have a custom file loader that is removing or renaming exports. + The file was indexed as if the story was there, but then after importing the file in the browser + we didn't find the story. Possible reasons: + - You are using a custom story indexer that is misbehaving. + - You have a custom file loader that is removing or renaming exports. - Please check your browser console and terminal for errors that may explain the issue.`; + Please check your browser console and terminal for errors that may explain the issue.`, + }); } } export class StoryStoreAccessedBeforeInitializationError extends StorybookError { - readonly category = Category.PREVIEW_API; - - readonly code = 11; - - template() { - return dedent` - Cannot access the Story Store until the index is ready. + constructor() { + super({ + category: Category.PREVIEW_API, + code: 11, + template: dedent` + Cannot access the Story Store until the index is ready. - It is not recommended to use methods directly on the Story Store anyway, in Storybook 9 we will - remove access to the store entirely`; + It is not recommended to use methods directly on the Story Store anyway, in Storybook 9 we will + remove access to the store entirely`, + }); } } export class MountMustBeDestructuredError extends StorybookError { - readonly category = Category.PREVIEW_API; - - readonly code = 12; - constructor(public data: { playFunction: string }) { - super(); - } - - template() { - return dedent` - To use mount in the play function, you must use object destructuring, e.g. play: ({ mount }) => {}. - - Instead received: - ${this.data.playFunction} - `; + super({ + category: Category.PREVIEW_API, + code: 12, + template: dedent` + To use mount in the play function, you must use object destructuring, e.g. play: ({ mount }) => {}. + + Instead received: + ${data.playFunction} + `, + }); } } export class TestingLibraryMustBeConfiguredError extends StorybookError { - readonly category = Category.PREVIEW_API; - - readonly code = 13; - - template() { - return dedent` - You must configure testingLibraryRender to use play in portable stories. - - import { render } from '@testing-library/[renderer]'; - - setProjectAnnotations({ - testingLibraryRender: render, - }); - - For other testing renderers, you can configure renderToCanvas: - - import { render } from 'your-renderer'; - - setProjectAnnotations({ - renderToCanvas: ({ storyFn }) => { - const Story = storyFn(); + constructor(public data: { importType: string }) { + super({ + category: Category.PREVIEW_API, + code: 13, + template: dedent` + You must configure testingLibraryRender to use play in portable stories. + + import { render } from '@testing-library/[renderer]'; + + setProjectAnnotations({ + testingLibraryRender: render, + }); - // Svelte - render(Story.Component, Story.props); + For other testing renderers, you can configure \`renderToCanvas\` like so: - // Vue - render(Story); + import { render } from 'your-test-renderer'; - // or for React - render(); - }, + setProjectAnnotations({ + renderToCanvas: ({ storyFn }) => { + const Story = storyFn(); + + // Svelte + render(Story.Component, Story.props); + + // Vue + render(Story); + + // or for React + render(); + }, + }); + + `, }); - - `; } } export class NoRenderFunctionError extends StorybookError { - readonly category = Category.PREVIEW_API; - - readonly code = 14; - constructor(public data: { id: string }) { - super(); - } - - template() { - return dedent` - No render function available for storyId '${this.data.id}' - `; + super({ + category: Category.PREVIEW_API, + code: 14, + template: dedent` + No render function available for storyId '${data.id}' + `, + }); } } export class NoStoryMountedError extends StorybookError { - readonly category = Category.PREVIEW_API; - - readonly code = 15; - - template() { - return dedent` - No story is mounted in your story. - - This usually occurs when you destructure mount in the play function, but forgot to call it. - - For example: - - async play({ mount, canvasElement }) { - // 👈mount should be called: await mount(); - const canvas = within(canvasElement); - const button = await canvas.findByRole('button'); - await userEvent.click(button); - }; - `; + constructor(public data: { mountFunction: string }) { + super({ + category: Category.PREVIEW_API, + code: 15, + template: dedent` + No story is mounted in your story. + + This usually occurs when you destructure mount in the play function, but forgot to call it. + + For example: + + async play({ mount, canvasElement }) { + // 👈mount should be called: await mount(); + const canvas = within(canvasElement); + const button = await canvas.findByRole('button'); + await userEvent.click(button); + }; + `, + }); } } export class NextJsSharpError extends StorybookError { - readonly category = Category.FRAMEWORK_NEXTJS; - - readonly code = 1; - - readonly documentation = 'https://storybook.js.org/docs/get-started/nextjs#faq'; - - template() { - return dedent` - You are importing avif images, but you don't have sharp installed. - - You have to install sharp in order to use image optimization features in Next.js. - `; + constructor(public data: { importType: string }) { + super({ + category: Category.FRAMEWORK_NEXTJS, + code: 1, + template: dedent` + Tried to access sharp from "${data.importType}" but it was not available. You might be missing the required dependencies. + `, + }); } } @@ -370,15 +328,13 @@ export class UnknownArgTypesError extends StorybookError { code: 1, documentation: 'https://github.com/storybookjs/storybook/issues/26606', template: dedent` - There was a failure when generating detailed ArgTypes in ${data.language} for: - ${JSON.stringify(data.type, null, 2)} - - Storybook will fall back to use a generic type description instead. - - This type is either not supported or it is a bug in the docgen generation in Storybook. - If you think this is a bug, please detail it as much as possible in the Github issue. - - More info: https://github.com/storybookjs/storybook/issues/26606 + There was a failure when generating detailed ArgTypes in ${data.language} for: + ${JSON.stringify(data.type, null, 2)} + + Storybook will fall back to use a generic type description instead. + + This type is either not supported or it is a bug in the docgen generation in Storybook. + If you think this is a bug, please detail it as much as possible in the Github issue. `, }); } diff --git a/code/core/src/server-errors.ts b/code/core/src/server-errors.ts index 0c64ca405bb2..eb650c38a801 100644 --- a/code/core/src/server-errors.ts +++ b/code/core/src/server-errors.ts @@ -51,150 +51,131 @@ export enum Category { } export class NxProjectDetectedError extends StorybookError { - readonly category = Category.CLI_INIT; - - readonly code = 1; - - public readonly documentation = 'https://nx.dev/packages/storybook'; - - template() { - return dedent` - We have detected Nx in your project. Nx has its own Storybook initializer, so please use it instead. - Run "nx g @nx/storybook:configuration" to add Storybook to your project. - `; + constructor() { + super({ + category: Category.CLI_INIT, + code: 1, + documentation: 'https://nx.dev/packages/storybook', + template: dedent` + We have detected Nx in your project. Nx has its own Storybook initializer, so please use it instead. + Run "nx g @nx/storybook:configuration" to add Storybook to your project. + `, + }); } } export class MissingFrameworkFieldError extends StorybookError { - readonly category = Category.CORE_COMMON; - - readonly code = 1; - - public readonly documentation = - 'https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#new-framework-api'; - - template() { - return dedent` - Could not find a 'framework' field in Storybook config. - - Please run 'npx storybook automigrate' to automatically fix your config. - `; + constructor() { + super({ + category: Category.CORE_COMMON, + code: 1, + documentation: + 'https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#new-framework-api', + template: dedent` + Could not find a 'framework' field in Storybook config. + + Please run 'npx storybook automigrate' to automatically fix your config. + `, + }); } } export class InvalidFrameworkNameError extends StorybookError { - readonly category = Category.CORE_COMMON; - - readonly code = 2; - - public readonly documentation = - 'https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#new-framework-api'; - constructor(public data: { frameworkName: string }) { - super(); - } - - template() { - return dedent` - Invalid value of '${this.data.frameworkName}' in the 'framework' field of Storybook config. - - Please run 'npx storybook automigrate' to automatically fix your config. - `; + super({ + category: Category.CORE_COMMON, + code: 2, + documentation: + 'https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#new-framework-api', + template: dedent` + Invalid value of '${data.frameworkName}' in the 'framework' field of Storybook config. + + Please run 'npx storybook automigrate' to automatically fix your config. + `, + }); } } export class CouldNotEvaluateFrameworkError extends StorybookError { - readonly category = Category.CORE_COMMON; - - readonly code = 3; - constructor(public data: { frameworkName: string }) { - super(); - } - - template() { - return dedent` - Could not evaluate the '${this.data.frameworkName}' package from the 'framework' field of Storybook config. - - Are you sure it's a valid package and is installed? - `; + super({ + category: Category.CORE_COMMON, + code: 3, + documentation: '', + template: dedent` + Could not evaluate the '${data.frameworkName}' package from the 'framework' field of Storybook config. + + Are you sure it's a valid package and is installed? + `, + }); } } // this error is not used anymore, but we keep it to maintain unique its error code // which is used for telemetry export class ConflictingStaticDirConfigError extends StorybookError { - readonly category = Category.CORE_SERVER; - - readonly code = 1; - - public readonly documentation = - 'https://storybook.js.org/docs/react/configure/images-and-assets#serving-static-files-via-storybook-configuration'; - - template() { - return dedent` - Storybook encountered a conflict when trying to serve statics. You have configured both: - * Storybook's option in the config file: 'staticDirs' - * Storybook's (deprecated) CLI flag: '--staticDir' or '-s' - - Please remove the CLI flag from your storybook script and use only the 'staticDirs' option instead. - `; + constructor() { + super({ + category: Category.CORE_SERVER, + code: 1, + documentation: + 'https://storybook.js.org/docs/react/configure/images-and-assets#serving-static-files-via-storybook-configuration', + template: dedent` + Storybook encountered a conflict when trying to serve statics. You have configured both: + * Storybook's option in the config file: 'staticDirs' + * Storybook's (deprecated) CLI flag: '--staticDir' or '-s' + + Please remove the CLI flag from your storybook script and use only the 'staticDirs' option instead. + `, + }); } } -export class InvalidStoriesEntryError extends StorybookError { - readonly category = Category.CORE_COMMON; - readonly code = 4; - - public readonly documentation = - 'https://storybook.js.org/docs/react/faq#can-i-have-a-storybook-with-no-local-stories'; - - template() { - return dedent` - Storybook could not index your stories. - Your main configuration somehow does not contain a 'stories' field, or it resolved to an empty array. - - Please check your main configuration file and make sure it exports a 'stories' field that is not an empty array. - `; +export class InvalidStoriesEntryError extends StorybookError { + constructor() { + super({ + category: Category.CORE_COMMON, + code: 4, + documentation: + 'https://storybook.js.org/docs/react/faq#can-i-have-a-storybook-with-no-local-stories', + template: dedent` + Storybook could not index your stories. + Your main configuration somehow does not contain a 'stories' field, or it resolved to an empty array. + + Please check your main configuration file and make sure it exports a 'stories' field that is not an empty array. + `, + }); } } export class WebpackMissingStatsError extends StorybookError { - readonly category = Category.BUILDER_WEBPACK5; - - readonly code = 1; - - public documentation = [ - 'https://webpack.js.org/configuration/stats/', - 'https://storybook.js.org/docs/react/builders/webpack#configure', - ]; - - template() { - return dedent` - No Webpack stats found. Did you turn off stats reporting in your webpack config? - Storybook needs Webpack stats (including errors) in order to build correctly. - `; + constructor() { + super({ + category: Category.BUILDER_WEBPACK5, + code: 1, + documentation: [ + 'https://webpack.js.org/configuration/stats/', + 'https://storybook.js.org/docs/react/builders/webpack#configure', + ], + template: dedent` + No Webpack stats found. Did you turn off stats reporting in your Webpack config? + Storybook needs Webpack stats (including errors) in order to build correctly. + `, + }); } } export class WebpackInvocationError extends StorybookError { - readonly category = Category.BUILDER_WEBPACK5; - - readonly code = 2; - - private errorMessage = ''; - constructor( public data: { error: Error; } ) { - super(); - this.errorMessage = data.error.message; - } - - template() { - return this.errorMessage.trim(); + super({ + category: Category.BUILDER_WEBPACK5, + code: 2, + template: data.error.message.trim(), + }); } } @@ -203,10 +184,6 @@ function removeAnsiEscapeCodes(input = '') { } export class WebpackCompilationError extends StorybookError { - readonly category = Category.BUILDER_WEBPACK5; - - readonly code = 3; - constructor( public data: { errors: { @@ -216,9 +193,7 @@ export class WebpackCompilationError extends StorybookError { }[]; } ) { - super(); - - this.data.errors = data.errors.map((err) => { + data.errors = data.errors.map((err) => { return { ...err, message: removeAnsiEscapeCodes(err.message), @@ -226,206 +201,173 @@ export class WebpackCompilationError extends StorybookError { name: err.name, }; }); - } - template() { - // This error message is a followup of errors logged by Webpack to the user - return dedent` - There were problems when compiling your code with Webpack. - Run Storybook with --debug-webpack for more information. - `; + super({ + category: Category.BUILDER_WEBPACK5, + code: 3, + // This error message is a followup of errors logged by Webpack to the user + template: dedent` + There were problems when compiling your code with Webpack. + Run Storybook with --debug-webpack for more information. + `, + }); } } export class MissingAngularJsonError extends StorybookError { - readonly category = Category.CLI_INIT; - - readonly code = 2; - - public readonly documentation = - 'https://storybook.js.org/docs/angular/faq#error-no-angularjson-file-found'; - constructor( public data: { path: string; } ) { - super(); - } - - template() { - return dedent` - An angular.json file was not found in the current working directory: ${this.data.path} - Storybook needs it to work properly, so please rerun the command at the root of your project, where the angular.json file is located. - `; + super({ + category: Category.CLI_INIT, + code: 2, + documentation: 'https://storybook.js.org/docs/angular/faq#error-no-angularjson-file-found', + template: dedent` + An angular.json file was not found in the current working directory: ${data.path} + Storybook needs it to work properly, so please rerun the command at the root of your project, where the angular.json file is located. + `, + }); } } export class AngularLegacyBuildOptionsError extends StorybookError { - readonly category = Category.FRAMEWORK_ANGULAR; - - readonly code = 1; - - public readonly documentation = [ - 'https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#angular-drop-support-for-calling-storybook-directly', - 'https://github.com/storybookjs/storybook/tree/next/code/frameworks/angular#how-do-i-migrate-to-an-angular-storybook-builder', - ]; - - template() { - return dedent` - Your Storybook startup script uses a solution that is not supported anymore. - You must use Angular builder to have an explicit configuration on the project used in angular.json. - - Please run 'npx storybook automigrate' to automatically fix your config. - `; + constructor() { + super({ + category: Category.FRAMEWORK_ANGULAR, + code: 1, + documentation: [ + 'https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#angular-drop-support-for-calling-storybook-directly', + 'https://github.com/storybookjs/storybook/tree/next/code/frameworks/angular#how-do-i-migrate-to-an-angular-storybook-builder', + ], + template: dedent` + Your Storybook startup script uses a solution that is not supported anymore. + You must use Angular builder to have an explicit configuration on the project used in angular.json. + + Please run 'npx storybook automigrate' to automatically fix your config. + `, + }); } } export class CriticalPresetLoadError extends StorybookError { - readonly category = Category.CORE_SERVER; - - readonly code = 2; - constructor( public data: { error: Error; presetName: string; } ) { - super(); - } - - template() { - return dedent` - Storybook failed to load the following preset: ${this.data.presetName}. + super({ + category: Category.CORE_SERVER, + code: 2, + documentation: '', + template: dedent` + Storybook failed to load the following preset: ${data.presetName}. - Please check whether your setup is correct, the Storybook dependencies (and their peer dependencies) are installed correctly and there are no package version clashes. + Please check whether your setup is correct, the Storybook dependencies (and their peer dependencies) are installed correctly and there are no package version clashes. - If you believe this is a bug, please open an issue on Github. + If you believe this is a bug, please open an issue on Github. - ${this.data.error.stack || this.data.error.message} - `; + ${data.error.stack || data.error.message} + `, + }); } } export class MissingBuilderError extends StorybookError { - readonly category = Category.CORE_SERVER; - - readonly code = 3; - - public readonly documentation = 'https://github.com/storybookjs/storybook/issues/24071'; - - template() { - return dedent` - Storybook could not find a builder configuration for your project. - Builders normally come from a framework package e.g. '@storybook/react-vite', or from builder packages e.g. '@storybook/builder-vite'. - - - Does your main config file contain a 'framework' field configured correctly? - - Is the Storybook framework package installed correctly? - - If you don't use a framework, does your main config contain a 'core.builder' configured correctly? - - Are you in a monorepo and perhaps the framework package is hoisted incorrectly? - - If you believe this is a bug, please describe your issue in detail on Github. - `; + constructor() { + super({ + category: Category.CORE_SERVER, + code: 3, + documentation: 'https://github.com/storybookjs/storybook/issues/24071', + template: dedent` + Storybook could not find a builder configuration for your project. + Builders normally come from a framework package e.g. '@storybook/react-vite', or from builder packages e.g. '@storybook/builder-vite'. + + - Does your main config file contain a 'framework' field configured correctly? + - Is the Storybook framework package installed correctly? + - If you don't use a framework, does your main config contain a 'core.builder' configured correctly? + - Are you in a monorepo and perhaps the framework package is hoisted incorrectly? + + If you believe this is a bug, please describe your issue in detail on Github. + `, + }); } } export class GoogleFontsDownloadError extends StorybookError { - readonly category = Category.FRAMEWORK_NEXTJS; - - readonly code = 1; - - public readonly documentation = - 'https://github.com/storybookjs/storybook/blob/next/code/frameworks/nextjs/README.md#nextjs-font-optimization'; - constructor(public data: { fontFamily: string; url: string }) { - super(); - } - - template() { - return dedent` - Failed to fetch \`${this.data.fontFamily}\` from Google Fonts with URL: \`${this.data.url}\` - `; + super({ + category: Category.FRAMEWORK_NEXTJS, + code: 1, + documentation: + 'https://github.com/storybookjs/storybook/blob/next/code/frameworks/nextjs/README.md#nextjs-font-optimization', + template: dedent` + Failed to fetch \`${data.fontFamily}\` from Google Fonts with URL: \`${data.url}\` + `, + }); } } export class GoogleFontsLoadingError extends StorybookError { - readonly category = Category.FRAMEWORK_NEXTJS; - - readonly code = 2; - - public readonly documentation = - 'https://github.com/storybookjs/storybook/blob/next/code/frameworks/nextjs/README.md#nextjs-font-optimization'; - constructor(public data: { error: unknown | Error; url: string }) { - super(); - } - - template() { - return dedent` - An error occurred when trying to load Google Fonts with URL \`${this.data.url}\`. - - ${this.data.error instanceof Error ? this.data.error.message : ''} - `; + super({ + category: Category.FRAMEWORK_NEXTJS, + code: 2, + documentation: + 'https://github.com/storybookjs/storybook/blob/next/code/frameworks/nextjs/README.md#nextjs-font-optimization', + template: dedent` + An error occurred when trying to load Google Fonts with URL \`${data.url}\`. + + ${data.error instanceof Error ? data.error.message : ''} + `, + }); } } export class NoMatchingExportError extends StorybookError { - readonly category = Category.CORE_SERVER; - - readonly code = 4; - constructor(public data: { error: unknown | Error }) { - super(); - } - - template() { - return dedent` - There was an exports mismatch error when trying to build Storybook. - Please check whether the versions of your Storybook packages match whenever possible, as this might be the cause. - - Problematic example: - { "@storybook/react": "7.5.3", "@storybook/react-vite": "7.4.5", "storybook": "7.3.0" } - - Correct example: - { "@storybook/react": "7.5.3", "@storybook/react-vite": "7.5.3", "storybook": "7.5.3" } - - Please run \`npx storybook doctor\` for guidance on how to fix this issue. - `; + super({ + category: Category.CORE_SERVER, + code: 4, + documentation: '', + template: dedent` + There was an exports mismatch error when trying to build Storybook. + Please check whether the versions of your Storybook packages match whenever possible, as this might be the cause. + + Problematic example: + { "@storybook/react": "7.5.3", "@storybook/react-vite": "7.4.5", "storybook": "7.3.0" } + + Correct example: + { "@storybook/react": "7.5.3", "@storybook/react-vite": "7.5.3", "storybook": "7.5.3" } + + Please run \`npx storybook doctor\` for guidance on how to fix this issue. + `, + }); } } export class MainFileESMOnlyImportError extends StorybookError { - readonly category = Category.CORE_SERVER; - - readonly code = 5; - - public documentation = - 'https://github.com/storybookjs/storybook/issues/23972#issuecomment-1948534058'; - constructor( public data: { location: string; line: string | undefined; num: number | undefined } ) { - super(); - } - - template() { const message = [ - `Storybook failed to load ${this.data.location}`, + `Storybook failed to load ${data.location}`, '', `It looks like the file tried to load/import an ESM only module.`, - `Support for this is currently limited in ${this.data.location}`, + `Support for this is currently limited in ${data.location}`, `You can import ESM modules in your main file, but only as dynamic import.`, '', ]; - if (this.data.line) { + if (data.line) { message.push( chalk.white( - `In your ${chalk.yellow(this.data.location)} file, line ${chalk.bold.cyan( - this.data.num + `In your ${chalk.yellow(data.location)} file, line ${chalk.bold.cyan( + data.num )} threw an error:` ), - chalk.grey(this.data.line) + chalk.grey(data.line) ); } @@ -438,190 +380,171 @@ export class MainFileESMOnlyImportError extends StorybookError { '' ); - return message.join('\n'); + super({ + category: Category.CORE_SERVER, + code: 5, + documentation: + 'https://github.com/storybookjs/storybook/issues/23972#issuecomment-1948534058', + template: message.join('\n'), + }); } } export class MainFileMissingError extends StorybookError { - readonly category = Category.CORE_SERVER; - - readonly code = 6; - - readonly stack = ''; - - public readonly documentation = 'https://storybook.js.org/docs/configure'; - constructor(public data: { location: string }) { - super(); - } - - template() { - return dedent` - No configuration files have been found in your configDir: ${chalk.yellow(this.data.location)}. - Storybook needs a "main.js" file, please add it. - - You can pass a --config-dir flag to tell Storybook, where your main.js file is located at). - `; + super({ + category: Category.CORE_SERVER, + code: 6, + documentation: 'https://storybook.js.org/docs/configure', + template: dedent` + No configuration files have been found in your configDir: ${chalk.yellow(data.location)}. + Storybook needs a "main.js" file, please add it. + + You can pass a --config-dir flag to tell Storybook, where your main.js file is located at). + `, + }); } } export class MainFileEvaluationError extends StorybookError { - readonly category = Category.CORE_SERVER; - - readonly code = 7; - + // TODO: check if this error is still showing proper stack readonly stack = ''; constructor(public data: { location: string; error: Error }) { - super(); - } - - template() { const errorText = chalk.white( - (this.data.error.stack || this.data.error.message).replaceAll(process.cwd(), '') + (data.error.stack || data.error.message).replaceAll(process.cwd(), '') ); - return dedent` - Storybook couldn't evaluate your ${chalk.yellow(this.data.location)} file. + super({ + category: Category.CORE_SERVER, + code: 7, + template: dedent` + Storybook couldn't evaluate your ${chalk.yellow(data.location)} file. ${errorText} - `; + `, + }); } } export class GenerateNewProjectOnInitError extends StorybookError { - readonly category = Category.CLI_INIT; - - readonly code = 3; - constructor( public data: { error: unknown | Error; packageManager: string; projectType: string } ) { - super(); - } - - template() { - return dedent` - There was an error while using ${this.data.packageManager} to create a new ${ - this.data.projectType - } project. - - ${this.data.error instanceof Error ? this.data.error.message : ''} - `; + super({ + category: Category.CLI_INIT, + code: 3, + documentation: '', + template: dedent` + There was an error while using ${data.packageManager} to create a new ${ + data.projectType + } project. + + ${data.error instanceof Error ? data.error.message : ''} + `, + }); } } export class UpgradeStorybookToLowerVersionError extends StorybookError { - readonly category = Category.CLI_UPGRADE; - - readonly code = 3; - constructor(public data: { beforeVersion: string; currentVersion: string }) { - super(); - } - - template() { - return dedent` - You are trying to upgrade Storybook to a lower version than the version currently installed. This is not supported. - - Storybook version ${this.data.beforeVersion} was detected in your project, but you are trying to "upgrade" to version ${this.data.currentVersion}. - - 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" - `; + super({ + category: Category.CLI_UPGRADE, + code: 3, + template: dedent` + You are trying to upgrade Storybook to a lower version than the version currently installed. This is not supported. + + Storybook version ${data.beforeVersion} was detected in your project, but you are trying to "upgrade" to version ${data.currentVersion}. + + 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(); - } + super({ + category: Category.CLI_UPGRADE, + code: 4, + template: dedent` + You are upgrading Storybook to the same version that is currently installed in the project, version ${data.beforeVersion}. + + 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. - template() { - return dedent` - You are upgrading Storybook to the same version that is currently installed in the project, version ${this.data.beforeVersion}. - - 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: - 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" - "npx storybook@latest upgrade" + If you intended to re-run automigrations, you should run the "automigrate" command directly instead: - If you intended to re-run automigrations, you should run the "automigrate" command directly instead: - - "npx storybook automigrate" - `; + "npx storybook automigrate" + `, + }); } } export class UpgradeStorybookUnknownCurrentVersionError extends StorybookError { - readonly category = Category.CLI_UPGRADE; - - readonly code = 5; - - template() { - return dedent` - We couldn't determine the current version of Storybook in your project. - - Are you running the Storybook CLI in a project without Storybook? - It might help if you specify your Storybook config directory with the --config-dir flag. - `; + constructor() { + super({ + category: Category.CLI_UPGRADE, + code: 5, + template: dedent` + We couldn't determine the current version of Storybook in your project. + + Are you running the Storybook CLI in a project without Storybook? + It might help if you specify your Storybook config directory with the --config-dir flag. + `, + }); } } export class UpgradeStorybookInWrongWorkingDirectory extends StorybookError { - readonly category = Category.CLI_UPGRADE; - - readonly code = 6; - - template() { - return dedent` - You are running the upgrade command in a CWD that does not contain Storybook dependencies. - - Did you mean to run it in a different directory? Make sure the directory you run this command in contains a package.json with your Storybook dependencies. - `; + constructor() { + super({ + category: Category.CLI_UPGRADE, + code: 6, + template: dedent` + You are running the upgrade command in a CWD that does not contain Storybook dependencies. + + Did you mean to run it in a different directory? Make sure the directory you run this command in contains a package.json with your Storybook dependencies. + `, + }); } } export class NoStatsForViteDevError extends StorybookError { - readonly category = Category.BUILDER_VITE; - - readonly code = 1; - - template() { - return dedent` - Unable to write preview stats as the Vite builder does not support stats in dev mode. - - Please remove the \`--stats-json\` flag when running in dev mode. - `; + constructor() { + super({ + category: Category.BUILDER_VITE, + code: 1, + template: dedent` + Unable to write preview stats as the Vite builder does not support stats in dev mode. + + Please remove the \`--stats-json\` flag when running in dev mode. + `, + }); } } export class FindPackageVersionsError extends StorybookError { - readonly category = Category.CLI; - - readonly code = 1; - constructor( public data: { error: Error | unknown; packageName: string; packageManager: string } ) { - super(); - } - - template() { - return dedent` - Unable to find versions of "${this.data.packageName}" using ${this.data.packageManager} - ${this.data.error && `Reason: ${this.data.error}`} - `; + super({ + category: Category.CLI, + code: 1, + template: dedent` + Unable to find versions of "${data.packageName}" using ${data.packageManager} + ${data.error && `Reason: ${data.error}`} + `, + }); } } diff --git a/code/core/src/storybook-error.ts b/code/core/src/storybook-error.ts index cc9d46ad802a..6c3d8994f7ac 100644 --- a/code/core/src/storybook-error.ts +++ b/code/core/src/storybook-error.ts @@ -1,3 +1,13 @@ +import dedent from 'ts-dedent'; + +function parseErrorCode({ + code, + category, +}: Pick): `SB_${typeof category}_${string}` { + const paddedCode = String(code).padStart(4, '0'); + return `SB_${category}_${paddedCode}`; +} + export abstract class StorybookError extends Error { /** * Category of the error. Used to classify the type of error, e.g., 'PREVIEW_API'. @@ -16,7 +26,7 @@ export abstract class StorybookError extends Error { /** * Specifies the documentation for the error. - * - If `true`, links to a documentation page on the Storybook website (make sure it exists before enabling). + * - If `true`, links to a documentation page on the Storybook website (make sure it exists before enabling) – This is not implemented yet. * - If a string, uses the provided URL for documentation (external or FAQ links). * - If `false` (default), no documentation link is added. */ @@ -28,7 +38,7 @@ export abstract class StorybookError extends Error { readonly fromStorybook: true = true as const; get fullErrorCode() { - return fullErrorCode({ code: this.code, category: this.category }); + return parseErrorCode({ code: this.code, category: this.category }); } /** @@ -46,7 +56,7 @@ export abstract class StorybookError extends Error { template: string; documentation?: boolean | string | string[]; }) { - super(StorybookError.message(props)); + super(StorybookError.getFullMessage(props)); this.category = props.category; this.documentation = props.documentation ?? false; this.code = props.code; @@ -55,7 +65,7 @@ export abstract class StorybookError extends Error { /** * Generates the error message along with additional documentation link (if applicable). */ - static message({ + static getFullMessage({ documentation, code, category, @@ -64,21 +74,13 @@ export abstract class StorybookError extends Error { let page: string | undefined; if (documentation === true) { - page = `https://storybook.js.org/error/${fullErrorCode({ code, category })}`; + page = `https://storybook.js.org/error/${parseErrorCode({ code, category })}`; } else if (typeof documentation === 'string') { page = documentation; } else if (Array.isArray(documentation)) { page = `\n${documentation.map((doc) => `\t- ${doc}`).join('\n')}`; } - return `${template}${page != null ? `\n\nMore info: ${page}\n` : ''}`; + return dedent`${template}${page != null ? `\n\nMore info: ${page}\n` : ''}`; } } - -function fullErrorCode({ - code, - category, -}: Pick): `SB_${typeof category}_${string}` { - const paddedCode = String(code).padStart(4, '0'); - return `SB_${category}_${paddedCode}`; -} From d65b11795eb3dcb75247ab3ce0b535663cbbae73 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 5 Jul 2024 18:03:41 +0200 Subject: [PATCH 03/12] rename template to message --- code/core/src/__tests/storybook-error.test.ts | 2 +- code/core/src/manager-errors.ts | 4 +- code/core/src/preview-errors.ts | 36 ++++++------- code/core/src/server-errors.ts | 52 +++++++++---------- code/core/src/storybook-error.ts | 6 +-- 5 files changed, 50 insertions(+), 50 deletions(-) diff --git a/code/core/src/__tests/storybook-error.test.ts b/code/core/src/__tests/storybook-error.test.ts index 10803f21d062..dfbec1546375 100644 --- a/code/core/src/__tests/storybook-error.test.ts +++ b/code/core/src/__tests/storybook-error.test.ts @@ -7,7 +7,7 @@ describe('StorybookError', () => { super({ category: 'TEST_CATEGORY', code: 123, - template: 'This is a test error.', + message: 'This is a test error.', documentation, }); } diff --git a/code/core/src/manager-errors.ts b/code/core/src/manager-errors.ts index 6c90caa5bb1f..d5234bf1ec52 100644 --- a/code/core/src/manager-errors.ts +++ b/code/core/src/manager-errors.ts @@ -25,7 +25,7 @@ export class ProviderDoesNotExtendBaseProviderError extends StorybookError { super({ category: Category.MANAGER_UI, code: 1, - template: `The Provider passed into Storybook's UI is not extended from the base Provider. Please check your Provider implementation.`, + message: `The Provider passed into Storybook's UI is not extended from the base Provider. Please check your Provider implementation.`, }); } } @@ -39,7 +39,7 @@ export class UncaughtManagerError extends StorybookError { super({ category: Category.MANAGER_UNCAUGHT, code: 1, - template: data.error.message, + message: data.error.message, }); this.stack = data.error.stack; } diff --git a/code/core/src/preview-errors.ts b/code/core/src/preview-errors.ts index c082f8fb1162..58c29cb4c19d 100644 --- a/code/core/src/preview-errors.ts +++ b/code/core/src/preview-errors.ts @@ -37,7 +37,7 @@ export class MissingStoryAfterHmrError extends StorybookError { super({ category: Category.PREVIEW_API, code: 1, - template: dedent` + message: dedent` Couldn't find story matching id '${data.storyId}' after HMR. - Did you just rename a story? - Did you remove it from your CSF file? @@ -55,7 +55,7 @@ export class ImplicitActionsDuringRendering extends StorybookError { code: 2, documentation: 'https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#using-implicit-actions-during-rendering-is-deprecated-for-example-in-the-play-function', - template: dedent` + message: dedent` We detected that you use an implicit action arg while ${data.phase} of your story. ${data.deprecated ? `\nThis is deprecated and won't work in Storybook 8 anymore.\n` : ``} Please provide an explicit spy to your args like this: @@ -74,7 +74,7 @@ export class CalledExtractOnStoreError extends StorybookError { super({ category: Category.PREVIEW_API, code: 3, - template: dedent` + message: dedent` Cannot call \`storyStore.extract()\` without calling \`storyStore.cacheAllCsfFiles()\` first. You probably meant to call \`await preview.extract()\` which does the above for you.`, @@ -87,7 +87,7 @@ export class MissingRenderToCanvasError extends StorybookError { super({ category: Category.PREVIEW_API, code: 4, - template: dedent` + message: dedent` Expected your framework's preset to export a \`renderToCanvas\` field. Perhaps it needs to be upgraded for Storybook 6.4?`, @@ -102,7 +102,7 @@ export class CalledPreviewMethodBeforeInitializationError extends StorybookError super({ category: Category.PREVIEW_API, code: 5, - template: dedent` + message: dedent` Called \`Preview.${data.methodName}()\` before initialization. The preview needs to load the story index before most methods can be called. If you want @@ -119,7 +119,7 @@ export class StoryIndexFetchError extends StorybookError { super({ category: Category.PREVIEW_API, code: 6, - template: dedent` + message: dedent` Error fetching \`/index.json\`: ${data.text} @@ -138,7 +138,7 @@ export class MdxFileWithNoCsfReferencesError extends StorybookError { super({ category: Category.PREVIEW_API, code: 7, - template: dedent` + message: dedent` Tried to render docs entry ${data.storyId} but it is a MDX file that has no CSF references, or autodocs for a CSF file that some doesn't refer to itself. @@ -153,7 +153,7 @@ export class EmptyIndexError extends StorybookError { super({ category: Category.PREVIEW_API, code: 8, - template: dedent` + message: dedent` Couldn't find any stories in your Storybook. - Please check your stories field of your main.js config: does it match correctly? @@ -167,7 +167,7 @@ export class NoStoryMatchError extends StorybookError { super({ category: Category.PREVIEW_API, code: 9, - template: dedent` + message: dedent` Couldn't find story matching '${data.storySpecifier}'. - Are you sure a story with that id exists? @@ -182,7 +182,7 @@ export class MissingStoryFromCsfFileError extends StorybookError { super({ category: Category.PREVIEW_API, code: 10, - template: dedent` + message: dedent` Couldn't find story matching id '${data.storyId}' after importing a CSF file. The file was indexed as if the story was there, but then after importing the file in the browser @@ -200,7 +200,7 @@ export class StoryStoreAccessedBeforeInitializationError extends StorybookError super({ category: Category.PREVIEW_API, code: 11, - template: dedent` + message: dedent` Cannot access the Story Store until the index is ready. It is not recommended to use methods directly on the Story Store anyway, in Storybook 9 we will @@ -214,7 +214,7 @@ export class MountMustBeDestructuredError extends StorybookError { super({ category: Category.PREVIEW_API, code: 12, - template: dedent` + message: dedent` To use mount in the play function, you must use object destructuring, e.g. play: ({ mount }) => {}. Instead received: @@ -229,7 +229,7 @@ export class TestingLibraryMustBeConfiguredError extends StorybookError { super({ category: Category.PREVIEW_API, code: 13, - template: dedent` + message: dedent` You must configure testingLibraryRender to use play in portable stories. import { render } from '@testing-library/[renderer]'; @@ -267,7 +267,7 @@ export class NoRenderFunctionError extends StorybookError { super({ category: Category.PREVIEW_API, code: 14, - template: dedent` + message: dedent` No render function available for storyId '${data.id}' `, }); @@ -279,7 +279,7 @@ export class NoStoryMountedError extends StorybookError { super({ category: Category.PREVIEW_API, code: 15, - template: dedent` + message: dedent` No story is mounted in your story. This usually occurs when you destructure mount in the play function, but forgot to call it. @@ -302,7 +302,7 @@ export class NextJsSharpError extends StorybookError { super({ category: Category.FRAMEWORK_NEXTJS, code: 1, - template: dedent` + message: dedent` Tried to access sharp from "${data.importType}" but it was not available. You might be missing the required dependencies. `, }); @@ -314,7 +314,7 @@ export class NextjsRouterMocksNotAvailable extends StorybookError { super({ category: Category.FRAMEWORK_NEXTJS, code: 2, - template: dedent` + message: dedent` Tried to access router mocks from "${data.importType}" but they were not created yet. You might be running code in an unsupported environment. `, }); @@ -327,7 +327,7 @@ export class UnknownArgTypesError extends StorybookError { category: Category.DOCS_TOOLS, code: 1, documentation: 'https://github.com/storybookjs/storybook/issues/26606', - template: dedent` + message: dedent` There was a failure when generating detailed ArgTypes in ${data.language} for: ${JSON.stringify(data.type, null, 2)} diff --git a/code/core/src/server-errors.ts b/code/core/src/server-errors.ts index eb650c38a801..354d08ff18da 100644 --- a/code/core/src/server-errors.ts +++ b/code/core/src/server-errors.ts @@ -56,7 +56,7 @@ export class NxProjectDetectedError extends StorybookError { category: Category.CLI_INIT, code: 1, documentation: 'https://nx.dev/packages/storybook', - template: dedent` + message: dedent` We have detected Nx in your project. Nx has its own Storybook initializer, so please use it instead. Run "nx g @nx/storybook:configuration" to add Storybook to your project. `, @@ -71,7 +71,7 @@ export class MissingFrameworkFieldError extends StorybookError { code: 1, documentation: 'https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#new-framework-api', - template: dedent` + message: dedent` Could not find a 'framework' field in Storybook config. Please run 'npx storybook automigrate' to automatically fix your config. @@ -87,7 +87,7 @@ export class InvalidFrameworkNameError extends StorybookError { code: 2, documentation: 'https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#new-framework-api', - template: dedent` + message: dedent` Invalid value of '${data.frameworkName}' in the 'framework' field of Storybook config. Please run 'npx storybook automigrate' to automatically fix your config. @@ -102,7 +102,7 @@ export class CouldNotEvaluateFrameworkError extends StorybookError { category: Category.CORE_COMMON, code: 3, documentation: '', - template: dedent` + message: dedent` Could not evaluate the '${data.frameworkName}' package from the 'framework' field of Storybook config. Are you sure it's a valid package and is installed? @@ -120,7 +120,7 @@ export class ConflictingStaticDirConfigError extends StorybookError { code: 1, documentation: 'https://storybook.js.org/docs/react/configure/images-and-assets#serving-static-files-via-storybook-configuration', - template: dedent` + message: dedent` Storybook encountered a conflict when trying to serve statics. You have configured both: * Storybook's option in the config file: 'staticDirs' * Storybook's (deprecated) CLI flag: '--staticDir' or '-s' @@ -138,7 +138,7 @@ export class InvalidStoriesEntryError extends StorybookError { code: 4, documentation: 'https://storybook.js.org/docs/react/faq#can-i-have-a-storybook-with-no-local-stories', - template: dedent` + message: dedent` Storybook could not index your stories. Your main configuration somehow does not contain a 'stories' field, or it resolved to an empty array. @@ -157,7 +157,7 @@ export class WebpackMissingStatsError extends StorybookError { 'https://webpack.js.org/configuration/stats/', 'https://storybook.js.org/docs/react/builders/webpack#configure', ], - template: dedent` + message: dedent` No Webpack stats found. Did you turn off stats reporting in your Webpack config? Storybook needs Webpack stats (including errors) in order to build correctly. `, @@ -174,7 +174,7 @@ export class WebpackInvocationError extends StorybookError { super({ category: Category.BUILDER_WEBPACK5, code: 2, - template: data.error.message.trim(), + message: data.error.message.trim(), }); } } @@ -206,7 +206,7 @@ export class WebpackCompilationError extends StorybookError { category: Category.BUILDER_WEBPACK5, code: 3, // This error message is a followup of errors logged by Webpack to the user - template: dedent` + message: dedent` There were problems when compiling your code with Webpack. Run Storybook with --debug-webpack for more information. `, @@ -224,7 +224,7 @@ export class MissingAngularJsonError extends StorybookError { category: Category.CLI_INIT, code: 2, documentation: 'https://storybook.js.org/docs/angular/faq#error-no-angularjson-file-found', - template: dedent` + message: dedent` An angular.json file was not found in the current working directory: ${data.path} Storybook needs it to work properly, so please rerun the command at the root of your project, where the angular.json file is located. `, @@ -241,7 +241,7 @@ export class AngularLegacyBuildOptionsError extends StorybookError { 'https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#angular-drop-support-for-calling-storybook-directly', 'https://github.com/storybookjs/storybook/tree/next/code/frameworks/angular#how-do-i-migrate-to-an-angular-storybook-builder', ], - template: dedent` + message: dedent` Your Storybook startup script uses a solution that is not supported anymore. You must use Angular builder to have an explicit configuration on the project used in angular.json. @@ -262,7 +262,7 @@ export class CriticalPresetLoadError extends StorybookError { category: Category.CORE_SERVER, code: 2, documentation: '', - template: dedent` + message: dedent` Storybook failed to load the following preset: ${data.presetName}. Please check whether your setup is correct, the Storybook dependencies (and their peer dependencies) are installed correctly and there are no package version clashes. @@ -281,7 +281,7 @@ export class MissingBuilderError extends StorybookError { category: Category.CORE_SERVER, code: 3, documentation: 'https://github.com/storybookjs/storybook/issues/24071', - template: dedent` + message: dedent` Storybook could not find a builder configuration for your project. Builders normally come from a framework package e.g. '@storybook/react-vite', or from builder packages e.g. '@storybook/builder-vite'. @@ -303,7 +303,7 @@ export class GoogleFontsDownloadError extends StorybookError { code: 1, documentation: 'https://github.com/storybookjs/storybook/blob/next/code/frameworks/nextjs/README.md#nextjs-font-optimization', - template: dedent` + message: dedent` Failed to fetch \`${data.fontFamily}\` from Google Fonts with URL: \`${data.url}\` `, }); @@ -317,7 +317,7 @@ export class GoogleFontsLoadingError extends StorybookError { code: 2, documentation: 'https://github.com/storybookjs/storybook/blob/next/code/frameworks/nextjs/README.md#nextjs-font-optimization', - template: dedent` + message: dedent` An error occurred when trying to load Google Fonts with URL \`${data.url}\`. ${data.error instanceof Error ? data.error.message : ''} @@ -332,7 +332,7 @@ export class NoMatchingExportError extends StorybookError { category: Category.CORE_SERVER, code: 4, documentation: '', - template: dedent` + message: dedent` There was an exports mismatch error when trying to build Storybook. Please check whether the versions of your Storybook packages match whenever possible, as this might be the cause. @@ -385,7 +385,7 @@ export class MainFileESMOnlyImportError extends StorybookError { code: 5, documentation: 'https://github.com/storybookjs/storybook/issues/23972#issuecomment-1948534058', - template: message.join('\n'), + message: message.join('\n'), }); } } @@ -396,7 +396,7 @@ export class MainFileMissingError extends StorybookError { category: Category.CORE_SERVER, code: 6, documentation: 'https://storybook.js.org/docs/configure', - template: dedent` + message: dedent` No configuration files have been found in your configDir: ${chalk.yellow(data.location)}. Storybook needs a "main.js" file, please add it. @@ -418,7 +418,7 @@ export class MainFileEvaluationError extends StorybookError { super({ category: Category.CORE_SERVER, code: 7, - template: dedent` + message: dedent` Storybook couldn't evaluate your ${chalk.yellow(data.location)} file. ${errorText} @@ -435,7 +435,7 @@ export class GenerateNewProjectOnInitError extends StorybookError { category: Category.CLI_INIT, code: 3, documentation: '', - template: dedent` + message: dedent` There was an error while using ${data.packageManager} to create a new ${ data.projectType } project. @@ -451,7 +451,7 @@ export class UpgradeStorybookToLowerVersionError extends StorybookError { super({ category: Category.CLI_UPGRADE, code: 3, - template: dedent` + message: dedent` You are trying to upgrade Storybook to a lower version than the version currently installed. This is not supported. Storybook version ${data.beforeVersion} was detected in your project, but you are trying to "upgrade" to version ${data.currentVersion}. @@ -472,7 +472,7 @@ export class UpgradeStorybookToSameVersionError extends StorybookError { super({ category: Category.CLI_UPGRADE, code: 4, - template: dedent` + message: dedent` You are upgrading Storybook to the same version that is currently installed in the project, version ${data.beforeVersion}. This usually happens when running the upgrade command without a version specifier, e.g. "npx storybook upgrade". @@ -496,7 +496,7 @@ export class UpgradeStorybookUnknownCurrentVersionError extends StorybookError { super({ category: Category.CLI_UPGRADE, code: 5, - template: dedent` + message: dedent` We couldn't determine the current version of Storybook in your project. Are you running the Storybook CLI in a project without Storybook? @@ -511,7 +511,7 @@ export class UpgradeStorybookInWrongWorkingDirectory extends StorybookError { super({ category: Category.CLI_UPGRADE, code: 6, - template: dedent` + message: dedent` You are running the upgrade command in a CWD that does not contain Storybook dependencies. Did you mean to run it in a different directory? Make sure the directory you run this command in contains a package.json with your Storybook dependencies. @@ -525,7 +525,7 @@ export class NoStatsForViteDevError extends StorybookError { super({ category: Category.BUILDER_VITE, code: 1, - template: dedent` + message: dedent` Unable to write preview stats as the Vite builder does not support stats in dev mode. Please remove the \`--stats-json\` flag when running in dev mode. @@ -541,7 +541,7 @@ export class FindPackageVersionsError extends StorybookError { super({ category: Category.CLI, code: 1, - template: dedent` + message: dedent` Unable to find versions of "${data.packageName}" using ${data.packageManager} ${data.error && `Reason: ${data.error}`} `, diff --git a/code/core/src/storybook-error.ts b/code/core/src/storybook-error.ts index 6c3d8994f7ac..b68a3d5ec35f 100644 --- a/code/core/src/storybook-error.ts +++ b/code/core/src/storybook-error.ts @@ -53,7 +53,7 @@ export abstract class StorybookError extends Error { constructor(props: { category: string; code: number; - template: string; + message: string; documentation?: boolean | string | string[]; }) { super(StorybookError.getFullMessage(props)); @@ -69,7 +69,7 @@ export abstract class StorybookError extends Error { documentation, code, category, - template, + message, }: ConstructorParameters[0]) { let page: string | undefined; @@ -81,6 +81,6 @@ export abstract class StorybookError extends Error { page = `\n${documentation.map((doc) => `\t- ${doc}`).join('\n')}`; } - return dedent`${template}${page != null ? `\n\nMore info: ${page}\n` : ''}`; + return dedent`${message}${page != null ? `\n\nMore info: ${page}\n` : ''}`; } } From ea9dd8c1df0c50e85e0be97ea210b5fb40371712 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 5 Jul 2024 18:07:49 +0200 Subject: [PATCH 04/12] update error docs --- code/core/src/ERRORS.md | 44 +++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/code/core/src/ERRORS.md b/code/core/src/ERRORS.md index 84ac44fc5eaa..3638cb7e4780 100644 --- a/code/core/src/ERRORS.md +++ b/code/core/src/ERRORS.md @@ -27,11 +27,15 @@ Second use the `StorybookError` class to define custom errors with specific code ```typescript import { StorybookError } from './storybook-error'; export class YourCustomError extends StorybookError { - readonly category: Category; // The category to which the error belongs. Check the source in client-errors.ts or server-errors.ts for reference. - readonly code: number; // The numeric code for the error. - - template(): string { - // A function that returns the error message. + constructor() { + super({ + // The category to which the error belongs. Check the source in client-errors.ts or server-errors.ts for reference. + category: Category, + // The numeric code for the error. + code: number, + // The error message. + message: string, + }); } } ``` @@ -42,7 +46,7 @@ export class YourCustomError extends StorybookError { | ------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | category | `Category` | The category to which the error belongs. | | code | `number` | The numeric code for the error. | -| template | `() => string` | Function that returns a properly written error message. | +| message | `string` | The error message. | | data | `Object` | Optional. Data associated with the error. Used to provide additional information in the error message or to be passed to telemetry. | | documentation | `boolean` or `string` | Optional. Should be set to `true` **if the error is documented on the Storybook website**. If defined as string, it should be a custom documentation link. | @@ -51,28 +55,26 @@ export class YourCustomError extends StorybookError { ```typescript // Define a custom error with a numeric code and a static error message template. export class StorybookIndexGenerationError extends StorybookError { - category = Category.Generic; - code = 1; - - template(): string { - return `Storybook failed when generating an index for your stories. Check the stories field in your main.js`; + constructor() { + super({ + category: Category.Generic, + code: 1, + message: `Storybook failed when generating an index for your stories. Check the stories field in your main.js`, + }); } } -// Define a custom error with a numeric code and a dynamic error message template based on properties from the constructor. +// Define a custom error with a numeric code and a dynamic error message based on properties from the constructor. export class InvalidFileExtensionError extends StorybookError { - category = Category.Validation; - code = 2; - documentation = 'https://some-custom-documentation.com/validation-errors'; - // extra properties are defined in the constructor via a data property, which is available in any class method // always use this data Object notation! constructor(public data: { extension: string }) { - super(); - } - - template(): string { - return `Invalid file extension found: ${this.data.extension}.`; + super({ + category: Category.Validation, + code: 2, + documentation: 'https://some-custom-documentation.com/validation-errors', + message: `Invalid file extension found: ${data.extension}.`, + }); } } From 4e4e6c0466b62f4c3629c8bda133c65d296b35df Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 5 Jul 2024 18:40:15 +0200 Subject: [PATCH 05/12] fix eslint issues and migrate detection to the new error format --- code/.eslintrc.js | 2 +- .../no-duplicated-error-codes.js | 71 +++++++++++++------ .../no-uncategorized-errors.js | 2 +- 3 files changed, 51 insertions(+), 24 deletions(-) diff --git a/code/.eslintrc.js b/code/.eslintrc.js index fbe9539ad32f..cb18114a6a94 100644 --- a/code/.eslintrc.js +++ b/code/.eslintrc.js @@ -209,7 +209,7 @@ module.exports = { }, }, { - files: ['**/core-events/src/**/*'], + files: ['./core/src/preview-errors.ts'], excludedFiles: ['**/*.test.*'], rules: { 'local-rules/no-duplicated-error-codes': 'error', diff --git a/scripts/eslint-plugin-local-rules/no-duplicated-error-codes.js b/scripts/eslint-plugin-local-rules/no-duplicated-error-codes.js index 53968edb572c..96d9294a861c 100644 --- a/scripts/eslint-plugin-local-rules/no-duplicated-error-codes.js +++ b/scripts/eslint-plugin-local-rules/no-duplicated-error-codes.js @@ -11,35 +11,62 @@ module.exports = { create(context) { const errorClasses = {}; + // both code and category are passed as arguments to the StorybookError constructor's super call + function findSuperArguments(node) { + let superArguments = []; + + node.body.body.forEach((method) => { + if (method.type === 'MethodDefinition' && method.kind === 'constructor') { + method.value.body.body.forEach((expression) => { + if ( + expression.type === 'ExpressionStatement' && + expression.expression.type === 'CallExpression' && + expression.expression.callee.type === 'Super' + ) { + superArguments = expression.expression.arguments; + } + }); + } + }); + + return superArguments; + } + return { ClassDeclaration(node) { - if (node.superClass.name === 'StorybookError') { - const categoryProperty = node.body.body.find((prop) => { - return prop.type === 'PropertyDefinition' && prop.key.name === 'category'; - }); + if (node.superClass && node.superClass.name === 'StorybookError') { + const superArguments = findSuperArguments(node); + const properties = { + category: null, + code: null, + }; - const codeProperty = node.body.body.find((prop) => { - return prop.type === 'PropertyDefinition' && prop.key.name === 'code'; + // Process the arguments to extract category and code + superArguments.forEach((arg) => { + if (arg.type === 'ObjectExpression') { + arg.properties.forEach((property) => { + if (Object.keys(properties).includes(property.key.name)) { + properties[property.key.name] = property; + } + }); + } }); - if (categoryProperty && categoryProperty.value.type === 'MemberExpression') { - const categoryName = categoryProperty.value.property.name; + const categoryValue = properties.category.value.property.name; + const codeValue = properties.code.value.value; - if (codeProperty && codeProperty.value.type === 'Literal') { - const errorCode = codeProperty.value.value; - - if (!errorClasses[categoryName]) { - errorClasses[categoryName] = new Set(); - } + if (categoryValue && codeValue) { + if (!errorClasses[categoryValue]) { + errorClasses[categoryValue] = new Set(); + } - if (errorClasses[categoryName].has(errorCode)) { - context.report({ - node: codeProperty, - message: `Duplicate error code '${errorCode}' in category '${categoryName}'.`, - }); - } else { - errorClasses[categoryName].add(errorCode); - } + if (errorClasses[categoryValue].has(codeValue)) { + context.report({ + node: properties.code.key, + message: `Duplicate error code '${codeValue}' in category '${categoryValue}'.`, + }); + } else { + errorClasses[categoryValue].add(codeValue); } } } diff --git a/scripts/eslint-plugin-local-rules/no-uncategorized-errors.js b/scripts/eslint-plugin-local-rules/no-uncategorized-errors.js index ce91cccf1040..c7629e719305 100644 --- a/scripts/eslint-plugin-local-rules/no-uncategorized-errors.js +++ b/scripts/eslint-plugin-local-rules/no-uncategorized-errors.js @@ -5,7 +5,7 @@ module.exports = { description: 'Disallow usage of the Error JavaScript class.', category: 'Best Practices', recommended: true, - url: 'https://github.com/storybookjs/storybook/blob/next/code/lib/core-events/src/errors/README.md', + url: 'https://github.com/storybookjs/storybook/blob/next/code/core/src/ERRORS.md', }, }, create(context) { From f54e780f0065578b76153396c1e3484230da675a Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 5 Jul 2024 18:47:52 +0200 Subject: [PATCH 06/12] update NoStoryMountedError error --- code/core/src/preview-errors.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/code/core/src/preview-errors.ts b/code/core/src/preview-errors.ts index 58c29cb4c19d..5f42486d5a85 100644 --- a/code/core/src/preview-errors.ts +++ b/code/core/src/preview-errors.ts @@ -275,23 +275,25 @@ export class NoRenderFunctionError extends StorybookError { } export class NoStoryMountedError extends StorybookError { - constructor(public data: { mountFunction: string }) { + constructor() { super({ category: Category.PREVIEW_API, code: 15, message: dedent` - No story is mounted in your story. + No component is mounted in your story. - This usually occurs when you destructure mount in the play function, but forgot to call it. + This usually occurs when you destructure mount in the play function, but forget to call it. For example: async play({ mount, canvasElement }) { - // 👈mount should be called: await mount(); + // 👈 mount should be called: await mount(); const canvas = within(canvasElement); const button = await canvas.findByRole('button'); await userEvent.click(button); }; + + Make sure to either remove it or call mount in your play function. `, }); } From 5dc7c1dcd34e399f0b2edd4161f2eec6aae900eb Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Fri, 5 Jul 2024 23:14:58 +0200 Subject: [PATCH 07/12] Fix wrong refactor --- code/core/src/preview-errors.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/core/src/preview-errors.ts b/code/core/src/preview-errors.ts index 5f42486d5a85..8de51e27d36c 100644 --- a/code/core/src/preview-errors.ts +++ b/code/core/src/preview-errors.ts @@ -225,7 +225,7 @@ export class MountMustBeDestructuredError extends StorybookError { } export class TestingLibraryMustBeConfiguredError extends StorybookError { - constructor(public data: { importType: string }) { + constructor() { super({ category: Category.PREVIEW_API, code: 13, From a0f818980ac3438aa9f8d19383559f726c018864 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Sat, 6 Jul 2024 00:19:26 +0200 Subject: [PATCH 08/12] Seems like a wrong refactor --- code/core/src/preview-errors.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/code/core/src/preview-errors.ts b/code/core/src/preview-errors.ts index 8de51e27d36c..b7f8b7ff2ba1 100644 --- a/code/core/src/preview-errors.ts +++ b/code/core/src/preview-errors.ts @@ -300,12 +300,15 @@ export class NoStoryMountedError extends StorybookError { } export class NextJsSharpError extends StorybookError { - constructor(public data: { importType: string }) { + constructor() { super({ category: Category.FRAMEWORK_NEXTJS, code: 1, + documentation: 'https://storybook.js.org/docs/get-started/nextjs#faq', message: dedent` - Tried to access sharp from "${data.importType}" but it was not available. You might be missing the required dependencies. + You are importing avif images, but you don't have sharp installed. + + You have to install sharp in order to use image optimization features in Next.js. `, }); } From 93002de5b419ffc7972b52adebebef42fc6e146c Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Sat, 6 Jul 2024 01:13:58 +0200 Subject: [PATCH 09/12] Revert dedent import --- code/core/src/storybook-error.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/code/core/src/storybook-error.ts b/code/core/src/storybook-error.ts index b68a3d5ec35f..2518e71fc43d 100644 --- a/code/core/src/storybook-error.ts +++ b/code/core/src/storybook-error.ts @@ -1,5 +1,3 @@ -import dedent from 'ts-dedent'; - function parseErrorCode({ code, category, @@ -81,6 +79,6 @@ export abstract class StorybookError extends Error { page = `\n${documentation.map((doc) => `\t- ${doc}`).join('\n')}`; } - return dedent`${message}${page != null ? `\n\nMore info: ${page}\n` : ''}`; + return `${message}${page != null ? `\n\nMore info: ${page}\n` : ''}`; } } From 19fbf2f2870ebf1c04406b452d73b07eb67c8ea9 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Sat, 6 Jul 2024 01:40:00 +0200 Subject: [PATCH 10/12] Fix indent --- code/core/src/server-errors.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/core/src/server-errors.ts b/code/core/src/server-errors.ts index 354d08ff18da..b6ee843174e2 100644 --- a/code/core/src/server-errors.ts +++ b/code/core/src/server-errors.ts @@ -141,7 +141,7 @@ export class InvalidStoriesEntryError extends StorybookError { message: dedent` Storybook could not index your stories. Your main configuration somehow does not contain a 'stories' field, or it resolved to an empty array. - + Please check your main configuration file and make sure it exports a 'stories' field that is not an empty array. `, }); From d81bba30be1e43ff357ba131bd78539209ac4b3d Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Sat, 6 Jul 2024 16:29:38 +0200 Subject: [PATCH 11/12] Align templates and address feedback --- code/core/src/preview-errors.ts | 12 +-- code/core/src/server-errors.ts | 162 ++++++++++++++------------------ 2 files changed, 73 insertions(+), 101 deletions(-) diff --git a/code/core/src/preview-errors.ts b/code/core/src/preview-errors.ts index b7f8b7ff2ba1..f832a4172a40 100644 --- a/code/core/src/preview-errors.ts +++ b/code/core/src/preview-errors.ts @@ -63,8 +63,7 @@ export class ImplicitActionsDuringRendering extends StorybookError { ... args: { ${data.name}: fn() - } - `, + }`, }); } } @@ -90,7 +89,7 @@ export class MissingRenderToCanvasError extends StorybookError { message: dedent` Expected your framework's preset to export a \`renderToCanvas\` field. - Perhaps it needs to be upgraded for Storybook 6.4?`, + Perhaps it needs to be upgraded for Storybook 7.0?`, documentation: 'https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#mainjs-framework-field', }); @@ -218,8 +217,7 @@ export class MountMustBeDestructuredError extends StorybookError { To use mount in the play function, you must use object destructuring, e.g. play: ({ mount }) => {}. Instead received: - ${data.playFunction} - `, + ${data.playFunction}`, }); } } @@ -255,9 +253,7 @@ export class TestingLibraryMustBeConfiguredError extends StorybookError { // or for React render(); }, - }); - - `, + });`, }); } } diff --git a/code/core/src/server-errors.ts b/code/core/src/server-errors.ts index b6ee843174e2..54cc3d3e0b02 100644 --- a/code/core/src/server-errors.ts +++ b/code/core/src/server-errors.ts @@ -58,8 +58,7 @@ export class NxProjectDetectedError extends StorybookError { documentation: 'https://nx.dev/packages/storybook', message: dedent` We have detected Nx in your project. Nx has its own Storybook initializer, so please use it instead. - Run "nx g @nx/storybook:configuration" to add Storybook to your project. - `, + Run "nx g @nx/storybook:configuration" to add Storybook to your project.`, }); } } @@ -73,9 +72,8 @@ export class MissingFrameworkFieldError extends StorybookError { 'https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#new-framework-api', message: dedent` Could not find a 'framework' field in Storybook config. - - Please run 'npx storybook automigrate' to automatically fix your config. - `, + + Please run 'npx storybook automigrate' to automatically fix your config.`, }); } } @@ -89,7 +87,7 @@ export class InvalidFrameworkNameError extends StorybookError { 'https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#new-framework-api', message: dedent` Invalid value of '${data.frameworkName}' in the 'framework' field of Storybook config. - + Please run 'npx storybook automigrate' to automatically fix your config. `, }); @@ -104,9 +102,8 @@ export class CouldNotEvaluateFrameworkError extends StorybookError { documentation: '', message: dedent` Could not evaluate the '${data.frameworkName}' package from the 'framework' field of Storybook config. - - Are you sure it's a valid package and is installed? - `, + + Are you sure it's a valid package and is installed?`, }); } } @@ -125,8 +122,7 @@ export class ConflictingStaticDirConfigError extends StorybookError { * Storybook's option in the config file: 'staticDirs' * Storybook's (deprecated) CLI flag: '--staticDir' or '-s' - Please remove the CLI flag from your storybook script and use only the 'staticDirs' option instead. - `, + Please remove the CLI flag from your storybook script and use only the 'staticDirs' option instead.`, }); } } @@ -142,8 +138,7 @@ export class InvalidStoriesEntryError extends StorybookError { Storybook could not index your stories. Your main configuration somehow does not contain a 'stories' field, or it resolved to an empty array. - Please check your main configuration file and make sure it exports a 'stories' field that is not an empty array. - `, + Please check your main configuration file and make sure it exports a 'stories' field that is not an empty array.`, }); } } @@ -159,8 +154,7 @@ export class WebpackMissingStatsError extends StorybookError { ], message: dedent` No Webpack stats found. Did you turn off stats reporting in your Webpack config? - Storybook needs Webpack stats (including errors) in order to build correctly. - `, + Storybook needs Webpack stats (including errors) in order to build correctly.`, }); } } @@ -226,8 +220,7 @@ export class MissingAngularJsonError extends StorybookError { documentation: 'https://storybook.js.org/docs/angular/faq#error-no-angularjson-file-found', message: dedent` An angular.json file was not found in the current working directory: ${data.path} - Storybook needs it to work properly, so please rerun the command at the root of your project, where the angular.json file is located. - `, + Storybook needs it to work properly, so please rerun the command at the root of your project, where the angular.json file is located.`, }); } } @@ -242,11 +235,10 @@ export class AngularLegacyBuildOptionsError extends StorybookError { 'https://github.com/storybookjs/storybook/tree/next/code/frameworks/angular#how-do-i-migrate-to-an-angular-storybook-builder', ], message: dedent` - Your Storybook startup script uses a solution that is not supported anymore. - You must use Angular builder to have an explicit configuration on the project used in angular.json. - - Please run 'npx storybook automigrate' to automatically fix your config. - `, + Your Storybook startup script uses a solution that is not supported anymore. + You must use Angular builder to have an explicit configuration on the project used in angular.json. + + Please run 'npx storybook automigrate' to automatically fix your config.`, }); } } @@ -263,14 +255,13 @@ export class CriticalPresetLoadError extends StorybookError { code: 2, documentation: '', message: dedent` - Storybook failed to load the following preset: ${data.presetName}. - - Please check whether your setup is correct, the Storybook dependencies (and their peer dependencies) are installed correctly and there are no package version clashes. - - If you believe this is a bug, please open an issue on Github. - - ${data.error.stack || data.error.message} - `, + Storybook failed to load the following preset: ${data.presetName}. + + Please check whether your setup is correct, the Storybook dependencies (and their peer dependencies) are installed correctly and there are no package version clashes. + + If you believe this is a bug, please open an issue on Github. + + ${data.error.stack || data.error.message}`, }); } } @@ -282,16 +273,15 @@ export class MissingBuilderError extends StorybookError { code: 3, documentation: 'https://github.com/storybookjs/storybook/issues/24071', message: dedent` - Storybook could not find a builder configuration for your project. - Builders normally come from a framework package e.g. '@storybook/react-vite', or from builder packages e.g. '@storybook/builder-vite'. - - - Does your main config file contain a 'framework' field configured correctly? - - Is the Storybook framework package installed correctly? - - If you don't use a framework, does your main config contain a 'core.builder' configured correctly? - - Are you in a monorepo and perhaps the framework package is hoisted incorrectly? - - If you believe this is a bug, please describe your issue in detail on Github. - `, + Storybook could not find a builder configuration for your project. + Builders normally come from a framework package e.g. '@storybook/react-vite', or from builder packages e.g. '@storybook/builder-vite'. + + - Does your main config file contain a 'framework' field configured correctly? + - Is the Storybook framework package installed correctly? + - If you don't use a framework, does your main config contain a 'core.builder' configured correctly? + - Are you in a monorepo and perhaps the framework package is hoisted incorrectly? + + If you believe this is a bug, please describe your issue in detail on Github.`, }); } } @@ -304,8 +294,7 @@ export class GoogleFontsDownloadError extends StorybookError { documentation: 'https://github.com/storybookjs/storybook/blob/next/code/frameworks/nextjs/README.md#nextjs-font-optimization', message: dedent` - Failed to fetch \`${data.fontFamily}\` from Google Fonts with URL: \`${data.url}\` - `, + Failed to fetch \`${data.fontFamily}\` from Google Fonts with URL: \`${data.url}\``, }); } } @@ -318,10 +307,9 @@ export class GoogleFontsLoadingError extends StorybookError { documentation: 'https://github.com/storybookjs/storybook/blob/next/code/frameworks/nextjs/README.md#nextjs-font-optimization', message: dedent` - An error occurred when trying to load Google Fonts with URL \`${data.url}\`. - - ${data.error instanceof Error ? data.error.message : ''} - `, + An error occurred when trying to load Google Fonts with URL \`${data.url}\`. + + ${data.error instanceof Error ? data.error.message : ''}`, }); } } @@ -333,17 +321,16 @@ export class NoMatchingExportError extends StorybookError { code: 4, documentation: '', message: dedent` - There was an exports mismatch error when trying to build Storybook. - Please check whether the versions of your Storybook packages match whenever possible, as this might be the cause. - - Problematic example: - { "@storybook/react": "7.5.3", "@storybook/react-vite": "7.4.5", "storybook": "7.3.0" } - - Correct example: - { "@storybook/react": "7.5.3", "@storybook/react-vite": "7.5.3", "storybook": "7.5.3" } - - Please run \`npx storybook doctor\` for guidance on how to fix this issue. - `, + There was an exports mismatch error when trying to build Storybook. + Please check whether the versions of your Storybook packages match whenever possible, as this might be the cause. + + Problematic example: + { "@storybook/react": "7.5.3", "@storybook/react-vite": "7.4.5", "storybook": "7.3.0" } + + Correct example: + { "@storybook/react": "7.5.3", "@storybook/react-vite": "7.5.3", "storybook": "7.5.3" } + + Please run \`npx storybook doctor\` for guidance on how to fix this issue.`, }); } } @@ -400,16 +387,12 @@ export class MainFileMissingError extends StorybookError { No configuration files have been found in your configDir: ${chalk.yellow(data.location)}. Storybook needs a "main.js" file, please add it. - You can pass a --config-dir flag to tell Storybook, where your main.js file is located at). - `, + You can pass a --config-dir flag to tell Storybook, where your main.js file is located at).`, }); } } export class MainFileEvaluationError extends StorybookError { - // TODO: check if this error is still showing proper stack - readonly stack = ''; - constructor(public data: { location: string; error: Error }) { const errorText = chalk.white( (data.error.stack || data.error.message).replaceAll(process.cwd(), '') @@ -419,10 +402,10 @@ export class MainFileEvaluationError extends StorybookError { category: Category.CORE_SERVER, code: 7, message: dedent` - Storybook couldn't evaluate your ${chalk.yellow(data.location)} file. - - ${errorText} - `, + Storybook couldn't evaluate your ${chalk.yellow(data.location)} file. + + Original error: + ${errorText}`, }); } } @@ -436,12 +419,11 @@ export class GenerateNewProjectOnInitError extends StorybookError { code: 3, documentation: '', message: dedent` - There was an error while using ${data.packageManager} to create a new ${ - data.projectType - } project. - - ${data.error instanceof Error ? data.error.message : ''} - `, + There was an error while using ${data.packageManager} to create a new ${ + data.projectType + } project. + + ${data.error instanceof Error ? data.error.message : ''}`, }); } } @@ -453,16 +435,15 @@ export class UpgradeStorybookToLowerVersionError extends StorybookError { code: 3, message: dedent` You are trying to upgrade Storybook to a lower version than the version currently installed. This is not supported. - + Storybook version ${data.beforeVersion} was detected in your project, but you are trying to "upgrade" to version ${data.currentVersion}. 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" - `, + "npx storybook@latest upgrade"`, }); } } @@ -478,15 +459,14 @@ export class UpgradeStorybookToSameVersionError extends StorybookError { 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 automigrate" - `, + + "npx storybook automigrate"`, }); } } @@ -498,10 +478,9 @@ export class UpgradeStorybookUnknownCurrentVersionError extends StorybookError { code: 5, message: dedent` We couldn't determine the current version of Storybook in your project. - + Are you running the Storybook CLI in a project without Storybook? - It might help if you specify your Storybook config directory with the --config-dir flag. - `, + It might help if you specify your Storybook config directory with the --config-dir flag.`, }); } } @@ -513,9 +492,8 @@ export class UpgradeStorybookInWrongWorkingDirectory extends StorybookError { code: 6, message: dedent` You are running the upgrade command in a CWD that does not contain Storybook dependencies. - - Did you mean to run it in a different directory? Make sure the directory you run this command in contains a package.json with your Storybook dependencies. - `, + + Did you mean to run it in a different directory? Make sure the directory you run this command in contains a package.json with your Storybook dependencies.`, }); } } @@ -527,9 +505,8 @@ export class NoStatsForViteDevError extends StorybookError { code: 1, message: dedent` Unable to write preview stats as the Vite builder does not support stats in dev mode. - - Please remove the \`--stats-json\` flag when running in dev mode. - `, + + Please remove the \`--stats-json\` flag when running in dev mode.`, }); } } @@ -543,8 +520,7 @@ export class FindPackageVersionsError extends StorybookError { code: 1, message: dedent` Unable to find versions of "${data.packageName}" using ${data.packageManager} - ${data.error && `Reason: ${data.error}`} - `, + ${data.error && `Reason: ${data.error}`}`, }); } } From 13338a26f0af037df314707245831b699ce5ee54 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Sun, 7 Jul 2024 12:25:23 +0200 Subject: [PATCH 12/12] Adress feedback and fix unit test --- .../core/src/preview-api/modules/preview-web/PreviewWeb.test.ts | 2 +- code/core/src/storybook-error.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/code/core/src/preview-api/modules/preview-web/PreviewWeb.test.ts b/code/core/src/preview-api/modules/preview-web/PreviewWeb.test.ts index 9c51892e78ff..c0862f643683 100644 --- a/code/core/src/preview-api/modules/preview-web/PreviewWeb.test.ts +++ b/code/core/src/preview-api/modules/preview-web/PreviewWeb.test.ts @@ -518,7 +518,7 @@ describe('PreviewWeb', () => { expect(vi.mocked(preview.view.showErrorDisplay).mock.calls[0][0]).toMatchInlineSnapshot(` [SB_PREVIEW_API_0004 (MissingRenderToCanvasError): Expected your framework's preset to export a \`renderToCanvas\` field. - Perhaps it needs to be upgraded for Storybook 6.4? + Perhaps it needs to be upgraded for Storybook 7.0? More info: https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#mainjs-framework-field ] diff --git a/code/core/src/storybook-error.ts b/code/core/src/storybook-error.ts index 2518e71fc43d..6cbcda3d28e3 100644 --- a/code/core/src/storybook-error.ts +++ b/code/core/src/storybook-error.ts @@ -28,7 +28,7 @@ export abstract class StorybookError extends Error { * - If a string, uses the provided URL for documentation (external or FAQ links). * - If `false` (default), no documentation link is added. */ - public documentation: boolean | string | string[]; + public readonly documentation: boolean | string | string[]; /** * Flag used to easily determine if the error originates from Storybook.