Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
2c7ac43
Enhance project scaffolding by adding 'Other' option for unsupported …
valentinpalkovic Nov 7, 2025
b5e87fc
Refactor command execution by removing 'shell: true' option in variou…
valentinpalkovic Nov 7, 2025
871c615
Update telemetry notification message to remove unnecessary formattin…
valentinpalkovic Nov 7, 2025
bbc59ea
Remove unnecessary formatting from abort messages in task execution f…
valentinpalkovic Nov 7, 2025
a960afd
Refactor FinalizationCommand to remove selectedFeatures parameter, si…
valentinpalkovic Nov 7, 2025
061781a
Remove unnecessary output from initiation completion logger for impro…
valentinpalkovic Nov 7, 2025
78236e1
Add silent flag for npm package manager in Storybook initiation to re…
valentinpalkovic Nov 7, 2025
3ffa86e
Refactor logging output in various components for improved clarity an…
valentinpalkovic Nov 7, 2025
6e1bb50
Update Playwright installation prompt to indicate abort option; remov…
valentinpalkovic Nov 7, 2025
e54f891
Update code/lib/cli-storybook/src/upgrade.ts
valentinpalkovic Nov 7, 2025
897cf7a
Refactor postinstall command execution to use spawnSync for synchrono…
valentinpalkovic Nov 7, 2025
ec6de36
Refactor sandbox function to remove unused borderColor variable and u…
valentinpalkovic Nov 7, 2025
64fbedc
Fix command argument formatting in dispatcher.ts to remove unnecessar…
valentinpalkovic Nov 7, 2025
9719b1d
Refactor postinstall command argument handling for improved clarity b…
valentinpalkovic Nov 7, 2025
bf72e84
Refactor command execution in link.ts to utilize executeCommand for i…
valentinpalkovic Nov 7, 2025
b56df2c
Remove 'skip-install' option from init function in sandbox-parts.ts f…
valentinpalkovic Nov 7, 2025
993b2dd
Add debug logging for version handling in postinstall and package man…
valentinpalkovic Nov 10, 2025
eee9dbe
Enhance version handling in JsPackageManager by incorporating version…
valentinpalkovic Nov 10, 2025
9ba0234
Refactor JsPackageManager tests to use spyOn for mocking latestVersio…
valentinpalkovic Nov 10, 2025
8e2937d
Merge branch 'valentin/cli-init-rework' into valentin/cli-init-rework…
valentinpalkovic Nov 12, 2025
baa5ad6
Refactor command execution in dispatcher and add synchronous command …
valentinpalkovic Nov 12, 2025
116cf23
Refactor taskLog implementation to simplify logging conditions and re…
valentinpalkovic Nov 12, 2025
a84ea41
Refactor command spawning in dispatcher to separate command and argum…
valentinpalkovic Nov 12, 2025
b9eb956
Refactor telemetry error handling to conditionally send errors only w…
valentinpalkovic Nov 12, 2025
f9221a8
Update stdio configuration in AddonVitestService to inherit and pipe …
valentinpalkovic Nov 12, 2025
ae93da3
Update Playwright installation command in AddonVitestService to use '…
valentinpalkovic Nov 12, 2025
80a78e7
Update automigration logging to handle optional fixId for improved cl…
valentinpalkovic Nov 12, 2025
a2d573b
Merge branch 'valentin/cli-init-rework' into valentin/cli-init-rework…
valentinpalkovic Nov 13, 2025
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
8 changes: 4 additions & 4 deletions code/addons/themes/src/postinstall.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { spawn } from 'child_process';
import { spawnSync } from 'child_process';

const PACKAGE_MANAGER_TO_COMMAND = {
npm: 'npx',
Expand All @@ -12,11 +12,11 @@ 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(' ');

await spawn(`${command} @storybook/auto-config themes`, {
spawnSync(command, [...commandArgs, '@storybook/auto-config', 'themes'], {
stdio: 'inherit',
cwd: process.cwd(),
shell: true,
});
}
8 changes: 5 additions & 3 deletions code/addons/vitest/src/postinstall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -328,21 +329,22 @@ 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) {
command.push('--skip-install');
}

if (options.configDir !== '.storybook') {
command.push(`--config-dir="${options.configDir}"`);
command.push('--config-dir', options.configDir);
}

await prompt.executeTask(
Expand Down
1 change: 0 additions & 1 deletion code/addons/vitest/src/vitest-plugin/global-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
1 change: 0 additions & 1 deletion code/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,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",
Expand Down
4 changes: 2 additions & 2 deletions code/core/src/bin/dispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
];
}
Expand All @@ -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);
});
Expand Down
2 changes: 1 addition & 1 deletion code/core/src/builder-manager/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ const starter: StarterFunction = async function* starterGeneratorFn({
router,
}) {
if (!options.quiet) {
logger.info('Starting manager..');
logger.info('Starting...');
}

const {
Expand Down
2 changes: 1 addition & 1 deletion code/core/src/cli/AddonVitestService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@
});
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,
Expand All @@ -411,7 +411,7 @@

await service.installPlaywright(mockPackageManager);

expect(mockPackageManager.runPackageCommand).toHaveBeenCalledWith({

Check failure on line 414 in code/core/src/cli/AddonVitestService.test.ts

View workflow job for this annotation

GitHub Actions / Core Unit Tests, windows-latest

src/cli/AddonVitestService.test.ts > AddonVitestService > installPlaywright > should execute playwright install command

AssertionError: expected "spy" to be called with arguments: [ { …(3) } ] Received: 1st spy call: @@ -5,8 +5,12 @@ "install", "chromium", "--with-deps", ], "signal": undefined, - "stdio": "ignore", + "stdio": [ + "inherit", + "pipe", + "pipe", + ], }, ] Number of calls: 1 ❯ src/cli/AddonVitestService.test.ts:414:52
args: ['playwright', 'install', 'chromium', '--with-deps'],
signal: undefined,
stdio: 'ignore',
Expand Down
6 changes: 3 additions & 3 deletions code/core/src/cli/AddonVitestService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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?',
Expand All @@ -139,12 +139,12 @@ export class AddonVitestService {
(signal) =>
packageManager.runPackageCommand({
args: playwrightCommand,
stdio: 'ignore',
stdio: ['inherit', 'pipe', 'pipe'],
signal,
}),
{
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,
Expand Down
15 changes: 8 additions & 7 deletions code/core/src/common/js-package-manager/JsPackageManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof vi.fn>;
let mockLatestVersion: ReturnType<typeof vi.spyOn>;

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();
});
Expand Down
9 changes: 6 additions & 3 deletions code/core/src/common/js-package-manager/JsPackageManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
Expand Down
98 changes: 56 additions & 42 deletions code/core/src/common/js-package-manager/JsPackageManagerFactory.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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;
}
5 changes: 5 additions & 0 deletions code/core/src/common/js-package-manager/Yarn2Proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 } from '../utils/command';
import { getProjectRoot } from '../utils/paths';
Expand Down Expand Up @@ -136,6 +137,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;
Expand Down Expand Up @@ -280,6 +283,7 @@ export class Yarn2Proxy extends JsPackageManager {
const duplicatedDependencies: Record<string, string[]> = {};

lines.forEach((packageName) => {
logger.debug(`Processing package ${packageName}`);
if (
!packageName ||
!pattern.some((p) => new RegExp(`${p.replace(/\*/g, '.*')}`).test(packageName))
Expand All @@ -288,6 +292,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);
Expand Down
16 changes: 14 additions & 2 deletions code/core/src/common/utils/command.ts
Original file line number Diff line number Diff line change
@@ -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',
Expand All @@ -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,
Expand All @@ -45,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 '';
}
}
4 changes: 1 addition & 3 deletions code/core/src/core-server/dev-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
Loading
Loading