diff --git a/code/lib/core-common/src/presets.test.ts b/code/lib/core-common/src/presets.test.ts index 92b27dbdcd22..667f1d01781e 100644 --- a/code/lib/core-common/src/presets.test.ts +++ b/code/lib/core-common/src/presets.test.ts @@ -102,6 +102,12 @@ describe('presets', () => { await expect(testPresets()).resolves.toBeUndefined(); }); + it('throws when preset can not be loaded and is critical', async () => { + const { getPresets } = jest.requireActual('./presets'); + + await expect(getPresets(['preset-foo'], { isCritical: true })).rejects.toThrow(); + }); + it('loads and applies presets when they are combined in another preset', async () => { mockPreset('preset-foo', { foo: (exec: string[]) => exec.concat('foo'), diff --git a/code/lib/core-common/src/presets.ts b/code/lib/core-common/src/presets.ts index 7680d3292abc..0d1de949aa7c 100644 --- a/code/lib/core-common/src/presets.ts +++ b/code/lib/core-common/src/presets.ts @@ -11,6 +11,7 @@ import type { Presets, } from '@storybook/types'; import { join, parse } from 'path'; +import { CriticalPresetLoadError } from '@storybook/core-events/server-errors'; import { loadCustomPresets } from './utils/load-custom-presets'; import { safeResolve, safeResolveFrom } from './utils/safeResolve'; import { interopRequireDefault } from './utils/interpret-require'; @@ -218,9 +219,10 @@ export async function loadPreset( level: number, storybookOptions: InterPresetOptions ): Promise { + // @ts-expect-error (Converted from ts-ignore) + const presetName: string = input.name ? input.name : input; + try { - // @ts-expect-error (Converted from ts-ignore) - const name: string = input.name ? input.name : input; // @ts-expect-error (Converted from ts-ignore) const presetOptions = input.options ? input.options : {}; @@ -250,7 +252,7 @@ export async function loadPreset( storybookOptions )), { - name, + name: presetName, preset: rest, options: presetOptions, }, @@ -260,14 +262,21 @@ export async function loadPreset( throw new Error(dedent` ${input} is not a valid preset `); - } catch (e: any) { + } catch (error: any) { + if (storybookOptions?.isCritical) { + throw new CriticalPresetLoadError({ + error, + presetName, + }); + } + const warning = level > 0 ? ` Failed to load preset: ${JSON.stringify(input)} on level ${level}` : ` Failed to load preset: ${JSON.stringify(input)}`; logger.warn(warning); - logger.error(e); + logger.error(error); return []; } } @@ -345,7 +354,10 @@ function applyPresets( }, presetResult); } -type InterPresetOptions = Omit; +type InterPresetOptions = Omit< + CLIOptions & LoadOptions & BuilderOptions & { isCritical?: boolean }, + 'frameworkPresets' +>; export async function getPresets( presets: PresetConfig[], @@ -365,6 +377,8 @@ export async function loadAllPresets( BuilderOptions & { corePresets: PresetConfig[]; overridePresets: PresetConfig[]; + /** Whether preset failures should be critical or not */ + isCritical?: boolean; } ) { const { corePresets = [], overridePresets = [], ...restOptions } = options; diff --git a/code/lib/core-events/src/errors/server-errors.ts b/code/lib/core-events/src/errors/server-errors.ts index f773a2fbf69e..e23b933fb049 100644 --- a/code/lib/core-events/src/errors/server-errors.ts +++ b/code/lib/core-events/src/errors/server-errors.ts @@ -279,3 +279,30 @@ export class AngularLegacyBuildOptionsError extends StorybookError { `; } } + +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}. + + 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. + + ${this.data.error.stack || this.data.error.message} + `; + } +} diff --git a/code/lib/core-server/src/build-dev.ts b/code/lib/core-server/src/build-dev.ts index 7785afdee7b7..ac769bc08962 100644 --- a/code/lib/core-server/src/build-dev.ts +++ b/code/lib/core-server/src/build-dev.ts @@ -84,6 +84,7 @@ export async function buildDevStandalone( require.resolve('@storybook/core-server/dist/presets/common-override-preset'), ], ...options, + isCritical: true, }); const { renderer, builder, disableTelemetry } = await presets.apply('core', {}); diff --git a/code/lib/core-server/src/build-static.ts b/code/lib/core-server/src/build-static.ts index b8ca4a95fa6c..8782337a6db0 100644 --- a/code/lib/core-server/src/build-static.ts +++ b/code/lib/core-server/src/build-static.ts @@ -87,6 +87,7 @@ export async function buildStaticStandalone(options: BuildStaticStandaloneOption overridePresets: [ require.resolve('@storybook/core-server/dist/presets/common-override-preset'), ], + isCritical: true, ...options, });