Conversation
WalkthroughAdds a refined removeConsole implementation in preset-bundler with minifier detection/option handling changes, plus comprehensive E2E tests covering basic, disabled, selective, and different minifier scenarios (esbuild, terser). Introduces test configs (.umirc.ts), pages with console calls, and assertions verifying build outputs. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Dev as Dev/Test Runner
participant Umi as Umi Service
participant PB as preset-bundler
participant RC as removeConsole feature
participant Bundler as Bundler (Rspack/Other)
participant Min as Minifier (esbuild/terser/swc)
Dev->>Umi: umi build
Umi->>PB: load presets
PB->>RC: configure removeConsole
RC->>RC: Detect isRspack (memo.rspack or userConfig.rspack)
alt Rspack detected
RC->>RC: Default jsMinifier = swc
opt removeConsole enabled with swc
Note over RC: Switch to terser (workaround for drop_console)
RC->>RC: Merge compressOptions (drop_console etc.)
end
else Non-Rspack
RC->>RC: Default jsMinifier = esbuild
RC->>RC: Map options to esbuild (pure/drop)
end
RC->>PB: Return jsMinifier + options
PB->>Bundler: Apply minifier config
Bundler->>Min: Minify with compress options
Min-->>Bundler: Outputs without selected console calls
Bundler-->>Umi: Emit assets
Umi-->>Dev: dist/*
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (23)
e2e/test-cases/remove-console/package.json (2)
6-8: Prefer non-watch test mode for CI stabilityUse
vitest runto avoid accidental watch mode in some environments."scripts": { - "test": "vitest" + "test": "vitest run" },
13-18: Add Node types for TS ergonomicsTests/fixtures often use Node APIs; include
@types/nodeto prevent TS annoyances."devDependencies": { + "@types/node": "^18.0.0", "@types/react": "^18.0.0", "@types/react-dom": "^18.0.0", "typescript": "^5.0.0", "vitest": "^1.0.0" }e2e/test-cases/remove-console/disabled/.umirc.ts (1)
1-5: Optional: use defineConfig for type-safetyUmi’s
defineConfighelps catch typos (e.g., option names) at author time.-export default { +import { defineConfig } from 'umi' +export default defineConfig({ presets: [require.resolve('../../../../packages/preset-bundler')], rspack: {}, // removeConsole is not configured - console statements should be kept -} +})e2e/test-cases/remove-console/basic/.umirc.ts (1)
1-5: Optional: wrap with defineConfig for better DXSame as the disabled case; adds typing to
removeConsole.-export default { +import { defineConfig } from 'umi' +export default defineConfig({ presets: [require.resolve('../../../../packages/preset-bundler')], rspack: {}, removeConsole: true, -} +})e2e/test-cases/remove-console/basic/pages/index.tsx (1)
1-1: Remove unused default React import and silence no-console lint in fixtureLean import and prevent lint noise in a test fixture intentionally using console.
-import React, { useEffect } from 'react' +/* eslint-disable no-console */ +import { useEffect } from 'react'e2e/test-cases/remove-console/disabled/pages/index.tsx (1)
1-1: Same: trim default React import and disable no-console for the fixtureKeeps fixtures focused and lint-clean.
-import React, { useEffect } from 'react' +/* eslint-disable no-console */ +import { useEffect } from 'react'e2e/test-cases/remove-console/basic/__tests__/index.test.ts (3)
5-13: Add generous test timeout to avoid CI flakiness during build.Builds can exceed Vitest’s default timeout on CI.
-test('removeConsole: true should remove all console statements', async () => { +test( + 'removeConsole: true should remove all console statements', + { timeout: 120_000 }, + async () => { const cwd = path.join(__dirname, '..') const service = createUmi(cwd) await service.run({ name: 'build' }) -}) + }, +)
21-43: DRY the console-method assertions.Reduces repetition and makes future changes easier.
- // 验证所有console语句都被移除 - for (const [fileName, content] of jsFiles) { - expect( - content, - `File ${fileName} should not contain console.log`, - ).not.toContain('console.log') - expect( - content, - `File ${fileName} should not contain console.warn`, - ).not.toContain('console.warn') - expect( - content, - `File ${fileName} should not contain console.error`, - ).not.toContain('console.error') - expect( - content, - `File ${fileName} should not contain console.info`, - ).not.toContain('console.info') - expect( - content, - `File ${fileName} should not contain console.debug`, - ).not.toContain('console.debug') - } + // 验证所有console语句都被移除 + const methods = ['log', 'warn', 'error', 'info', 'debug'] as const + for (const [fileName, content] of jsFiles) { + for (const m of methods) { + expect(content, `File ${fileName} should not contain console.${m}`).not.toContain( + `console.${m}`, + ) + } + }
46-56: Prefer ESM import for fs and align pre-build checks with post-build assertions.Use
import fs from 'node:fs'for consistency; also consider assertingconsole.debughere or drop thedebugcheck above for symmetry.Would you like me to update the test page to include a
console.debugso both tests align?e2e/test-cases/remove-console/disabled/__tests__/index.test.ts (3)
5-13: Add generous test timeout to avoid CI flakiness during build.-test('without removeConsole config - all console statements should be kept', async () => { +test( + 'without removeConsole config - all console statements should be kept', + { timeout: 120_000 }, + async () => { const cwd = path.join(__dirname, '..') const service = createUmi(cwd) await service.run({ name: 'build' }) -}) + }, +)
21-35: DRY the console-method assertions.- // 验证所有console语句都被保留 - for (const [fileName, content] of jsFiles) { - expect(content, `File ${fileName} should contain console.log`).toContain( - 'console.log', - ) - expect(content, `File ${fileName} should contain console.warn`).toContain( - 'console.warn', - ) - expect(content, `File ${fileName} should contain console.error`).toContain( - 'console.error', - ) - expect(content, `File ${fileName} should contain console.info`).toContain( - 'console.info', - ) - } + // 验证所有console语句都被保留 + const methods = ['log', 'warn', 'error', 'info'] as const + for (const [fileName, content] of jsFiles) { + for (const m of methods) { + expect(content, `File ${fileName} should contain console.${m}`).toContain( + `console.${m}`, + ) + } + }
38-47: Prefer ESM import for fs and (optionally) assert console.debug for symmetry with “basic” test.Do you want me to also include a
console.debugin the disabled fixture and assertion for parity?packages/preset-bundler/src/features/removeConsole/removeConsole.ts (3)
37-57: Silent minifier switch could surprise users; emit a warning.Auto-switching SWC→terser is sensible, but add a warning so users understand why the minifier changed.
if (isRspack && jsMinifier === 'swc') { - // SWC configuration issue: drop_console doesn't work in current Rspack version - // Workaround: Automatically switch to terser when removeConsole is enabled + // SWC configuration issue: drop_console doesn't work in current Rspack version + // Workaround: Automatically switch to terser when removeConsole is enabled + api.logger?.warn?.( + '[removeConsole] Rspack+swc does not support console removal; switching jsMinifier to "terser".', + )
59-62: Avoid duplicate compressOptions declarations.Define once and reuse to reduce noise.
- const compressOptions = Array.isArray(removeConsole) - ? { pure_funcs: removeConsole.map((method) => `console.${method}`) } - : { drop_console: true } + const compressOptions = + Array.isArray(removeConsole) + ? { pure_funcs: removeConsole.map((m) => `console.${m}`) } + : { drop_console: true }
17-26: Minor: keep detection logic consistent across hooks.
onCheckConfigusesapi.appData.bundlerwhilemodifyConfigused config-based detection earlier. After applying the unifiedisRspack, consider reusing it here for clarity.e2e/test-cases/remove-console/with-different-minifiers/terser/__tests__/index.test.ts (1)
21-39: Prefer regex over substring checks for robustness.Regex avoids matching within unrelated strings and covers minifier formatting. Optional if you prefer current style.
- for (const [fileName, content] of jsFiles) { - expect( - content, - `File ${fileName} should not contain console.log`, - ).not.toContain('console.log') - expect( - content, - `File ${fileName} should not contain console.warn`, - ).not.toContain('console.warn') - expect( - content, - `File ${fileName} should not contain console.error`, - ).not.toContain('console.error') - expect( - content, - `File ${fileName} should not contain console.info`, - ).not.toContain('console.info') - } + const pattern = /\bconsole\.(log|warn|error|info|debug)\s*\(/; + for (const [fileName, content] of jsFiles) { + expect(content, `File ${fileName} should not contain console.* calls`).not.toMatch(pattern) + }e2e/test-cases/remove-console/with-different-minifiers/terser/.umirc.ts (1)
3-3: Drop empty rspack block (minor).If not used by this test, remove to keep the fixture minimal.
- rspack: {},e2e/test-cases/remove-console/with-different-minifiers/esbuild/__tests__/index.test.ts (1)
21-39: De-duplicate assertions with a shared helper (optional).Both minifier tests share identical logic. Consider extracting to @e2e/helper (e.g., assertNoConsole(distPath)).
Example helper (TypeScript) to add in @e2e/helper:
export async function assertNoConsole(distPath: string) { const files = await unwrapOutputJSON(distPath) const jsFiles = Object.entries(files).filter(([n]) => n.endsWith('.js') || n.endsWith('.mjs')) expect(jsFiles.length).toBeGreaterThan(0) const pattern = /\bconsole\.(log|warn|error|info|debug)\s*\(/; for (const [fileName, content] of jsFiles) { expect(content, `File ${fileName} should not contain console.* calls`).not.toMatch(pattern) } }e2e/test-cases/remove-console/selective/pages/index.tsx (1)
25-27: Clarify copy to include debug for consistency.You keep debug as well; reflect that in the description to avoid confusion.
- <p>Only log and warn should be removed, error and info should be kept</p> + <p>Only log and warn should be removed; error, info, and debug should be kept</p>e2e/test-cases/remove-console/with-different-minifiers/esbuild/pages/index.tsx (2)
1-1: Drop unused default React import.Not using the
Reactidentifier; keep onlyuseEffect.-import React, { useEffect } from 'react' +import { useEffect } from 'react'
24-27: Unify “esbuild” casing in UI copy.Currently mixed: “ESBuild” (title) vs “esbuild” (text). Pick one (typically “esbuild”).
- <h1>Remove Console Test - ESBuild Minifier</h1> + <h1>Remove Console Test - esbuild Minifier</h1> - All console statements should be removed when using esbuild minifier + All console statements should be removed when using esbuild minifiere2e/test-cases/remove-console/selective/__tests__/index.test.ts (2)
32-38: Also assertconsole.debugis preserved (if present).Selective removal keeps methods other than
log/warn. Optional but increases confidence.expect(content, `File ${fileName} should contain console.info`).toContain( 'console.info', ) + // If the source contains console.debug, it should be preserved too. + if (content.includes('console.debug') || content.includes('console["debug"]')) { + expect(content, `File ${fileName} should contain console.debug`).toContain( + 'console.debug', + ) + }
42-52: Use ESM import forfsand verifyconsole.debugexists pre-build.Keep module style consistent and ensure the page truly exercises the “debug” path.
-test('should verify test page contains all console statements before build', () => { +test('should verify test page contains all console statements before build', () => { const testPagePath = path.join(__dirname, '../pages/index.tsx') - const fs = require('fs') - const content = fs.readFileSync(testPagePath, 'utf-8') + const content = fs.readFileSync(testPagePath, 'utf-8') // 确保测试页面确实包含所有类型的console语句 expect(content).toContain('console.log') expect(content).toContain('console.warn') expect(content).toContain('console.error') expect(content).toContain('console.info') + expect(content).toContain('console.debug') })Add this top-level import outside the shown range:
import fs from 'node:fs'
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (17)
e2e/test-cases/remove-console/basic/.umirc.ts(1 hunks)e2e/test-cases/remove-console/basic/__tests__/index.test.ts(1 hunks)e2e/test-cases/remove-console/basic/pages/index.tsx(1 hunks)e2e/test-cases/remove-console/disabled/.umirc.ts(1 hunks)e2e/test-cases/remove-console/disabled/__tests__/index.test.ts(1 hunks)e2e/test-cases/remove-console/disabled/pages/index.tsx(1 hunks)e2e/test-cases/remove-console/package.json(1 hunks)e2e/test-cases/remove-console/selective/.umirc.ts(1 hunks)e2e/test-cases/remove-console/selective/__tests__/index.test.ts(1 hunks)e2e/test-cases/remove-console/selective/pages/index.tsx(1 hunks)e2e/test-cases/remove-console/with-different-minifiers/esbuild/.umirc.ts(1 hunks)e2e/test-cases/remove-console/with-different-minifiers/esbuild/__tests__/index.test.ts(1 hunks)e2e/test-cases/remove-console/with-different-minifiers/esbuild/pages/index.tsx(1 hunks)e2e/test-cases/remove-console/with-different-minifiers/terser/.umirc.ts(1 hunks)e2e/test-cases/remove-console/with-different-minifiers/terser/__tests__/index.test.ts(1 hunks)e2e/test-cases/remove-console/with-different-minifiers/terser/pages/index.tsx(1 hunks)packages/preset-bundler/src/features/removeConsole/removeConsole.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (11)
e2e/test-cases/remove-console/disabled/pages/index.tsx (4)
e2e/test-cases/remove-console/basic/pages/index.tsx (1)
HomePage(3-30)e2e/test-cases/remove-console/selective/pages/index.tsx (1)
HomePage(3-31)e2e/test-cases/remove-console/with-different-minifiers/esbuild/pages/index.tsx (1)
HomePage(3-32)e2e/test-cases/remove-console/with-different-minifiers/terser/pages/index.tsx (1)
HomePage(3-30)
e2e/test-cases/remove-console/with-different-minifiers/esbuild/pages/index.tsx (2)
e2e/test-cases/remove-console/basic/pages/index.tsx (1)
HomePage(3-30)e2e/test-cases/remove-console/with-different-minifiers/terser/pages/index.tsx (1)
HomePage(3-30)
e2e/test-cases/remove-console/with-different-minifiers/terser/pages/index.tsx (1)
e2e/test-cases/remove-console/with-different-minifiers/esbuild/pages/index.tsx (1)
HomePage(3-32)
e2e/test-cases/remove-console/with-different-minifiers/esbuild/__tests__/index.test.ts (1)
e2e/helper/helper.ts (2)
createUmi(25-31)unwrapOutputJSON(53-58)
e2e/test-cases/remove-console/selective/pages/index.tsx (4)
e2e/test-cases/remove-console/basic/pages/index.tsx (1)
HomePage(3-30)e2e/test-cases/remove-console/disabled/pages/index.tsx (1)
HomePage(3-33)e2e/test-cases/remove-console/with-different-minifiers/esbuild/pages/index.tsx (1)
HomePage(3-32)e2e/test-cases/remove-console/with-different-minifiers/terser/pages/index.tsx (1)
HomePage(3-30)
e2e/test-cases/remove-console/with-different-minifiers/terser/__tests__/index.test.ts (1)
e2e/helper/helper.ts (2)
createUmi(25-31)unwrapOutputJSON(53-58)
e2e/test-cases/remove-console/disabled/__tests__/index.test.ts (1)
e2e/helper/helper.ts (2)
createUmi(25-31)unwrapOutputJSON(53-58)
e2e/test-cases/remove-console/basic/pages/index.tsx (4)
e2e/test-cases/remove-console/disabled/pages/index.tsx (1)
HomePage(3-33)e2e/test-cases/remove-console/selective/pages/index.tsx (1)
HomePage(3-31)e2e/test-cases/remove-console/with-different-minifiers/esbuild/pages/index.tsx (1)
HomePage(3-32)e2e/test-cases/remove-console/with-different-minifiers/terser/pages/index.tsx (1)
HomePage(3-30)
e2e/test-cases/remove-console/selective/__tests__/index.test.ts (1)
e2e/helper/helper.ts (2)
createUmi(25-31)unwrapOutputJSON(53-58)
e2e/test-cases/remove-console/basic/__tests__/index.test.ts (1)
e2e/helper/helper.ts (2)
createUmi(25-31)unwrapOutputJSON(53-58)
packages/preset-bundler/src/features/removeConsole/removeConsole.ts (1)
packages/preset-bundler/src/index.ts (1)
api(3-28)
⏰ 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: build-and-test (22.x, windows-latest)
- GitHub Check: build-and-test (22.x, ubuntu-latest)
🔇 Additional comments (8)
packages/preset-bundler/src/features/removeConsole/removeConsole.ts (3)
63-73: LGTM on esbuild option mapping.
purefor selective anddrop: ['console']for full removal aligns with esbuild’s API.
76-85: LGTM on terser option mapping.Merging into
compresswithpure_funcs/drop_consoleis correct.
28-87: No downstream modifyConfig overrides jsMinifiere2e/test-cases/remove-console/with-different-minifiers/terser/.umirc.ts (1)
3-5: Config looks correct for terser + removeConsole.No issues spotted.
e2e/test-cases/remove-console/selective/pages/index.tsx (1)
1-31: Component content aligned with selective behavior.Matches removeConsole: ['log', 'warn'] expectations.
e2e/test-cases/remove-console/with-different-minifiers/terser/pages/index.tsx (1)
1-30: Fixture content is appropriate for terser scenario.No issues spotted.
e2e/test-cases/remove-console/selective/.umirc.ts (1)
1-5: Config looks good for selective removal.Matches the intended behavior to drop only
console.logandconsole.warn.e2e/test-cases/remove-console/with-different-minifiers/esbuild/.umirc.ts (1)
4-5: No action needed:'esbuild'is a supportedjsMinifiervalue in preset-bundler.
e2e/test-cases/remove-console/selective/__tests__/index.test.ts
Outdated
Show resolved
Hide resolved
| const jsFiles = Object.entries(files).filter( | ||
| ([name]) => name.endsWith('.js') && name.includes('umi'), | ||
| ) |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Don’t rely on 'umi' naming; scan all JS assets.
Same concern as the terser test—vendors/runtime may be missed.
- const jsFiles = Object.entries(files).filter(
- ([name]) => name.endsWith('.js') && name.includes('umi'),
- )
+ const jsFiles = Object.entries(files).filter(
+ ([name]) => name.endsWith('.js') || name.endsWith('.mjs'),
+ )📝 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.
| const jsFiles = Object.entries(files).filter( | |
| ([name]) => name.endsWith('.js') && name.includes('umi'), | |
| ) | |
| const jsFiles = Object.entries(files).filter( | |
| ([name]) => name.endsWith('.js') || name.endsWith('.mjs'), | |
| ) |
🤖 Prompt for AI Agents
In
e2e/test-cases/remove-console/with-different-minifiers/esbuild/__tests__/index.test.ts
around lines 15 to 17, the test filters JS files by name.includes('umi') which
can miss vendor/runtime assets; change the selection to include all JS assets
(e.g., only filter by name.endsWith('.js')) so the test scans every JavaScript
output, and update any subsequent assertions/expectations to iterate over that
full set instead of assuming 'umi' files only.
e2e/test-cases/remove-console/with-different-minifiers/esbuild/__tests__/index.test.ts
Outdated
Show resolved
Hide resolved
e2e/test-cases/remove-console/with-different-minifiers/terser/__tests__/index.test.ts
Outdated
Show resolved
Hide resolved
| // 验证所有console语句都被移除 | ||
| for (const [fileName, content] of jsFiles) { | ||
| expect( | ||
| content, | ||
| `File ${fileName} should not contain console.log`, | ||
| ).not.toContain('console.log') | ||
| expect( | ||
| content, | ||
| `File ${fileName} should not contain console.warn`, | ||
| ).not.toContain('console.warn') | ||
| expect( | ||
| content, | ||
| `File ${fileName} should not contain console.error`, | ||
| ).not.toContain('console.error') | ||
| expect( | ||
| content, | ||
| `File ${fileName} should not contain console.info`, | ||
| ).not.toContain('console.info') | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Also assert removal of console.debug.
The page emits console.debug but the test doesn’t check it.
expect(
content,
`File ${fileName} should not contain console.info`,
).not.toContain('console.info')
+ expect(
+ content,
+ `File ${fileName} should not contain console.debug`,
+ ).not.toContain('console.debug')📝 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.
| // 验证所有console语句都被移除 | |
| for (const [fileName, content] of jsFiles) { | |
| expect( | |
| content, | |
| `File ${fileName} should not contain console.log`, | |
| ).not.toContain('console.log') | |
| expect( | |
| content, | |
| `File ${fileName} should not contain console.warn`, | |
| ).not.toContain('console.warn') | |
| expect( | |
| content, | |
| `File ${fileName} should not contain console.error`, | |
| ).not.toContain('console.error') | |
| expect( | |
| content, | |
| `File ${fileName} should not contain console.info`, | |
| ).not.toContain('console.info') | |
| } | |
| // 验证所有console语句都被移除 | |
| for (const [fileName, content] of jsFiles) { | |
| expect( | |
| content, | |
| `File ${fileName} should not contain console.log`, | |
| ).not.toContain('console.log') | |
| expect( | |
| content, | |
| `File ${fileName} should not contain console.warn`, | |
| ).not.toContain('console.warn') | |
| expect( | |
| content, | |
| `File ${fileName} should not contain console.error`, | |
| ).not.toContain('console.error') | |
| expect( | |
| content, | |
| `File ${fileName} should not contain console.info`, | |
| ).not.toContain('console.info') | |
| expect( | |
| content, | |
| `File ${fileName} should not contain console.debug`, | |
| ).not.toContain('console.debug') | |
| } |
🤖 Prompt for AI Agents
In
e2e/test-cases/remove-console/with-different-minifiers/terser/__tests__/index.test.ts
around lines 21 to 39, the test verifies removal of console.log/warn/error/info
but omits console.debug; update the assertions to also assert that each file's
content does not contain 'console.debug' by adding an expect similar to the
others (with the same File ${fileName} should not contain console.debug message)
inside the loop so console.debug occurrences are checked and cause the test to
fail if present.
| ? { pure_funcs: removeConsole.map((method) => `console.${method}`) } | ||
| : { drop_console: true } | ||
| // Fix: Check for rspack config instead of bundler name | ||
| const isRspack = !!memo.rspack || !!api.userConfig.rspack |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Unify Rspack detection with appData to avoid false positives.
!!api.userConfig.rspack can be set even when bundler isn’t Rspack, causing misclassification. Prefer api.appData.bundler with a fallback.
- const isRspack = !!memo.rspack || !!api.userConfig.rspack
+ const isRspack =
+ api.appData.bundler === 'rspack' || Boolean(memo.rspack)📝 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.
| const isRspack = !!memo.rspack || !!api.userConfig.rspack | |
| const isRspack = | |
| api.appData.bundler === 'rspack' || Boolean(memo.rspack) |
🤖 Prompt for AI Agents
In packages/preset-bundler/src/features/removeConsole/removeConsole.ts around
line 31, the current Rspack detection uses !!api.userConfig.rspack which can
misclassify projects; change the logic to prefer api.appData.bundler (e.g.
api.appData?.bundler === 'rspack') and fall back to memo.rspack if appData is
unavailable, removing reliance on userConfig.rspack to avoid false positives.
Summary by CodeRabbit
Bug Fixes
Tests