Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 10.1.3

- Angular: Honor --loglevel and --logfile in dev/build - [#33212](https://github.com/storybookjs/storybook/pull/33212), thanks @valentinpalkovic!
- Core: Minor UI fixes - [#33218](https://github.com/storybookjs/storybook/pull/33218), thanks @ghengeveld!
- Telemetry: Add playwright-prompt - [#33229](https://github.com/storybookjs/storybook/pull/33229), thanks @valentinpalkovic!

## 10.1.2

- Checklist: Fix how state changes are reported and drop some completion restrictions - [#33217](https://github.com/storybookjs/storybook/pull/33217), thanks @ghengeveld!
Expand Down
12 changes: 8 additions & 4 deletions code/core/src/bin/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@ addToGlobalContext('cliVersion', version);
*/

const handleCommandFailure = async (logFilePath: string | boolean): Promise<never> => {
const logFile = await logTracker.writeToFile(logFilePath);
logger.log(`Debug logs are written to: ${logFile}`);
try {
const logFile = await logTracker.writeToFile(logFilePath);
logger.log(`Debug logs are written to: ${logFile}`);
} catch {}
logger.outro('Storybook exited with an error');
process.exit(1);
};
Expand Down Expand Up @@ -66,8 +68,10 @@ const command = (name: string) =>
})
.hook('postAction', async (command) => {
if (logTracker.shouldWriteLogsToFile) {
const logFile = await logTracker.writeToFile(command.getOptionValue('logfile'));
logger.outro(`Debug logs are written to: ${logFile}`);
try {
const logFile = await logTracker.writeToFile(command.getOptionValue('logfile'));
logger.outro(`Debug logs are written to: ${logFile}`);
} catch {}
}
});

Expand Down
17 changes: 14 additions & 3 deletions code/core/src/cli/AddonVitestService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,16 @@ export class AddonVitestService {
* @param options - Installation options
* @returns Array of error messages if installation fails
*/
async installPlaywright(options: { yes?: boolean } = {}): Promise<{ errors: string[] }> {
async installPlaywright(
options: { yes?: boolean } = {}
): Promise<{ errors: string[]; result: 'installed' | 'skipped' | 'aborted' | 'failed' }> {
const errors: string[] = [];

const playwrightCommand = ['playwright', 'install', 'chromium', '--with-deps'];
const playwrightCommandString = this.packageManager.getPackageCommand(playwrightCommand);

let result: 'installed' | 'skipped' | 'aborted' | 'failed';

try {
const shouldBeInstalled = options.yes
? true
Expand All @@ -135,7 +139,7 @@ export class AddonVitestService {
})();

if (shouldBeInstalled) {
await prompt.executeTaskWithSpinner(
const processAborted = await prompt.executeTaskWithSpinner(
(signal) =>
this.packageManager.runPackageCommand({
args: playwrightCommand,
Expand All @@ -150,10 +154,17 @@ export class AddonVitestService {
abortable: true,
}
);
if (processAborted) {
result = 'aborted';
} else {
result = 'installed';
}
} else {
logger.warn('Playwright installation skipped');
result = 'skipped';
}
} catch (e) {
result = 'failed';
ErrorCollector.addError(e);
if (e instanceof Error) {
errors.push(e.stack ?? e.message);
Expand All @@ -162,7 +173,7 @@ export class AddonVitestService {
}
}

return { errors };
return { errors, result };
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,7 @@ export const PopoverProvider = ({
{...props}
>
<Pressable>{children}</Pressable>
<PopoverUpstream
placement={placement}
offset={offset}
style={{ outlineColor: 'transparent' }}
>
<PopoverUpstream placement={placement} offset={offset} style={{ outline: 'none' }}>
<Popover
hasChrome={hasChrome}
hideLabel={closeLabel}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ const TooltipProvider = ({
placement={placement}
offset={offset}
onOpenChange={onOpenChange}
style={{ outlineColor: 'transparent' }}
style={{ outline: 'none' }}
{...props}
>
{tooltip}
Expand Down
2 changes: 0 additions & 2 deletions code/core/src/core-server/withTelemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import {
import type { EventType } from 'storybook/internal/telemetry';
import type { CLIOptions } from 'storybook/internal/types';

import { dedent } from 'ts-dedent';

import { StorybookError } from '../storybook-error';

type TelemetryOptions = {
Expand Down
3 changes: 2 additions & 1 deletion code/core/src/manager/components/sidebar/TagsFilterPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export const TagsFilterPanel = ({
id: `filter-${type}-${id}`,
content: (
<ActionList.HoverItem targetId={`filter-${type}-${id}`}>
<ActionList.Action as="label" tabIndex={-1} tooltip={toggleTooltip}>
<ActionList.Action as="label" ariaLabel={false} tabIndex={-1} tooltip={toggleTooltip}>
<ActionList.Icon>
{isExcluded ? <DeleteIcon /> : isIncluded ? null : icon}
<Form.Checkbox
Expand Down Expand Up @@ -201,6 +201,7 @@ export const TagsFilterPanel = ({
<ActionList as="div">
<ActionList.Item as="div">
<ActionList.Link
ariaLabel={false}
href={api.getDocsUrl({ subpath: 'writing-stories/tags#custom-tags' })}
target="_blank"
>
Expand Down
2 changes: 2 additions & 0 deletions code/core/src/node-logger/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export { protectUrls, createHyperlink } from './wrap-utils';
export { CLI_COLORS } from './logger/colors';
export { ConsoleLogger, StyledConsoleLogger } from './logger/console';

export type { LogLevel } from './logger/logger';

// The default is stderr, which can cause some tools (like rush.js) to think
// there are issues with the build: https://github.com/storybookjs/storybook/issues/14621
npmLog.stream = process.stdout;
Expand Down
75 changes: 75 additions & 0 deletions code/core/src/node-logger/tasks.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { describe, expect, it, vi } from 'vitest';

// eslint-disable-next-line depend/ban-dependencies
import type { ExecaChildProcess } from 'execa';

import { executeTaskWithSpinner } from './tasks';

// Create a minimal fake ExecaChildProcess
const makeChild = (onStart?: (cp: Partial<ExecaChildProcess>) => void): ExecaChildProcess => {
const listeners: Record<string, Function[]> = {};
const stdout = {
on: vi.fn((event: string, cb: (data: Buffer) => void) => {
listeners[event] ||= [];
listeners[event].push(cb);
}),
} as any;

const cp: Partial<ExecaChildProcess> = {
stdout: stdout as any,
then: undefined as any,
catch: undefined as any,
finally: undefined as any,
};

const promise = Promise.resolve() as any;
Object.setPrototypeOf(cp, promise);
(cp as any).then = promise.then.bind(promise);
(cp as any).catch = promise.catch.bind(promise);
(cp as any).finally = promise.finally.bind(promise);

onStart?.(cp);
return cp as ExecaChildProcess;
};

describe('executeTaskWithSpinner', () => {
it('returns "aborted" when the child process rejects with an abort error', async () => {
const outcome = await executeTaskWithSpinner(() => makeChild(), {
id: 'test',
intro: 'Intro',
error: 'Error',
success: 'Success',
abortable: true,
});

// Non-abort path returns undefined
expect(outcome).toBeUndefined();

// Simulate an aborted child process by rejecting with an abort-like error message
const outcome2 = await executeTaskWithSpinner(
() => {
const err = new Error('The operation was aborted');
const p = Promise.reject(err);
// Avoid unhandled rejection warnings
p.catch(() => {});
const cp: any = makeChild();
// Make the thenable reject
const rejected = p as any;
Object.setPrototypeOf(cp, rejected);
cp.then = rejected.then.bind(rejected);
cp.catch = rejected.catch.bind(rejected);
cp.finally = rejected.finally.bind(rejected);
return cp;
},
{
id: 'test2',
intro: 'Intro',
error: 'Error',
success: 'Success',
abortable: true,
}
);

expect(outcome2).toBe('aborted');
});
});
10 changes: 6 additions & 4 deletions code/core/src/node-logger/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export const executeTask = async (
success,
abortable = false,
}: { intro: string; error: string; success: string; abortable?: boolean }
) => {
): Promise<'aborted' | void> => {
logTracker.addLog('info', intro);
log(intro);

Expand Down Expand Up @@ -99,7 +99,7 @@ export const executeTask = async (
if (isAborted) {
logTracker.addLog('info', `${intro} aborted`);
log(CLI_COLORS.error(`${intro} aborted`));
return;
return 'aborted';
}
const errorMessage = err instanceof Error ? (err.stack ?? err.message) : String(err);
logTracker.addLog('error', error, { error: errorMessage });
Expand All @@ -108,6 +108,7 @@ export const executeTask = async (
} finally {
cleanup?.();
}
return undefined;
};

export const executeTaskWithSpinner = async (
Expand All @@ -119,7 +120,7 @@ export const executeTaskWithSpinner = async (
success,
abortable = false,
}: { id: string; intro: string; error: string; success: string; abortable?: boolean }
) => {
): Promise<'aborted' | void> => {
logTracker.addLog('info', intro);

let abortController: AbortController | undefined;
Expand Down Expand Up @@ -159,7 +160,7 @@ export const executeTaskWithSpinner = async (
if (isAborted) {
logTracker.addLog('info', `${intro} aborted`);
task.cancel(CLI_COLORS.warning(`${intro} aborted`));
return;
return 'aborted';
}
const errorMessage = err instanceof Error ? (err.stack ?? err.message) : String(err);
logTracker.addLog('error', error, { error: errorMessage });
Expand All @@ -168,4 +169,5 @@ export const executeTaskWithSpinner = async (
} finally {
cleanup?.();
}
return undefined;
};
4 changes: 3 additions & 1 deletion code/core/src/types/modules/core-common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import type { FileSystemCache } from 'storybook/internal/common';
import { type StoryIndexGenerator } from 'storybook/internal/core-server';
import { type CsfFile } from 'storybook/internal/csf-tools';
import type { LogLevel } from 'storybook/internal/node-logger';

import type { Server as HttpServer, IncomingMessage, ServerResponse } from 'http';
import type { Server as NetServer } from 'net';
Expand Down Expand Up @@ -173,7 +174,8 @@ export interface CLIBaseOptions {
disableTelemetry?: boolean;
enableCrashReports?: boolean;
configDir?: string;
loglevel?: string;
loglevel?: LogLevel;
logfile?: string | boolean;
quiet?: boolean;
}

Expand Down
6 changes: 5 additions & 1 deletion code/frameworks/angular/build-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@
"loglevel": {
"type": "string",
"description": "Controls level of logging during build. Can be one of: [silly, verbose, info (default), warn, error, silent].",
"pattern": "(silly|verbose|info|warn|silent)"
"pattern": "(trace|debug|info|warn|error|silent)"
},
"logfile": {
"type": "string",
"description": "If provided, the log output will be written to the specified file path."
},
"debugWebpack": {
"type": "boolean",
Expand Down
15 changes: 14 additions & 1 deletion code/frameworks/angular/src/builders/build-storybook/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { getEnvConfig, getProjectRoot, versions } from 'storybook/internal/commo
import { buildStaticStandalone, withTelemetry } from 'storybook/internal/core-server';
import { addToGlobalContext } from 'storybook/internal/telemetry';
import type { CLIOptions } from 'storybook/internal/types';
import { logger } from 'storybook/internal/node-logger';
import { logger, logTracker } from 'storybook/internal/node-logger';

import type {
BuilderContext,
Expand Down Expand Up @@ -60,6 +60,7 @@ export type StorybookBuilderOptions = JsonObject & {
| 'statsJson'
| 'disableTelemetry'
| 'debugWebpack'
| 'logfile'
| 'previewUrl'
>;

Expand All @@ -71,6 +72,14 @@ const commandBuilder: BuilderHandlerFn<StorybookBuilderOptions> = async (
options,
context
): Promise<BuilderOutput> => {
// Apply logger configuration from builder options
if (options.loglevel) {
logger.setLogLevel(options.loglevel);
}
if (options.logfile) {
logTracker.enableLogWriting();
}

logger.intro('Building Storybook');

const { tsConfig } = await setup(options, context);
Expand Down Expand Up @@ -147,6 +156,10 @@ const commandBuilder: BuilderHandlerFn<StorybookBuilderOptions> = async (
};

await runInstance({ ...standaloneOptions, mode: 'static' });
if (logTracker.shouldWriteLogsToFile) {
const logFile = await logTracker.writeToFile(options.logfile as any);
logger.info(`Debug logs are written to: ${logFile}`);
}
logger.outro('Storybook build completed successfully');
return { success: true } as BuilderOutput;
};
Expand Down
20 changes: 19 additions & 1 deletion code/frameworks/angular/src/builders/start-storybook/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { getEnvConfig, getProjectRoot, versions } from 'storybook/internal/commo
import { buildDevStandalone, withTelemetry } from 'storybook/internal/core-server';
import { addToGlobalContext } from 'storybook/internal/telemetry';
import type { CLIOptions } from 'storybook/internal/types';
import { logger } from 'storybook/internal/node-logger';
import { logger, logTracker } from 'storybook/internal/node-logger';

import type {
BuilderContext,
Expand Down Expand Up @@ -65,6 +65,7 @@ export type StorybookBuilderOptions = JsonObject & {
| 'open'
| 'docs'
| 'debugWebpack'
| 'logfile'
| 'webpackStatsJson'
| 'statsJson'
| 'loglevel'
Expand All @@ -80,6 +81,14 @@ const commandBuilder: BuilderHandlerFn<StorybookBuilderOptions> = (
return new Observable<BuilderOutput>((observer) => {
(async () => {
try {
// Apply logger configuration from builder options
if (options.loglevel) {
logger.setLogLevel(options.loglevel);
}
if (options.logfile) {
logTracker.enableLogWriting();
}

logger.intro('Starting Storybook');

const { tsConfig } = await setup(options, context);
Expand Down Expand Up @@ -187,6 +196,15 @@ const commandBuilder: BuilderHandlerFn<StorybookBuilderOptions> = (
// so the dev server continues running. Architect will keep subscribing
// until the Observable completes, which allows watch mode to work.
} catch (error) {
// Write logs to file on failure when enabled
try {
if (logTracker.shouldWriteLogsToFile) {
try {
const logFile = await logTracker.writeToFile(options.logfile as any);
logger.outro(`Debug logs are written to: ${logFile}`);
} catch {}
}
} catch {}
observer.error(error);
}
})();
Expand Down
Loading