Skip to content

Conversation

kasperpeulen
Copy link
Contributor

@kasperpeulen kasperpeulen commented Oct 9, 2025

closes #32706, closes #32705

Create components.json file for dev and build with examples snippets generated from the stories files

What I did

Checklist for Contributors

Testing

The changes in this PR are covered in the following automated tests:

  • stories
  • unit tests
  • integration tests
  • end-to-end tests

Manual testing

This section is mandatory for all contributions. If you believe no manual test is necessary, please state so explicitly. Thanks!

Documentation

  • Add or update documentation reflecting your changes
  • If you are deprecating/removing a feature, make sure to update
    MIGRATION.MD

Checklist for Maintainers

  • When this PR is ready for testing, make sure to add ci:normal, ci:merged or ci:daily GH label to it to run a specific set of sandboxes. The particular set of sandboxes can be found in code/lib/cli-storybook/src/sandbox-templates.ts

  • Make sure this PR contains one of the labels below:

    Available labels
    • bug: Internal changes that fixes incorrect behavior.
    • maintenance: User-facing maintenance tasks.
    • dependencies: Upgrading (sometimes downgrading) dependencies.
    • build: Internal-facing build tooling & test updates. Will not show up in release changelog.
    • cleanup: Minor cleanup style change. Will not show up in release changelog.
    • documentation: Documentation only changes. Will not show up in release changelog.
    • feature request: Introducing a new feature.
    • BREAKING CHANGE: Changes that break compatibility in some way with current major version.
    • other: Changes that don't fit in the above categories.

🦋 Canary release

This PR does not have a canary release associated. You can request a canary release of this pull request by mentioning the @storybookjs/core team here.

core team members can create a canary release here or locally with gh workflow run --repo storybookjs/storybook publish.yml --field pr=<PR_NUMBER>

Summary by CodeRabbit

  • New Features

    • Component manifests are generated during static builds and served at /manifests/components.json; dev server exposes the manifest endpoint when enabled.
    • React renderer produces ready-to-use example code snippets for stories included in manifests.
  • Configuration

    • Opt-in feature flag and configurable manifest generator; story index entries may include optional component path and a defined manifest shape.
  • Tests

    • Extensive tests covering React code-snippet generation across many story patterns.
  • Refactor

    • Type refinements and relaxed function typings to simplify handling.

@kasperpeulen kasperpeulen changed the title generate code snippets from csf file MCP: Generate code snippets from csf file Oct 9, 2025
Copy link
Contributor

coderabbitai bot commented Oct 9, 2025

📝 Walkthrough

Walkthrough

Adds a component-manifest pipeline: new manifest types and feature flag, a React renderer preset that generates per-component example snippets from CSF files, a dev HTTP endpoint and static-build writer for manifests/components.json, a comprehensive snippet-generation test, and a CSF meta-node type narrowing.

Changes

Cohort / File(s) Summary of changes
Core server: static build hook
code/core/src/core-server/build-static.ts
Import writeFile and type ComponentManifestGenerator; when features.experimental_componentsManifest is enabled and a generator plus initialized StoryIndexGenerator are present, generate manifests and write outputDir/manifests/components.json.
Core server: dev endpoint
code/core/src/core-server/dev-server.ts
Register GET /manifests/components.json: apply presets for componentManifestGenerator, await initializedStoryIndexGenerator, call generator if available, return JSON or 400/500 on missing/config/errors; errors are logged.
Public types: manifest and feature flag
code/core/src/types/modules/core-common.ts
Add ComponentManifest interface and ComponentManifestGenerator type; extend StorybookConfigRaw with componentManifestGenerator?: ComponentManifestGenerator and features.experimental_componentsManifest?: boolean.
Index types: componentPath field
code/core/src/types/modules/indexer.ts
Add optional componentPath?: Path to BaseIndexEntry and componentPath?: string to StoryIndexEntry.
React renderer: snippet generator
code/renderers/react/src/component-manifest/generateCodeSnippet.ts
New module exporting getCodeSnippet(...) that resolves CSF story shapes, merges args, transforms JSX/spreads and .bind patterns, inlines args/children, and returns a Babel VariableDeclaration representing a simplified React snippet.
React renderer: preset generator
code/renderers/react/src/preset.ts
New exported componentManifestGenerator: groups story index entries by component, reads component source files, parses CSF, derives component names, generates example snippets via getCodeSnippet + recast, and returns a map of component manifests.
React renderer: tests for snippet generation
code/renderers/react/src/component-manifest/generateCodeSnippet.test.tsx
New Vitest test suite covering many CSF3/CSF4 and story-shape scenarios, asserting generated snippets via inline snapshots.
CSF tools: meta node typing
code/core/src/csf-tools/CsfFile.ts
Narrow _metaNode type from `t.Expression
Preview API: loosened mount types
code/core/src/preview-api/modules/preview-web/render/mount-utils.ts
Relaxed function types to (...args: any[]) => any for mountDestructured and getUsedProps, replacing previous generic signatures.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant CLI as Builder/Dev
  participant Presets
  participant CoreServer
  participant SIG as StoryIndexGenerator
  participant Gen as ComponentManifestGenerator
  participant FS as File System
  rect #f8fbff
    note left of CoreServer: Static build flow (feature flag enabled)
    CLI->>CoreServer: run build-static
    CoreServer->>SIG: init StoryIndexGenerator
    CoreServer->>Presets: apply('componentManifestGenerator')
    Presets-->>CoreServer: Gen?
    alt Gen present && SIG ready
      CoreServer->>Gen: generate(SIG)
      Gen-->>CoreServer: manifest map
      CoreServer->>FS: write outputDir/manifests/components.json
      FS-->>CoreServer: OK
    else
      CoreServer-->>CLI: skip manifest generation
    end
  end
Loading
sequenceDiagram
  autonumber
  participant Browser
  participant DevServer as Core Dev Server
  participant Presets
  participant SIG as StoryIndexGenerator
  participant Gen as ComponentManifestGenerator
  rect #f8fbff
    note left of DevServer: Dev endpoint /manifests/components.json
    Browser->>DevServer: GET /manifests/components.json
    DevServer->>Presets: apply('componentManifestGenerator')
    Presets-->>DevServer: Gen?
    DevServer->>SIG: await initializedStoryIndexGenerator
    alt Gen present && SIG ready
      DevServer->>Gen: generate(SIG)
      Gen-->>DevServer: manifest JSON
      DevServer-->>Browser: 200 application/json
    else
      DevServer-->>Browser: 400/500 error (missing generator/prereqs)
    end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch kasper/code-snippets

Comment @coderabbitai help to get the list of available commands and usage tips.

@kasperpeulen kasperpeulen marked this pull request as draft October 9, 2025 13:53
Copy link

nx-cloud bot commented Oct 9, 2025

View your CI Pipeline Execution ↗ for commit a6f6673

Command Status Duration Result
nx run-many -t build --parallel=3 ✅ Succeeded 44s View ↗

☁️ Nx Cloud last updated this comment at 2025-10-16 18:39:30 UTC

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
code/core/src/csf-tools/CsfFile.ts (1)

1049-1056: Bug: writeCsf ignores computed fname and may write to undefined

If fileName is omitted but csf._options.fileName is set, fname is computed but not used; writeFile(fileName as string, ...) can fail.

Apply this diff:

 export const writeCsf = async (csf: CsfFile, fileName?: string) => {
   const fname = fileName || csf._options.fileName;

   if (!fname) {
     throw new Error('Please specify a fileName for writeCsf');
   }
-  await writeFile(fileName as string, printCsf(csf).code);
+  await writeFile(fname, printCsf(csf).code);
 };
🧹 Nitpick comments (4)
code/core/src/csf-tools/generateCodeSnippet.test.tsx (1)

14-87: Add edge-case tests to harden generator

Please add coverage for:

  • Function-declared stories (export function Story() {}) to ensure non-variable exports work.
  • Multiple declarators in one export (export const A = {}, B = {}) to ensure correct binding.
  • meta.story() with no arguments (Default = meta.story()) to verify zero-arg handling.
  • meta.component as member expression (component: DesignSystem.Button) to ensure JSX name building.

Also applies to: 89-158

code/core/src/csf-tools/generateCodeSnippet.ts (3)

85-95: Component name handling breaks on member expressions (e.g., DesignSystem.Button)

Using jsxIdentifier(componentName) fails for dotted names; derive a JSX name from meta.component instead, with fallback.

Apply this diff and helper (see additional code block):

-  const name = t.jsxIdentifier(componentName);
+  const name =
+    jsxNameFromMeta(metaObj) ?? t.jsxIdentifier('Unknown');
 
   const arrow = t.arrowFunctionExpression(

Add helper functions:

// Place near other helpers
const jsxNameFromMeta = (
  metaObj?: t.ObjectExpression | null
): t.JSXIdentifier | t.JSXMemberExpression | null => {
  if (!metaObj) return null;
  const comp = (metaObj.properties as t.ObjectProperty[])
    .filter((p): p is t.ObjectProperty => t.isObjectProperty(p))
    .find((p) => keyOf(p) === 'component')?.value as t.Node | undefined;

  if (!comp || !t.isExpression(comp)) return null;
  return jsxNameFromExpr(comp);
};

const jsxNameFromExpr = (
  expr: t.Expression
): t.JSXIdentifier | t.JSXMemberExpression | null => {
  if (t.isIdentifier(expr)) return t.jsxIdentifier(expr.name);
  if (t.isMemberExpression(expr) && !expr.computed && t.isIdentifier(expr.property)) {
    const left = jsxNameFromExpr(expr.object as t.Expression);
    return left ? t.jsxMemberExpression(left, t.jsxIdentifier(expr.property.name)) : null;
  }
  return null;
};

102-112: Ordering and selection: iterate entries to pass exportName; ensure named export order is respected

Object.values(csf._storyPaths) loses export names and may not respect __namedExportsOrder. Iterate entries and pass exportName to getCodeSnippet.

Apply this diff:

-export function getAllCodeSnippets(csf: CsfFile) {
-  const component = csf._meta?.component ?? 'Unknown';
-
-  const snippets = Object.values(csf._storyPaths)
-    .map((path: NodePath<t.ExportNamedDeclaration>) =>
-      getCodeSnippet(path, csf._metaNode ?? null, component)
-    )
-    .filter(Boolean);
-
-  return t.program(snippets);
-}
+export function getAllCodeSnippets(csf: CsfFile) {
+  const entries = Object.entries(csf._storyPaths);
+  // If CsfFile sorted exports, prefer that order via _storyExports keys
+  const orderedNames = Object.keys(csf._storyExports);
+  const orderedEntries =
+    orderedNames.length === entries.length
+      ? orderedNames.map((name) => [name, csf._storyPaths[name]] as const)
+      : entries;
+
+  const snippets = orderedEntries
+    .map(([exportName, path]) => getCodeSnippet(path, exportName, csf._metaNode ?? null))
+    .filter(Boolean);
+
+  return t.program(snippets);
+}

152-161: Resolve meta.args when it’s an identifier (common pattern)

metaArgsRecord only handles inline object. Consider resolving identifiers (e.g., args: defaultArgs) via findVarInitialization and the Program AST.

If acceptable, I can wire in the program AST and reuse findVarInitialization from CsfFile for robust resolution.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a47e297 and 67d5780.

📒 Files selected for processing (3)
  • code/core/src/csf-tools/CsfFile.ts (1 hunks)
  • code/core/src/csf-tools/generateCodeSnippet.test.tsx (1 hunks)
  • code/core/src/csf-tools/generateCodeSnippet.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Adhere to ESLint and Prettier rules across all JS/TS source files

Files:

  • code/core/src/csf-tools/generateCodeSnippet.ts
  • code/core/src/csf-tools/generateCodeSnippet.test.tsx
  • code/core/src/csf-tools/CsfFile.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Fix type errors and prefer precise typings instead of using any or suppressions, consistent with strict mode

Files:

  • code/core/src/csf-tools/generateCodeSnippet.ts
  • code/core/src/csf-tools/generateCodeSnippet.test.tsx
  • code/core/src/csf-tools/CsfFile.ts
code/**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

code/**/*.{test,spec}.{ts,tsx}: Place all test files under the code/ directory
Name test files as *.test.ts, *.test.tsx, *.spec.ts, or *.spec.tsx

Files:

  • code/core/src/csf-tools/generateCodeSnippet.test.tsx
**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/spy-mocking.mdc)

**/*.test.{ts,tsx,js,jsx}: Use vi.mock() with the spy: true option for all package and file mocks in Vitest tests
Place all mocks at the top of the test file before any test cases
Use vi.mocked() to type and access mocked functions
Implement mock behaviors in beforeEach blocks
Mock all required dependencies that the test subject uses
Mock implementations should be placed in beforeEach blocks
Each mock implementation should return a Promise for async functions
Mock implementations should match the expected return type of the original function
Use vi.mocked() to access and implement mock behaviors
Mock all required properties and methods that the test subject uses
Avoid direct function mocking without vi.mocked()
Avoid mock implementations outside of beforeEach blocks
Avoid mocking without the spy: true option
Avoid inline mock implementations within test cases
Avoid mocking only a subset of required dependencies
Mock at the highest level of abstraction needed
Keep mock implementations simple and focused
Use type-safe mocking with vi.mocked()
Document complex mock behaviors
Group related mocks together

Files:

  • code/core/src/csf-tools/generateCodeSnippet.test.tsx
🧬 Code graph analysis (2)
code/core/src/csf-tools/generateCodeSnippet.ts (1)
code/core/src/csf-tools/CsfFile.ts (1)
  • CsfFile (277-1006)
code/core/src/csf-tools/generateCodeSnippet.test.tsx (2)
code/core/src/csf-tools/CsfFile.ts (1)
  • loadCsf (1021-1025)
code/core/src/csf-tools/generateCodeSnippet.ts (1)
  • getAllCodeSnippets (102-112)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Danger JS
  • GitHub Check: Core Unit Tests, windows-latest
🔇 Additional comments (1)
code/core/src/csf-tools/CsfFile.ts (1)

298-299: Type narrowing of _metaNode is correct

Limiting _metaNode to ObjectExpression matches how _parseMeta assigns it and aligns with snippet generation usage.

Comment on lines +12 to +18
const declaration = storyExportPath.get('declaration') as NodePath<t.Declaration>;
invariant(declaration.isVariableDeclaration(), 'Expected variable declaration');

const declarator = declaration.get('declarations')[0] as NodePath<t.VariableDeclarator>;
const init = declarator.get('init') as NodePath<t.Expression>;
invariant(init.isExpression(), 'Expected story initializer to be an expression');

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Handles only variable declarations; breaks on function-declared stories and multi-declarators

The generator assumes ExportNamedDeclaration always contains a VariableDeclaration and then blindly takes the first declarator. This fails for:

  • export function Story() {} (FunctionDeclaration)
  • export const A = {}, B = {} (selects wrong declarator)

Refactor to:

  • Accept exportName to select the matching declarator.
  • Handle FunctionDeclaration by converting to a function expression.

Apply these diffs:

-export function getCodeSnippet(
-  storyExportPath: NodePath<t.ExportNamedDeclaration>,
-  metaObj: t.ObjectExpression | null | undefined,
-  componentName: string
-): t.VariableDeclaration {
-  const declaration = storyExportPath.get('declaration') as NodePath<t.Declaration>;
-  invariant(declaration.isVariableDeclaration(), 'Expected variable declaration');
+export function getCodeSnippet(
+  storyExportPath: NodePath<t.ExportNamedDeclaration>,
+  exportName: string,
+  metaObj: t.ObjectExpression | null | undefined
+): t.VariableDeclaration {
+  const declaration = storyExportPath.get('declaration') as NodePath<t.Declaration>;
+  // Handle `export function Story() {}`
+  if (declaration.isFunctionDeclaration()) {
+    const fn = declaration.node;
+    const expr = t.functionExpression(
+      fn.id ?? t.identifier(exportName),
+      fn.params,
+      fn.body,
+      fn.generator,
+      fn.async
+    );
+    return t.variableDeclaration('const', [
+      t.variableDeclarator(t.identifier(exportName), expr),
+    ]);
+  }
+  invariant(declaration.isVariableDeclaration(), 'Expected variable declaration');
 
-  const declarator = declaration.get('declarations')[0] as NodePath<t.VariableDeclarator>;
+  // Select the matching declarator in `export const A = ..., B = ...`
+  const declarators = declaration.get('declarations') as NodePath<t.VariableDeclarator>[];
+  const declarator =
+    declarators.find((d) => {
+      const id = d.get('id');
+      return id.isIdentifier() && id.node.name === exportName;
+    }) ?? declarators[0];
   const init = declarator.get('init') as NodePath<t.Expression>;
   invariant(init.isExpression(), 'Expected story initializer to be an expression');
 
   const storyId = declarator.get('id');
   invariant(storyId.isIdentifier(), 'Expected named const story export');

Comment on lines +50 to +56
const renderPath = storyObjPath
?.get('properties')
.filter((p) => p.isObjectProperty())
.filter((p) => keyOf(p.node) === 'render')
.map((p) => p.get('value'))
.find((value) => value.isExpression());

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Restrict render to function expressions

Currently accepts any expression as render. Ensure it’s arrow/function for safety.

Apply this diff:

   const renderPath = storyObjPath
     ?.get('properties')
     .filter((p) => p.isObjectProperty())
     .filter((p) => keyOf(p.node) === 'render')
     .map((p) => p.get('value'))
-    .find((value) => value.isExpression());
+    .find(
+      (value) => value.isArrowFunctionExpression() || value.isFunctionExpression()
+    );
📝 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
const renderPath = storyObjPath
?.get('properties')
.filter((p) => p.isObjectProperty())
.filter((p) => keyOf(p.node) === 'render')
.map((p) => p.get('value'))
.find((value) => value.isExpression());
const renderPath = storyObjPath
?.get('properties')
.filter((p) => p.isObjectProperty())
.filter((p) => keyOf(p.node) === 'render')
.map((p) => p.get('value'))
.find(
(value) => value.isArrowFunctionExpression() || value.isFunctionExpression()
);
🤖 Prompt for AI Agents
In code/core/src/csf-tools/generateCodeSnippet.ts around lines 50 to 56, the
current logic finds any expression for the story "render" property but should
only accept function expressions; change the final predicate in the chain so the
found value must be a function — i.e., replace the .find((value) =>
value.isExpression()) with a predicate that checks
value.isArrowFunctionExpression() || value.isFunctionExpression(), so only arrow
or function expressions are accepted.

@kasperpeulen kasperpeulen changed the title MCP: Generate code snippets from csf file React: Create components.json file for dev and build with examples snippets generated from the stories files Oct 15, 2025
@kasperpeulen kasperpeulen marked this pull request as ready for review October 15, 2025 13:42
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
code/renderers/react/src/component-manifest/generateCodeSnippet.ts (2)

18-31: Blocker: breaks on function‑declared stories and multi‑declarators; add exportName and handle FunctionDeclaration.

Current code assumes a single VariableDeclaration and always takes the first declarator. It fails for:

  • export function Story() {}
  • export const A = {...}, B = {...}

Apply:

-export function getCodeSnippet(
-  storyExportPath: NodePath<t.ExportNamedDeclaration>,
-  metaObj: t.ObjectExpression | null | undefined,
-  componentName: string
-): t.VariableDeclaration {
+export function getCodeSnippet(
+  storyExportPath: NodePath<t.ExportNamedDeclaration>,
+  exportName: string,
+  metaObj: t.ObjectExpression | null | undefined,
+  componentName: string
+): t.VariableDeclaration {
   const declaration = storyExportPath.get('declaration') as NodePath<t.Declaration>;
-  invariant(declaration.isVariableDeclaration(), 'Expected variable declaration');
+  // Handle `export function Story() {}`
+  if (declaration.isFunctionDeclaration()) {
+    const fn = declaration.node;
+    const expr = t.functionExpression(
+      fn.id ?? t.identifier(exportName),
+      fn.params,
+      fn.body,
+      fn.generator,
+      fn.async
+    );
+    return t.variableDeclaration('const', [
+      t.variableDeclarator(t.identifier(exportName), expr),
+    ]);
+  }
+  invariant(declaration.isVariableDeclaration(), 'Expected variable declaration');
 
-  const declarator = declaration.get('declarations')[0] as NodePath<t.VariableDeclarator>;
+  // Select the matching declarator in `export const A = ..., B = ...`
+  const declarators = declaration.get('declarations') as NodePath<t.VariableDeclarator>[];
+  const declarator =
+    declarators.find((d) => {
+      const id = d.get('id');
+      return id.isIdentifier() && id.node.name === exportName;
+    }) ?? declarators[0];
   const init = declarator.get('init') as NodePath<t.Expression>;
   invariant(init.isExpression(), 'Expected story initializer to be an expression');
 
   const storyId = declarator.get('id');
   invariant(storyId.isIdentifier(), 'Expected named const story export');

This unblocks both patterns.


76-83: Constrain render to functions (reject arbitrary expressions).

Only accept arrow/function expressions for render, not any expression.

   const renderPath = storyObjPath
     ?.get('properties')
     .filter((p) => p.isObjectProperty())
     .filter((p) => keyOf(p.node) === 'render')
     .map((p) => p.get('value'))
-    .find((value) => value.isExpression());
+    .find(
+      (value) => value.isArrowFunctionExpression() || value.isFunctionExpression()
+    );
🧹 Nitpick comments (8)
code/core/src/types/modules/indexer.ts (1)

73-79: Avoid re-declaring componentPath on StoryIndexEntry; keep a single source of truth

componentPath already exists on BaseIndexEntry (as Path). Re-declaring it on StoryIndexEntry (as string) is redundant and can confuse types. Remove the duplicate field and rely on BaseIndexEntry.

 export type StoryIndexEntry = BaseIndexEntry & {
   type: 'story';
   subtype: 'story' | 'test';
-  componentPath?: string;
   exportName?: string;
   parent?: StoryId; // exists only on tests
   parentName?: StoryName; // exists only on tests
 };
code/core/src/core-server/build-static.ts (1)

168-179: Reuse existing features; add error handling and pretty JSON output

  • Redundant re-apply of features; reuse the earlier features to avoid shadowing.
  • Guard manifest generation with try/catch and log a warning; don’t fail the build on generator errors.
  • Pretty-print JSON for easier inspection.
-    const features = await presets.apply('features');
-
-    if (features?.componentManifestGenerator) {
-      const componentManifestGenerator: ComponentManifestGenerator = await presets.apply(
-        'componentManifestGenerator'
-      );
-      const indexGenerator = await initializedStoryIndexGenerator;
-      if (componentManifestGenerator && indexGenerator) {
-        const manifests = await componentManifestGenerator(indexGenerator);
-        await writeFile(join(options.outputDir, 'components.json'), JSON.stringify(manifests));
-      }
-    }
+    if (features?.componentManifestGenerator) {
+      try {
+        const componentManifestGenerator =
+          await presets.apply<ComponentManifestGenerator>('componentManifestGenerator');
+        const indexGenerator = await initializedStoryIndexGenerator;
+        if (componentManifestGenerator && indexGenerator) {
+          const manifests = await componentManifestGenerator(indexGenerator);
+          await writeFile(
+            join(options.outputDir, 'components.json'),
+            JSON.stringify(manifests, null, 2)
+          );
+        }
+      } catch (e) {
+        logger.warn('Failed to generate components.json', e as Error);
+      }
+    }
code/core/src/core-server/dev-server.ts (1)

139-154: Serve via GET, handle errors, and return 404 when disabled

  • Use app.get for the JSON endpoint.
  • Add try/catch to avoid unhandled rejections crashing the server.
  • Return 404 when the feature/generator is not configured (500 implies server fault).
  • Set JSON content type.
-  app.use('/components.json', async (req, res) => {
-    const componentManifestGenerator: ComponentManifestGenerator = await options.presets.apply(
-      'componentManifestGenerator'
-    );
-    const indexGenerator = await initializedStoryIndexGenerator;
-    const features = await options.presets.apply('features');
-    if (features?.componentManifestGenerator && componentManifestGenerator && indexGenerator) {
-      const manifest = await componentManifestGenerator(indexGenerator);
-      res.setHeader('Content-Type', 'application/json');
-      res.end(JSON.stringify(manifest));
-    } else {
-      res.statusCode = 500;
-      res.end('No component manifest generator configured.');
-    }
-  });
+  app.get('/components.json', async (req, res) => {
+    try {
+      const componentManifestGenerator =
+        await options.presets.apply<ComponentManifestGenerator>('componentManifestGenerator');
+      const indexGenerator = await initializedStoryIndexGenerator;
+      const features = await options.presets.apply('features');
+      if (features?.componentManifestGenerator && componentManifestGenerator && indexGenerator) {
+        const manifest = await componentManifestGenerator(indexGenerator);
+        res.setHeader('Content-Type', 'application/json; charset=utf-8');
+        res.end(JSON.stringify(manifest));
+      } else {
+        res.statusCode = 404;
+        res.end('Component manifest not enabled.');
+      }
+    } catch (err) {
+      logger.warn('Failed to generate component manifest', err as Error);
+      res.statusCode = 500;
+      res.end('Failed to generate component manifest.');
+    }
+  });
code/renderers/react/src/preset.ts (2)

18-49: Type as PresetProperty, add per-component error handling, and minor cleanups

  • Export as PresetProperty<'componentManifestGenerator'> for consistency with presets.
  • Protect the Promise.all with per-item try/catch; skip failing components and log a warning.
  • Use null when meta.component is missing; remove redundant .filter(Boolean) on mapped objects.
-import { type ComponentManifestGenerator } from 'storybook/internal/types';
+import { type ComponentManifestGenerator } from 'storybook/internal/types';
+import { logger } from 'storybook/internal/node-logger';
@@
-export const componentManifestGenerator = async () => {
-  return (async (storyIndexGenerator) => {
+export const componentManifestGenerator: PresetProperty<'componentManifestGenerator'> = async () => {
+  const generator: ComponentManifestGenerator = async (storyIndexGenerator) => {
     const index = await storyIndexGenerator.getIndex();
     const groupByComponentId = groupBy(
       Object.values(index.entries).filter(
         (entry) => entry.type === 'story' && entry.subtype === 'story' && entry.componentPath
       ),
       (it) => it.id.split('--')[0]
     );
-    const singleEntryPerComponent = Object.values(groupByComponentId).flatMap((group) =>
-      group && group?.length > 0 ? [group[0]] : []
-    );
+    const singleEntryPerComponent = Object.values(groupByComponentId)
+      .map((group) => group?.[0])
+      .filter(Boolean);
     const components = await Promise.all(
       singleEntryPerComponent.map(async (entry) => {
-        const code = await readFile(path.join(process.cwd(), entry.importPath), 'utf-8');
-        const csf = loadCsf(code, { makeTitle: (title) => title }).parse();
-        const component = csf._meta?.component ?? 'Unknown';
-        return {
-          id: entry.id.split('--')[0],
-          examples: Object.entries(csf._storyPaths)
-            .map(([name, path]) => ({
-              name,
-              snippet: recast.print(getCodeSnippet(path, csf._metaNode ?? null, component)).code,
-            }))
-            .filter(Boolean),
-        };
+        try {
+          const code = await readFile(path.join(process.cwd(), entry.importPath), 'utf-8');
+          const csf = loadCsf(code, { makeTitle: (title) => title }).parse();
+          const component = csf._meta?.component ?? null;
+          return {
+            id: entry.id.split('--')[0],
+            examples: Object.entries(csf._storyPaths).map(([name, path]) => ({
+              name,
+              snippet: recast.print(getCodeSnippet(path, csf._metaNode ?? null, component)).code,
+            })),
+          };
+        } catch (err) {
+          logger.warn(`Skipping manifest generation for ${entry.id}: ${(err as Error).message}`);
+          return null as any;
+        }
       })
     );
 
-    return Object.fromEntries(components.map((component) => [component.id, component]));
-  }) satisfies ComponentManifestGenerator;
-};
+    return Object.fromEntries(
+      components.filter(Boolean).map((component) => [component!.id, component!])
+    );
+  };
+  return generator;
+};

51-63: groupBy polyfill is fine; optional micro‑nit

Looks good. If desired, remove the reducer’s default param value since an explicit initial value is already provided.

code/renderers/react/src/component-manifest/generateCodeSnippet.test.tsx (2)

174-195: Remove unnecessary async from tests.

These tests don’t await anything. Drop async to reduce noise.

Also applies to: 197-219, 221-231, 233-242, 244-253, 255-264, 266-281, 471-489


137-164: Add missing cases: function exports and multi‑declarators.

Please add:

  • export function Story() { return }
  • export const A = {...}, B = {...}

These catch current generator edge cases.

code/renderers/react/src/component-manifest/generateCodeSnippet.ts (1)

221-236: Support dotted component names (e.g., UI.Button) when creating JSX.

Build JSX name from a possibly dotted string to avoid invalid identifiers:

-  const name = t.jsxIdentifier(componentName);
+  const name = toJsxName(componentName);
 
   const arrow = t.arrowFunctionExpression(
     [],
     t.jsxElement(
       t.jsxOpeningElement(name, openingElAttrs, false),
       t.jsxClosingElement(name),
       toJsxChildren(merged.children),
       false
     )
   );

Add helper (place near other helpers):

function toJsxName(name: string): t.JSXIdentifier | t.JSXMemberExpression {
  if (name.includes('.')) {
    const parts = name.split('.');
    let acc: t.JSXIdentifier | t.JSXMemberExpression = t.jsxIdentifier(parts[0]!);
    for (let i = 1; i < parts.length; i++) {
      acc = t.jsxMemberExpression(acc as any, t.jsxIdentifier(parts[i]!));
    }
    return acc as t.JSXMemberExpression;
  }
  return t.jsxIdentifier(name);
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 67d5780 and 50ea146.

📒 Files selected for processing (7)
  • code/core/src/core-server/build-static.ts (3 hunks)
  • code/core/src/core-server/dev-server.ts (2 hunks)
  • code/core/src/types/modules/core-common.ts (4 hunks)
  • code/core/src/types/modules/indexer.ts (1 hunks)
  • code/renderers/react/src/component-manifest/generateCodeSnippet.test.tsx (1 hunks)
  • code/renderers/react/src/component-manifest/generateCodeSnippet.ts (1 hunks)
  • code/renderers/react/src/preset.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{js,jsx,json,html,ts,tsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{js,jsx,json,html,ts,tsx,mjs}: Run Prettier formatting on changed files before committing
Run ESLint on changed files and fix all errors/warnings before committing (use yarn lint:js:cmd <file>)

Files:

  • code/core/src/core-server/dev-server.ts
  • code/renderers/react/src/preset.ts
  • code/core/src/types/modules/core-common.ts
  • code/renderers/react/src/component-manifest/generateCodeSnippet.test.tsx
  • code/core/src/types/modules/indexer.ts
  • code/renderers/react/src/component-manifest/generateCodeSnippet.ts
  • code/core/src/core-server/build-static.ts
**/*.{ts,tsx,js,jsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Export functions from modules when they need to be unit-tested

Files:

  • code/core/src/core-server/dev-server.ts
  • code/renderers/react/src/preset.ts
  • code/core/src/types/modules/core-common.ts
  • code/renderers/react/src/component-manifest/generateCodeSnippet.test.tsx
  • code/core/src/types/modules/indexer.ts
  • code/renderers/react/src/component-manifest/generateCodeSnippet.ts
  • code/core/src/core-server/build-static.ts
code/**/*.{ts,tsx,js,jsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

In application code, use Storybook loggers instead of console.* (client code: storybook/internal/client-logger; server code: storybook/internal/node-logger)

Files:

  • code/core/src/core-server/dev-server.ts
  • code/renderers/react/src/preset.ts
  • code/core/src/types/modules/core-common.ts
  • code/renderers/react/src/component-manifest/generateCodeSnippet.test.tsx
  • code/core/src/types/modules/indexer.ts
  • code/renderers/react/src/component-manifest/generateCodeSnippet.ts
  • code/core/src/core-server/build-static.ts
{code/**,scripts/**}/**/*.{ts,tsx,js,jsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Do not use console.log, console.warn, or console.error directly unless in isolated files where importing loggers would significantly increase bundle size

Files:

  • code/core/src/core-server/dev-server.ts
  • code/renderers/react/src/preset.ts
  • code/core/src/types/modules/core-common.ts
  • code/renderers/react/src/component-manifest/generateCodeSnippet.test.tsx
  • code/core/src/types/modules/indexer.ts
  • code/renderers/react/src/component-manifest/generateCodeSnippet.ts
  • code/core/src/core-server/build-static.ts
code/**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

code/**/*.{test,spec}.{ts,tsx}: Place all test files under the code/ directory
Name test files as *.test.ts, *.test.tsx, *.spec.ts, or *.spec.tsx

Files:

  • code/renderers/react/src/component-manifest/generateCodeSnippet.test.tsx
**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/spy-mocking.mdc)

**/*.test.{ts,tsx,js,jsx}: Use vi.mock() with the spy: true option for all package and file mocks in Vitest tests
Place all mocks at the top of the test file before any test cases
Use vi.mocked() to type and access mocked functions
Implement mock behaviors in beforeEach blocks
Mock all required dependencies that the test subject uses
Mock implementations should be placed in beforeEach blocks
Each mock implementation should return a Promise for async functions
Mock implementations should match the expected return type of the original function
Use vi.mocked() to access and implement mock behaviors
Mock all required properties and methods that the test subject uses
Avoid direct function mocking without vi.mocked()
Avoid mock implementations outside of beforeEach blocks
Avoid mocking without the spy: true option
Avoid inline mock implementations within test cases
Avoid mocking only a subset of required dependencies
Mock at the highest level of abstraction needed
Keep mock implementations simple and focused
Use type-safe mocking with vi.mocked()
Document complex mock behaviors
Group related mocks together

Files:

  • code/renderers/react/src/component-manifest/generateCodeSnippet.test.tsx
**/*.@(test|spec).{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.@(test|spec).{ts,tsx,js,jsx}: Unit tests should import and execute the functions under test rather than only asserting on syntax patterns
Mock external dependencies in tests using vi.mock() (e.g., filesystem, loggers)

Files:

  • code/renderers/react/src/component-manifest/generateCodeSnippet.test.tsx
🔇 Additional comments (5)
code/core/src/types/modules/core-common.ts (2)

348-356: Types look good; feature flag wiring aligns with usage

New ComponentManifest/Generator types and config/feature flags are coherent with server usage.

Also applies to: 370-371, 469-470


65-110: Add Presets.apply overload for componentManifestGenerator

Typing presets.apply('componentManifestGenerator') currently falls back to the generic signature, yielding unknown. Add a dedicated overload to ensure correct return typing.

   apply(
     extension: 'staticDirs',
     config?: StorybookConfigRaw['staticDirs'],
     args?: any
   ): Promise<StorybookConfigRaw['staticDirs']>;
+  apply(
+    extension: 'componentManifestGenerator',
+    config?: StorybookConfigRaw['componentManifestGenerator'],
+    args?: any
+  ): Promise<NonNullable<StorybookConfigRaw['componentManifestGenerator']>>;
   apply<T>(extension: string, config?: T, args?: unknown): Promise<T>;

Please also verify that ComponentManifest and ComponentManifestGenerator are re-exported from the package types entrypoint used by core-server imports.

code/renderers/react/src/component-manifest/generateCodeSnippet.ts (2)

53-64: Good fix: zero‑arg story factories no longer access args[0].

Guarding args[0] behind args.length resolves the crash for meta.story() calls.


246-246: Tighten attribute name validation; '.' and ':' produce invalid JSXIdentifier.

Exclude '.' and ':' to avoid generating which is invalid.

-const isValidJsxAttrName = (n: string) => /^[A-Za-z_][A-Za-z0-9_.:-]*$/.test(n);
+const isValidJsxAttrName = (n: string) => /^[A-Za-z_][A-Za-z0-9_-]*$/.test(n);

code/renderers/react/src/component-manifest/generateCodeSnippet.test.tsx (1)

12-23: Pass export name to getCodeSnippet (prep for multi‑declarator/function exports).

To support selecting the correct declarator and function‑declared stories, update generateExample to pass the export name:

-  const snippets = Object.values(csf._storyPaths)
-    .map((path: NodePath<t.ExportNamedDeclaration>) =>
-      getCodeSnippet(path, csf._metaNode ?? null, component)
-    )
+  const snippets = Object.entries(csf._storyPaths)
+    .map(([exportName, path]: [string, NodePath<t.ExportNamedDeclaration>]) =>
+      getCodeSnippet(path, exportName, csf._metaNode ?? null, component)
+    )
     .filter(Boolean);

@kasperpeulen kasperpeulen changed the base branch from next to 10.1 October 15, 2025 14:44
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
code/core/src/preview-api/modules/preview-web/render/mount-utils.ts (1)

3-5: Constrain or document loosened mountDestructured signature

  • mountDestructured’s parameter was changed to (...args: any[]) => any, removing the original PlayFunction<TRenderer> constraint and weakening compile-time checks.
  • Usages found in csf-factories.ts:206 and prepareStory.ts:128.

Either restore a tighter PlayFunction<TRenderer> signature or add a comment in mount-utils.ts explaining why a broader any-typed signature is necessary.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 50ea146 and 4ed3856.

📒 Files selected for processing (1)
  • code/core/src/preview-api/modules/preview-web/render/mount-utils.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,jsx,json,html,ts,tsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{js,jsx,json,html,ts,tsx,mjs}: Run Prettier formatting on changed files before committing
Run ESLint on changed files and fix all errors/warnings before committing (use yarn lint:js:cmd <file>)

Files:

  • code/core/src/preview-api/modules/preview-web/render/mount-utils.ts
**/*.{ts,tsx,js,jsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Export functions from modules when they need to be unit-tested

Files:

  • code/core/src/preview-api/modules/preview-web/render/mount-utils.ts
code/**/*.{ts,tsx,js,jsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

In application code, use Storybook loggers instead of console.* (client code: storybook/internal/client-logger; server code: storybook/internal/node-logger)

Files:

  • code/core/src/preview-api/modules/preview-web/render/mount-utils.ts
{code/**,scripts/**}/**/*.{ts,tsx,js,jsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Do not use console.log, console.warn, or console.error directly unless in isolated files where importing loggers would significantly increase bundle size

Files:

  • code/core/src/preview-api/modules/preview-web/render/mount-utils.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: normal
  • GitHub Check: Core Unit Tests, windows-latest
🔇 Additional comments (1)
code/core/src/preview-api/modules/preview-web/render/mount-utils.ts (1)

7-31: Awareness: String parsing approach is fragile in production.

The getUsedProps function uses fn.toString() and string parsing to extract destructured props. While this is pre-existing code, be aware that this approach:

  • May break with code minification/uglification
  • Can produce different results across JavaScript engines
  • Could fail in production builds where function representations differ

Consider verifying this works correctly in your production build pipeline, or exploring more robust alternatives like static analysis at build time if this becomes problematic.

@storybook-pr-benchmarking
Copy link

storybook-pr-benchmarking bot commented Oct 15, 2025

Package Benchmarks

Commit: a6f6673, ran on 16 October 2025 at 18:30:35 UTC

The following packages have significant changes to their size or dependencies:

@storybook/addon-a11y

Before After Difference
Dependency count 0 2 🚨 +2 🚨
Self size 0 B 508 KB 🚨 +508 KB 🚨
Dependency size 0 B 2.97 MB 🚨 +2.97 MB 🚨
Bundle Size Analyzer Link Link

@storybook/addon-docs

Before After Difference
Dependency count 0 18 🚨 +18 🚨
Self size 0 B 2.07 MB 🚨 +2.07 MB 🚨
Dependency size 0 B 10.21 MB 🚨 +10.21 MB 🚨
Bundle Size Analyzer Link Link

@storybook/addon-jest

Before After Difference
Dependency count 0 2 🚨 +2 🚨
Self size 0 B 47 KB 🚨 +47 KB 🚨
Dependency size 0 B 53 KB 🚨 +53 KB 🚨
Bundle Size Analyzer Link Link

@storybook/addon-links

Before After Difference
Dependency count 0 1 🚨 +1 🚨
Self size 0 B 15 KB 🚨 +15 KB 🚨
Dependency size 0 B 5 KB 🚨 +5 KB 🚨
Bundle Size Analyzer Link Link

@storybook/addon-onboarding

Before After Difference
Dependency count 0 0 0
Self size 0 B 333 KB 🚨 +333 KB 🚨
Dependency size 0 B 670 B 🚨 +670 B 🚨
Bundle Size Analyzer Link Link

storybook-addon-pseudo-states

Before After Difference
Dependency count 0 0 0
Self size 0 B 23 KB 🚨 +23 KB 🚨
Dependency size 0 B 689 B 🚨 +689 B 🚨
Bundle Size Analyzer Link Link

@storybook/addon-themes

Before After Difference
Dependency count 0 1 🚨 +1 🚨
Self size 0 B 20 KB 🚨 +20 KB 🚨
Dependency size 0 B 28 KB 🚨 +28 KB 🚨
Bundle Size Analyzer Link Link

@storybook/addon-vitest

Before After Difference
Dependency count 0 6 🚨 +6 🚨
Self size 0 B 501 KB 🚨 +501 KB 🚨
Dependency size 0 B 1.53 MB 🚨 +1.53 MB 🚨
Bundle Size Analyzer Link Link

@storybook/builder-vite

Before After Difference
Dependency count 0 11 🚨 +11 🚨
Self size 0 B 330 KB 🚨 +330 KB 🚨
Dependency size 0 B 1.30 MB 🚨 +1.30 MB 🚨
Bundle Size Analyzer Link Link

@storybook/builder-webpack5

Before After Difference
Dependency count 0 187 🚨 +187 🚨
Self size 0 B 68 KB 🚨 +68 KB 🚨
Dependency size 0 B 31.87 MB 🚨 +31.87 MB 🚨
Bundle Size Analyzer Link Link

storybook

Before After Difference
Dependency count 0 43 🚨 +43 🚨
Self size 0 B 30.20 MB 🚨 +30.20 MB 🚨
Dependency size 0 B 17.36 MB 🚨 +17.36 MB 🚨
Bundle Size Analyzer Link Link

@storybook/angular

Before After Difference
Dependency count 0 187 🚨 +187 🚨
Self size 0 B 126 KB 🚨 +126 KB 🚨
Dependency size 0 B 30.00 MB 🚨 +30.00 MB 🚨
Bundle Size Analyzer Link Link

@storybook/ember

Before After Difference
Dependency count 0 191 🚨 +191 🚨
Self size 0 B 17 KB 🚨 +17 KB 🚨
Dependency size 0 B 28.58 MB 🚨 +28.58 MB 🚨
Bundle Size Analyzer Link Link

@storybook/html-vite

Before After Difference
Dependency count 0 14 🚨 +14 🚨
Self size 0 B 23 KB 🚨 +23 KB 🚨
Dependency size 0 B 1.67 MB 🚨 +1.67 MB 🚨
Bundle Size Analyzer Link Link

@storybook/nextjs

Before After Difference
Dependency count 0 532 🚨 +532 🚨
Self size 0 B 950 KB 🚨 +950 KB 🚨
Dependency size 0 B 58.59 MB 🚨 +58.59 MB 🚨
Bundle Size Analyzer Link Link

@storybook/nextjs-vite

Before After Difference
Dependency count 0 124 🚨 +124 🚨
Self size 0 B 4.10 MB 🚨 +4.10 MB 🚨
Dependency size 0 B 21.66 MB 🚨 +21.66 MB 🚨
Bundle Size Analyzer Link Link

@storybook/preact-vite

Before After Difference
Dependency count 0 14 🚨 +14 🚨
Self size 0 B 14 KB 🚨 +14 KB 🚨
Dependency size 0 B 1.65 MB 🚨 +1.65 MB 🚨
Bundle Size Analyzer Link Link

@storybook/react-native-web-vite

Before After Difference
Dependency count 0 157 🚨 +157 🚨
Self size 0 B 31 KB 🚨 +31 KB 🚨
Dependency size 0 B 23.04 MB 🚨 +23.04 MB 🚨
Bundle Size Analyzer Link Link

@storybook/react-vite

Before After Difference
Dependency count 0 114 🚨 +114 🚨
Self size 0 B 37 KB 🚨 +37 KB 🚨
Dependency size 0 B 19.60 MB 🚨 +19.60 MB 🚨
Bundle Size Analyzer Link Link

@storybook/react-webpack5

Before After Difference
Dependency count 0 272 🚨 +272 🚨
Self size 0 B 25 KB 🚨 +25 KB 🚨
Dependency size 0 B 43.55 MB 🚨 +43.55 MB 🚨
Bundle Size Analyzer Link Link

@storybook/server-webpack5

Before After Difference
Dependency count 0 199 🚨 +199 🚨
Self size 0 B 17 KB 🚨 +17 KB 🚨
Dependency size 0 B 33.12 MB 🚨 +33.12 MB 🚨
Bundle Size Analyzer Link Link

@storybook/svelte-vite

Before After Difference
Dependency count 0 19 🚨 +19 🚨
Self size 0 B 59 KB 🚨 +59 KB 🚨
Dependency size 0 B 26.79 MB 🚨 +26.79 MB 🚨
Bundle Size Analyzer Link Link

@storybook/sveltekit

Before After Difference
Dependency count 0 20 🚨 +20 🚨
Self size 0 B 58 KB 🚨 +58 KB 🚨
Dependency size 0 B 26.85 MB 🚨 +26.85 MB 🚨
Bundle Size Analyzer Link Link

@storybook/vue3-vite

Before After Difference
Dependency count 0 109 🚨 +109 🚨
Self size 0 B 38 KB 🚨 +38 KB 🚨
Dependency size 0 B 43.78 MB 🚨 +43.78 MB 🚨
Bundle Size Analyzer Link Link

@storybook/web-components-vite

Before After Difference
Dependency count 0 15 🚨 +15 🚨
Self size 0 B 20 KB 🚨 +20 KB 🚨
Dependency size 0 B 1.70 MB 🚨 +1.70 MB 🚨
Bundle Size Analyzer Link Link

@storybook/cli

Before After Difference
Dependency count 0 187 🚨 +187 🚨
Self size 0 B 921 KB 🚨 +921 KB 🚨
Dependency size 0 B 79.95 MB 🚨 +79.95 MB 🚨
Bundle Size Analyzer Link Link

@storybook/codemod

Before After Difference
Dependency count 0 169 🚨 +169 🚨
Self size 0 B 35 KB 🚨 +35 KB 🚨
Dependency size 0 B 76.38 MB 🚨 +76.38 MB 🚨
Bundle Size Analyzer Link Link

@storybook/core-webpack

Before After Difference
Dependency count 0 1 🚨 +1 🚨
Self size 0 B 12 KB 🚨 +12 KB 🚨
Dependency size 0 B 28 KB 🚨 +28 KB 🚨
Bundle Size Analyzer Link Link

create-storybook

Before After Difference
Dependency count 0 44 🚨 +44 🚨
Self size 0 B 1.55 MB 🚨 +1.55 MB 🚨
Dependency size 0 B 47.56 MB 🚨 +47.56 MB 🚨
Bundle Size Analyzer node node

@storybook/csf-plugin

Before After Difference
Dependency count 0 9 🚨 +9 🚨
Self size 0 B 9 KB 🚨 +9 KB 🚨
Dependency size 0 B 1.27 MB 🚨 +1.27 MB 🚨
Bundle Size Analyzer Link Link

eslint-plugin-storybook

Before After Difference
Dependency count 0 35 🚨 +35 🚨
Self size 0 B 139 KB 🚨 +139 KB 🚨
Dependency size 0 B 3.15 MB 🚨 +3.15 MB 🚨
Bundle Size Analyzer Link Link

@storybook/react-dom-shim

Before After Difference
Dependency count 0 0 0
Self size 0 B 22 KB 🚨 +22 KB 🚨
Dependency size 0 B 788 B 🚨 +788 B 🚨
Bundle Size Analyzer Link Link

@storybook/preset-create-react-app

Before After Difference
Dependency count 0 68 🚨 +68 🚨
Self size 0 B 36 KB 🚨 +36 KB 🚨
Dependency size 0 B 5.98 MB 🚨 +5.98 MB 🚨
Bundle Size Analyzer Link Link

@storybook/preset-react-webpack

Before After Difference
Dependency count 0 170 🚨 +170 🚨
Self size 0 B 21 KB 🚨 +21 KB 🚨
Dependency size 0 B 31.02 MB 🚨 +31.02 MB 🚨
Bundle Size Analyzer Link Link

@storybook/preset-server-webpack

Before After Difference
Dependency count 0 10 🚨 +10 🚨
Self size 0 B 8 KB 🚨 +8 KB 🚨
Dependency size 0 B 1.20 MB 🚨 +1.20 MB 🚨
Bundle Size Analyzer Link Link

@storybook/html

Before After Difference
Dependency count 0 2 🚨 +2 🚨
Self size 0 B 30 KB 🚨 +30 KB 🚨
Dependency size 0 B 32 KB 🚨 +32 KB 🚨
Bundle Size Analyzer Link Link

@storybook/preact

Before After Difference
Dependency count 0 2 🚨 +2 🚨
Self size 0 B 17 KB 🚨 +17 KB 🚨
Dependency size 0 B 32 KB 🚨 +32 KB 🚨
Bundle Size Analyzer Link Link

@storybook/react

Before After Difference
Dependency count 0 2 🚨 +2 🚨
Self size 0 B 917 KB 🚨 +917 KB 🚨
Dependency size 0 B 28 KB 🚨 +28 KB 🚨
Bundle Size Analyzer Link Link

@storybook/server

Before After Difference
Dependency count 0 3 🚨 +3 🚨
Self size 0 B 9 KB 🚨 +9 KB 🚨
Dependency size 0 B 716 KB 🚨 +716 KB 🚨
Bundle Size Analyzer Link Link

@storybook/svelte

Before After Difference
Dependency count 0 2 🚨 +2 🚨
Self size 0 B 48 KB 🚨 +48 KB 🚨
Dependency size 0 B 230 KB 🚨 +230 KB 🚨
Bundle Size Analyzer Link Link

@storybook/vue3

Before After Difference
Dependency count 0 3 🚨 +3 🚨
Self size 0 B 61 KB 🚨 +61 KB 🚨
Dependency size 0 B 211 KB 🚨 +211 KB 🚨
Bundle Size Analyzer Link Link

@storybook/web-components

Before After Difference
Dependency count 0 3 🚨 +3 🚨
Self size 0 B 43 KB 🚨 +43 KB 🚨
Dependency size 0 B 47 KB 🚨 +47 KB 🚨
Bundle Size Analyzer Link Link

@shilman shilman changed the title React: Create components.json file for dev and build with examples snippets generated from the stories files React: Add components.json for dev and build with examples snippets Oct 15, 2025
Copy link
Contributor

@JReinhold JReinhold left a comment

Choose a reason for hiding this comment

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

Love it!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
code/core/src/core-server/dev-server.ts (2)

139-139: Typo in URL path: mainfests should be manifests.

This typo was already flagged in previous reviews.


140-145: Move generator retrieval inside the feature flag check.

For consistency with the static build implementation and to avoid unnecessary work when the feature is disabled, retrieve the generators only after confirming the feature flag is enabled. This was already noted in previous reviews.

🧹 Nitpick comments (2)
code/core/src/core-server/dev-server.ts (2)

150-151: Return JSON error responses for consistency.

The endpoint returns application/json for success but plain text for errors. For API consistency, error responses should also be JSON.

This is addressed in the previous comment's diff, which returns JSON-formatted error responses.


139-153: Consider caching the generated manifest.

The manifest is regenerated on every request, which could be expensive for large codebases with many components. Since the manifest depends on the story index (which may change during development), consider implementing a caching strategy that invalidates when the index changes, similar to how the story index itself is cached and invalidated on file changes.

Example approach: store the manifest and a reference to the index version, regenerate only when the index version changes.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4ed3856 and 1bcd00b.

📒 Files selected for processing (2)
  • code/core/src/core-server/build-static.ts (3 hunks)
  • code/core/src/core-server/dev-server.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • code/core/src/core-server/build-static.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,jsx,json,html,ts,tsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{js,jsx,json,html,ts,tsx,mjs}: Run Prettier formatting on changed files before committing
Run ESLint on changed files and fix all errors/warnings before committing (use yarn lint:js:cmd <file>)

Files:

  • code/core/src/core-server/dev-server.ts
**/*.{ts,tsx,js,jsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Export functions from modules when they need to be unit-tested

Files:

  • code/core/src/core-server/dev-server.ts
code/**/*.{ts,tsx,js,jsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

In application code, use Storybook loggers instead of console.* (client code: storybook/internal/client-logger; server code: storybook/internal/node-logger)

Files:

  • code/core/src/core-server/dev-server.ts
{code/**,scripts/**}/**/*.{ts,tsx,js,jsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Do not use console.log, console.warn, or console.error directly unless in isolated files where importing loggers would significantly increase bundle size

Files:

  • code/core/src/core-server/dev-server.ts
🧬 Code graph analysis (1)
code/core/src/core-server/dev-server.ts (3)
code/renderers/react/src/preset.ts (1)
  • componentManifestGenerator (18-49)
code/core/src/types/modules/core-common.ts (1)
  • ComponentManifestGenerator (353-355)
code/core/src/core-server/presets/common-preset.ts (1)
  • features (197-210)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: normal
  • GitHub Check: Core Unit Tests, windows-latest
🔇 Additional comments (1)
code/core/src/core-server/dev-server.ts (1)

11-11: LGTM!

The type-only import of ComponentManifestGenerator is appropriate and follows TypeScript best practices.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1bcd00b and c0df22c.

📒 Files selected for processing (2)
  • code/core/src/core-server/dev-server.ts (2 hunks)
  • code/core/src/types/modules/core-common.ts (4 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,jsx,json,html,ts,tsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{js,jsx,json,html,ts,tsx,mjs}: Run Prettier formatting on changed files before committing
Run ESLint on changed files and fix all errors/warnings before committing (use yarn lint:js:cmd <file>)

Files:

  • code/core/src/core-server/dev-server.ts
  • code/core/src/types/modules/core-common.ts
**/*.{ts,tsx,js,jsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Export functions from modules when they need to be unit-tested

Files:

  • code/core/src/core-server/dev-server.ts
  • code/core/src/types/modules/core-common.ts
code/**/*.{ts,tsx,js,jsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

In application code, use Storybook loggers instead of console.* (client code: storybook/internal/client-logger; server code: storybook/internal/node-logger)

Files:

  • code/core/src/core-server/dev-server.ts
  • code/core/src/types/modules/core-common.ts
{code/**,scripts/**}/**/*.{ts,tsx,js,jsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Do not use console.log, console.warn, or console.error directly unless in isolated files where importing loggers would significantly increase bundle size

Files:

  • code/core/src/core-server/dev-server.ts
  • code/core/src/types/modules/core-common.ts
🧬 Code graph analysis (2)
code/core/src/core-server/dev-server.ts (3)
code/core/src/core-server/presets/common-preset.ts (1)
  • features (197-210)
code/renderers/react/src/preset.ts (1)
  • componentManifestGenerator (18-49)
code/core/src/types/modules/core-common.ts (1)
  • ComponentManifestGenerator (353-355)
code/core/src/types/modules/core-common.ts (1)
code/core/src/core-server/utils/StoryIndexGenerator.ts (1)
  • StoryIndexGenerator (103-878)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: normal
  • GitHub Check: Core Unit Tests, windows-latest

Comment on lines 158 to 160
console.error(e);
res.statusCode = 500;
res.end(e instanceof Error ? e.toString() : String(e));
Copy link
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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
code/core/src/core-server/build-static.ts (1)

178-178: Critical: Fix typo in directory path.

The path contains a typo: "mainfests" should be "manifests". This issue was already identified in previous review comments.

Apply this diff:

-          join(options.outputDir, 'mainfests', 'components.json'),
+          join(options.outputDir, 'manifests', 'components.json'),
🧹 Nitpick comments (1)
code/core/src/core-server/build-static.ts (1)

179-179: Consider pretty-printing the JSON output.

For better developer experience when inspecting the generated manifest file, consider formatting the JSON with indentation.

Apply this diff:

-          JSON.stringify(manifests)
+          JSON.stringify(manifests, null, 2)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c0df22c and 25b197e.

📒 Files selected for processing (1)
  • code/core/src/core-server/build-static.ts (3 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,jsx,json,html,ts,tsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{js,jsx,json,html,ts,tsx,mjs}: Run Prettier formatting on changed files before committing
Run ESLint on changed files and fix all errors/warnings before committing (use yarn lint:js:cmd <file>)

Files:

  • code/core/src/core-server/build-static.ts
**/*.{ts,tsx,js,jsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Export functions from modules when they need to be unit-tested

Files:

  • code/core/src/core-server/build-static.ts
code/**/*.{ts,tsx,js,jsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

In application code, use Storybook loggers instead of console.* (client code: storybook/internal/client-logger; server code: storybook/internal/node-logger)

Files:

  • code/core/src/core-server/build-static.ts
{code/**,scripts/**}/**/*.{ts,tsx,js,jsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Do not use console.log, console.warn, or console.error directly unless in isolated files where importing loggers would significantly increase bundle size

Files:

  • code/core/src/core-server/build-static.ts
🧬 Code graph analysis (1)
code/core/src/core-server/build-static.ts (3)
code/core/src/core-server/presets/common-preset.ts (1)
  • features (197-210)
code/renderers/react/src/preset.ts (1)
  • componentManifestGenerator (18-49)
code/core/src/types/modules/core-common.ts (1)
  • ComponentManifestGenerator (353-355)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: normal
  • GitHub Check: Core Unit Tests, windows-latest
🔇 Additional comments (1)
code/core/src/core-server/build-static.ts (1)

1-1: LGTM: Import additions are correct.

The writeFile import and ComponentManifestGenerator type import are properly used in the manifest generation logic below.

Also applies to: 21-21

Comment on lines 167 to 182

const features = await presets.apply('features');

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)
);
}
}
Copy link
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.

throw indexError;
}

app.use('/manifests/components.json', async (req, res) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think we should call this app.use if the feature flag is not enabled.

app.use('/manifests/components.json', async (req, res) => {
try {
const features = await options.presets.apply('features');
if (!features?.experimental_componentsManifest) {
Copy link
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) {

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bd91ab6 and a6f6673.

📒 Files selected for processing (2)
  • code/core/src/core-server/build-static.ts (3 hunks)
  • code/core/src/core-server/dev-server.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • code/core/src/core-server/build-static.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,jsx,json,html,ts,tsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{js,jsx,json,html,ts,tsx,mjs}: Run Prettier formatting on changed files before committing
Run ESLint on changed files and fix all errors/warnings before committing (use yarn lint:js:cmd <file>)

Files:

  • code/core/src/core-server/dev-server.ts
**/*.{ts,tsx,js,jsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Export functions from modules when they need to be unit-tested

Files:

  • code/core/src/core-server/dev-server.ts
code/**/*.{ts,tsx,js,jsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

In application code, use Storybook loggers instead of console.* (client code: storybook/internal/client-logger; server code: storybook/internal/node-logger)

Files:

  • code/core/src/core-server/dev-server.ts
{code/**,scripts/**}/**/*.{ts,tsx,js,jsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Do not use console.log, console.warn, or console.error directly unless in isolated files where importing loggers would significantly increase bundle size

Files:

  • code/core/src/core-server/dev-server.ts
🧠 Learnings (2)
📚 Learning: 2025-10-13T13:33:14.659Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-13T13:33:14.659Z
Learning: Applies to code/**/*.{ts,tsx,js,jsx,mjs} : In application code, use Storybook loggers instead of `console.*` (client code: `storybook/internal/client-logger`; server code: `storybook/internal/node-logger`)

Applied to files:

  • code/core/src/core-server/dev-server.ts
📚 Learning: 2025-10-13T13:33:14.659Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-13T13:33:14.659Z
Learning: Applies to scripts/**/*.{ts,js,mjs} : In Node.js scripts, use `storybook/internal/node-logger` instead of `console.*`

Applied to files:

  • code/core/src/core-server/dev-server.ts
🧬 Code graph analysis (1)
code/core/src/core-server/dev-server.ts (3)
code/core/src/core-server/presets/common-preset.ts (1)
  • features (197-210)
code/renderers/react/src/preset.ts (1)
  • componentManifestGenerator (18-49)
code/core/src/types/modules/core-common.ts (1)
  • ComponentManifestGenerator (353-355)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: normal
  • GitHub Check: Core Unit Tests, windows-latest

Comment on lines +141 to +163
app.use('/manifests/components.json', async (req, res) => {
try {
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.');
return;
} catch (e) {
logger.error(e instanceof Error ? e : String(e));
res.statusCode = 500;
res.end(e instanceof Error ? e.toString() : String(e));
return;
}
});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Ensure consistent JSON response format and appropriate status codes.

The endpoint has good error handling, but there are inconsistencies:

  1. Response format inconsistency: Success path returns JSON (line 150), but error paths return plain text (lines 154, 159). For a JSON API endpoint, all responses should use JSON format with appropriate Content-Type headers.

  2. Inappropriate 400 status code: Line 153 returns 400 (client error) for "No component manifest generator configured," but this is a server configuration issue, not a client request problem. Use 500 or 503 instead.

  3. Logger lacks context: Line 157's logger.error(e instanceof Error ? e : String(e)) doesn't explain what operation failed. Add a descriptive message like 'Failed to generate component manifest'.

Apply this diff to improve consistency and clarity:

-        res.statusCode = 400;
-        res.end('No component manifest generator configured.');
+        res.statusCode = 500;
+        res.setHeader('Content-Type', 'application/json');
+        res.end(JSON.stringify({ error: 'No component manifest generator configured.' }));
         return;
       } catch (e) {
-        logger.error(e instanceof Error ? e : String(e));
+        logger.error('Failed to generate component manifest:', e instanceof Error ? e : String(e));
         res.statusCode = 500;
-        res.end(e instanceof Error ? e.toString() : String(e));
+        res.setHeader('Content-Type', 'application/json');
+        res.end(JSON.stringify({ error: e instanceof Error ? e.message : String(e) }));
         return;
📝 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
app.use('/manifests/components.json', async (req, res) => {
try {
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.');
return;
} catch (e) {
logger.error(e instanceof Error ? e : String(e));
res.statusCode = 500;
res.end(e instanceof Error ? e.toString() : String(e));
return;
}
});
}
app.use('/manifests/components.json', async (req, res) => {
try {
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 = 500;
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify({ error: 'No component manifest generator configured.' }));
return;
} catch (e) {
logger.error('Failed to generate component manifest:', e instanceof Error ? e : String(e));
res.statusCode = 500;
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify({ error: e instanceof Error ? e.message : String(e) }));
return;
}
});
🤖 Prompt for AI Agents
In code/core/src/core-server/dev-server.ts around lines 141 to 163, the endpoint
mixes JSON and plain-text responses, uses an incorrect 400 status for a
server/configuration problem, and logs errors without context; update the
handler to always return JSON with Content-Type: application/json, return a 500
(or 503) status for the missing componentManifestGenerator case (use 500 unless
you prefer 503 for temporary service unavailability), and change logger.error to
include a descriptive message like "Failed to generate component manifest" along
with the error; ensure both success and error bodies are JSON objects (e.g., {
ok: true, manifest: ... } and { ok: false, error: "message" }), and set
res.statusCode accordingly before res.end(JSON.stringify(...)).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants