CLI: Fix angular-to-angular-vite migration failing to configure addon-vitest#35404
Conversation
…-vitest
The migration's TS config loader broke whenever `skipCache`'s cache-busting
query string was appended to a `.ts` main config URL: the loader matched
files via `url.endsWith('.ts')`, which silently failed on `main.ts?<ts>`
and fell back to Node's native type-stripping, which (unlike the esbuild
transform it bypassed) doesn't elide type-only named imports. That caused
a hard crash for any project importing `StorybookConfig` without `type`,
and separately let stale, pre-migration framework/builder data leak into
the addon-vitest compatibility check, producing a false
"Non-Vite builder is not supported" failure right after a successful
migration to `@storybook/angular-vite`.
Also fixes an inverted `--yes` default on the webpackFinal continuation
prompt, which cancelled the whole migration in non-interactive mode, and
threads `skipCache` through `getStorybookData` so `automigrate`/`add`
read the freshly-rewritten main config instead of a cached evaluation.
… fixes Covers the loader.ts esbuild transform still applying to a `.ts?<query>` URL, the webpackFinal prompt proceeding automatically under `--yes`, and the migration marking a plain StorybookConfig import as `import type`.
…port rewrite The loader.ts fix already makes esbuild's transform apply to every .ts main config load regardless of skipCache's query string, and esbuild elides a type-only StorybookConfig import whether or not it's marked `type` — so the type-import rewrite in this fixer was redundant. Cancelling the migration under --yes when a webpackFinal hook is present is intentional: silently dropping a hook that won't carry over to Vite in unattended mode is worse than requiring an explicit rerun after porting it.
📝 WalkthroughWalkthroughThe loader's ChangesESM Loader Query String Fix
skipCache Option for Storybook Data
Estimated code review effort: 2 (Simple) | ~10 minutes Sequence Diagram(s)sequenceDiagram
participant NodeESM
participant load
participant esbuild
NodeESM->>load: url = file:///main.ts?123
load->>load: strip query string
load->>esbuild: transform(readFile(urlWithoutQuery))
esbuild-->>load: transformed code
load-->>NodeESM: return transformed source
✨ Finishing Touches📝 Generate docstrings
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
code/core/src/bin/loader.test.ts (1)
34-38: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAvoid
as anycast on mock return value.Casting the
transformmock's resolved value withas anybypasses the type contract expected ofTransformResult.As per coding guidelines, "Mock implementations should match the expected return type of the original function" and "Use type-safe mocking with
vi.mocked()in Vitest tests."🤖 Prompt for 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. In `@code/core/src/bin/loader.test.ts` around lines 34 - 38, The `transform` mock in `loader.test.ts` is using an `as any` cast to bypass the expected `TransformResult` type. Update the `vi.mocked(transform).mockResolvedValue(...)` setup to return a value that matches the real `transform` return contract without casting, using the proper typed shape for `TransformResult` so the mock stays type-safe.Source: Coding guidelines
🤖 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/bin/loader.test.ts`:
- Line 19: Add the required spy option to the new Vitest mocks in
loader.test.ts: update the vi.mock calls for node:fs/promises and esbuild to use
spy: true. Keep the existing test setup intact, and make the change wherever the
mock declarations appear so they comply with the project’s Vitest mocking
guideline.
- Line 2: The test in loader.test.ts is mocking the entire node:fs/promises
module, which should be replaced with a memfs-based filesystem for this
filesystem-touching test. Update the loader.test.ts setup to use memfs for file
reads/writes and keep the real node:fs/promises behavior otherwise, removing the
wholesale vi.mock('node:fs/promises') approach. Use the existing test setup
around readFile and any loader helpers to route filesystem access through memfs
instead of a full module mock.
---
Nitpick comments:
In `@code/core/src/bin/loader.test.ts`:
- Around line 34-38: The `transform` mock in `loader.test.ts` is using an `as
any` cast to bypass the expected `TransformResult` type. Update the
`vi.mocked(transform).mockResolvedValue(...)` setup to return a value that
matches the real `transform` return contract without casting, using the proper
typed shape for `TransformResult` so the mock stays type-safe.
🪄 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: 8e364fb9-6f00-4866-8385-12e14847fe58
📒 Files selected for processing (5)
code/core/src/bin/loader.test.tscode/core/src/bin/loader.tscode/core/src/cli/getStorybookData.tscode/core/src/common/utils/get-storybook-info.tscode/lib/cli-storybook/src/add.ts
Closes #
What I did
While reproducing an
angular-to-angular-viteautomigration failure against a real monorepo (Bitwarden'sclients), the migration reported success, but the deferredaddon-vitestpostinstall then failed with:getStorybookDatahad noskipCacheoption. It's the canonical collector used byautomigrate/doctor/add(per its own docstring), but unlikegetStorybookInfo/loadMainConfigit always read the ES-module-cached, pre-migration evaluation ofmain.ts. AddedskipCacheand threaded it through;add()now passesskipCache: true, since it runs both standalone and mid-automigration right aftermain.tsgets rewritten.code/core/src/bin/loader.ts. The custom TS loader decides whether to esbuild-transform a.tsconfig viaurl.endsWith('.ts').skipCache's cache-busting?<timestamp>query string breaks that check, so it silently falls back to Node's native type-stripping, which — unlike esbuild — doesn't elide a plain (non-type) named import that's only ever used as a type. Anymain.tswithimport { StorybookConfig } from '...'(harmless under a normal load) then hard-crashes with "does not provide an export named 'StorybookConfig'" the moment it's read withskipCache: true. Verified in isolation with a throwaway fake package before landing the fix — I initially assumed it was a difference between@storybook/angularand@storybook/angular-vitethemselves, but it isn't; both packages behave identically here, and esbuild elides the type-only import regardless of whether it's markedtype, so no change to the fixer's own rewrite logic was needed.I also considered flipping the fixer's
--yesdefault on the webpackFinal-detected prompt (it currently always cancels the migration under--yes), but decided to leave that alone: silently dropping a hook that won't carry over to Vite in unattended mode seemed worse than requiring an explicit rerun after porting it, so that stays as-is.Fairly confident in this one — reproduced end-to-end against the real project several times, with and without the fix, before landing on this.
Checklist for Contributors
Testing
The changes in this PR are covered in the following automated tests:
Manual testing
Point at (or create) a Storybook project still on the old webpack-based
@storybook/angular, whose.storybook/main.tscontains a non-typeStorybookConfigimport (nowebpackFinalhook — that hits a separate, intentional--yescancellation path unrelated to this fix), e.g.:Build the CLI locally:
yarn nx run-many -t compile --projects=core,cli,addon-vitestFrom that project's directory, run the CLI non-interactively against it:
Confirm: the migration completes, and
addon-vitestgets configured without the "Non-Vite builder is not supported" error.Regression check: revert just the
code/core/src/bin/loader.tschange, recompile, rerun step 3 — it should fail again withSyntaxError: The requested module '@storybook/angular-vite' does not provide an export named 'StorybookConfig'.Documentation
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.tsDeclare whether manual QA will be needed for this PR during the next release, through
qa:neededorqa:skipMake 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 pull request has been released as version
0.0.0-pr-35404-sha-848adf5e. Try it out in a new sandbox by runningnpx storybook@0.0.0-pr-35404-sha-848adf5e sandboxor in an existing project withnpx storybook@0.0.0-pr-35404-sha-848adf5e upgrade.More information
0.0.0-pr-35404-sha-848adf5evalentin/fix-automigrate-angular-vite-skipcache848adf5e1783459399)To request a new release of this pull request, mention the
@storybookjs/coreteam.core team members can create a new canary release here or locally with
gh workflow run --repo storybookjs/storybook publish.yml --field pr=35404