Skip to content

Core: Incorrect package json handling#34515

Merged
valentinpalkovic merged 3 commits into
storybookjs:nextfrom
lino-levan:lino/fix-incorrect-package-json-handling
May 22, 2026
Merged

Core: Incorrect package json handling#34515
valentinpalkovic merged 3 commits into
storybookjs:nextfrom
lino-levan:lino/fix-incorrect-package-json-handling

Conversation

@lino-levan
Copy link
Copy Markdown
Contributor

@lino-levan lino-levan commented Apr 9, 2026

What I did

At work, I was getting this warning

┌  storybook v10.3.5
│
●  Starting...
│
▲  unable to find package.json for refractor

on storybook startup and I thought it was weird (and I don't like the noise that it adds for no reason). I went to look into what storybook was doing and saw this code, then I went into refractor and saw that it had "./*": "./lang/*.js" configured in the exports. The package.json was being remapped to ./lang/package.json.js which obviously does not exist. At first I patched refractor because I thought it was a bug over there, then I realized that storybook should literally just not be using a require.resolve here. It's the wrong tool for the job, we should absolutely not be respecting exports.

It looks like this bug existed from the beginning of autoconfig (#10753). Eventually someone hit the same issue as me and patched it, but incorrectly, adding the try catch and the warning that I got annoyed at today (#11023). A follow up changed the dependency emulating require.resolve but again missed the logic issue (#31338).

I'm running this patch in prod at work. Thought it made sense to upstream it or the good of open source. Cheers.

Checklist for Contributors

Testing

This change is completely untested, to be honest. We don't use this feature at work so I can't pretend to say that I know the code works. It probably should, it's a simple change, but I don't have enough work time to learn storybook testing infra to write proper tests here. Hopefully this inspires some movement on this issue. Feel free to close if that's unacceptable.

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

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

Manual testing

Untested, don't have time to test, sorry, code diff is simple though.

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

  • Refactor
    • Improved dependency manifest resolution by centralizing manifest lookup for more reliable detection of external Storybook references.
    • Made the manifest-reading utility available for consumers.
    • Enhanced error handling to avoid throws and reduce noisy failures, improving stability and robustness.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 9, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b299a8b6-fa36-4c8c-84a8-8d45dd19e150

📥 Commits

Reviewing files that changed from the base of the PR and between 119a775 and 6b5cded.

📒 Files selected for processing (1)
  • code/core/src/common/index.ts

📝 Walkthrough

Walkthrough

Adds a reusable async helper readDependencyManifest that resolves and reads a dependency's package.json (fast-path subpath, fallback by resolving main entry and walking upward). get-storybook-refs now calls this helper and only produces auto-refs for manifests with storybook.url; the helper is re-exported from common/index.

Changes

Dependency manifest resolution

Layer / File(s) Summary
readDependencyManifest implementation
code/core/src/common/utils/read-dependency-manifest.ts
Adds readDependencyManifest(directory, dependency) with a fast-path resolution of <dep>/package.json and a fallback that resolves the package main entry and walks upward looking for a valid package.json; returns `PackageJson
getAutoRefs integration
code/core/src/common/utils/get-storybook-refs.ts
Replaces in-function resolution/parsing with a call to readDependencyManifest(directory, d) and filters auto-refs to only those manifests containing storybook.url.
Public re-export
code/core/src/common/index.ts
Re-exports ./utils/read-dependency-manifest.ts from the common module.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown
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.

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/common/utils/get-storybook-refs.ts (1)

25-35: ⚠️ Potential issue | 🟠 Major

Hard-coded node_modules path can miss valid dependencies in hoisted workspaces

At Line 25, resolution is pinned to <packageDir>/node_modules/.... In hoisted setups, the dependency may exist only in an ancestor node_modules, so this returns ENOENT and the ref is silently skipped at Line 34.

💡 Suggested fix (walk up parent directories before giving up)
+const findDependencyPackageJson = async (startDir: string, dep: string) => {
+  let current = startDir;
+  while (true) {
+    const candidate = join(current, 'node_modules', dep, 'package.json');
+    try {
+      await readFile(candidate, { encoding: 'utf8' });
+      return candidate;
+    } catch (error) {
+      if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
+        throw error;
+      }
+    }
+
+    const parent = dirname(current);
+    if (parent === current) {
+      return undefined;
+    }
+    current = parent;
+  }
+};
...
-        const l = join(directory, 'node_modules', d, 'package.json');
+        const l = await findDependencyPackageJson(directory, d);
+        if (!l) {
+          return undefined;
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/core/src/common/utils/get-storybook-refs.ts` around lines 25 - 35, The
code currently constructs a path using join(directory, 'node_modules', d,
'package.json') and returns undefined on ENOENT, which misses hoisted packages;
modify the lookup in the function in get-storybook-refs.ts to walk up parent
directories searching for node_modules/<dep>/package.json (e.g., loop using
path.dirname until root or use a find-up helper) before giving up, then readFile
and parse that first-found package.json (preserving the existing handling of
storybook.url and returning { id: name, ...storybook, version }).
🧹 Nitpick comments (1)
code/core/src/common/utils/get-storybook-refs.ts (1)

22-42: Please add regression tests for the new lookup/error behavior

This path is fragile and should be covered with focused tests for:

  1. package with exports mapping (original bug),
  2. missing package.json (ENOENT), and
  3. hoisted dependency resolution from ancestor directories.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/core/src/common/utils/get-storybook-refs.ts` around lines 22 - 42, Add
focused regression tests for get-storybook-refs.ts covering the three behaviors:
(1) a package with an "exports" mapping/modern package.json that previously
broke the JSON lookup — create a fixture package.json with exports and a
storybook.url and assert the function returns the expected ref (refer to how
deps is processed and readFile is used in the async map), (2) missing
package.json ENOENT handling — simulate a dep folder without package.json (or
mock readFile to throw an error with code 'ENOENT') and assert the function
returns undefined and does not log a non-ENOENT warning, and (3) hoisted
dependency resolution — create a nested directory structure with node_modules in
an ancestor directory (or simulate lookup behavior) and assert the code can
locate/resolve the hoisted package.json for the dep; implement tests using temp
directories or fs mocks and call the module that contains the Promise.all over
deps so you exercise the try/catch branch and verify the returned list contains
undefined or the ref as appropriate.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@code/core/src/common/utils/get-storybook-refs.ts`:
- Around line 25-35: The code currently constructs a path using join(directory,
'node_modules', d, 'package.json') and returns undefined on ENOENT, which misses
hoisted packages; modify the lookup in the function in get-storybook-refs.ts to
walk up parent directories searching for node_modules/<dep>/package.json (e.g.,
loop using path.dirname until root or use a find-up helper) before giving up,
then readFile and parse that first-found package.json (preserving the existing
handling of storybook.url and returning { id: name, ...storybook, version }).

---

Nitpick comments:
In `@code/core/src/common/utils/get-storybook-refs.ts`:
- Around line 22-42: Add focused regression tests for get-storybook-refs.ts
covering the three behaviors: (1) a package with an "exports" mapping/modern
package.json that previously broke the JSON lookup — create a fixture
package.json with exports and a storybook.url and assert the function returns
the expected ref (refer to how deps is processed and readFile is used in the
async map), (2) missing package.json ENOENT handling — simulate a dep folder
without package.json (or mock readFile to throw an error with code 'ENOENT') and
assert the function returns undefined and does not log a non-ENOENT warning, and
(3) hoisted dependency resolution — create a nested directory structure with
node_modules in an ancestor directory (or simulate lookup behavior) and assert
the code can locate/resolve the hoisted package.json for the dep; implement
tests using temp directories or fs mocks and call the module that contains the
Promise.all over deps so you exercise the try/catch branch and verify the
returned list contains undefined or the ref as appropriate.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 84c00454-bbd7-427f-bb38-6fc0fa91f9ef

📥 Commits

Reviewing files that changed from the base of the PR and between aae6c95 and 37e8acb.

📒 Files selected for processing (1)
  • code/core/src/common/utils/get-storybook-refs.ts

@valentinpalkovic
Copy link
Copy Markdown
Contributor

valentinpalkovic commented May 22, 2026

Thanks for digging into this @lino-levan — the diagnosis is spot on. Resolving <dep>/package.json as a subpath runs it through the package's exports field, and refractor's "./*": "./lang/*.js" wildcard remaps package.json to a file that doesn't exist. We shouldn't be doing exports-aware resolution to read a manifest. 👍

One concern with the original fix, though. empathic/resolve.from() is a thin wrapper over Node's createRequire().resolve(), so it walks up the node_modules chain. Replacing it with join(directory, 'node_modules', d, 'package.json') only checks the project's immediate node_modules, which silently regresses:

  • Hoisted deps in monorepos / workspaces / pnpm (the dep lives in a higher node_modules) → ENOENT → the ref is silently dropped.
  • Yarn PnP, where there is no node_modules directory at all.

So it trades a noisy-but-harmless warning for a quiet loss of auto-refs. (Switching to oxc-resolver wouldn't help either — it has no PnP support and does its own node_modules walking.)

The bug isn't the resolver — it's that we resolve a subpath. The fix is to keep createRequire-based resolution (PnP- and hoist-correct) but resolve the bare package specifier instead of <dep>/package.json. The "." export is never affected by a "./*" wildcard, so we can resolve the package entry and walk up to its own package.json.

I pushed that approach directly to this PR branch (commit 119a775), extracted as a shared readDependencyManifest util. Two passes:

  1. Try <dep>/package.json directly — correct for packages with no exports, or that explicitly expose "./package.json".
  2. On failure, resolve the "." entry and walk up to the package root (skipping nested dist/package.json type-markers, which carry no name).

Let me know if you'd like anything adjusted — otherwise this should be good to go. Thanks again for upstreaming the fix!

…de_modules join

A flat `node_modules/<dep>/package.json` join only checks the project's
immediate `node_modules`, which silently drops refs for hoisted
dependencies (monorepos / workspaces / pnpm) and breaks entirely under
Yarn PnP, where there is no `node_modules` directory.

Extract a shared `readDependencyManifest` utility instead. The original
bug — a spurious "unable to find package.json" warning — happens because
`<dep>/package.json` is resolved as a subpath, which runs through the
package's `exports` field; a `"./*"` wildcard (e.g. refractor's
`"./*": "./lang/*.js"`) remaps `package.json` to a non-existent file.

The util:
- tries the subpath first (works for packages with no `exports`, or
  that expose `"./package.json"`);
- on failure resolves the package's `"."` entry — which a `"./*"`
  wildcard never affects — and walks up to the package's own manifest.

Both passes go through `empathic/resolve` (a wrapper over Node's
`createRequire().resolve()`), so resolution stays correct under hoisted
monorepos and Yarn PnP.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@code/core/src/common/utils/read-dependency-manifest.ts`:
- Line 38: The code uses join(dependency, 'package.json') which can produce
Windows backslashes and break resolve.from; change the subpath construction in
the subpath assignment so it uses a forward-slash package-subpath string (e.g.
`${dependency}/package.json` or path.posix.join(dependency, 'package.json'))
before calling resolve.from in the same expression that sets subpath so
resolve.from receives a POSIX-style specifier.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8a8c99fb-7706-4d37-87cc-52a9a496b55b

📥 Commits

Reviewing files that changed from the base of the PR and between 37e8acb and 119a775.

📒 Files selected for processing (3)
  • code/core/src/common/index.ts
  • code/core/src/common/utils/get-storybook-refs.ts
  • code/core/src/common/utils/read-dependency-manifest.ts

Comment thread code/core/src/common/utils/read-dependency-manifest.ts
@valentinpalkovic valentinpalkovic changed the title fix: incorrect package json handling Core: Incorrect package json handling May 22, 2026
@valentinpalkovic valentinpalkovic moved this from Empathy Queue (prioritized) to In Progress in Core Team Projects May 22, 2026
@valentinpalkovic valentinpalkovic self-assigned this May 22, 2026
@storybook-app-bot
Copy link
Copy Markdown

Package Benchmarks

Commit: 6b5cded, ran on 22 May 2026 at 10:59:22 UTC

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

@storybook/builder-webpack5

Before After Difference
Dependency count 184 184 0
Self size 79 KB 79 KB 🎉 -48 B 🎉
Dependency size 33.35 MB 33.36 MB 🚨 +16 KB 🚨
Bundle Size Analyzer Link Link

@storybook/angular

Before After Difference
Dependency count 185 185 0
Self size 160 KB 160 KB 0 B
Dependency size 30.73 MB 30.75 MB 🚨 +16 KB 🚨
Bundle Size Analyzer Link Link

@storybook/ember

Before After Difference
Dependency count 188 188 0
Self size 15 KB 15 KB 🚨 +18 B 🚨
Dependency size 30.07 MB 30.08 MB 🚨 +16 KB 🚨
Bundle Size Analyzer Link Link

@storybook/nextjs

Before After Difference
Dependency count 534 534 0
Self size 662 KB 662 KB 🎉 -120 B 🎉
Dependency size 61.51 MB 61.52 MB 🚨 +16 KB 🚨
Bundle Size Analyzer Link Link

@storybook/react-webpack5

Before After Difference
Dependency count 271 271 0
Self size 23 KB 23 KB 0 B
Dependency size 46.05 MB 46.06 MB 🚨 +16 KB 🚨
Bundle Size Analyzer Link Link

@storybook/server-webpack5

Before After Difference
Dependency count 196 196 0
Self size 16 KB 16 KB 🚨 +12 B 🚨
Dependency size 34.61 MB 34.63 MB 🚨 +16 KB 🚨
Bundle Size Analyzer Link Link

@storybook/preset-react-webpack

Before After Difference
Dependency count 164 164 0
Self size 18 KB 18 KB 0 B
Dependency size 32.35 MB 32.37 MB 🚨 +16 KB 🚨
Bundle Size Analyzer Link Link

@valentinpalkovic valentinpalkovic merged commit a7cee07 into storybookjs:next May 22, 2026
106 of 107 checks passed
@github-project-automation github-project-automation Bot moved this from In Progress to Done in Core Team Projects May 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants