Skip to content

Commit b2fb7d8

Browse files
Merge pull request #34193 from storybookjs/valentin/streamline-config-validation-detection-across-init-and-postinstall
Addon-Vitest: Streamline vite(st) config detection across init and postinstall
2 parents cd857b9 + 4ef9a1e commit b2fb7d8

File tree

7 files changed

+715
-226
lines changed

7 files changed

+715
-226
lines changed

code/addons/vitest/src/postinstall.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { existsSync } from 'node:fs';
21
import * as fs from 'node:fs/promises';
32
import { writeFile } from 'node:fs/promises';
43
import os from 'node:os';

code/addons/vitest/src/updateVitestFile.ts

Lines changed: 7 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { resolveExpression } from 'storybook/internal/babel';
1+
import {
2+
getConfigObjectFromMergeArg,
3+
getEffectiveMergeConfigCall,
4+
getTargetConfigObject,
5+
isDefineConfigLike,
6+
resolveExpression,
7+
} from 'storybook/internal/babel';
28
import type { BabelFile, types as t } from 'storybook/internal/babel';
39

410
import { normalize } from 'pathe';
@@ -69,57 +75,6 @@ const mergeProperties = (
6975
}
7076
};
7177

72-
/**
73-
* Returns true if the identifier is a local alias for `defineConfig`/`defineProject` imported from
74-
* either `vitest/config` or `vite`.
75-
*/
76-
const isImportedDefineConfigLikeIdentifier = (localName: string, ast: BabelFile['ast']): boolean =>
77-
ast.program.body.some(
78-
(node): boolean =>
79-
node.type === 'ImportDeclaration' &&
80-
(node.source.value === 'vitest/config' || node.source.value === 'vite') &&
81-
node.specifiers.some(
82-
(specifier) =>
83-
specifier.type === 'ImportSpecifier' &&
84-
specifier.local.type === 'Identifier' &&
85-
specifier.local.name === localName &&
86-
specifier.imported.type === 'Identifier' &&
87-
(specifier.imported.name === 'defineConfig' ||
88-
specifier.imported.name === 'defineProject')
89-
)
90-
);
91-
92-
/** Returns true if the call expression is a defineConfig or defineProject call (including aliases). */
93-
const isDefineConfigLike = (node: t.CallExpression, ast: BabelFile['ast']): boolean =>
94-
node.callee.type === 'Identifier' &&
95-
(node.callee.name === 'defineConfig' ||
96-
node.callee.name === 'defineProject' ||
97-
isImportedDefineConfigLikeIdentifier(node.callee.name, ast));
98-
99-
/**
100-
* Resolves a mergeConfig argument to a config object expression when possible. Supports both direct
101-
* object args and wrapped forms like `defineConfig({ ... })`.
102-
*/
103-
const getConfigObjectFromMergeArg = (
104-
arg: t.Expression,
105-
ast: BabelFile['ast']
106-
): t.ObjectExpression | null => {
107-
const resolved = resolveExpression(arg, ast);
108-
if (!resolved) {
109-
return null;
110-
}
111-
112-
if (resolved.type === 'ObjectExpression') {
113-
return resolved;
114-
}
115-
116-
if (resolved.type === 'CallExpression' && resolved.arguments[0]?.type === 'ObjectExpression') {
117-
return resolved.arguments[0] as t.ObjectExpression;
118-
}
119-
120-
return null;
121-
};
122-
12378
/**
12479
* Resolves the value of a `test` ObjectProperty to an ObjectExpression. Handles both inline objects
12580
* and shorthand identifier references, e.g.: `{ test: { ... } }` → returns the inline
@@ -368,69 +323,6 @@ const mergeTemplateIntoConfigObject = (
368323
mergeProperties(properties, targetConfigObject.properties);
369324
};
370325

371-
/**
372-
* Extracts the effective mergeConfig call from a declaration, handling wrappers:
373-
*
374-
* - TypeScript type annotations (as X, satisfies X)
375-
* - DefineConfig(mergeConfig(...)) outer wrapper
376-
* - Variable references (export default config where config = mergeConfig(...))
377-
*/
378-
const getEffectiveMergeConfigCall = (
379-
decl: t.Expression | t.Declaration,
380-
ast: BabelFile['ast']
381-
): t.CallExpression | null => {
382-
const resolved = resolveExpression(decl, ast);
383-
if (!resolved || resolved.type !== 'CallExpression') {
384-
return null;
385-
}
386-
387-
// Handle defineConfig(mergeConfig(...)) – arg may itself be wrapped in a TS type expression
388-
if (isDefineConfigLike(resolved, ast) && resolved.arguments.length > 0) {
389-
const innerArg = resolveExpression(resolved.arguments[0] as t.Expression, ast);
390-
if (
391-
innerArg?.type === 'CallExpression' &&
392-
innerArg.callee.type === 'Identifier' &&
393-
innerArg.callee.name === 'mergeConfig'
394-
) {
395-
return innerArg;
396-
}
397-
}
398-
399-
// Handle mergeConfig(...) directly
400-
if (resolved.callee.type === 'Identifier' && resolved.callee.name === 'mergeConfig') {
401-
return resolved;
402-
}
403-
404-
return null;
405-
};
406-
407-
/**
408-
* Resolves the target's default export to the actual config object expression we can merge into.
409-
* Handles: export default defineConfig({}), export default defineProject({}), export default {},
410-
* and export default config (where config is a variable holding one of those), as well as
411-
* TypeScript type annotations on the declaration.
412-
*/
413-
const getTargetConfigObject = (
414-
target: BabelFile['ast'],
415-
exportDefault: t.ExportDefaultDeclaration
416-
): t.ObjectExpression | null => {
417-
const resolved = resolveExpression(exportDefault.declaration, target);
418-
if (!resolved) {
419-
return null;
420-
}
421-
if (resolved.type === 'ObjectExpression') {
422-
return resolved;
423-
}
424-
if (
425-
resolved.type === 'CallExpression' &&
426-
isDefineConfigLike(resolved, target) &&
427-
resolved.arguments[0]?.type === 'ObjectExpression'
428-
) {
429-
return resolved.arguments[0] as t.ObjectExpression;
430-
}
431-
return null;
432-
};
433-
434326
/**
435327
* Merges a source Vitest configuration AST into a target configuration AST.
436328
*

code/core/src/babel/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,15 @@ import * as recast from 'recast';
1515

1616
export * from './babelParse';
1717
export { unwrapTSExpression, resolveExpression } from './expression-resolver';
18+
export {
19+
isImportedDefineConfigLikeIdentifier,
20+
isDefineConfigLike,
21+
getConfigObjectFromMergeArg,
22+
getEffectiveMergeConfigCall,
23+
getTargetConfigObject,
24+
canUpdateVitestConfigFile,
25+
canUpdateVitestWorkspaceFile,
26+
} from './vitest-config-helpers';
1827

1928
// @ts-expect-error (needed due to it's use of `exports.default`)
2029
const traverse = (bt.default || bt) as typeof bt;

0 commit comments

Comments
 (0)