Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
67d5780
generate code snippets from csf file
kasperpeulen Oct 9, 2025
5efe183
Lot more cases
kasperpeulen Oct 10, 2025
446abe5
Cleanup code
kasperpeulen Oct 10, 2025
ef96eff
Inline args in JSX
kasperpeulen Oct 10, 2025
2d86537
More test examples
kasperpeulen Oct 10, 2025
3738a60
Add extra tests
kasperpeulen Oct 10, 2025
2442574
Fix type error
kasperpeulen Oct 10, 2025
18aaaf4
Template.bind expressions
kasperpeulen Oct 10, 2025
50ea146
Add componentManifestGenerator preset and implement for react
kasperpeulen Oct 15, 2025
addd229
Merge remote-tracking branch 'origin/10.1' into kasper/code-snippets
kasperpeulen Oct 15, 2025
4ed3856
Fix types
kasperpeulen Oct 15, 2025
a449847
Update code/core/src/core-server/dev-server.ts
kasperpeulen Oct 16, 2025
4ce972a
Update code/core/src/core-server/dev-server.ts
kasperpeulen Oct 16, 2025
1599fac
Update code/core/src/core-server/build-static.ts
kasperpeulen Oct 16, 2025
1bcd00b
Update code/core/src/core-server/build-static.ts
kasperpeulen Oct 16, 2025
bf9a49a
Improve dev server logic
kasperpeulen Oct 16, 2025
f2e3ddb
Merge remote-tracking branch 'origin/kasper/code-snippets' into kaspe…
kasperpeulen Oct 16, 2025
c0df22c
Add type
kasperpeulen Oct 16, 2025
25b197e
Fix lint
kasperpeulen Oct 16, 2025
18c671e
Use node logger
kasperpeulen Oct 16, 2025
11ac914
Fix
kasperpeulen Oct 16, 2025
bd91ab6
Fix typos
kasperpeulen Oct 16, 2025
1e84547
Remove redundant features fetch
kasperpeulen Oct 16, 2025
a6f6673
Apply feedback
kasperpeulen Oct 16, 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
19 changes: 18 additions & 1 deletion code/core/src/core-server/build-static.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { cp, mkdir } from 'node:fs/promises';
import { cp, mkdir, writeFile } from 'node:fs/promises';
import { rm } from 'node:fs/promises';
import { join, relative, resolve } from 'node:path';

Expand All @@ -18,6 +18,7 @@ import { global } from '@storybook/global';
import picocolors from 'picocolors';

import { resolvePackageDir } from '../shared/utils/module';
import { type ComponentManifestGenerator } from '../types';
import { StoryIndexGenerator } from './utils/StoryIndexGenerator';
import { buildOrThrow } from './utils/build-or-throw';
import { copyAllStaticFilesRelativeToMain } from './utils/copy-all-static-files';
Expand Down Expand Up @@ -163,6 +164,22 @@ export async function buildStaticStandalone(options: BuildStaticStandaloneOption
initializedStoryIndexGenerator as Promise<StoryIndexGenerator>
)
);

const features = await presets.apply('features');
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

if (features?.experimental_componentsManifest) {
const componentManifestGenerator: ComponentManifestGenerator = await presets.apply(
'componentManifestGenerator'
);
const indexGenerator = await initializedStoryIndexGenerator;
if (componentManifestGenerator && indexGenerator) {
const manifests = await componentManifestGenerator(indexGenerator);
await writeFile(
join(options.outputDir, 'mainfests', 'components.json'),
JSON.stringify(manifests)
);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}
Comment on lines +167 to +181

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add error handling for manifest generation.

The manifest generation and file write operations have no error handling. If any step fails, it will crash the entire build process. Consider wrapping this block in a try-catch to log errors gracefully without breaking the build.

Apply this diff:

     if (features?.experimental_componentsManifest) {
-      const componentManifestGenerator: ComponentManifestGenerator = await presets.apply(
-        'componentManifestGenerator'
-      );
-      const indexGenerator = await initializedStoryIndexGenerator;
-      if (componentManifestGenerator && indexGenerator) {
-        const manifests = await componentManifestGenerator(indexGenerator);
-        const manifestsDir = join(options.outputDir, 'manifests');
-        await mkdir(manifestsDir, { recursive: true });
-        await writeFile(
-          join(manifestsDir, 'components.json'),
-          JSON.stringify(manifests)
-        );
+      try {
+        const componentManifestGenerator: ComponentManifestGenerator = await presets.apply(
+          'componentManifestGenerator'
+        );
+        const indexGenerator = await initializedStoryIndexGenerator;
+        if (componentManifestGenerator && indexGenerator) {
+          const manifests = await componentManifestGenerator(indexGenerator);
+          const manifestsDir = join(options.outputDir, 'manifests');
+          await mkdir(manifestsDir, { recursive: true });
+          await writeFile(
+            join(manifestsDir, 'components.json'),
+            JSON.stringify(manifests)
+          );
+        }
+      } catch (error) {
+        logger.warn('Failed to generate component manifests:', error);
       }
     }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In code/core/src/core-server/build-static.ts around lines 167 to 182, the
manifest generation and write operations are unguarded and can crash the build;
wrap the entire conditional block that calls componentManifestGenerator, awaits
indexGenerator, and writes the file in a try-catch, log any caught error with
context (including whether generation or file write failed) using the existing
logger (or processLogger), ensure the target directory is created before writing
(or handle write errors), and do not rethrow so the build continues gracefully
on failure.

}

if (!core?.disableProjectJson) {
Expand Down
27 changes: 27 additions & 0 deletions code/core/src/core-server/dev-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import polka from 'polka';
import invariant from 'tiny-invariant';

import { telemetry } from '../telemetry';
import { type ComponentManifestGenerator } from '../types';
import type { StoryIndexGenerator } from './utils/StoryIndexGenerator';
import { doTelemetry } from './utils/doTelemetry';
import { getManagerBuilder, getPreviewBuilder } from './utils/get-builders';
Expand Down Expand Up @@ -135,6 +136,32 @@ export async function storybookDevServer(options: Options) {
throw indexError;
}

app.use('/mainfests/components.json', async (req, res) => {
try {
const features = await options.presets.apply('features');
if (!features?.experimental_componentsManifest) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't this condition backwards?

Suggested change
if (!features?.experimental_componentsManifest) {
if (features?.experimental_componentsManifest) {

const componentManifestGenerator: ComponentManifestGenerator = await options.presets.apply(
'componentManifestGenerator'
);
const indexGenerator = await initializedStoryIndexGenerator;
if (componentManifestGenerator && indexGenerator) {
const manifest = await componentManifestGenerator(indexGenerator);
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(manifest));
return;
}
}
res.statusCode = 400;
res.end('No component manifest generator configured.');
Comment thread
kasperpeulen marked this conversation as resolved.
Outdated
return;
} catch (e) {
console.error(e);
res.statusCode = 500;
res.end(e instanceof Error ? e.toString() : String(e));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Use Storybook logger instead of console.error.

Server-side code should log through storybook/internal/node-logger per our guidelines. Please swap in logger.error (optionally with a contextual message) so the log stream stays consistent.

-    } catch (e) {
-      console.error(e);
+    } catch (e) {
+      logger.error('Failed to generate component manifest', e);
       res.statusCode = 500;
       res.end(e instanceof Error ? e.toString() : String(e));

[As per coding guidelines]

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.error(e);
res.statusCode = 500;
res.end(e instanceof Error ? e.toString() : String(e));
} catch (e) {
logger.error('Failed to generate component manifest', e);
res.statusCode = 500;
res.end(e instanceof Error ? e.toString() : String(e));
🤖 Prompt for AI Agents
In code/core/src/core-server/dev-server.ts around lines 158 to 160, replace the
direct console.error usage with the Storybook node logger: import logger from
'storybook/internal/node-logger' (or add to existing imports) and call
logger.error with a contextual message and the error (e.g. logger.error('Dev
server error', e)) instead of console.error(e); keep the response handling the
same (res.statusCode = 500; res.end(...)). Ensure the import is added if missing
and use the logger API consistently.

return;
}
});

// Now the preview has successfully started, we can count this as a 'dev' event.
doTelemetry(app, core, initializedStoryIndexGenerator, options);

Expand Down
2 changes: 1 addition & 1 deletion code/core/src/csf-tools/CsfFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ export class CsfFile {

_metaStatement: t.Statement | undefined;

_metaNode: t.Expression | undefined;
_metaNode: t.ObjectExpression | undefined;

_metaPath: NodePath<t.ExportDefaultDeclaration> | undefined;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
// Inspired by Vitest fixture implementation:
// https://github.com/vitest-dev/vitest/blob/200a4349a2f85686bc7005dce686d9d1b48b84d2/packages/runner/src/fixture.ts
import type { PlayFunction } from 'storybook/internal/csf';
import { type Renderer } from 'storybook/internal/types';

export function mountDestructured<TRenderer extends Renderer>(
playFunction?: PlayFunction<TRenderer>
): boolean {
export function mountDestructured(playFunction?: (...args: any[]) => any): boolean {
return playFunction != null && getUsedProps(playFunction).includes('mount');
}
export function getUsedProps(fn: Function) {

export function getUsedProps(fn: (...args: any[]) => any) {
const match = fn.toString().match(/[^(]*\(([^)]*)/);

if (!match) {
Expand Down
14 changes: 14 additions & 0 deletions code/core/src/types/modules/core-common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { Server as NetServer } from 'net';
import type { Options as TelejsonOptions } from 'telejson';
import type { PackageJson as PackageJsonFromTypeFest } from 'type-fest';

import { type StoryIndexGenerator } from '../../core-server';
import type { Indexer, StoriesEntry } from './indexer';

/** ⚠️ This file contains internal WIP types they MUST NOT be exported outside this package for now! */
Expand Down Expand Up @@ -343,6 +344,16 @@ export type TagsOptions = Record<Tag, Partial<TagOptions>>;
* The interface for Storybook configuration used internally in presets The difference is that these
* values are the raw values, AKA, not wrapped with `PresetValue<>`
*/

export interface ComponentManifest {
id: string;
examples: { name: string; snippet: string }[];
}

export type ComponentManifestGenerator = (
storyIndexGenerator: StoryIndexGenerator
) => Promise<Record<string, ComponentManifest>>;

export interface StorybookConfigRaw {
/**
* Sets the addons you want to use with Storybook.
Expand All @@ -356,6 +367,7 @@ export interface StorybookConfigRaw {
*/
addons?: Preset[];
core?: CoreConfig;
componentManifestGenerator?: ComponentManifestGenerator;
staticDirs?: (DirectoryMapping | string)[];
logLevel?: string;
features?: {
Expand Down Expand Up @@ -453,6 +465,8 @@ export interface StorybookConfigRaw {
developmentModeForBuild?: boolean;
/** Only show input controls in Angular */
angularFilterNonInputControls?: boolean;

experimental_componentsManifest?: boolean;
};

build?: TestBuildConfig;
Expand Down
1 change: 1 addition & 0 deletions code/core/src/types/modules/indexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export interface BaseIndexEntry {
title: ComponentTitle;
tags?: Tag[];
importPath: Path;
componentPath?: Path;
}
export type StoryIndexEntry = BaseIndexEntry & {
type: 'story';
Expand Down
Loading