Core: Incorrect package json handling#34515
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughAdds 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. ChangesDependency manifest resolution
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Warning Review ran into problems🔥 ProblemsGit: Failed to clone repository. Please run the 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. Comment |
There was a problem hiding this comment.
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 | 🟠 MajorHard-coded
node_modulespath can miss valid dependencies in hoisted workspacesAt Line 25, resolution is pinned to
<packageDir>/node_modules/.... In hoisted setups, the dependency may exist only in an ancestornode_modules, so this returnsENOENTand 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 behaviorThis path is fragile and should be covered with focused tests for:
- package with exports mapping (original bug),
- missing
package.json(ENOENT), and- 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
📒 Files selected for processing (1)
code/core/src/common/utils/get-storybook-refs.ts
|
Thanks for digging into this @lino-levan — the diagnosis is spot on. Resolving One concern with the original fix, though.
So it trades a noisy-but-harmless warning for a quiet loss of auto-refs. (Switching to The bug isn't the resolver — it's that we resolve a subpath. The fix is to keep I pushed that approach directly to this PR branch (commit 119a775), extracted as a shared
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>
There was a problem hiding this comment.
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
📒 Files selected for processing (3)
code/core/src/common/index.tscode/core/src/common/utils/get-storybook-refs.tscode/core/src/common/utils/read-dependency-manifest.ts
Package BenchmarksCommit: The following packages have significant changes to their size or dependencies:
|
| 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 |
What I did
At work, I was getting this warning
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.jswhich 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 arequire.resolvehere. 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.resolvebut 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:
Manual testing
Untested, don't have time to test, sorry, code diff is simple though.
Documentation
MIGRATION.MD
Checklist for Maintainers
When this PR is ready for testing, make sure to add
ci:normal,ci:mergedorci:dailyGH label to it to run a specific set of sandboxes. The particular set of sandboxes can be found incode/lib/cli-storybook/src/sandbox-templates.tsMake 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/coreteam 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