Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
c27f4c7
Prompt: Run vitest less times, improve play functions
yannbf Apr 28, 2026
5583710
Prettify package manager name to help show Yarn 1 vs Berry
Sidnioulz Apr 29, 2026
b7b9961
Avoid using console.log for stdout writes
Sidnioulz Apr 29, 2026
426a2ec
Address PR feedback
Sidnioulz Apr 29, 2026
ee0c5e1
Apply suggestion from @Sidnioulz
Sidnioulz Apr 29, 2026
712228e
Improve package manager code handling in ai setup
Sidnioulz Apr 29, 2026
ec34264
Mock package manager in AI tests
Sidnioulz Apr 30, 2026
21af0fa
reformat this file AGAIN
Sidnioulz Apr 30, 2026
97ca530
fix types
yannbf Apr 30, 2026
bcd3bf3
Merge branch 'project/sb-agentic-setup' into yann/prompt-optimized-tests
yannbf Apr 30, 2026
08b6cd2
use the new prompt format
yannbf Apr 30, 2026
81f0911
Add setup instructions for local monorepo deps
Sidnioulz Apr 29, 2026
0de626f
Monorepo prompt with relaxed limits
Sidnioulz Apr 30, 2026
a48040d
refactor prompts
Sidnioulz Apr 30, 2026
dd40a5b
Merge pull request #34673 from storybookjs/sidnioulz/prompt-monorepo-…
Sidnioulz Apr 30, 2026
617d22d
Factorise prompt building blocks for faster experimentation
Sidnioulz Apr 30, 2026
a411b1f
Convert one more prompt
Sidnioulz Apr 30, 2026
8a1186e
fix invalid imports missed by IDE, and old prompt regression
Sidnioulz Apr 30, 2026
0bd1037
Ensure eval script uses local dispatcher rather than project SB CLI c…
Sidnioulz Apr 30, 2026
5591c98
Update code/core/src/common/js-package-manager/BUNProxy.ts
yannbf May 4, 2026
c2afa4c
Merge branch 'next' into yann/prompt-optimized-tests
yannbf May 4, 2026
4288044
fix CI issues
Sidnioulz May 4, 2026
8d9e3c8
Fix coverage type
JReinhold May 4, 2026
f1d8228
Add setup instructions for local monorepo deps
Sidnioulz Apr 29, 2026
e91e71f
Agentic Setup: Update project list in tests
Sidnioulz May 4, 2026
5a24a5c
Fix project name
Sidnioulz May 4, 2026
69e3dc2
Update default prompt
Sidnioulz May 4, 2026
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
2 changes: 1 addition & 1 deletion code/addons/vitest/src/node/vitest-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export class VitestManager {
'@storybook/addon-vitest/internal/coverage-reporter',
{
testManager: this.testManager,
coverageOptions: this.vitest?.config?.coverage as ResolvedCoverageOptions<'v8'> | undefined,
coverageOptions: this.vitest?.config?.coverage as ResolvedCoverageOptions | undefined,
},
];
const coverageOptions = (
Expand Down
8 changes: 8 additions & 0 deletions code/core/src/common/js-package-manager/BUNProxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,14 @@ export class BUNProxy extends JsPackageManager {
return executeCommand({ command: 'bun', args: ['init'] });
}

getCommandName(): string {
return 'bun';
}

getInstallCommand(deps: string[], dev: boolean): string {
return `bun add ${dev ? '-D ' : ''}${deps.join(' ')}`;
}

getRunStorybookCommand(): string {
return 'bun run storybook';
}
Expand Down
33 changes: 33 additions & 0 deletions code/core/src/common/js-package-manager/JsPackageManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,33 @@ type PackageJsonWithIndent = PackageJsonWithDepsAndDevDeps & {
[indentSymbol]?: any;
};

/** Human-friendly labels for each package manager type */
const PACKAGE_MANAGER_LABEL: Record<PackageManagerName, string> = {
[PackageManagerName.NPM]: 'npm',
[PackageManagerName.YARN1]: 'Yarn Classic (v1)',
[PackageManagerName.YARN2]: 'Yarn Berry',
[PackageManagerName.PNPM]: 'pnpm',
[PackageManagerName.BUN]: 'Bun',
};

function isValidPackageManagerName(name: string): name is PackageManagerName {
return Object.hasOwn(PACKAGE_MANAGER_LABEL, name);
}

/**
* Prints a package manager's name in a way that's easier to compute by humans and agents.
* @param packageManager The package manager's internal name (e.g. "yarn1")
* @return A human-friendly name for the package manager (e.g. "Yarn Classic (v1)")
*/
export function getPrettyPackageManagerName(packageManager: string | undefined): string {
if (!packageManager) {
return 'unknown package manager';
}
return isValidPackageManagerName(packageManager)
? PACKAGE_MANAGER_LABEL[packageManager]
: packageManager;
}

/**
* Extract package name and version from input
*
Expand Down Expand Up @@ -109,6 +136,12 @@ export abstract class JsPackageManager {
this.primaryPackageJson = this.#getPrimaryPackageJson();
}

/** Returns the name of the command to invoke this package manager. */
abstract getCommandName(): string;

/** Installs package dependencies. */
abstract getInstallCommand(deps: string[], dev?: boolean): string;

/** Runs arbitrary package scripts (as a string for display). */
abstract getRunCommand(command: string): string;

Expand Down
8 changes: 8 additions & 0 deletions code/core/src/common/js-package-manager/NPMProxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,14 @@ export class NPMProxy extends JsPackageManager {

installArgs: string[] | undefined;

getCommandName(): string {
return 'npm';
}

getInstallCommand(deps: string[], dev: boolean): string {
return `npm install ${dev ? '-D ' : ''}${deps.join(' ')}`;
}

getRunCommand(command: string): string {
return `npm run ${command}`;
}
Expand Down
8 changes: 8 additions & 0 deletions code/core/src/common/js-package-manager/PNPMProxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ export class PNPMProxy extends JsPackageManager {
return existsSync(pnpmWorkspaceYaml);
}

getCommandName(): string {
return 'pnpm';
}

getInstallCommand(deps: string[], dev: boolean): string {
return `pnpm add ${dev ? '-D ' : ''}${deps.join(' ')}`;
}

getRunCommand(command: string): string {
return `pnpm run ${command}`;
}
Expand Down
8 changes: 8 additions & 0 deletions code/core/src/common/js-package-manager/Yarn1Proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,18 @@ export class Yarn1Proxy extends JsPackageManager {
return this.installArgs;
}

getCommandName(): string {
return 'yarn';
}

getRunCommand(command: string): string {
return `yarn ${command}`;
}

getInstallCommand(deps: string[], dev: boolean): string {
return `yarn add ${dev ? '-D ' : ''}${deps.join(' ')}`;
}

getPackageCommand(args: string[]): string {
const [command, ...rest] = args;
return `yarn exec ${command} -- ${rest.join(' ')}`;
Expand Down
8 changes: 8 additions & 0 deletions code/core/src/common/js-package-manager/Yarn2Proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,18 @@ export class Yarn2Proxy extends JsPackageManager {
return this.installArgs;
}

getCommandName(): string {
return 'yarn';
}

getRunCommand(command: string): string {
return `yarn ${command}`;
}

getInstallCommand(deps: string[], dev: boolean): string {
return `yarn add ${dev ? '-D ' : ''}${deps.join(' ')}`;
}

getPackageCommand(args: string[]): string {
return `yarn exec ${args.join(' ')}`;
}
Expand Down
4 changes: 2 additions & 2 deletions code/core/src/manager/settings/Checklist/AiSetupBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { CheckIcon, UndoIcon } from '@storybook/icons';
import { type API, useStorybookApi } from 'storybook/manager-api';
import { styled } from 'storybook/theming';

import { AI_SETUP_PROMPT } from '../../../shared/constants/ai-prompts.ts';
import { getAiSetupPrompt } from '../../../shared/utils/ai-prompts.ts';
import { useCopyButton } from '../../../shared/useCopyButton.ts';
import type { ItemId } from '../../../shared/checklist-store/index.ts';
import type { ChecklistItem } from '../../components/sidebar/useChecklist.ts';
Expand Down Expand Up @@ -61,7 +61,7 @@ const CopyButton = ({ api }: { api: API }) => {
<CheckIcon /> Copied!
</>
),
content: AI_SETUP_PROMPT,
content: getAiSetupPrompt(),
onCopy: () => {
api.emit(AI_PROMPT_NUDGE, { id: 'setup', origin: 'onboarding-guide-page' });
},
Expand Down
4 changes: 2 additions & 2 deletions code/core/src/shared/checklist-store/checklistData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import { ADDON_ID as ADDON_DOCS_ID } from '../../docs-tools/shared.ts';
import { TourGuide } from '../../manager/components/TourGuide/TourGuide.tsx';
import { LocationMonitor } from '../../manager/hooks/useLocation.ts';
import type { initialState } from './checklistData.state.ts';
import { AI_SETUP_PROMPT } from '../constants/ai-prompts.ts';
import { getAiSetupPrompt } from '../utils/ai-prompts.ts';

const CodeWrapper = styled.div(({ theme }) => ({
alignSelf: 'stretch',
Expand Down Expand Up @@ -176,7 +176,7 @@ export const checklistData = {
showOnGuidePage: false,
action: {
label: 'Copy prompt',
copyContent: AI_SETUP_PROMPT,
copyContent: getAiSetupPrompt(),
onClick: ({ api }) => {
api.emit(AI_PROMPT_NUDGE, { id: 'setup', origin: 'onboarding-checklist-side' });
},
Expand Down
5 changes: 0 additions & 5 deletions code/core/src/shared/constants/ai-prompts.ts

This file was deleted.

7 changes: 7 additions & 0 deletions code/core/src/shared/utils/ai-prompts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* Source of truth for AI prompts throughout the application.
*/

export function getAiSetupPrompt(executor = 'npx storybook') {
return `Run \`${executor} ai setup\` and follow its instructions precisely.`;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { describe, expect, it, vi } from 'vitest';

import { getMonorepoType, monorepoConfigs } from './get-monorepo-type.ts';

vi.mock('node:fs', async () => import('../../../__mocks__/fs.ts'));
vi.mock('node:fs', async () => import('../../../../__mocks__/fs.ts'));

vi.mock('storybook/internal/common', async (importOriginal) => {
return {
Expand All @@ -23,7 +23,7 @@ const checkMonorepoType = ({ monorepoConfigFile, isYarnWorkspace = false }: any)
mockFiles[join('root', monorepoConfigFile)] = '{}';
}

vi.mocked<typeof import('../../../__mocks__/fs')>(fs as any).__setMockFiles(mockFiles);
vi.mocked<typeof import('../../../../__mocks__/fs.ts')>(fs as any).__setMockFiles(mockFiles);

return getMonorepoType();
};
Expand Down
4 changes: 2 additions & 2 deletions code/core/src/telemetry/storybook-metadata.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { type Settings, globalSettings } from '../cli/globalSettings.ts';
import { detectAgent } from './detect-agent.ts';
import { getApplicationFileCount } from '../telemetry/get-application-file-count.ts';
import { analyzeEcosystemPackages } from '../telemetry/get-known-packages.ts';
import { getMonorepoType } from '../telemetry/get-monorepo-type.ts';
import { getMonorepoType } from '../shared/utils/get-monorepo-type.ts';
import { getPackageManagerInfo } from '../telemetry/get-package-manager-info.ts';
import { getPortableStoriesFileCount } from '../telemetry/get-portable-stories-usage.ts';
import {
Expand All @@ -37,7 +37,7 @@ vi.mock('./detect-agent.ts', () => ({
detectAgent: vi.fn().mockReturnValue(undefined),
}));
vi.mock(import('./package-json.ts'), { spy: true });
vi.mock(import('./get-monorepo-type.ts'), { spy: true });
vi.mock(import('../shared/utils/get-monorepo-type.ts'), { spy: true });
vi.mock(import('./get-framework-info.ts'), { spy: true });
vi.mock(import('./get-package-manager-info.ts'), { spy: true });
vi.mock(import('./get-portable-stories-usage.ts'), { spy: true });
Expand Down
2 changes: 1 addition & 1 deletion code/core/src/telemetry/storybook-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { getChromaticVersionSpecifier } from './get-chromatic-version.ts';
import { getFrameworkInfo } from './get-framework-info.ts';
import { getHasRouterPackage } from './get-has-router-package.ts';
import { analyzeEcosystemPackages } from './get-known-packages.ts';
import { getMonorepoType } from './get-monorepo-type.ts';
import { getMonorepoType } from '../shared/utils/get-monorepo-type.ts';
import { getPackageManagerInfo } from './get-package-manager-info.ts';
import { getPortableStoriesFileCount } from './get-portable-stories-usage.ts';
import { getActualPackageVersion, getActualPackageVersions } from './package-json.ts';
Expand Down
2 changes: 1 addition & 1 deletion code/core/src/telemetry/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { DetectResult } from 'package-manager-detector';

import type { AgentInfo } from './detect-agent.ts';
import type { KnownPackagesList } from './get-known-packages.ts';
import type { MonorepoType } from './get-monorepo-type.ts';
import type { MonorepoType } from '../shared/utils/get-monorepo-type.ts';

export type EventType =
| 'boot'
Expand Down
14 changes: 13 additions & 1 deletion code/lib/cli-storybook/src/ai/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,19 @@ vi.mock('../automigrate/helpers/mainConfigFile.ts', () => ({
configDir: '/proj/.storybook',
storiesPaths: [],
hasCsfFactoryPreview: false,
packageManager: {},
packageManager: {
type: 'npm',
version: '9.5.0',
getCommandName(): string {
return 'npm';
},
getInstallCommand(deps: string[], dev: boolean): string {
return `npm install ${dev ? '-D ' : ''}${deps.join(' ')}`;
},
getRunCommand(command: string): string {
return `npm run ${command}`;
},
},
}),
}));

Expand Down
27 changes: 15 additions & 12 deletions code/lib/cli-storybook/src/ai/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { writeFile } from 'node:fs/promises';
import { resolve } from 'node:path';

import type { PackageManagerName } from 'storybook/internal/common';
import { getPrettyPackageManagerName } from 'storybook/internal/common';
import { cache } from 'storybook/internal/common';
import { logger } from 'storybook/internal/node-logger';
import {
Expand All @@ -16,22 +17,19 @@ import { SupportedLanguage } from 'storybook/internal/types';
import { ProjectTypeService } from '../../../create-storybook/src/services/ProjectTypeService.ts';

import { getStorybookData } from '../automigrate/helpers/mainConfigFile.ts';
import { generateMarkdownOutput } from './prompt.ts';
import { getAiSetupMarkdownOutput } from './setup-prompts/index.ts';
import type { ProjectInfo, AiSetupOptions } from './types.ts';

export async function aiSetup(options: AiSetupOptions): Promise<void> {
const { configDir: userConfigDir, packageManager: packageManagerName, output } = options;
const { configDir: userConfigDir, packageManager, output } = options;

let projectInfo: ProjectInfo;

try {
const data = await getStorybookData({
configDir: userConfigDir,
packageManagerName: packageManagerName as PackageManagerName | undefined,
packageManagerName: packageManager as PackageManagerName | undefined,
});
const majorVersion = data.versionInstalled
? parseMajorVersion(data.versionInstalled)
: undefined;

if (!data.frameworkPackage || !data.rendererPackage || !data.builderPackage) {
logger.error(
Expand All @@ -40,6 +38,10 @@ export async function aiSetup(options: AiSetupOptions): Promise<void> {
return;
}

const majorVersion = data.versionInstalled
? parseMajorVersion(data.versionInstalled)
: undefined;

const projectTypeService = new ProjectTypeService(data.packageManager);
const detectedLanguage = await projectTypeService.detectLanguage();
const language = detectedLanguage === SupportedLanguage.TYPESCRIPT ? 'ts' : 'js';
Expand All @@ -54,10 +56,12 @@ export async function aiSetup(options: AiSetupOptions): Promise<void> {
addons: data.addons ?? [],
configDir: data.configDir,
storiesPaths: data.storiesPaths,
hasCsfFactoryPreview: data.hasCsfFactoryPreview,
packageManager: data.packageManager,
packageManagerName: getPrettyPackageManagerName(data.packageManager.type),
language,
hasCsfFactoryPreview: data.hasCsfFactoryPreview,
};
} catch (err: unknown) {
} catch (err) {
logger.error(
`Failed to read Storybook configuration: ${err instanceof Error ? err.message : String(err)}`
);
Expand All @@ -80,21 +84,20 @@ export async function aiSetup(options: AiSetupOptions): Promise<void> {
return;
}

const result = await generateMarkdownOutput(projectInfo);
const result = await getAiSetupMarkdownOutput(projectInfo);
const markdownOutput = result.markdown;

await telemetry('ai-setup', {
cliOptions: {
output: output ? 'file' : undefined,
configDir: projectInfo.configDir,
packageManager: packageManagerName,
packageManager: projectInfo.packageManager.type,
},
project: {
framework: projectInfo.framework,
renderer: projectInfo.rendererPackage,
builder: projectInfo.builderPackage,
language: projectInfo.language,
hasCsfFactoryPreview: projectInfo.hasCsfFactoryPreview,
},
});

Expand All @@ -121,7 +124,7 @@ export async function aiSetup(options: AiSetupOptions): Promise<void> {
await writeFile(outputPath, markdownOutput, 'utf-8');
logger.log(`Prompt written to ${outputPath}`);
} else {
logger.log(markdownOutput);
process.stdout.write(`${markdownOutput}\n`);
}
}

Expand Down
40 changes: 0 additions & 40 deletions code/lib/cli-storybook/src/ai/prompt.ts

This file was deleted.

Loading
Loading