Skip to content

fix: incorrect package json handling#34515

Open
lino-levan wants to merge 1 commit intostorybookjs:nextfrom
lino-levan:lino/fix-incorrect-package-json-handling
Open

fix: incorrect package json handling#34515
lino-levan wants to merge 1 commit intostorybookjs:nextfrom
lino-levan:lino/fix-incorrect-package-json-handling

Conversation

@lino-levan
Copy link
Copy Markdown

@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
    • Enhanced dependency file resolution with improved error handling for better reliability.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 9, 2026

📝 Walkthrough

Walkthrough

The pull request modifies dependency resolution logic in a utility file by replacing empathic/resolve with direct node_modules filesystem path lookup to locate package files, and updates error handling to check for file-not-found (ENOENT) exceptions instead of package export restrictions.

Changes

Cohort / File(s) Summary
Dependency Resolution Refactor
code/core/src/common/utils/get-storybook-refs.ts
Replaced empathic/resolve with direct filesystem path to node_modules/<dep>/package.json; updated error handling from ERR_PACKAGE_PATH_NOT_EXPORTED to ENOENT file-not-found checks.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes


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

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant