Skip to content

Commit 9b895ec

Browse files
Merge pull request #33718 from storybookjs/valentin/bundle-addon-vitest-postinstall-into-storybook
CLI: Support addon-vitest setup when --skip-install is passed (cherry picked from commit 6d0104a)
1 parent 69d8d3b commit 9b895ec

File tree

15 files changed

+399
-162
lines changed

15 files changed

+399
-162
lines changed

code/addons/a11y/src/postinstall.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1-
import { JsPackageManagerFactory } from 'storybook/internal/common';
1+
import { JsPackageManagerFactory, versions } from 'storybook/internal/common';
22

33
import type { PostinstallOptions } from '../../../lib/cli-storybook/src/add';
44

55
export default async function postinstall(options: PostinstallOptions) {
6-
const args = ['storybook', 'automigrate', 'addon-a11y-addon-test'];
6+
const args = [
7+
options.skipInstall ? `storybook@${versions.storybook}` : `storybook`,
8+
'automigrate',
9+
'addon-a11y-addon-test',
10+
];
711

812
args.push('--loglevel', 'silent');
913
args.push('--skip-doctor');
@@ -25,5 +29,5 @@ export default async function postinstall(options: PostinstallOptions) {
2529
configDir: options.configDir,
2630
});
2731

28-
await jsPackageManager.runPackageCommand({ args });
32+
await jsPackageManager.runPackageCommand({ args, useRemotePkg: !!options.skipInstall });
2933
}

