Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 13 additions & 10 deletions code/addons/vitest/src/postinstall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,14 @@ import {
getStorybookInfo,
} from 'storybook/internal/common';
import { CLI_COLORS } from 'storybook/internal/node-logger';
import type { StorybookError } from 'storybook/internal/server-errors';
import {
AddonVitestPostinstallConfigUpdateError,
AddonVitestPostinstallError,
AddonVitestPostinstallExistingSetupFileError,
AddonVitestPostinstallFailedAddonA11yError,
AddonVitestPostinstallPrerequisiteCheckError,
AddonVitestPostinstallWorkspaceUpdateError,
} from 'storybook/internal/server-errors';
import { SupportedFramework } from 'storybook/internal/types';

Expand All @@ -32,7 +37,7 @@ const EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.cts', '.mts', '.cjs', '.mjs'
const addonA11yName = '@storybook/addon-a11y';

export default async function postInstall(options: PostinstallOptions) {
const errors: string[] = [];
const errors: InstanceType<typeof StorybookError>[] = [];
const { logger, prompt } = options;

const packageManager = JsPackageManagerFactory.getPackageManager({
Expand Down Expand Up @@ -138,10 +143,9 @@ export default async function postInstall(options: PostinstallOptions) {
// Install Playwright browser binaries using AddonVitestService
if (!options.skipDependencyManagement) {
if (!options.skipInstall) {
const { errors: playwrightErrors } = await addonVitestService.installPlaywright({
await addonVitestService.installPlaywright({
yes: options.yes,
});
Comment on lines +146 to 148
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

find . -name "AddonVitestService.ts" -o -name "*AddonVitest*" | head -20

Repository: storybookjs/storybook

Length of output: 205


🏁 Script executed:

cd code && find . -path "*vitest*" -name "*.ts" -type f | head -20

Repository: storybookjs/storybook

Length of output: 814


🏁 Script executed:

cat -n code/core/src/cli/AddonVitestService.ts | head -100

Repository: storybookjs/storybook

Length of output: 4307


🏁 Script executed:

wc -l code/core/src/cli/AddonVitestService.ts

Repository: storybookjs/storybook

Length of output: 108


🏁 Script executed:

rg -n 'installPlaywright' code/core/src/cli/AddonVitestService.ts -A 30

Repository: storybookjs/storybook

Length of output: 1509


🏁 Script executed:

cat -n code/addons/vitest/src/postinstall.ts | head -160

Repository: storybookjs/storybook

Length of output: 7263


🏁 Script executed:

rg -n 'installPlaywright' code/core/src/cli/AddonVitestService.ts -A 50 | head -80

Repository: storybookjs/storybook

Length of output: 2291


🏁 Script executed:

rg -n 'installPlaywright' code/core/src/cli/AddonVitestService.ts -A 70 | tail -40

Repository: storybookjs/storybook

Length of output: 1379


🏁 Script executed:

sed -n '166,190p' code/core/src/cli/AddonVitestService.ts

Repository: storybookjs/storybook

Length of output: 710


🏁 Script executed:

sed -n '39,160p' code/addons/vitest/src/postinstall.ts | cat -n

Repository: storybookjs/storybook

Length of output: 5533


Handle Playwright installation failures by checking the return value.

The call to addonVitestService.installPlaywright() ignores the returned { errors, result } object. If installation fails (result='failed'), execution continues silently without notifying the user. Capture the return value and either throw an error or log a warning when result === 'failed' to maintain consistency with the error handling pattern used elsewhere in this function (e.g., lines 56-61 for compatibility checks).

🤖 Prompt for AI Agents
In code/addons/vitest/src/postinstall.ts around lines 146 to 148, the call to
addonVitestService.installPlaywright(...) currently ignores the returned {
errors, result } object; capture the return value, check if result === 'failed',
and handle it like the compatibility checks earlier (lines ~56-61) by either
throwing an Error (including the errors payload or a descriptive message) or
logging a warning and exiting—ensure you pass through options.yes as before and
include clear context in the error/warning so installation failures do not
continue silently.

errors.push(...playwrightErrors);
} else {
logger.warn(dedent`
Playwright browser binaries installation skipped. Please run the following command manually later:
Expand All @@ -164,7 +168,7 @@ export default async function postInstall(options: PostinstallOptions) {
`;
logger.line();
logger.error(`${errorMessage}\n`);
errors.push('Found existing Vitest setup file');
errors.push(new AddonVitestPostinstallExistingSetupFileError({ filePath: vitestSetupFile }));
} else {
logger.step(`Creating a Vitest setup file for Storybook:`);
logger.log(`${vitestSetupFile}\n`);
Expand Down Expand Up @@ -255,7 +259,9 @@ export default async function postInstall(options: PostinstallOptions) {
https://storybook.js.org/docs/next/${DOCUMENTATION_LINK}#manual-setup
`
);
errors.push('Unable to update existing Vitest workspace file');
errors.push(
new AddonVitestPostinstallWorkspaceUpdateError({ filePath: vitestWorkspaceFile })
);
}
}
// If there's an existing Vite/Vitest config with workspaces, we update it to include the Storybook Addon Vitest plugin.
Expand Down Expand Up @@ -300,7 +306,7 @@ export default async function postInstall(options: PostinstallOptions) {
Please refer to the documentation to complete the setup manually:
https://storybook.js.org/docs/writing-tests/integrations/vitest-addon#manual-setup
`);
errors.push('Unable to update existing Vitest config file');
errors.push(new AddonVitestPostinstallConfigUpdateError({ filePath: rootConfig }));
}
}
// If there's no existing Vitest/Vite config, we create a new Vitest config file.
Expand Down Expand Up @@ -361,10 +367,7 @@ export default async function postInstall(options: PostinstallOptions) {
Please refer to the documentation to complete the setup manually:
https://storybook.js.org/docs/writing-tests/accessibility-testing#test-addon-integration
`);
errors.push(
"The @storybook/addon-a11y couldn't be set up for the Vitest addon" +
(e instanceof Error ? e.stack : String(e))
);
errors.push(new AddonVitestPostinstallFailedAddonA11yError({ error: e }));
}
}

Expand Down
14 changes: 13 additions & 1 deletion code/core/src/core-server/withTelemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ export async function sendTelemetryError(
_error: unknown,
eventType: EventType,
options: TelemetryOptions,
blocking = true
blocking = true,
parent?: StorybookError
) {
try {
let errorLevel = 'error';
Expand Down Expand Up @@ -125,13 +126,24 @@ export async function sendTelemetryError(
errorHash,
// if we ever end up sending a non-error instance, we'd like to know
isErrorInstance: error instanceof Error,
// Include parent error information if this is a sub-error
...(parent ? { parent: parent.fullErrorCode } : {}),
},
{
immediate: true,
configDir: options.cliOptions.configDir || options.presetOptions?.configDir,
enableCrashReports: errorLevel === 'full',
}
);

// If this is a StorybookError with sub-errors, send telemetry for each sub-error separately
if (error instanceof StorybookError && error.subErrors.length > 0) {
for (const subError of error.subErrors) {
if (subError instanceof StorybookError) {
await sendTelemetryError(subError, eventType, options, blocking, error);
}
}
}
}
} catch (err) {
// if this throws an error, we just move on
Expand Down
65 changes: 64 additions & 1 deletion code/core/src/server-errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import type { Status } from './shared/status-store';
import type { StatusTypeId } from './shared/status-store';
import { StorybookError } from './storybook-error';

export { StorybookError } from './storybook-error';

/**
* If you can't find a suitable category for your error, create one based on the package name/file
* path of which the error is thrown. For instance: If it's from `@storybook/node-logger`, then
Expand Down Expand Up @@ -468,14 +470,75 @@ export class AddonVitestPostinstallPrerequisiteCheckError extends StorybookError
}
}

export class AddonVitestPostinstallFailedAddonA11yError extends StorybookError {
constructor(public data: { error: unknown | Error }) {
super({
name: 'AddonVitestPostinstallFailedAddonA11yError',
message: "The @storybook/addon-a11y couldn't be set up for the Vitest addon",
category: Category.CLI_INIT,
isHandledError: true,
code: 6,
});
}
}

export class AddonVitestPostinstallExistingSetupFileError extends StorybookError {
constructor(public data: { filePath: string }) {
super({
name: 'AddonVitestPostinstallExistingSetupFileError',
category: Category.CLI_INIT,
isHandledError: true,
code: 7,
documentation: `https://storybook.js.org/docs/writing-tests/integrations/vitest-addon#manual-setup`,
message: dedent`
Found an existing Vitest setup file: ${data.filePath}
Please refer to the documentation to complete the setup manually.
`,
});
}
}

export class AddonVitestPostinstallWorkspaceUpdateError extends StorybookError {
constructor(public data: { filePath: string }) {
super({
name: 'AddonVitestPostinstallWorkspaceUpdateError',
category: Category.CLI_INIT,
isHandledError: true,
code: 8,
documentation: `https://storybook.js.org/docs/writing-tests/integrations/vitest-addon#manual-setup`,
message: dedent`
Could not update existing Vitest workspace file: ${data.filePath}
Please refer to the documentation to complete the setup manually.
`,
});
}
}

export class AddonVitestPostinstallConfigUpdateError extends StorybookError {
constructor(public data: { filePath: string }) {
super({
name: 'AddonVitestPostinstallConfigUpdateError',
category: Category.CLI_INIT,
isHandledError: true,
code: 9,
documentation: `https://storybook.js.org/docs/writing-tests/integrations/vitest-addon#manual-setup`,
message: dedent`
Unable to update existing Vitest config file: ${data.filePath}
Please refer to the documentation to complete the setup manually.
`,
});
}
}

export class AddonVitestPostinstallError extends StorybookError {
constructor(public data: { errors: string[] }) {
constructor(public data: { errors: StorybookError[] }) {
super({
name: 'AddonVitestPostinstallError',
category: Category.CLI_INIT,
isHandledError: true,
code: 5,
message: 'The Vitest addon setup failed.',
subErrors: data.errors,
});
}
}
Expand Down
26 changes: 26 additions & 0 deletions code/core/src/storybook-error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,20 +71,46 @@ export abstract class StorybookError extends Error {
this._name = name;
}

/**
* A collection of sub errors which relate to a parent error.
*
* Sub-errors are used to represent multiple related errors that occurred together. When a
* StorybookError with sub-errors is sent to telemetry, both the parent error and each sub-error
* are sent as separate telemetry events. This allows for better error tracking and debugging.
*
* @example
*
* ```ts
* const error1 = new SomeError();
* const error2 = new AnotherError();
* const parentError = new ParentError({
* // ... other props
* subErrors: [error1, error2],
* });
* ```
*/
subErrors: StorybookError[] = [];

constructor(props: {
category: string;
code: number;
message: string;
documentation?: boolean | string | string[];
isHandledError?: boolean;
name: string;
/**
* Optional array of sub-errors that are related to this error. When this error is sent to
* telemetry, each sub-error will be sent as a separate event.
*/
subErrors?: StorybookError[];
}) {
super(StorybookError.getFullMessage(props));
this.category = props.category;
this.documentation = props.documentation ?? false;
this.code = props.code;
this.isHandledError = props.isHandledError ?? false;
this.name = props.name;
this.subErrors = props.subErrors ?? [];
}

/** Generates the error message along with additional documentation link (if applicable). */
Expand Down
2 changes: 1 addition & 1 deletion code/lib/create-storybook/src/initiate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export async function doInitiate(options: CommandOptions): Promise<
!!options.dev &&
!options.skipInstall &&
shouldRunDev !== false &&
ErrorCollector.getErrors().length === 0,
dependencyInstallationResult.status === 'success',
shouldOnboard: newUser,
projectType,
packageManager,
Expand Down
Loading