-
-
Notifications
You must be signed in to change notification settings - Fork 9.8k
Addon-Vitest: Skip postinstall setup when configured #33712
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: next
Are you sure you want to change the base?
Conversation
|
View your CI Pipeline Execution ↗ for commit 2b12f5b
☁️ Nx Cloud last updated this comment at |
a03ec3f to
2b12f5b
Compare
📝 WalkthroughWalkthroughThis PR adds AST-based configuration detection to the Vitest addon's postinstall flow. The new Changes
Sequence DiagramsequenceDiagram
actor User
participant PostInstall as Postinstall Handler
participant ConfigDetector as isConfigAlreadySetup
participant AST as AST Parser<br/>(Traverse)
participant VitestConfig as Vitest Config
participant SetupFile as Setup File
User->>PostInstall: Run postinstall
PostInstall->>PostInstall: Check for vitest.workspace
alt Workspace exists
PostInstall->>ConfigDetector: Check if already configured
ConfigDetector->>VitestConfig: Read config content
ConfigDetector->>AST: Parse & traverse AST
AST->>AST: Find storybookTest plugin
AST-->>ConfigDetector: Plugin found or not found
ConfigDetector-->>PostInstall: isConfigAlreadySetup result
alt Already configured
PostInstall->>PostInstall: Log success & exit
else Not configured
PostInstall->>SetupFile: Create setup file
SetupFile-->>PostInstall: File created
end
else Root config exists
PostInstall->>ConfigDetector: Check if already configured
ConfigDetector->>VitestConfig: Read config content
ConfigDetector->>AST: Parse & traverse AST
AST-->>ConfigDetector: Plugin detection result
ConfigDetector-->>PostInstall: isConfigAlreadySetup result
alt Already configured
PostInstall->>PostInstall: Skip update & log
else Not configured
PostInstall->>VitestConfig: Update config with plugin
PostInstall->>SetupFile: Create or reuse setup file
end
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
✨ Finishing touches
Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
code/addons/vitest/src/postinstall.ts (1)
241-287: Avoid returning early so downstream setup still runs.
The early return skips a11y automigration and the final success/error messaging for workspace users, while the root-config path continues. Consider logging and then continuing (or guarding only the update block).🛠️ Suggested fix
- if (alreadyConfigured) { - logger.step( - CLI_COLORS.success('Vitest for Storybook is already properly configured. Skipping setup.') - ); - return; - } - - const workspaceTemplate = await loadTemplate('vitest.workspace.template.ts', { - EXTENDS_WORKSPACE: viteConfigFile - ? relative(dirname(vitestWorkspaceFile), viteConfigFile) - : '', - CONFIG_DIR: options.configDir, - SETUP_FILE: relative(dirname(vitestWorkspaceFile), existingSetupFile ?? vitestSetupFile), - }).then((t) => t.replace(`\n 'ROOT_CONFIG',`, '').replace(/\s+extends: '',/, '')); - const source = babelParse(workspaceTemplate); - const target = babelParse(workspaceFileContent); - - const updated = updateWorkspaceFile(source, target); - if (updated) { - logger.step(`Updating your Vitest workspace file...`); - - logger.log(`${vitestWorkspaceFile}`); - - const formattedContent = await formatFileContent(vitestWorkspaceFile, generate(target).code); - await writeFile(vitestWorkspaceFile, formattedContent); - } else { - logger.error( - dedent` - Could not update existing Vitest workspace file: - ${vitestWorkspaceFile} - - I was able to configure most of the addon but could not safely extend - your existing workspace file automatically, you must do it yourself. - - Please refer to the documentation to complete the setup manually: - https://storybook.js.org/docs/next/${DOCUMENTATION_LINK}#manual-setup-advanced - ` - ); - errors.push( - new AddonVitestPostinstallWorkspaceUpdateError({ filePath: vitestWorkspaceFile }) - ); - } + if (alreadyConfigured) { + logger.step( + CLI_COLORS.success('Vitest for Storybook is already properly configured. Skipping setup.') + ); + } else { + const workspaceTemplate = await loadTemplate('vitest.workspace.template.ts', { + EXTENDS_WORKSPACE: viteConfigFile + ? relative(dirname(vitestWorkspaceFile), viteConfigFile) + : '', + CONFIG_DIR: options.configDir, + SETUP_FILE: relative(dirname(vitestWorkspaceFile), existingSetupFile ?? vitestSetupFile), + }).then((t) => t.replace(`\n 'ROOT_CONFIG',`, '').replace(/\s+extends: '',/, '')); + const source = babelParse(workspaceTemplate); + const target = babelParse(workspaceFileContent); + + const updated = updateWorkspaceFile(source, target); + if (updated) { + logger.step(`Updating your Vitest workspace file...`); + + logger.log(`${vitestWorkspaceFile}`); + + const formattedContent = await formatFileContent( + vitestWorkspaceFile, + generate(target).code + ); + await writeFile(vitestWorkspaceFile, formattedContent); + } else { + logger.error( + dedent` + Could not update existing Vitest workspace file: + ${vitestWorkspaceFile} + + I was able to configure most of the addon but could not safely extend + your existing workspace file automatically, you must do it yourself. + + Please refer to the documentation to complete the setup manually: + https://storybook.js.org/docs/next/${DOCUMENTATION_LINK}#manual-setup-advanced + ` + ); + errors.push( + new AddonVitestPostinstallWorkspaceUpdateError({ filePath: vitestWorkspaceFile }) + ); + } + }
🤖 Fix all issues with AI agents
In `@code/addons/vitest/src/postinstall.ts`:
- Around line 427-473: The current isConfigAlreadySetup only checks for a plugin
reference and returns true even if setupFiles is missing; update
isConfigAlreadySetup to require both the plugin reference and that the config
object defines a non-empty setupFiles entry. Keep the existing AST traversal
that collects pluginIdentifiers (ImportDeclaration) and detects plugin usage
(CallExpression), then add a traversal that finds the exported config object
(e.g., ExportDefaultDeclaration with an ObjectExpression and/or
AssignmentExpression to module.exports) and looks for a setupFiles property
whose value is an ArrayExpression with at least one element or a non-empty
string; only return true when pluginReferenced is true AND setupFiles is
present/valid. Ensure you update references inside isConfigAlreadySetup
(pluginIdentifiers, pluginReferenced, and the AST traversals) rather than
changing callers.
🧹 Nitpick comments (1)
code/addons/vitest/src/postinstall.test.ts (1)
31-49: Add a negative case for missingsetupFiles.
OnceisConfigAlreadySetupenforces setupFiles presence, a test where the plugin exists but setupFiles is missing will lock in the smart-skip behavior.As per coding guidelines: Cover all branches, conditions, edge cases, error paths, and different input variations in unit tests.✅ Proposed test
it('returns false when storybookTest plugin is not used', () => { @@ expect(isConfigAlreadySetup('/project/vitest.config.ts', config, setupPath)).toBe(false); }); + + it('returns false when setupFiles is missing even if the plugin is present', () => { + const config = ` + import { defineConfig } from 'vitest/config'; + import { storybookTest } from '@storybook/addon-vitest/vitest-plugin'; + + export default defineConfig({ + test: { + projects: [ + { + extends: true, + plugins: [storybookTest({ configDir: '.storybook' })], + }, + ], + }, + }); + `; + + expect(isConfigAlreadySetup('/project/vitest.config.ts', config, setupPath)).toBe(false); + }); });
| function isStorybookTestPluginSource(value: string) { | ||
| return value === STORYBOOK_TEST_PLUGIN_SOURCE; | ||
| } | ||
|
|
||
| export function isConfigAlreadySetup(_configPath: string, configContent: string) { | ||
| let ast: ReturnType<typeof babelParse>; | ||
| try { | ||
| ast = babelParse(configContent); | ||
| } catch (e) { | ||
| return false; | ||
| } | ||
|
|
||
| const pluginIdentifiers = new Set<string>(); | ||
|
|
||
| traverse(ast, { | ||
| ImportDeclaration(path) { | ||
| const source = path.node.source.value; | ||
| if (typeof source === 'string' && isStorybookTestPluginSource(source)) { | ||
| path.node.specifiers.forEach((specifier) => { | ||
| if ('local' in specifier && specifier.local?.name) { | ||
| pluginIdentifiers.add(specifier.local.name); | ||
| } | ||
| }); | ||
| } | ||
| }, | ||
| }); | ||
|
|
||
| let pluginReferenced = false; | ||
|
|
||
| traverse(ast, { | ||
| CallExpression(path) { | ||
| if (pluginReferenced) { | ||
| path.stop(); | ||
| return; | ||
| } | ||
| const callee = path.node.callee; | ||
| if ( | ||
| callee.type === 'Identifier' && | ||
| (pluginIdentifiers.has(callee.name) || callee.name === 'storybookTest') | ||
| ) { | ||
| pluginReferenced = true; | ||
| path.stop(); | ||
| } | ||
| }, | ||
| }); | ||
|
|
||
| return pluginReferenced; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isConfigAlreadySetup should also verify setupFiles.
Right now any config that references the plugin is treated as fully configured, so setup can be skipped even when setupFiles is missing. That contradicts the “smart skip” requirement and leaves users partially configured.
🛠️ Suggested fix (plugin + setupFiles required)
-export function isConfigAlreadySetup(_configPath: string, configContent: string) {
+export function isConfigAlreadySetup(
+ configPath: string,
+ configContent: string,
+ setupPath?: string
+) {
let ast: ReturnType<typeof babelParse>;
try {
ast = babelParse(configContent);
} catch (e) {
return false;
}
const pluginIdentifiers = new Set<string>();
@@
- let pluginReferenced = false;
+ let pluginReferenced = false;
+ let setupFileReferenced = !setupPath;
+ const expectedSetupFiles = setupPath ? new Set<string>([setupPath]) : null;
+ if (setupPath) {
+ const relativeSetup = relative(dirname(configPath), setupPath);
+ expectedSetupFiles?.add(relativeSetup);
+ expectedSetupFiles?.add(`./${relativeSetup}`);
+ }
traverse(ast, {
+ ObjectProperty(path) {
+ if (!expectedSetupFiles) return;
+ const key = path.node.key;
+ const keyName =
+ key.type === 'Identifier' ? key.name : key.type === 'StringLiteral' ? key.value : null;
+ if (keyName !== 'setupFiles') return;
+ const value = path.node.value;
+ if (value.type !== 'ArrayExpression') return;
+ for (const element of value.elements) {
+ if (element?.type === 'StringLiteral' && expectedSetupFiles.has(element.value)) {
+ setupFileReferenced = true;
+ if (pluginReferenced) path.stop();
+ return;
+ }
+ }
+ },
CallExpression(path) {
- if (pluginReferenced) {
+ if (pluginReferenced && setupFileReferenced) {
path.stop();
return;
}
@@
- pluginReferenced = true;
- path.stop();
+ pluginReferenced = true;
+ if (setupFileReferenced) path.stop();
}
},
});
- return pluginReferenced;
+ return pluginReferenced && setupFileReferenced;
}🔧 Call-site updates (outside this range)
- const alreadyConfigured = isConfigAlreadySetup(vitestWorkspaceFile, workspaceFileContent);
+ const alreadyConfigured = isConfigAlreadySetup(
+ vitestWorkspaceFile,
+ workspaceFileContent,
+ existingSetupFile ?? vitestSetupFile
+ );- const alreadyConfigured = isConfigAlreadySetup(rootConfig, configFile);
+ const alreadyConfigured = isConfigAlreadySetup(
+ rootConfig,
+ configFile,
+ existingSetupFile ?? vitestSetupFile
+ );🤖 Prompt for AI Agents
In `@code/addons/vitest/src/postinstall.ts` around lines 427 - 473, The current
isConfigAlreadySetup only checks for a plugin reference and returns true even if
setupFiles is missing; update isConfigAlreadySetup to require both the plugin
reference and that the config object defines a non-empty setupFiles entry. Keep
the existing AST traversal that collects pluginIdentifiers (ImportDeclaration)
and detects plugin usage (CallExpression), then add a traversal that finds the
exported config object (e.g., ExportDefaultDeclaration with an ObjectExpression
and/or AssignmentExpression to module.exports) and looks for a setupFiles
property whose value is an ArrayExpression with at least one element or a
non-empty string; only return true when pluginReferenced is true AND setupFiles
is present/valid. Ensure you update references inside isConfigAlreadySetup
(pluginIdentifiers, pluginReferenced, and the AST traversals) rather than
changing callers.
Closes #33695
What I did
This PR refactors the
@storybook/addon-vitestpostinstall script to be more idempotent and user-friendly. Instead of failing when it encounters existing configurations or setup files, the script now intelligently detects existing setups and avoids redundant modifications.Checklist for Contributors
Testing
The changes in this PR are covered in the following automated tests:
Manual testing
Caution
This section is mandatory for all contributions. If you believe no manual test is necessary, please state so explicitly. Thanks!
Documentation
MIGRATION.MD
Checklist for Maintainers
When this PR is ready for testing, make sure to add
ci:normal,ci:mergedorci:dailyGH label to it to run a specific set of sandboxes. The particular set of sandboxes can be found incode/lib/cli-storybook/src/sandbox-templates.tsMake sure this PR contains one of the labels below:
Available labels
bug: Internal changes that fixes incorrect behavior.maintenance: User-facing maintenance tasks.dependencies: Upgrading (sometimes downgrading) dependencies.build: Internal-facing build tooling & test updates. Will not show up in release changelog.cleanup: Minor cleanup style change. Will not show up in release changelog.documentation: Documentation only changes. Will not show up in release changelog.feature request: Introducing a new feature.BREAKING CHANGE: Changes that break compatibility in some way with current major version.other: Changes that don't fit in the above categories.🦋 Canary release
This pull request has been released as version
0.0.0-pr-33712-sha-2b12f5bb. Try it out in a new sandbox by runningnpx storybook@0.0.0-pr-33712-sha-2b12f5bb sandboxor in an existing project withnpx storybook@0.0.0-pr-33712-sha-2b12f5bb upgrade.More information
0.0.0-pr-33712-sha-2b12f5bbvalentin/addon-vitest-skip-existing-configs2b12f5bb1769698848)To request a new release of this pull request, mention the
@storybookjs/coreteam.core team members can create a new canary release here or locally with
gh workflow run --repo storybookjs/storybook publish.yml --field pr=33712Summary by CodeRabbit
New Features
Tests
✏️ Tip: You can customize this high-level summary in your review settings.