From 2c7ac43a7f43fd8cb39d5f33ec55740d35985379 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Fri, 7 Nov 2025 10:51:43 +0100 Subject: [PATCH 01/26] Enhance project scaffolding by adding 'Other' option for unsupported frameworks; update warning message for clarity and exit process on selection. --- .../src/scaffold-new-project.ts | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/code/lib/create-storybook/src/scaffold-new-project.ts b/code/lib/create-storybook/src/scaffold-new-project.ts index c38deace446b..fbd5406d4be1 100644 --- a/code/lib/create-storybook/src/scaffold-new-project.ts +++ b/code/lib/create-storybook/src/scaffold-new-project.ts @@ -123,18 +123,29 @@ export const scaffoldNewProject = async ( if (!projectStrategy) { projectStrategy = await prompt.select({ - message: dedent` - Empty directory detected: - Would you like to generate a new project from the following list? - Storybook supports many more frameworks and bundlers than listed below. If you don't see your preferred setup, you can still generate a project then rerun this command to add Storybook. - `, - options: Object.entries(SUPPORTED_PROJECTS).map(([key, value]) => ({ - label: buildProjectDisplayNameForPrint(value), - value: key, - })), + message: 'Empty directory detected:', + options: [ + ...Object.entries(SUPPORTED_PROJECTS).map(([key, value]) => ({ + label: buildProjectDisplayNameForPrint(value), + value: key, + })), + { + label: 'Other', + value: 'other', + hint: 'To install Storybook on another framework, first generate a project with that framework and then rerun this command.', + }, + ], }); } + if (projectStrategy === 'other') { + logger.warn( + 'To install Storybook on another framework, first generate a project with that framework and then rerun this command.' + ); + logger.outro('Exiting...'); + process.exit(1); + } + const projectStrategyConfig = SUPPORTED_PROJECTS[projectStrategy]; const projectDisplayName = buildProjectDisplayNameForPrint(projectStrategyConfig); const createScript = projectStrategyConfig.createScript[packageManagerName]; From b5e87fcdb0fcb2d207674319416da32dd8cc517e Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Fri, 7 Nov 2025 11:19:06 +0100 Subject: [PATCH 02/26] Refactor command execution by removing 'shell: true' option in various spawn and execa calls for improved consistency and security across the codebase. --- code/addons/themes/src/postinstall.ts | 3 +- .../vitest/src/vitest-plugin/global-setup.ts | 1 - code/core/src/bin/dispatcher.ts | 2 +- .../JsPackageManagerFactory.ts | 98 +++++++++++-------- code/core/src/common/utils/command.ts | 1 - code/core/src/node-logger/wrap-utils.ts | 8 +- .../src/telemetry/exec-command-count-lines.ts | 2 +- code/lib/cli-storybook/src/upgrade.ts | 2 +- code/lib/codemod/src/index.ts | 1 - .../src/scaffold-new-project.ts | 1 - 10 files changed, 63 insertions(+), 56 deletions(-) diff --git a/code/addons/themes/src/postinstall.ts b/code/addons/themes/src/postinstall.ts index 991529112e7c..bc8f496f9d68 100644 --- a/code/addons/themes/src/postinstall.ts +++ b/code/addons/themes/src/postinstall.ts @@ -14,9 +14,8 @@ const selectPackageManagerCommand = (packageManager: string) => export default async function postinstall({ packageManager = 'npm' }) { const command = selectPackageManagerCommand(packageManager); - await spawn(`${command} @storybook/auto-config themes`, { + spawn(`${command} @storybook/auto-config themes`, { stdio: 'inherit', cwd: process.cwd(), - shell: true, }); } diff --git a/code/addons/vitest/src/vitest-plugin/global-setup.ts b/code/addons/vitest/src/vitest-plugin/global-setup.ts index 098810f87b91..2d11a8afbff5 100644 --- a/code/addons/vitest/src/vitest-plugin/global-setup.ts +++ b/code/addons/vitest/src/vitest-plugin/global-setup.ts @@ -47,7 +47,6 @@ const startStorybookIfNotRunning = async () => { storybookProcess = spawn(storybookScript, [], { stdio: process.env.DEBUG === 'storybook' ? 'pipe' : 'ignore', cwd: process.cwd(), - shell: true, }); storybookProcess.on('error', (error) => { diff --git a/code/core/src/bin/dispatcher.ts b/code/core/src/bin/dispatcher.ts index 71cc19f5e076..718bad4cdc55 100644 --- a/code/core/src/bin/dispatcher.ts +++ b/code/core/src/bin/dispatcher.ts @@ -70,7 +70,7 @@ async function run() { } command ??= ['npx', '--yes', `${targetCli.pkg}@${versions[targetCli.pkg]}`, ...targetCli.args]; - const child = spawn(command[0], command.slice(1), { stdio: 'inherit', shell: true }); + const child = spawn(command[0], command.slice(1), { stdio: 'inherit' }); child.on('exit', (code) => { process.exit(code); }); diff --git a/code/core/src/common/js-package-manager/JsPackageManagerFactory.ts b/code/core/src/common/js-package-manager/JsPackageManagerFactory.ts index d7917cb9909d..d8f8376f1cf7 100644 --- a/code/core/src/common/js-package-manager/JsPackageManagerFactory.ts +++ b/code/core/src/common/js-package-manager/JsPackageManagerFactory.ts @@ -1,8 +1,8 @@ import { basename, parse, relative } from 'node:path'; -import { sync as spawnSync } from 'cross-spawn'; import * as find from 'empathic/find'; +import { executeCommandSync } from '../utils/command'; import { getProjectRoot } from '../utils/paths'; import { BUNProxy } from './BUNProxy'; import type { JsPackageManager, PackageManagerName } from './JsPackageManager'; @@ -195,56 +195,70 @@ export class JsPackageManagerFactory { } function hasNPM(cwd?: string) { - const npmVersionCommand = spawnSync('npm --version', { - cwd, - shell: true, - env: { - ...process.env, - ...COMMON_ENV_VARS, - }, - }); - return npmVersionCommand.status === 0; + try { + executeCommandSync({ + command: 'npm', + args: ['--version'], + cwd, + env: { + ...process.env, + ...COMMON_ENV_VARS, + }, + }); + return true; + } catch (err) { + return false; + } } function hasBun(cwd?: string) { - const pnpmVersionCommand = spawnSync('bun --version', { - cwd, - shell: true, - env: { - ...process.env, - ...COMMON_ENV_VARS, - }, - }); - return pnpmVersionCommand.status === 0; + try { + executeCommandSync({ + command: 'bun', + args: ['--version'], + cwd, + env: { + ...process.env, + ...COMMON_ENV_VARS, + }, + }); + return true; + } catch (err) { + return false; + } } function hasPNPM(cwd?: string) { - const pnpmVersionCommand = spawnSync('pnpm --version', { - cwd, - shell: true, - env: { - ...process.env, - ...COMMON_ENV_VARS, - }, - }); - return pnpmVersionCommand.status === 0; + try { + executeCommandSync({ + command: 'pnpm', + args: ['--version'], + cwd, + env: { + ...process.env, + ...COMMON_ENV_VARS, + }, + }); + + return true; + } catch (err) { + return false; + } } function getYarnVersion(cwd?: string): 1 | 2 | undefined { - const yarnVersionCommand = spawnSync('yarn --version', { - cwd, - shell: true, - env: { - ...process.env, - ...COMMON_ENV_VARS, - }, - }); - - if (yarnVersionCommand.status !== 0) { + try { + const yarnVersion = executeCommandSync({ + command: 'yarn', + args: ['--version'], + cwd, + env: { + ...process.env, + ...COMMON_ENV_VARS, + }, + }); + return /^1\.+/.test(yarnVersion.trim()) ? 1 : 2; + } catch (err) { return undefined; } - - const yarnVersion = yarnVersionCommand.output.toString().replace(/,/g, '').replace(/"/g, ''); - - return /^1\.+/.test(yarnVersion) ? 1 : 2; } diff --git a/code/core/src/common/utils/command.ts b/code/core/src/common/utils/command.ts index 3262c6f08103..b525637ec32d 100644 --- a/code/core/src/common/utils/command.ts +++ b/code/core/src/common/utils/command.ts @@ -22,7 +22,6 @@ function getExecaOptions({ stdio, cwd, env, ...execaOptions }: ExecuteCommandOpt cwd, stdio: stdio ?? prompt.getPreferredStdio(), encoding: 'utf8' as const, - shell: true, cleanup: true, env: { ...COMMON_ENV_VARS, diff --git a/code/core/src/node-logger/wrap-utils.ts b/code/core/src/node-logger/wrap-utils.ts index f15348f27e27..8382c58bb2d4 100644 --- a/code/core/src/node-logger/wrap-utils.ts +++ b/code/core/src/node-logger/wrap-utils.ts @@ -1,6 +1,4 @@ import { S_BAR } from '@clack/prompts'; -// eslint-disable-next-line depend/ban-dependencies -import { execaSync } from 'execa'; import { cyan, dim, reset } from 'picocolors'; import wrapAnsi from 'wrap-ansi'; @@ -32,7 +30,7 @@ function getVisibleLength(str: string): number { } function getEnvFromTerminal(key: string): string { - return execaSync('echo', [`$${key}`], { shell: true }).stdout.trim(); + return (process.env[key] || '').trim(); } /** @@ -62,8 +60,8 @@ function supportsHyperlinks(): boolean { // Most other modern terminals support hyperlinks return true; } - } catch (error) { - // If we can't execute shell commands, fall back to conservative default + } catch { + // If we can't access environment variables, fall back to conservative default return false; } } diff --git a/code/core/src/telemetry/exec-command-count-lines.ts b/code/core/src/telemetry/exec-command-count-lines.ts index fdc4547ce464..2399f94d43d9 100644 --- a/code/core/src/telemetry/exec-command-count-lines.ts +++ b/code/core/src/telemetry/exec-command-count-lines.ts @@ -14,7 +14,7 @@ export async function execCommandCountLines( command: string, options?: Parameters[1] ) { - const process = execaCommand(command, { shell: true, buffer: false, ...options }); + const process = execaCommand(command, { buffer: false, ...options }); if (!process.stdout) { // eslint-disable-next-line local-rules/no-uncategorized-errors throw new Error('Unexpected missing stdout'); diff --git a/code/lib/cli-storybook/src/upgrade.ts b/code/lib/cli-storybook/src/upgrade.ts index bd5b3aef93a5..184c155479cc 100644 --- a/code/lib/cli-storybook/src/upgrade.ts +++ b/code/lib/cli-storybook/src/upgrade.ts @@ -76,7 +76,7 @@ const formatPackage = (pkg: Package) => `${pkg.package}@${pkg.version}`; const warnPackages = (pkgs: Package[]) => pkgs.map((pkg) => `- ${formatPackage(pkg)}`).join('\n'); export const checkVersionConsistency = () => { - const lines = spawnSync('npm ls', { stdio: 'pipe', shell: true }).output.toString().split('\n'); + const lines = spawnSync('npm ls', { stdio: 'pipe' }).output.toString().split('\n'); const storybookPackages = lines .map(getStorybookVersion) .filter((item): item is NonNullable => !!item) diff --git a/code/lib/codemod/src/index.ts b/code/lib/codemod/src/index.ts index 6bb0b93be03a..9ed9c6686700 100644 --- a/code/lib/codemod/src/index.ts +++ b/code/lib/codemod/src/index.ts @@ -90,7 +90,6 @@ export async function runCodemod( ], { stdio: 'inherit', - shell: true, } ); diff --git a/code/lib/create-storybook/src/scaffold-new-project.ts b/code/lib/create-storybook/src/scaffold-new-project.ts index fbd5406d4be1..59d0a629b432 100644 --- a/code/lib/create-storybook/src/scaffold-new-project.ts +++ b/code/lib/create-storybook/src/scaffold-new-project.ts @@ -178,7 +178,6 @@ export const scaffoldNewProject = async ( spinner.message(`Executing ${createScript}`); await execa.command(createScript, { stdio: 'pipe', - shell: true, cwd: targetDir, cleanup: true, }); From 871c615f7268c873c433bb8d50a9af455f377747 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Fri, 7 Nov 2025 11:21:41 +0100 Subject: [PATCH 03/26] Update telemetry notification message to remove unnecessary formatting for improved clarity. --- code/core/src/telemetry/notify.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/core/src/telemetry/notify.ts b/code/core/src/telemetry/notify.ts index 7de631331195..db2260def91a 100644 --- a/code/core/src/telemetry/notify.ts +++ b/code/core/src/telemetry/notify.ts @@ -19,7 +19,7 @@ export const notify = async () => { logger.log( dedent` - ${CLI_COLORS.info('Attention:')} Storybook now collects completely anonymous telemetry regarding usage. This information is used to shape Storybook's roadmap and prioritize features. You can learn more, including how to opt-out if you'd not like to participate in this anonymous program, by visiting the following URL: + Attention: Storybook now collects completely anonymous telemetry regarding usage. This information is used to shape Storybook's roadmap and prioritize features. You can learn more, including how to opt-out if you'd not like to participate in this anonymous program, by visiting the following URL: https://storybook.js.org/telemetry ` ); From bbc59eaf294ea64b32b9ba5647b9072ac07eda8f Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Fri, 7 Nov 2025 11:23:24 +0100 Subject: [PATCH 04/26] Remove unnecessary formatting from abort messages in task execution for improved clarity. --- code/core/src/node-logger/tasks.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/core/src/node-logger/tasks.ts b/code/core/src/node-logger/tasks.ts index 9be49937572d..c3da9230aced 100644 --- a/code/core/src/node-logger/tasks.ts +++ b/code/core/src/node-logger/tasks.ts @@ -65,7 +65,7 @@ export const executeTask = async ( let cleanup: (() => void) | undefined; if (abortable) { - log(CLI_COLORS.info('Press "c" to abort')); + log('Press "c" to abort'); const result = setupAbortController(); abortController = result.abortController; cleanup = result.cleanup; @@ -123,7 +123,7 @@ export const executeTaskWithSpinner = async ( let cleanup: (() => void) | undefined; if (abortable) { - log(CLI_COLORS.info('Press "c" to abort')); + log('Press "c" to abort'); const result = setupAbortController(); abortController = result.abortController; cleanup = result.cleanup; From a960afd871bb6c443bdcc10cfacd9dc8a1f223fe Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Fri, 7 Nov 2025 11:25:46 +0100 Subject: [PATCH 05/26] Refactor FinalizationCommand to remove selectedFeatures parameter, simplifying execution and logging process. --- .../src/commands/FinalizationCommand.test.ts | 49 ------------------- .../src/commands/FinalizationCommand.ts | 28 +++-------- code/lib/create-storybook/src/initiate.ts | 1 - 3 files changed, 8 insertions(+), 70 deletions(-) diff --git a/code/lib/create-storybook/src/commands/FinalizationCommand.test.ts b/code/lib/create-storybook/src/commands/FinalizationCommand.test.ts index 7fe35e2863ad..1978a5f1599f 100644 --- a/code/lib/create-storybook/src/commands/FinalizationCommand.test.ts +++ b/code/lib/create-storybook/src/commands/FinalizationCommand.test.ts @@ -5,7 +5,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { ProjectType } from 'storybook/internal/cli'; import { getProjectRoot } from 'storybook/internal/common'; import { logger } from 'storybook/internal/node-logger'; -import { Feature } from 'storybook/internal/types'; import * as find from 'empathic/find'; @@ -36,11 +35,8 @@ describe('FinalizationCommand', () => { vi.mocked(fs.readFile).mockResolvedValue('node_modules/\n' as any); vi.mocked(fs.appendFile).mockResolvedValue(undefined); - const selectedFeatures = new Set([Feature.DOCS, Feature.TEST]); - await command.execute({ projectType: ProjectType.REACT, - selectedFeatures, storybookCommand: 'npm run storybook', }); @@ -55,11 +51,8 @@ describe('FinalizationCommand', () => { it('should not update gitignore if file not found', async () => { vi.mocked(find.up).mockReturnValue(undefined); - const selectedFeatures = new Set([]); - await command.execute({ projectType: ProjectType.VUE3, - selectedFeatures, storybookCommand: 'yarn storybook', }); @@ -72,11 +65,8 @@ describe('FinalizationCommand', () => { vi.mocked(find.up).mockReturnValue('/other/path/.gitignore'); vi.mocked(getProjectRoot).mockReturnValue('/test/project'); - const selectedFeatures = new Set([]); - await command.execute({ projectType: ProjectType.REACT, - selectedFeatures, storybookCommand: 'npm run storybook', }); @@ -90,11 +80,8 @@ describe('FinalizationCommand', () => { 'node_modules/\n*storybook.log\nstorybook-static\n' as any ); - const selectedFeatures = new Set([]); - await command.execute({ projectType: ProjectType.REACT, - selectedFeatures, storybookCommand: 'npm run storybook', }); @@ -106,11 +93,8 @@ describe('FinalizationCommand', () => { vi.mocked(fs.readFile).mockResolvedValue('node_modules/\n*storybook.log\n' as any); vi.mocked(fs.appendFile).mockResolvedValue(undefined); - const selectedFeatures = new Set([]); - await command.execute({ projectType: ProjectType.REACT, - selectedFeatures, storybookCommand: 'npm run storybook', }); @@ -120,44 +104,11 @@ describe('FinalizationCommand', () => { ); }); - it('should print features as "none" when no features selected', async () => { - vi.mocked(find.up).mockReturnValue(undefined); - - const selectedFeatures = new Set([]); - - await command.execute({ - projectType: ProjectType.REACT, - selectedFeatures, - storybookCommand: 'npm run storybook', - }); - - expect(logger.log).toHaveBeenCalledWith(expect.stringContaining('Additional features: none')); - }); - - it('should print all selected features', async () => { - vi.mocked(find.up).mockReturnValue(undefined); - - const selectedFeatures = new Set([Feature.DOCS, Feature.TEST, Feature.ONBOARDING]); - - await command.execute({ - projectType: ProjectType.NEXTJS, - selectedFeatures, - storybookCommand: 'npm run storybook', - }); - - expect(logger.log).toHaveBeenCalledWith( - expect.stringContaining('Additional features: docs, test, onboarding') - ); - }); - it('should include storybook command in output', async () => { vi.mocked(find.up).mockReturnValue(undefined); - const selectedFeatures = new Set([]); - await command.execute({ projectType: ProjectType.ANGULAR, - selectedFeatures, storybookCommand: 'ng run my-app:storybook', }); diff --git a/code/lib/create-storybook/src/commands/FinalizationCommand.ts b/code/lib/create-storybook/src/commands/FinalizationCommand.ts index 8316dbc74554..98caf2ac71e3 100644 --- a/code/lib/create-storybook/src/commands/FinalizationCommand.ts +++ b/code/lib/create-storybook/src/commands/FinalizationCommand.ts @@ -4,14 +4,12 @@ import type { ProjectType } from 'storybook/internal/cli'; import { getProjectRoot } from 'storybook/internal/common'; import { CLI_COLORS, logTracker, logger } from 'storybook/internal/node-logger'; import { ErrorCollector } from 'storybook/internal/telemetry'; -import type { Feature } from 'storybook/internal/types'; import * as find from 'empathic/find'; import { dedent } from 'ts-dedent'; type ExecuteFinalizationParams = { projectType: ProjectType; - selectedFeatures: Set; storybookCommand?: string | null; }; @@ -27,16 +25,16 @@ type ExecuteFinalizationParams = { */ export class FinalizationCommand { /** Execute finalization steps */ - async execute({ selectedFeatures, storybookCommand }: ExecuteFinalizationParams): Promise { + async execute({ storybookCommand }: ExecuteFinalizationParams): Promise { // Update .gitignore await this.updateGitignore(); const errors = ErrorCollector.getErrors(); if (errors.length > 0) { - await this.printFailureMessage(selectedFeatures, storybookCommand); + await this.printFailureMessage(storybookCommand); } else { - this.printSuccessMessage(selectedFeatures, storybookCommand); + this.printSuccessMessage(storybookCommand); } } @@ -65,31 +63,21 @@ export class FinalizationCommand { } } - private async printFailureMessage( - selectedFeatures: Set, - storybookCommand?: string | null - ): Promise { + private async printFailureMessage(storybookCommand?: string | null): Promise { logger.warn('Storybook setup completed, but some non-blocking errors occurred.'); - this.printNextSteps(selectedFeatures, storybookCommand); + this.printNextSteps(storybookCommand); const logFile = await logTracker.writeToFile(); logger.warn(`Storybook debug logs can be found at: ${logFile}`); } /** Print success message with feature summary */ - private printSuccessMessage( - selectedFeatures: Set, - storybookCommand?: string | null - ): void { + private printSuccessMessage(storybookCommand?: string | null): void { logger.step(CLI_COLORS.success('Storybook was successfully installed in your project!')); - this.printNextSteps(selectedFeatures, storybookCommand); + this.printNextSteps(storybookCommand); } - private printNextSteps(selectedFeatures: Set, storybookCommand?: string | null): void { - const printFeatures = (features: Set) => Array.from(features).join(', ') || 'none'; - - logger.log(`Additional features: ${printFeatures(selectedFeatures)}`); - + private printNextSteps(storybookCommand?: string | null): void { if (storybookCommand) { logger.log( `To run Storybook manually, run ${CLI_COLORS.cta(storybookCommand)}. CTRL+C to stop.` diff --git a/code/lib/create-storybook/src/initiate.ts b/code/lib/create-storybook/src/initiate.ts index e000422e8c98..e8f356ae5fbc 100644 --- a/code/lib/create-storybook/src/initiate.ts +++ b/code/lib/create-storybook/src/initiate.ts @@ -105,7 +105,6 @@ export async function doInitiate(options: CommandOptions): Promise< // Step 8: Print final summary await executeFinalization({ projectType, - selectedFeatures, storybookCommand, }); From 061781a7b03557ddacbccf47ac656417806dc2e4 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Fri, 7 Nov 2025 11:26:18 +0100 Subject: [PATCH 06/26] Remove unnecessary output from initiation completion logger for improved clarity. --- code/lib/create-storybook/src/initiate.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/lib/create-storybook/src/initiate.ts b/code/lib/create-storybook/src/initiate.ts index e8f356ae5fbc..9c58a7060aa1 100644 --- a/code/lib/create-storybook/src/initiate.ts +++ b/code/lib/create-storybook/src/initiate.ts @@ -147,7 +147,7 @@ export async function initiate(options: CommandOptions): Promise { async () => { const result = await doInitiate(options); - logger.outro('Initiation completed'); + logger.outro(''); return result; } From 78236e1f7f6db3d2a198ed1bfb6fb0ec7fb2ba3f Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Fri, 7 Nov 2025 11:38:33 +0100 Subject: [PATCH 07/26] Add silent flag for npm package manager in Storybook initiation to reduce output --- code/lib/create-storybook/src/initiate.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/code/lib/create-storybook/src/initiate.ts b/code/lib/create-storybook/src/initiate.ts index 9c58a7060aa1..e6925b937fea 100644 --- a/code/lib/create-storybook/src/initiate.ts +++ b/code/lib/create-storybook/src/initiate.ts @@ -176,6 +176,10 @@ async function runStorybookDev(result: { const flags = []; + if (packageManager.type === 'npm') { + flags.push('--silent'); + } + // npm needs extra -- to pass flags to the command // in the case of Angular, we are calling `ng run` which doesn't need the extra `--` if (packageManager.type === 'npm' && projectType !== ProjectType.ANGULAR) { From 3ffa86e9b62ed19023cd6db05380d06b6fb9f9a4 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Fri, 7 Nov 2025 15:35:33 +0100 Subject: [PATCH 08/26] Refactor logging output in various components for improved clarity and consistency; remove cli-table3 dependency. --- code/core/package.json | 1 - code/core/src/builder-manager/index.ts | 2 +- code/core/src/core-server/dev-server.ts | 4 +- .../utils/output-startup-information.ts | 54 ++++++------------- .../src/core-server/utils/server-statics.ts | 2 +- code/core/src/node-logger/logger/colors.ts | 1 + code/core/src/node-logger/logger/logger.ts | 8 +-- code/yarn.lock | 21 -------- 8 files changed, 21 insertions(+), 72 deletions(-) diff --git a/code/core/package.json b/code/core/package.json index 9bb552b5eaa7..8fbc0a9dd281 100644 --- a/code/core/package.json +++ b/code/core/package.json @@ -257,7 +257,6 @@ "bundle-require": "^5.1.0", "camelcase": "^8.0.0", "chai": "^5.1.1", - "cli-table3": "^0.6.1", "commander": "^14.0.1", "comment-parser": "^1.4.1", "copy-to-clipboard": "^3.3.1", diff --git a/code/core/src/builder-manager/index.ts b/code/core/src/builder-manager/index.ts index ae06b0d8dcc2..7daba1650222 100644 --- a/code/core/src/builder-manager/index.ts +++ b/code/core/src/builder-manager/index.ts @@ -141,7 +141,7 @@ const starter: StarterFunction = async function* starterGeneratorFn({ router, }) { if (!options.quiet) { - logger.info('Starting manager..'); + logger.info('Starting...'); } const { diff --git a/code/core/src/core-server/dev-server.ts b/code/core/src/core-server/dev-server.ts index 44c90385b0e8..1a67dedd3f92 100644 --- a/code/core/src/core-server/dev-server.ts +++ b/code/core/src/core-server/dev-server.ts @@ -94,9 +94,7 @@ export async function storybookDevServer(options: Options) { await Promise.resolve(); if (!options.ignorePreview) { - if (!options.quiet) { - logger.info('Starting preview..'); - } + logger.debug('Starting preview..'); previewResult = await previewBuilder .start({ startTime: process.hrtime(), diff --git a/code/core/src/core-server/utils/output-startup-information.ts b/code/core/src/core-server/utils/output-startup-information.ts index 8460cb192e11..f86cf5d050cd 100644 --- a/code/core/src/core-server/utils/output-startup-information.ts +++ b/code/core/src/core-server/utils/output-startup-information.ts @@ -1,7 +1,6 @@ import { CLI_COLORS, logger } from 'storybook/internal/node-logger'; import type { VersionCheck } from 'storybook/internal/types'; -import Table from 'cli-table3'; import picocolors from 'picocolors'; import prettyTime from 'pretty-hrtime'; import { dedent } from 'ts-dedent'; @@ -22,34 +21,22 @@ export function outputStartupInformation(options: { const updateMessage = createUpdateMessage(updateInfo, version); - const serveMessage = new Table({ - chars: { - top: '', - 'top-mid': '', - 'top-left': '', - 'top-right': '', - bottom: '', - 'bottom-mid': '', - 'bottom-left': '', - 'bottom-right': '', - left: '', - 'left-mid': '', - mid: '', - 'mid-mid': '', - right: '', - 'right-mid': '', - middle: '', - }, - // @ts-expect-error (Converted from ts-ignore) - paddingLeft: 0, - paddingRight: 0, - paddingTop: 0, - paddingBottom: 0, - }); + const serverMessages = [ + `- Local: ${address}`, + `- On your network: ${networkAddress}`, + ]; - serveMessage.push( - ['Local:', picocolors.cyan(address)], - ['On your network:', picocolors.cyan(networkAddress)] + logger.logBox( + dedent` + Storybook ready! + + ${serverMessages.join('\n')}${updateMessage ? `\n\n${updateMessage}` : ''} + `, + { + formatBorder: CLI_COLORS.storybook, + contentPadding: 3, + rounded: true, + } ); const timeStatement = [ @@ -59,14 +46,5 @@ export function outputStartupInformation(options: { .filter(Boolean) .join(' and '); - logger.logBox( - dedent` - ${CLI_COLORS.success( - `Storybook ${picocolors.bold(version)} for ${picocolors.bold(name)} started` - )} - ${timeStatement} - - ${serveMessage.toString()}${updateMessage ? `\n\n${updateMessage}` : ''} - ` - ); + logger.info(timeStatement); } diff --git a/code/core/src/core-server/utils/server-statics.ts b/code/core/src/core-server/utils/server-statics.ts index a152f35feccd..966fdd2f5789 100644 --- a/code/core/src/core-server/utils/server-statics.ts +++ b/code/core/src/core-server/utils/server-statics.ts @@ -119,7 +119,7 @@ export async function useStatics(app: Polka, options: Options): Promise { // Don't log for internal static dirs if (!targetEndpoint.startsWith('/sb-') && !staticDir.startsWith(cacheDir)) { const relativeStaticDir = relative(getProjectRoot(), staticDir); - logger.info( + logger.debug( `Serving static files from ${CLI_COLORS.info(relativeStaticDir)} at ${CLI_COLORS.info(targetEndpoint)}` ); } diff --git a/code/core/src/node-logger/logger/colors.ts b/code/core/src/node-logger/logger/colors.ts index 9619ab688b77..6dcdb7acf611 100644 --- a/code/core/src/node-logger/logger/colors.ts +++ b/code/core/src/node-logger/logger/colors.ts @@ -9,4 +9,5 @@ export const CLI_COLORS = { // Only color a link if it is the primary call to action, otherwise links shouldn't be colored cta: picocolors.cyan, dimmed: picocolors.dim, + storybook: (text: string) => `\x1b[38;2;255;71;133m${text}\x1b[39m`, }; diff --git a/code/core/src/node-logger/logger/logger.ts b/code/core/src/node-logger/logger/logger.ts index 95accd136f63..1d31f3dfedca 100644 --- a/code/core/src/node-logger/logger/logger.ts +++ b/code/core/src/node-logger/logger/logger.ts @@ -152,14 +152,8 @@ export const warn = createLogger('warn', LOG_FUNCTIONS.warn()); export const error = createLogger('error', LOG_FUNCTIONS.error()); type BoxOptions = { - borderStyle?: 'round' | 'none'; - contentPadding?: number; title?: string; - titleAlign?: 'left' | 'center' | 'right'; - borderColor?: string; - backgroundColor?: string; - width?: number | 'auto'; -}; +} & clack.BoxOptions; export const logBox = (message: string, { title, ...options }: BoxOptions = {}) => { if (shouldLog('info')) { diff --git a/code/yarn.lock b/code/yarn.lock index 00e5ebdd4b48..b58f1bf90de9 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -2125,13 +2125,6 @@ __metadata: languageName: node linkType: hard -"@colors/colors@npm:1.5.0": - version: 1.5.0 - resolution: "@colors/colors@npm:1.5.0" - checksum: 10c0/eb42729851adca56d19a08e48d5a1e95efd2a32c55ae0323de8119052be0510d4b7a1611f2abcbf28c044a6c11e6b7d38f99fccdad7429300c37a8ea5fb95b44 - languageName: node - linkType: hard - "@design-systems/utils@npm:2.12.0": version: 2.12.0 resolution: "@design-systems/utils@npm:2.12.0" @@ -11301,19 +11294,6 @@ __metadata: languageName: node linkType: hard -"cli-table3@npm:^0.6.1": - version: 0.6.5 - resolution: "cli-table3@npm:0.6.5" - dependencies: - "@colors/colors": "npm:1.5.0" - string-width: "npm:^4.2.0" - dependenciesMeta: - "@colors/colors": - optional: true - checksum: 10c0/d7cc9ed12212ae68241cc7a3133c52b844113b17856e11f4f81308acc3febcea7cc9fd298e70933e294dd642866b29fd5d113c2c098948701d0c35f09455de78 - languageName: node - linkType: hard - "cli-truncate@npm:^3.1.0": version: 3.1.0 resolution: "cli-truncate@npm:3.1.0" @@ -24436,7 +24416,6 @@ __metadata: bundle-require: "npm:^5.1.0" camelcase: "npm:^8.0.0" chai: "npm:^5.1.1" - cli-table3: "npm:^0.6.1" commander: "npm:^14.0.1" comment-parser: "npm:^1.4.1" copy-to-clipboard: "npm:^3.3.1" From 6e1bb50f4be37214e7d7a7a77094d7ca1f964b82 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Fri, 7 Nov 2025 15:35:45 +0100 Subject: [PATCH 09/26] Update Playwright installation prompt to indicate abort option; remove redundant abort message logging in task execution for clarity. --- code/core/src/cli/AddonVitestService.test.ts | 2 +- code/core/src/cli/AddonVitestService.ts | 2 +- code/core/src/node-logger/tasks.ts | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/code/core/src/cli/AddonVitestService.test.ts b/code/core/src/cli/AddonVitestService.test.ts index 66343e7acf10..b48664a159bc 100644 --- a/code/core/src/cli/AddonVitestService.test.ts +++ b/code/core/src/cli/AddonVitestService.test.ts @@ -379,7 +379,7 @@ describe('AddonVitestService', () => { }); expect(prompt.executeTaskWithSpinner).toHaveBeenCalledWith(expect.any(Function), { id: 'playwright-installation', - intro: 'Installing Playwright browser binaries', + intro: 'Installing Playwright browser binaries (Press "c" to abort)', error: expect.stringContaining('An error occurred'), success: 'Playwright browser binaries installed successfully', abortable: true, diff --git a/code/core/src/cli/AddonVitestService.ts b/code/core/src/cli/AddonVitestService.ts index 637b9f532db9..9d5321256c3f 100644 --- a/code/core/src/cli/AddonVitestService.ts +++ b/code/core/src/cli/AddonVitestService.ts @@ -143,7 +143,7 @@ export class AddonVitestService { }), { id: 'playwright-installation', - intro: 'Installing Playwright browser binaries', + intro: 'Installing Playwright browser binaries (Press "c" to abort)', error: `An error occurred while installing Playwright browser binaries. Please run the following command later: npx ${playwrightCommand.join(' ')}`, success: 'Playwright browser binaries installed successfully', abortable: true, diff --git a/code/core/src/node-logger/tasks.ts b/code/core/src/node-logger/tasks.ts index c3da9230aced..0776a5085e49 100644 --- a/code/core/src/node-logger/tasks.ts +++ b/code/core/src/node-logger/tasks.ts @@ -65,7 +65,6 @@ export const executeTask = async ( let cleanup: (() => void) | undefined; if (abortable) { - log('Press "c" to abort'); const result = setupAbortController(); abortController = result.abortController; cleanup = result.cleanup; @@ -123,7 +122,6 @@ export const executeTaskWithSpinner = async ( let cleanup: (() => void) | undefined; if (abortable) { - log('Press "c" to abort'); const result = setupAbortController(); abortController = result.abortController; cleanup = result.cleanup; From e54f8912ee9c92273d079062a0f418e370a7838a Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Fri, 7 Nov 2025 15:44:11 +0100 Subject: [PATCH 10/26] Update code/lib/cli-storybook/src/upgrade.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- code/lib/cli-storybook/src/upgrade.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/lib/cli-storybook/src/upgrade.ts b/code/lib/cli-storybook/src/upgrade.ts index 184c155479cc..c8f09b4fc42a 100644 --- a/code/lib/cli-storybook/src/upgrade.ts +++ b/code/lib/cli-storybook/src/upgrade.ts @@ -76,7 +76,7 @@ const formatPackage = (pkg: Package) => `${pkg.package}@${pkg.version}`; const warnPackages = (pkgs: Package[]) => pkgs.map((pkg) => `- ${formatPackage(pkg)}`).join('\n'); export const checkVersionConsistency = () => { - const lines = spawnSync('npm ls', { stdio: 'pipe' }).output.toString().split('\n'); + const lines = spawnSync('npm', ['ls'], { stdio: 'pipe' }).output.toString().split('\n'); const storybookPackages = lines .map(getStorybookVersion) .filter((item): item is NonNullable => !!item) From 897cf7a04003985e8d035fe3e745436fc36c5859 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Fri, 7 Nov 2025 18:53:20 +0100 Subject: [PATCH 11/26] Refactor postinstall command execution to use spawnSync for synchronous execution; improve command argument handling. --- code/addons/themes/src/postinstall.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/code/addons/themes/src/postinstall.ts b/code/addons/themes/src/postinstall.ts index bc8f496f9d68..3d99b67f3d37 100644 --- a/code/addons/themes/src/postinstall.ts +++ b/code/addons/themes/src/postinstall.ts @@ -1,4 +1,4 @@ -import { spawn } from 'child_process'; +import { spawnSync } from 'child_process'; const PACKAGE_MANAGER_TO_COMMAND = { npm: 'npx', @@ -12,9 +12,10 @@ const selectPackageManagerCommand = (packageManager: string) => PACKAGE_MANAGER_TO_COMMAND[packageManager as keyof typeof PACKAGE_MANAGER_TO_COMMAND]; export default async function postinstall({ packageManager = 'npm' }) { - const command = selectPackageManagerCommand(packageManager); + const commandString = selectPackageManagerCommand(packageManager); + const [command, ...commandArgs] = commandString.split(' '); - spawn(`${command} @storybook/auto-config themes`, { + spawnSync(command, [...commandArgs, '@storybook/auto-config', 'themes'], { stdio: 'inherit', cwd: process.cwd(), }); From ec6de36486a13ce86c885a93c02a351b25a8fac2 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Fri, 7 Nov 2025 18:55:23 +0100 Subject: [PATCH 12/26] Refactor sandbox function to remove unused borderColor variable and update border styling to use rounded property for improved clarity in output. --- code/lib/cli-storybook/src/sandbox.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/code/lib/cli-storybook/src/sandbox.ts b/code/lib/cli-storybook/src/sandbox.ts index e1be443744cb..32b8de73c4c9 100644 --- a/code/lib/cli-storybook/src/sandbox.ts +++ b/code/lib/cli-storybook/src/sandbox.ts @@ -1,5 +1,5 @@ import { existsSync } from 'node:fs'; -import { mkdir, readdir, rm } from 'node:fs/promises'; +import { readdir, rm } from 'node:fs/promises'; import { isAbsolute } from 'node:path'; import type { PackageManagerName } from 'storybook/internal/common'; @@ -52,7 +52,6 @@ export const sandbox = async ({ const currentVersion = versions.storybook; const isPrerelease = prerelease(currentVersion); const isOutdated = lt(currentVersion, isPrerelease ? nextVersion : latestVersion); - const borderColor = isOutdated ? '#FC521F' : '#F1618C'; const downloadType = !isOutdated && init ? 'after-storybook' : 'before-storybook'; const branch = isPrerelease ? 'next' : 'main'; @@ -78,7 +77,9 @@ export const sandbox = async ({ .concat(init && (isOutdated || isPrerelease) ? [messages.longInitTime] : []) .concat(isPrerelease ? [messages.prerelease] : []) .join('\n'), - { borderStyle: 'round', borderColor } + { + rounded: true, + } ); if (!selectedConfig) { @@ -256,7 +257,7 @@ export const sandbox = async ({ Having a clean repro helps us solve your issue faster! 🙏 `.trim(), - { borderStyle: 'round', borderColor: '#F1618C' } + { rounded: true } ); } catch (error) { logger.error('🚨 Failed to create sandbox'); From 64fbedc8cc8d12b32655d4c57056446a8ac5e7b2 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Fri, 7 Nov 2025 19:31:25 +0100 Subject: [PATCH 13/26] Fix command argument formatting in dispatcher.ts to remove unnecessary quotes around the index.js path for improved execution clarity. --- code/core/src/bin/dispatcher.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/core/src/bin/dispatcher.ts b/code/core/src/bin/dispatcher.ts index 718bad4cdc55..f5a9e15a32cb 100644 --- a/code/core/src/bin/dispatcher.ts +++ b/code/core/src/bin/dispatcher.ts @@ -61,7 +61,7 @@ async function run() { if (targetCliPackageJson.version === versions[targetCli.pkg]) { command = [ 'node', - `"${join(resolvePackageDir(targetCli.pkg), 'dist/bin/index.js')}"`, + join(resolvePackageDir(targetCli.pkg), 'dist/bin/index.js'), ...targetCli.args, ]; } From 9719b1dcdbdfa9824cf55ab0a91af167ea6a11e1 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Fri, 7 Nov 2025 19:31:35 +0100 Subject: [PATCH 14/26] Refactor postinstall command argument handling for improved clarity by removing unnecessary quotes and using array syntax. --- code/addons/vitest/src/postinstall.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/code/addons/vitest/src/postinstall.ts b/code/addons/vitest/src/postinstall.ts index 6c73e8483db8..9082b74ebf58 100644 --- a/code/addons/vitest/src/postinstall.ts +++ b/code/addons/vitest/src/postinstall.ts @@ -328,13 +328,14 @@ export default async function postInstall(options: PostinstallOptions) { 'storybook', 'automigrate', 'addon-a11y-addon-test', - '--loglevel=silent', + '--loglevel', + 'silent', '--yes', '--skip-doctor', ]; if (options.packageManager) { - command.push(`--package-manager=${options.packageManager}`); + command.push('--package-manager', options.packageManager); } if (options.skipInstall) { @@ -342,7 +343,7 @@ export default async function postInstall(options: PostinstallOptions) { } if (options.configDir !== '.storybook') { - command.push(`--config-dir="${options.configDir}"`); + command.push('--config-dir', options.configDir); } await prompt.executeTask( From bf72e849e3df9d41af11d6f9756bc739a5785b31 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Fri, 7 Nov 2025 19:31:48 +0100 Subject: [PATCH 15/26] Refactor command execution in link.ts to utilize executeCommand for improved clarity and consistency in handling git and yarn commands. --- code/lib/cli-storybook/src/link.ts | 74 +++++++++--------------------- 1 file changed, 22 insertions(+), 52 deletions(-) diff --git a/code/lib/cli-storybook/src/link.ts b/code/lib/cli-storybook/src/link.ts index 99fbbaea0df4..65f06c0a4418 100644 --- a/code/lib/cli-storybook/src/link.ts +++ b/code/lib/cli-storybook/src/link.ts @@ -1,12 +1,10 @@ import { mkdir, readFile, writeFile } from 'node:fs/promises'; import { basename, extname, join } from 'node:path'; +import { executeCommand } from 'storybook/internal/common'; import { logger } from 'storybook/internal/node-logger'; -import { spawn as spawnAsync, sync as spawnSync } from 'cross-spawn'; -import picocolors from 'picocolors'; - -type ExecOptions = Parameters[2]; +import { sync as spawnSync } from 'cross-spawn'; interface LinkOptions { target: string; @@ -14,50 +12,6 @@ interface LinkOptions { start: boolean; } -// TODO: Extract this to somewhere else, or use `exec` from a different file that might already have it -export const exec = async ( - command: string, - options: ExecOptions = {}, - { - startMessage, - errorMessage, - dryRun, - }: { startMessage?: string; errorMessage?: string; dryRun?: boolean } = {} -) => { - if (startMessage) { - logger.info(startMessage); - } - - if (dryRun) { - logger.info(`\n> ${command}\n`); - return undefined; - } - - logger.info(command); - return new Promise((resolve, reject) => { - const child = spawnAsync(command, { - ...options, - shell: true, - stdio: 'pipe', - }); - - child.stderr.pipe(process.stdout); - child.stdout.pipe(process.stdout); - - child.on('exit', (code) => { - if (code === 0) { - resolve(undefined); - } else { - logger.error(picocolors.red(`An error occurred while executing: \`${command}\``)); - if (errorMessage) { - logger.info(errorMessage); - } - reject(new Error(`command exited with code: ${code}: `)); - } - }); - }); -}; - export const link = async ({ target, local, start }: LinkOptions) => { const storybookDir = process.cwd(); try { @@ -80,7 +34,11 @@ export const link = async ({ target, local, start }: LinkOptions) => { await mkdir(reprosDir, { recursive: true }); logger.info(`Cloning ${target}`); - await exec(`git clone ${target}`, { cwd: reprosDir }); + await executeCommand({ + command: 'git', + args: ['clone', target], + cwd: reprosDir, + }); // Extract a repro name from url given as input (take the last part of the path and remove the extension) reproName = basename(target, extname(target)); reproDir = join(reprosDir, reproName); @@ -101,7 +59,11 @@ export const link = async ({ target, local, start }: LinkOptions) => { } logger.info(`Linking ${reproDir}`); - await exec(`yarn link --all --relative "${storybookDir}"`, { cwd: reproDir }); + await executeCommand({ + command: 'yarn', + args: ['link', '--all', '--relative', storybookDir], + cwd: reproDir, + }); logger.info(`Installing ${reproName}`); @@ -124,10 +86,18 @@ export const link = async ({ target, local, start }: LinkOptions) => { await writeFile(join(reproDir, 'package.json'), JSON.stringify(reproPackageJson, null, 2)); - await exec(`yarn install`, { cwd: reproDir }); + await executeCommand({ + command: 'yarn', + args: ['install'], + cwd: reproDir, + }); if (start) { logger.info(`Running ${reproName} storybook`); - await exec(`yarn run storybook`, { cwd: reproDir }); + await executeCommand({ + command: 'yarn', + args: ['run', 'storybook'], + cwd: reproDir, + }); } }; From b56df2c33b24f8e34ef10e410a9fe4826d3a0ad1 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Fri, 7 Nov 2025 19:31:59 +0100 Subject: [PATCH 16/26] Remove 'skip-install' option from init function in sandbox-parts.ts for improved clarity in option handling. --- scripts/tasks/sandbox-parts.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/tasks/sandbox-parts.ts b/scripts/tasks/sandbox-parts.ts index a72afcf0bcd8..5282e3cd6331 100644 --- a/scripts/tasks/sandbox-parts.ts +++ b/scripts/tasks/sandbox-parts.ts @@ -199,7 +199,6 @@ export const init: Task['run'] = async ( optionValues: { debug, yes: true, - 'skip-install': true, ...extra, ...(template.initOptions || {}), }, From 993b2ddd835fe1794816e39a4630e531df569285 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Mon, 10 Nov 2025 10:56:52 +0100 Subject: [PATCH 17/26] Add debug logging for version handling in postinstall and package manager classes for improved traceability --- code/addons/vitest/src/postinstall.ts | 1 + code/core/src/common/js-package-manager/Yarn2Proxy.ts | 5 +++++ scripts/tasks/sandbox-parts.ts | 2 +- scripts/utils/cli-step.ts | 2 +- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/code/addons/vitest/src/postinstall.ts b/code/addons/vitest/src/postinstall.ts index 9082b74ebf58..dd1532317478 100644 --- a/code/addons/vitest/src/postinstall.ts +++ b/code/addons/vitest/src/postinstall.ts @@ -46,6 +46,7 @@ export default async function postInstall(options: PostinstallOptions) { ); const vitestVersionSpecifier = await packageManager.getInstalledVersion('vitest'); + logger.debug(`Vitest version specifier: ${vitestVersionSpecifier}`); const isVitest3_2To4 = vitestVersionSpecifier ? satisfies(vitestVersionSpecifier, '>=3.2.0 <4.0.0') : false; diff --git a/code/core/src/common/js-package-manager/Yarn2Proxy.ts b/code/core/src/common/js-package-manager/Yarn2Proxy.ts index 5e02d14e7dbe..e5b079a56dc7 100644 --- a/code/core/src/common/js-package-manager/Yarn2Proxy.ts +++ b/code/core/src/common/js-package-manager/Yarn2Proxy.ts @@ -11,6 +11,7 @@ import * as find from 'empathic/find'; // eslint-disable-next-line depend/ban-dependencies import type { ExecaChildProcess } from 'execa'; +import { logger } from '../../node-logger'; import type { ExecuteCommandOptions } from '../utils/command'; import { executeCommand, executeCommandSync } from '../utils/command'; import { getProjectRoot } from '../utils/paths'; @@ -147,6 +148,8 @@ export class Yarn2Proxy extends JsPackageManager { }); const commandResult = childProcess.stdout ?? ''; + logger.debug(`Installation found for ${pattern.join(', ')}: ${commandResult}`); + return this.mapDependencies(commandResult, pattern); } catch (e) { return undefined; @@ -291,6 +294,7 @@ export class Yarn2Proxy extends JsPackageManager { const duplicatedDependencies: Record = {}; lines.forEach((packageName) => { + logger.debug(`Processing package ${packageName}`); if ( !packageName || !pattern.some((p) => new RegExp(`${p.replace(/\*/g, '.*')}`).test(packageName)) @@ -299,6 +303,7 @@ export class Yarn2Proxy extends JsPackageManager { } const { name, value } = parsePackageData(packageName.replaceAll(`"`, '')); + logger.debug(`Package ${name} found with version ${value.version}`); if (!existingVersions[name]?.includes(value.version)) { if (acc[name]) { acc[name].push(value); diff --git a/scripts/tasks/sandbox-parts.ts b/scripts/tasks/sandbox-parts.ts index 5282e3cd6331..ac7511f9eec7 100644 --- a/scripts/tasks/sandbox-parts.ts +++ b/scripts/tasks/sandbox-parts.ts @@ -197,7 +197,7 @@ export const init: Task['run'] = async ( await executeCLIStep(steps.init, { cwd, optionValues: { - debug, + loglevel: debug ? 'debug' : 'info', yes: true, ...extra, ...(template.initOptions || {}), diff --git a/scripts/utils/cli-step.ts b/scripts/utils/cli-step.ts index f9944a85b291..05b809b11cc5 100644 --- a/scripts/utils/cli-step.ts +++ b/scripts/utils/cli-step.ts @@ -41,7 +41,7 @@ export const steps = { options: createOptions({ yes: { type: 'boolean' }, type: { type: 'string' }, - debug: { type: 'boolean' }, + loglevel: { type: 'string' }, builder: { type: 'string' }, 'skip-install': { type: 'boolean' }, }), From eee9dbe707af0fe6cd060a93358c78147a9163a1 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Mon, 10 Nov 2025 10:56:59 +0100 Subject: [PATCH 18/26] Enhance version handling in JsPackageManager by incorporating version coercion for improved accuracy in installed version reporting. --- .../src/common/js-package-manager/JsPackageManager.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/code/core/src/common/js-package-manager/JsPackageManager.ts b/code/core/src/common/js-package-manager/JsPackageManager.ts index 02e70431d074..0ac0d7a42abf 100644 --- a/code/core/src/common/js-package-manager/JsPackageManager.ts +++ b/code/core/src/common/js-package-manager/JsPackageManager.ts @@ -9,7 +9,7 @@ import { type ExecaChildProcess } from 'execa'; // eslint-disable-next-line depend/ban-dependencies import { globSync } from 'glob'; import picocolors from 'picocolors'; -import { gt, satisfies } from 'semver'; +import { coerce, gt, satisfies } from 'semver'; import invariant from 'tiny-invariant'; import { HandledError } from '../utils/HandledError'; @@ -635,10 +635,13 @@ export abstract class JsPackageManager { const version = Object.entries(installations.dependencies)[0]?.[1]?.[0].version || null; + const coercedVersion = coerce(version, { includePrerelease: true })?.toString() ?? version; + + logger.debug(`Installed version for ${packageName}: ${coercedVersion}`); // Cache the result - JsPackageManager.installedVersionCache.set(cacheKey, version); + JsPackageManager.installedVersionCache.set(cacheKey, coercedVersion); - return version; + return coercedVersion; } catch (e) { JsPackageManager.installedVersionCache.set(cacheKey, null); return null; From 9ba02343e8a4ec33c78d100d7118b9f809a197a6 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Mon, 10 Nov 2025 11:05:50 +0100 Subject: [PATCH 19/26] Refactor JsPackageManager tests to use spyOn for mocking latestVersion method, enhancing test accuracy and clarity. --- .../js-package-manager/JsPackageManager.test.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/code/core/src/common/js-package-manager/JsPackageManager.test.ts b/code/core/src/common/js-package-manager/JsPackageManager.test.ts index 7c0b0c5e12aa..3156f14cf1ec 100644 --- a/code/core/src/common/js-package-manager/JsPackageManager.test.ts +++ b/code/core/src/common/js-package-manager/JsPackageManager.test.ts @@ -2,22 +2,23 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { JsPackageManager } from './JsPackageManager'; +const mockVersions = vi.hoisted(() => ({ + '@storybook/react': '8.3.0', +})); + vi.mock('../versions', () => ({ - default: { - '@storybook/react': '8.3.0', - }, + default: mockVersions, })); describe('JsPackageManager', () => { let jsPackageManager: JsPackageManager; - let mockLatestVersion: ReturnType; + let mockLatestVersion: ReturnType; beforeEach(() => { - mockLatestVersion = vi.fn(); - // @ts-expect-error Ignore abstract class error jsPackageManager = new JsPackageManager(); - jsPackageManager.latestVersion = mockLatestVersion; + // @ts-expect-error latestVersion is a method that exists on the instance + mockLatestVersion = vi.spyOn(jsPackageManager, 'latestVersion'); vi.clearAllMocks(); }); From baa5ad6428fa66cd28458c876d332bc00555550d Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Wed, 12 Nov 2025 14:21:43 +0100 Subject: [PATCH 20/26] Refactor command execution in dispatcher and add synchronous command execution utility --- code/core/src/bin/dispatcher.ts | 2 +- code/core/src/common/utils/command.ts | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/code/core/src/bin/dispatcher.ts b/code/core/src/bin/dispatcher.ts index f5a9e15a32cb..486b3c5e9f08 100644 --- a/code/core/src/bin/dispatcher.ts +++ b/code/core/src/bin/dispatcher.ts @@ -70,7 +70,7 @@ async function run() { } command ??= ['npx', '--yes', `${targetCli.pkg}@${versions[targetCli.pkg]}`, ...targetCli.args]; - const child = spawn(command[0], command.slice(1), { stdio: 'inherit' }); + const child = spawn(command.join(' '), { stdio: 'inherit' }); child.on('exit', (code) => { process.exit(code); }); diff --git a/code/core/src/common/utils/command.ts b/code/core/src/common/utils/command.ts index f77e0a2fd05f..b525637ec32d 100644 --- a/code/core/src/common/utils/command.ts +++ b/code/core/src/common/utils/command.ts @@ -1,7 +1,7 @@ import { logger, prompt } from 'storybook/internal/node-logger'; // eslint-disable-next-line depend/ban-dependencies -import { type CommonOptions, type ExecaChildProcess, execa } from 'execa'; +import { type CommonOptions, type ExecaChildProcess, execa, execaCommandSync } from 'execa'; const COMMON_ENV_VARS = { COREPACK_ENABLE_STRICT: '0', @@ -44,3 +44,16 @@ export function executeCommand(options: ExecuteCommandOptions): ExecaChildProces return execaProcess; } + +export function executeCommandSync(options: ExecuteCommandOptions): string { + const { command, args = [], ignoreError = false } = options; + try { + const commandResult = execaCommandSync([command, ...args].join(' '), getExecaOptions(options)); + return commandResult.stdout ?? ''; + } catch (err) { + if (!ignoreError) { + throw err; + } + return ''; + } +} From 116cf2386ce2ed6169865f56571f4383c9bff916 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Wed, 12 Nov 2025 14:31:42 +0100 Subject: [PATCH 21/26] Refactor taskLog implementation to simplify logging conditions and remove redundant checks for log levels --- .../node-logger/prompts/prompt-functions.ts | 42 +++++++------------ 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/code/core/src/node-logger/prompts/prompt-functions.ts b/code/core/src/node-logger/prompts/prompt-functions.ts index bcec0cfddfa6..f78c5696c9ad 100644 --- a/code/core/src/node-logger/prompts/prompt-functions.ts +++ b/code/core/src/node-logger/prompts/prompt-functions.ts @@ -157,47 +157,35 @@ export const spinner = (options: SpinnerOptions): SpinnerInstance => { }; export const taskLog = (options: TaskLogOptions): TaskLogInstance => { - if (isInteractiveTerminal()) { + if (isInteractiveTerminal() || shouldLog('info')) { const task = getPromptProvider().taskLog(options); // Wrap the task log methods to handle console.log patching const wrappedTaskLog: TaskLogInstance = { message: (message: string) => { - if (shouldLog('info')) { - task.message(wrapTextForClack(message)); - } + task.message(wrapTextForClack(message)); }, success: (message: string, options?: { showLog?: boolean }) => { activeTaskLog = null; restoreConsoleLog(); - if (shouldLog('info')) { - task.success(message, options); - } + task.success(message, options); }, error: (message: string) => { activeTaskLog = null; restoreConsoleLog(); - if (shouldLog('error')) { - task.error(message); - } + task.error(message); }, group: function (title: string) { this.message(`\n${title}\n`); return { message: (message: string) => { - if (shouldLog('info')) { - task.message(wrapTextForClack(message)); - } + task.message(wrapTextForClack(message)); }, success: (message: string) => { - if (shouldLog('info')) { - task.success(message); - } + task.success(message); }, error: (message: string) => { - if (shouldLog('error')) { - task.error(message); - } + task.error(message); }, }; }, @@ -209,27 +197,29 @@ export const taskLog = (options: TaskLogOptions): TaskLogInstance => { return wrappedTaskLog; } else { + const maybeLog = shouldLog('info') ? logger.log : (_: string) => {}; + return { message: (message: string) => { - logger.log(message); + maybeLog(message); }, success: (message: string) => { - logger.log(message); + maybeLog(message); }, error: (message: string) => { - logger.log(message); + maybeLog(message); }, group: (title: string) => { - logger.log(`\n${title}\n`); + maybeLog(`\n${title}\n`); return { message: (message: string) => { - logger.log(message); + maybeLog(message); }, success: (message: string) => { - logger.log(message); + maybeLog(message); }, error: (message: string) => { - logger.log(message); + maybeLog(message); }, }; }, From a84ea41dd251151a979d18232167d7a75d0abda3 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Wed, 12 Nov 2025 14:31:50 +0100 Subject: [PATCH 22/26] Refactor command spawning in dispatcher to separate command and arguments for improved clarity --- code/core/src/bin/dispatcher.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/core/src/bin/dispatcher.ts b/code/core/src/bin/dispatcher.ts index 486b3c5e9f08..f5a9e15a32cb 100644 --- a/code/core/src/bin/dispatcher.ts +++ b/code/core/src/bin/dispatcher.ts @@ -70,7 +70,7 @@ async function run() { } command ??= ['npx', '--yes', `${targetCli.pkg}@${versions[targetCli.pkg]}`, ...targetCli.args]; - const child = spawn(command.join(' '), { stdio: 'inherit' }); + const child = spawn(command[0], command.slice(1), { stdio: 'inherit' }); child.on('exit', (code) => { process.exit(code); }); From b9eb9566753669cf1d77e5c461da7b7dae58fc23 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Wed, 12 Nov 2025 14:36:13 +0100 Subject: [PATCH 23/26] Refactor telemetry error handling to conditionally send errors only when telemetry is enabled --- code/core/src/core-server/withTelemetry.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/code/core/src/core-server/withTelemetry.ts b/code/core/src/core-server/withTelemetry.ts index c3dbc8f7aee6..8538060ba38b 100644 --- a/code/core/src/core-server/withTelemetry.ts +++ b/code/core/src/core-server/withTelemetry.ts @@ -194,10 +194,12 @@ export async function withTelemetry( throw error; } finally { - const errors = ErrorCollector.getErrors(); - for (const error of errors) { - await sendTelemetryError(error, eventType, options, false); + if (enableTelemetry) { + const errors = ErrorCollector.getErrors(); + for (const error of errors) { + await sendTelemetryError(error, eventType, options, false); + } + process.off('SIGINT', cancelTelemetry); } - process.off('SIGINT', cancelTelemetry); } } From f9221a8277ca057f850ee91bfbe7c441cb5bc985 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Wed, 12 Nov 2025 14:48:25 +0100 Subject: [PATCH 24/26] Update stdio configuration in AddonVitestService to inherit and pipe for improved command output handling --- code/core/src/cli/AddonVitestService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/core/src/cli/AddonVitestService.ts b/code/core/src/cli/AddonVitestService.ts index de79522294a8..d4e152734474 100644 --- a/code/core/src/cli/AddonVitestService.ts +++ b/code/core/src/cli/AddonVitestService.ts @@ -139,7 +139,7 @@ export class AddonVitestService { (signal) => packageManager.runPackageCommand({ args: playwrightCommand, - stdio: 'ignore', + stdio: ['inherit', 'pipe', 'pipe'], signal, }), { From ae93da37e31aee83a70b1ae9a346fe0b88ccf5a2 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Wed, 12 Nov 2025 15:21:28 +0100 Subject: [PATCH 25/26] Update Playwright installation command in AddonVitestService to use 'npx' for improved clarity --- code/core/src/cli/AddonVitestService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/core/src/cli/AddonVitestService.ts b/code/core/src/cli/AddonVitestService.ts index d4e152734474..94a06ec66ba1 100644 --- a/code/core/src/cli/AddonVitestService.ts +++ b/code/core/src/cli/AddonVitestService.ts @@ -126,7 +126,7 @@ export class AddonVitestService { : await (async () => { logger.log(dedent` Playwright browser binaries are necessary for @storybook/addon-vitest. The download can take some time. If you don't want to wait, you can skip the installation and run the following command manually later: - ${CLI_COLORS.cta(playwrightCommand.join(' '))} + ${CLI_COLORS.cta(`npx ${playwrightCommand.join(' ')}`)} `); return prompt.confirm({ message: 'Do you want to install Playwright with Chromium now?', From 80a78e7e19e7afce6b5208750445a48792d99ab6 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Wed, 12 Nov 2025 15:34:26 +0100 Subject: [PATCH 26/26] Update automigration logging to handle optional fixId for improved clarity --- code/lib/cli-storybook/src/bin/run.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/lib/cli-storybook/src/bin/run.ts b/code/lib/cli-storybook/src/bin/run.ts index a4779699f028..6a916e49e190 100644 --- a/code/lib/cli-storybook/src/bin/run.ts +++ b/code/lib/cli-storybook/src/bin/run.ts @@ -264,7 +264,7 @@ command('automigrate [fixId]') .option('--skip-doctor', 'Skip doctor check') .action(async (fixId, options) => { withTelemetry('automigrate', { cliOptions: options }, async () => { - logger.intro(`Running ${fixId} automigration`); + logger.intro(fixId ? `Running ${fixId} automigration` : 'Running automigrations'); await doAutomigrate({ fixId, ...options }); logger.outro('Done'); }).catch(handleCommandFailure(options.logfile));