code/addons/vitest/src/postinstall.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
formatFileContent,
1010
getProjectRoot,
1111
getStorybookInfo,
12+
versions,
1213
} from 'storybook/internal/common';
1314
import { CLI_COLORS } from 'storybook/internal/node-logger';
1415
import type { StorybookError } from 'storybook/internal/server-errors';
@@ -161,6 +162,7 @@ export default async function postInstall(options: PostinstallOptions) {
161162
if (!options.skipInstall) {
162163
await addonVitestService.installPlaywright({
163164
yes: options.yes,
165+
useRemotePkg: !!options.skipInstall,
164166
});
165167
} else {
166168
logger.warn(dedent`
@@ -229,11 +231,11 @@ export default async function postInstall(options: PostinstallOptions) {
229231

230232
const getTemplateName = () => {
231233
if (isVitest4OrNewer) {
232-
return 'vitest.config.4.template.ts';
234+
return 'vitest.config.4.template';
233235
} else if (isVitest3_2To4) {
234-
return 'vitest.config.3.2.template.ts';
236+
return 'vitest.config.3.2.template';
235237
}
236-
return 'vitest.config.template.ts';
238+
return 'vitest.config.template';
237239
};
238240

239241
// If there's an existing workspace file, we update that file to include the Storybook Addon Vitest plugin.
@@ -249,7 +251,7 @@ export default async function postInstall(options: PostinstallOptions) {
249251
return;
250252
}
251253

252-
const workspaceTemplate = await loadTemplate('vitest.workspace.template.ts', {
254+
const workspaceTemplate = await loadTemplate('vitest.workspace.template', {
253255
EXTENDS_WORKSPACE: viteConfigFile
254256
? relative(dirname(vitestWorkspaceFile), viteConfigFile)
255257
: '',
@@ -358,7 +360,7 @@ export default async function postInstall(options: PostinstallOptions) {
358360
if (a11yAddon) {
359361
try {
360362
const command = [
361-
'storybook',
363+
options.skipInstall ? `storybook@${versions.storybook}` : `storybook`,
362364
'automigrate',
363365
'addon-a11y-addon-test',
364366
'--loglevel',
@@ -381,7 +383,12 @@ export default async function postInstall(options: PostinstallOptions) {
381383

382384
await prompt.executeTask(
383385
// TODO: Remove stdio: 'ignore' once we have a way to log the output of the command properly
384-
() => packageManager.runPackageCommand({ args: command, stdio: 'ignore' }),
386+
() =>
387+
packageManager.runPackageCommand({
388+
args: command,
389+
stdio: 'ignore',
390+
useRemotePkg: !!options.skipInstall,
391+
}),
385392
{
386393
intro: 'Setting up a11y addon for @storybook/addon-vitest',
387394
error: 'Failed to setup a11y addon for @storybook/addon-vitest',

code/addons/vitest/src/typings.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,8 @@ interface ImportMetaEnv {
88
interface ImportMeta {
99
readonly env: ImportMetaEnv;
1010
}
11+
12+
declare module '*?raw' {
13+
const content: string;
14+
export default content;
15+
}

code/addons/vitest/src/updateVitestFile.test.ts

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ vi.mock('../../../core/src/shared/utils/module', () => ({
2222
describe('updateConfigFile', () => {
2323
it('updates vite config file', async () => {
2424
const source = babel.babelParse(
25-
await loadTemplate('vitest.config.template.ts', {
25+
await loadTemplate('vitest.config.template', {
2626
CONFIG_DIR: '.storybook',
2727
BROWSER_CONFIG: "{ provider: 'playwright' }",
2828
SETUP_FILE: '../.storybook/vitest.setup.ts',
@@ -102,7 +102,7 @@ describe('updateConfigFile', () => {
102102

103103
it('supports object notation without defineConfig', async () => {
104104
const source = babel.babelParse(
105-
await loadTemplate('vitest.config.template.ts', {
105+
await loadTemplate('vitest.config.template', {
106106
CONFIG_DIR: '.storybook',
107107
BROWSER_CONFIG: "{ provider: 'playwright' }",
108108
SETUP_FILE: '../.storybook/vitest.setup.ts',
@@ -182,7 +182,7 @@ describe('updateConfigFile', () => {
182182

183183
it('does not support function notation', async () => {
184184
const source = babel.babelParse(
185-
await loadTemplate('vitest.config.template.ts', {
185+
await loadTemplate('vitest.config.template', {
186186
CONFIG_DIR: '.storybook',
187187
BROWSER_CONFIG: "{ provider: 'playwright' }",
188188
SETUP_FILE: '../.storybook/vitest.setup.ts',
@@ -214,7 +214,7 @@ describe('updateConfigFile', () => {
214214

215215
it('adds projects property to test config', async () => {
216216
const source = babel.babelParse(
217-
await loadTemplate('vitest.config.3.2.template.ts', {
217+
await loadTemplate('vitest.config.3.2.template', {
218218
CONFIG_DIR: '.storybook',
219219
BROWSER_CONFIG: "{ provider: 'playwright' }",
220220
SETUP_FILE: '../.storybook/vitest.setup.ts',
@@ -378,7 +378,7 @@ describe('updateConfigFile', () => {
378378

379379
it('edits projects property of test config', async () => {
380380
const source = babel.babelParse(
381-
await loadTemplate('vitest.config.3.2.template.ts', {
381+
await loadTemplate('vitest.config.3.2.template', {
382382
CONFIG_DIR: '.storybook',
383383
BROWSER_CONFIG: "{ provider: 'playwright' }",
384384
SETUP_FILE: '../.storybook/vitest.setup.ts',
@@ -458,7 +458,7 @@ describe('updateConfigFile', () => {
458458

459459
it('adds workspace property to test config', async () => {
460460
const source = babel.babelParse(
461-
await loadTemplate('vitest.config.template.ts', {
461+
await loadTemplate('vitest.config.template', {
462462
CONFIG_DIR: '.storybook',
463463
BROWSER_CONFIG: "{ provider: 'playwright' }",
464464
SETUP_FILE: '../.storybook/vitest.setup.ts',
@@ -537,7 +537,7 @@ describe('updateConfigFile', () => {
537537

538538
it('adds test property to vite config', async () => {
539539
const source = babel.babelParse(
540-
await loadTemplate('vitest.config.template.ts', {
540+
await loadTemplate('vitest.config.template', {
541541
CONFIG_DIR: '.storybook',
542542
BROWSER_CONFIG: "{ provider: 'playwright' }",
543543
SETUP_FILE: '../.storybook/vitest.setup.ts',
@@ -612,7 +612,7 @@ describe('updateConfigFile', () => {
612612

613613
it('supports mergeConfig with multiple defineConfig calls, finding the one with test', async () => {
614614
const source = babel.babelParse(
615-
await loadTemplate('vitest.config.template.ts', {
615+
await loadTemplate('vitest.config.template', {
616616
CONFIG_DIR: '.storybook',
617617
BROWSER_CONFIG: "{ provider: 'playwright' }",
618618
SETUP_FILE: '../.storybook/vitest.setup.ts',
@@ -698,7 +698,7 @@ describe('updateConfigFile', () => {
698698
});
699699
it('supports mergeConfig without defineConfig calls', async () => {
700700
const source = babel.babelParse(
701-
await loadTemplate('vitest.config.template.ts', {
701+
await loadTemplate('vitest.config.template', {
702702
CONFIG_DIR: '.storybook',
703703
BROWSER_CONFIG: "{ provider: 'playwright' }",
704704
SETUP_FILE: '../.storybook/vitest.setup.ts',
@@ -781,7 +781,7 @@ describe('updateConfigFile', () => {
781781

782782
it('supports mergeConfig without config containing test property', async () => {
783783
const source = babel.babelParse(
784-
await loadTemplate('vitest.config.template.ts', {
784+
await loadTemplate('vitest.config.template', {
785785
CONFIG_DIR: '.storybook',
786786
BROWSER_CONFIG: "{ provider: 'playwright' }",
787787
SETUP_FILE: '../.storybook/vitest.setup.ts',
@@ -857,7 +857,7 @@ describe('updateConfigFile', () => {
857857

858858
it('supports mergeConfig with defineConfig pattern using projects (Vitest 3.2+)', async () => {
859859
const source = babel.babelParse(
860-
await loadTemplate('vitest.config.3.2.template.ts', {
860+
await loadTemplate('vitest.config.3.2.template', {
861861
CONFIG_DIR: '.storybook',
862862
BROWSER_CONFIG: "{ provider: 'playwright' }",
863863
SETUP_FILE: '../.storybook/vitest.setup.ts',
@@ -941,7 +941,7 @@ describe('updateConfigFile', () => {
941941

942942
it('appends storybook project to existing test.projects array (no double nesting)', async () => {
943943
const source = babel.babelParse(
944-
await loadTemplate('vitest.config.3.2.template.ts', {
944+
await loadTemplate('vitest.config.3.2.template', {
945945
CONFIG_DIR: '.storybook',
946946
BROWSER_CONFIG: "{ provider: 'playwright' }",
947947
SETUP_FILE: '../.storybook/vitest.setup.ts',
@@ -1030,7 +1030,7 @@ describe('updateConfigFile', () => {
10301030

10311031
it('extracts coverage config and keeps it at top level when using workspace', async () => {
10321032
const source = babel.babelParse(
1033-
await loadTemplate('vitest.config.template.ts', {
1033+
await loadTemplate('vitest.config.template', {
10341034
CONFIG_DIR: '.storybook',
10351035
BROWSER_CONFIG: "{ provider: 'playwright' }",
10361036
SETUP_FILE: '../.storybook/vitest.setup.ts',
@@ -1129,7 +1129,7 @@ describe('updateConfigFile', () => {
11291129

11301130
it('extracts coverage config and keeps it at top level when using projects', async () => {
11311131
const source = babel.babelParse(
1132-
await loadTemplate('vitest.config.3.2.template.ts', {
1132+
await loadTemplate('vitest.config.3.2.template', {
11331133
CONFIG_DIR: '.storybook',
11341134
BROWSER_CONFIG: "{ provider: 'playwright' }",
11351135
SETUP_FILE: '../.storybook/vitest.setup.ts',
@@ -1230,7 +1230,7 @@ describe('updateConfigFile', () => {
12301230
describe('updateWorkspaceFile', () => {
12311231
it('updates vitest workspace file using array syntax', async () => {
12321232
const source = babel.babelParse(
1233-
await loadTemplate('vitest.workspace.template.ts', {
1233+
await loadTemplate('vitest.workspace.template', {
12341234
EXTENDS_WORKSPACE: '',
12351235
CONFIG_DIR: '.storybook',
12361236
BROWSER_CONFIG: "{ provider: 'playwright' }",
@@ -1286,7 +1286,7 @@ describe('updateWorkspaceFile', () => {
12861286

12871287
it('updates vitest workspace file using defineWorkspace syntax', async () => {
12881288
const source = babel.babelParse(
1289-
await loadTemplate('vitest.workspace.template.ts', {
1289+
await loadTemplate('vitest.workspace.template', {
12901290
EXTENDS_WORKSPACE: '',
12911291
CONFIG_DIR: '.storybook',
12921292
BROWSER_CONFIG: "{ provider: 'playwright' }",
@@ -1349,7 +1349,7 @@ describe('loadTemplate', () => {
13491349
// Windows-style path with backslashes (need to escape them in JS strings)
13501350
const windowsPath = '.\\apps\\frontend-storybook\\.storybook';
13511351

1352-
const result = await loadTemplate('vitest.config.template.ts', {
1352+
const result = await loadTemplate('vitest.config.template', {
13531353
CONFIG_DIR: windowsPath,
13541354
SETUP_FILE: '.\\apps\\frontend-storybook\\.storybook\\vitest.setup.ts',
13551355
});
@@ -1363,7 +1363,7 @@ describe('loadTemplate', () => {
13631363
// Unix-style path with forward slashes
13641364
const unixPath = './apps/frontend-storybook/.storybook';
13651365

1366-
const result = await loadTemplate('vitest.config.template.ts', {
1366+
const result = await loadTemplate('vitest.config.template', {
13671367
CONFIG_DIR: unixPath,
13681368
SETUP_FILE: './apps/frontend-storybook/.storybook/vitest.setup.ts',
13691369
});

code/addons/vitest/src/updateVitestFile.ts

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,30 @@
1-
import * as fs from 'node:fs/promises';
2-
31
import type { BabelFile, types as t } from 'storybook/internal/babel';
42

5-
import { join, normalize } from 'pathe';
3+
import { normalize } from 'pathe';
64

7-
import { resolvePackageDir } from '../../../core/src/shared/utils/module';
5+
/**
6+
* Each template is imported separately to allow the build system to process the template as raw
7+
* text. A mix of globs and the "?raw" string query is not supported in esbuild
8+
*/
9+
async function getTemplatePath(name: string) {
10+
switch (name) {
11+
case 'vitest.config.template':
12+
return import('../templates/vitest.config.template?raw');
13+
case 'vitest.config.4.template':
14+
return import('../templates/vitest.config.4.template?raw');
15+
case 'vitest.config.3.2.template':
16+
return import('../templates/vitest.config.3.2.template?raw');
17+
case 'vitest.workspace.template':
18+
return import('../templates/vitest.workspace.template?raw');
19+
default:
20+
throw new Error(`Unknown template: ${name}`);
21+
}
22+
}
823

924
export const loadTemplate = async (name: string, replacements: Record<string, string>) => {
10-
let template = await fs.readFile(
11-
join(resolvePackageDir('@storybook/addon-vitest'), 'templates', name),
12-
'utf8'
13-
);
25+
// Dynamically import the template file as plain text
26+
const templateModule = await getTemplatePath(name);
27+
let template = templateModule.default;
1428
// Normalize Windows paths (backslashes) to forward slashes for JavaScript string compatibility
1529
Object.entries(replacements).forEach(
1630
([key, value]) => (template = template.replace(key, normalize(value)))

code/core/src/cli/AddonVitestService.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,11 @@ export class AddonVitestService {
115115
* @returns Array of error messages if installation fails
116116
*/
117117
async installPlaywright(
118-
options: { yes?: boolean } = {}
118+
options: {
119+
yes?: boolean;
120+
/** Is set to true if Storybook didn't install the dependencies yet */
121+
useRemotePkg?: boolean;
122+
} = {}
119123
): Promise<{ errors: string[]; result: 'installed' | 'skipped' | 'aborted' | 'failed' }> {
120124
const errors: string[] = [];
121125

@@ -148,6 +152,7 @@ export class AddonVitestService {
148152
(signal) =>
149153
this.packageManager.runPackageCommand({
150154
args: playwrightCommand,
155+
useRemotePkg: options.useRemotePkg,
151156
stdio: ['inherit', 'pipe', 'pipe'],
152157
signal,
153158
}),

code/core/src/common/js-package-manager/JsPackageManager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -624,7 +624,7 @@ export abstract class JsPackageManager {
624624
stdio?: 'inherit' | 'pipe' | 'ignore'
625625
): ResultPromise;
626626
public abstract runPackageCommand(
627-
options: Omit<ExecuteCommandOptions, 'command'> & { args: string[] }
627+
options: Omit<ExecuteCommandOptions, 'command'> & { args: string[]; useRemotePkg?: boolean }
628628
): ResultPromise;
629629
public abstract findInstallations(pattern?: string[]): Promise<InstallationMetadata | undefined>;
630630
public abstract findInstallations(

code/core/src/common/js-package-manager/PNPMProxy.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,15 @@ export class PNPMProxy extends JsPackageManager {
7979

8080
public runPackageCommand({
8181
args,
82+
useRemotePkg = false,
8283
...options
83-
}: Omit<ExecuteCommandOptions, 'command'> & { args: string[] }): ResultPromise {
84+
}: Omit<ExecuteCommandOptions, 'command'> & {
85+
args: string[];
86+
useRemotePkg?: boolean;
87+
}): ResultPromise {
8488
return executeCommand({
8589
command: 'pnpm',
86-
args: ['exec', ...args],
90+
args: [useRemotePkg ? 'dlx' : 'exec', ...args],
8791
...options,
8892
});
8993
}

code/lib/cli-storybook/src/automigrate/index.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { type JsPackageManager } from 'storybook/internal/common';
2+
import { versions } from 'storybook/internal/common';
23
import { logTracker, logger, prompt } from 'storybook/internal/node-logger';
34
import { AutomigrateError } from 'storybook/internal/server-errors';
45
import type { StorybookConfigRaw } from 'storybook/internal/types';
@@ -56,18 +57,14 @@ export const doAutomigrate = async (options: AutofixOptionsFromCLI) => {
5657
packageManagerName: options.packageManager,
5758
});
5859

59-
if (!versionInstalled) {
60-
throw new Error('Could not determine Storybook version');
61-
}
62-
6360
if (!mainConfigPath) {
6461
throw new Error('Could not determine main config path');
6562
}
6663

6764
const outcome = await automigrate({
6865
...options,
6966
packageManager,
70-
storybookVersion: versionInstalled,
67+
storybookVersion: versionInstalled || versions.storybook,
7168
mainConfigPath,
7269
mainConfig,
7370
previewConfigPath,

0 commit comments

Comments
 (0)