Skip to content

Bumped vite and vitest in shade and admin-x-design-system#27867

Merged
9larsons merged 1 commit into
mainfrom
security/vite-bump-step2
May 13, 2026
Merged

Bumped vite and vitest in shade and admin-x-design-system#27867
9larsons merged 1 commit into
mainfrom
security/vite-bump-step2

Conversation

@9larsons
Copy link
Copy Markdown
Contributor

Why

The two admin-lane stragglers — apps/shade and apps/admin-x-design-system — move from vite@5.4.21 + vitest@1.6.1 to vite@7.3.2 + vitest@4.1.2, aligning with the rest of the admin lane (admin, admin-x-framework, admin-x-settings, activitypub, posts, stats).

Continues the audit-flagged dependency cleanup from PR #27731 (framework + settings + activitypub) and commit ccf3d7368f (posts + stats). PR #27866 was the prerequisite lint-config cleanup that lets this PR land without any band-aid eslint-disable comments.

Substantive changes (beyond the bumps)

Two breaking changes hit:

1. vite-plugin-svgr v3 → v4 — SVG imports require ?react suffix

v3 exposed both default (URL) and named ReactComponent on every .svg import; v4 splits them — ./foo.svg is the URL, ./foo.svg?react is the React component (as default export). v3's dual-export came from its transform hook merging with Vite's URL handling; v4's load hook bypasses Vite's asset pipeline entirely, so the dual export cannot be preserved by configuration. Migrated:

  • 12 explicit imports (logo re-exports in shade/src/components.ts, admin-x-design-system/src/index.ts, plus icon imports in admin-x-design-system/src/global/avatar.tsx and color-picker.tsx)
  • 2 import.meta.glob patterns (shade/src/components/ui/icon.ts and admin-x-design-system/src/global/icon.tsx) — added {eager: true, query: '?react'}, callers switched from module.ReactComponent to module.default
  • *.svg ambient module declarations in both typings.d.ts files — split into *.svg (URL default) and *.svg?react (component default), using import type instead of legacy import React = require('react') so no @typescript-eslint/no-require-imports disable is needed

2. vitest 1 → 4 — ReturnType<typeof vi.fn> not directly callable

In vitest 4 it resolves to Mock<Procedure | Constructable>, which TS won't dispatch as either without a function-shape generic. One file affected: shade/test/unit/components/patterns/filters.test.tsxDateFiltersProps callback types moved to ReturnType<typeof vi.fn<(value: string) => void>>. This is the same idiom already used in apps/stats/test/unit/utils/content-helpers.test.ts.

No other vitest 1→4 patterns from PR #27731 (e.g. global.fetch, MockInstance, constructable mock implementations) appear in these packages.

Test plan

  • pnpm --filter @tryghost/shade test — 220/220 across 22 files
  • pnpm --filter @tryghost/shade build — exit 0
  • pnpm --filter @tryghost/shade lint — exit 0 (only the pre-existing react-hooks/exhaustive-deps warning in src/components/patterns/filters.tsx remains, unchanged by this PR)
  • pnpm --filter @tryghost/admin-x-design-system test — 23/23 across 3 files
  • pnpm --filter @tryghost/admin-x-design-system build — exit 0
  • Downstream regression check all green: admin-x-framework 350/350, admin-x-settings 156/156, activitypub 93/93, posts 173/173, stats 234/234
  • CI confirms the same against the canonical environment

The two admin-lane stragglers move from vite@5.4.21 + vitest@1.6.1 to
vite@7.3.2 + vitest@4.1.2, aligning with the rest of the admin lane
(admin, admin-x-framework, admin-x-settings, activitypub, posts, stats).

Two breaking changes had to be addressed:

1. vite-plugin-svgr v3 -> v4 changed the SVG import API. v3 exposed both
   `default` (URL) and named `ReactComponent` on every .svg import; v4
   splits them - ./foo.svg is the URL, ./foo.svg?react is the React
   component (as default export). v3's dual-export came from its
   `transform` hook merging with Vite's URL handling; v4's `load` hook
   bypasses Vite's asset pipeline entirely, so the dual export cannot
   be preserved by configuration. Migrated 12 explicit imports and 2
   import.meta.glob patterns across both packages, and split the *.svg
   ambient module declarations into *.svg (URL) and *.svg?react
   (component).

2. vitest 1 -> 4: ReturnType<typeof vi.fn> now resolves to
   Mock<Procedure | Constructable>, which TS won't dispatch as either
   without a function-shape generic. One test file affected (shade's
   filters.test.tsx); typed prop callbacks updated to use the
   function-shape generic ReturnType<typeof vi.fn<(value: string) => void>>.

Verification:
- shade: 220/220 tests, build, lint
- admin-x-design-system: 23/23 tests, build
- Downstream regression check all green: admin-x-framework 350/350,
  admin-x-settings 156/156, activitypub 93/93, posts 173/173,
  stats 234/234
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 13, 2026

Review Change Stack

Walkthrough

This PR migrates SVG imports across the admin-x-design-system and shade packages from using named ReactComponent exports to Vite's ?react query syntax with default exports. TypeScript module declarations are updated to reflect the new typing pattern using FunctionComponent and SVGProps type imports. Both packages' Vite, vite-plugin-svgr, and vitest dependencies are upgraded to newer versions to support this change. Component imports, import.meta.glob handlers, and public re-exports are updated accordingly, with minor test type refinements applied.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: dependency version bumps for vite and vitest in the two specified packages (shade and admin-x-design-system).
Description check ✅ Passed The description comprehensively covers the changes, including the dependency bumps, substantive breaking changes from vite-plugin-svgr v3→v4 and vitest 1→4, migration details, and a thorough test plan.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch security/vite-bump-step2

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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/shade/src/components/ui/icon.ts (1)

25-28: 💤 Low value

Refine the glob generic type for better type safety.

The generic type {default: React.FC<IconProps>} is slightly inaccurate. The actual SVG modules imported via ?react export components typed as React.FC<React.SVGProps<SVGSVGElement>> (per the updated typings.d.ts), not React.FC<IconProps>. The IconProps interface includes the size variant prop, which the raw SVG components don't understand—your wrapper component (lines 34-41) correctly handles this by extracting size and passing only the remaining props to the underlying SVG.

While this works at runtime and compiles successfully due to TypeScript's structural typing, using the more accurate type improves type safety and clarity.

♻️ Suggested type refinement
-const iconModules = import.meta.glob<{default: React.FC<IconProps>}>(
+const iconModules = import.meta.glob<{default: React.FC<React.SVGProps<SVGSVGElement>>}>(
     '../../assets/icons/*.svg',
     {eager: true, query: '?react'}
 );
🤖 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 `@apps/shade/src/components/ui/icon.ts` around lines 25 - 28, The glob's
generic currently types SVG modules as {default: React.FC<IconProps>} which is
inaccurate; change the import.meta.glob generic for iconModules to {default:
React.FC<React.SVGProps<SVGSVGElement>>} so the raw SVG components are typed
correctly, and keep the wrapper component that extracts size from IconProps and
forwards the remaining props to the underlying SVG (the wrapper that receives
size and spreads ...props) unchanged.
🤖 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 `@apps/admin-x-design-system/src/global/icon.tsx`:
- Line 31: The destructuring on line with "const {default: SvgComponent} =
icons[`../assets/icons/${name}.svg`]" can throw if icons[...] is undefined;
update the Icon lookup (in this module/file where SvgComponent and icons are
referenced) to first retrieve the lookup result into a variable (e.g., const
IconModule = icons[`../assets/icons/${name}.svg`]) and guard it (if falsy return
null) before destructuring/using SvgComponent so the intended fallback is
reached safely.

---

Nitpick comments:
In `@apps/shade/src/components/ui/icon.ts`:
- Around line 25-28: The glob's generic currently types SVG modules as {default:
React.FC<IconProps>} which is inaccurate; change the import.meta.glob generic
for iconModules to {default: React.FC<React.SVGProps<SVGSVGElement>>} so the raw
SVG components are typed correctly, and keep the wrapper component that extracts
size from IconProps and forwards the remaining props to the underlying SVG (the
wrapper that receives size and spreads ...props) unchanged.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ce61894c-9e05-4ceb-8926-f07582bb9f09

📥 Commits

Reviewing files that changed from the base of the PR and between 92e7b69 and b181969.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (11)
  • apps/admin-x-design-system/package.json
  • apps/admin-x-design-system/src/global/avatar.tsx
  • apps/admin-x-design-system/src/global/form/color-picker.tsx
  • apps/admin-x-design-system/src/global/icon.tsx
  • apps/admin-x-design-system/src/index.ts
  • apps/admin-x-design-system/src/typings.d.ts
  • apps/shade/package.json
  • apps/shade/src/components.ts
  • apps/shade/src/components/ui/icon.ts
  • apps/shade/src/typings.d.ts
  • apps/shade/test/unit/components/patterns/filters.test.tsx

*/
const Icon: React.FC<IconProps> = ({name, size = 'md', colorClass = '', className = ''}) => {
const {ReactComponent: SvgComponent} = icons[`../assets/icons/${name}.svg`];
const {default: SvgComponent} = icons[`../assets/icons/${name}.svg`];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard icon lookup before destructuring

Line 31 can throw when name doesn’t map to a known icon key (icons[...] is undefined), so the intended return null fallback is never reached.

Proposed fix
-    const {default: SvgComponent} = icons[`../assets/icons/${name}.svg`];
+    const iconModule = icons[`../assets/icons/${name}.svg`];
+    const SvgComponent = iconModule?.default;
📝 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.

Suggested change
const {default: SvgComponent} = icons[`../assets/icons/${name}.svg`];
const iconModule = icons[`../assets/icons/${name}.svg`];
const SvgComponent = iconModule?.default;
🤖 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 `@apps/admin-x-design-system/src/global/icon.tsx` at line 31, The destructuring
on line with "const {default: SvgComponent} =
icons[`../assets/icons/${name}.svg`]" can throw if icons[...] is undefined;
update the Icon lookup (in this module/file where SvgComponent and icons are
referenced) to first retrieve the lookup result into a variable (e.g., const
IconModule = icons[`../assets/icons/${name}.svg`]) and guard it (if falsy return
null) before destructuring/using SvgComponent so the intended fallback is
reached safely.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 13, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 73.76%. Comparing base (92e7b69) to head (b181969).

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #27867      +/-   ##
==========================================
- Coverage   73.76%   73.76%   -0.01%     
==========================================
  Files        1515     1515              
  Lines      127517   127517              
  Branches    15257    15257              
==========================================
- Hits        94069    94067       -2     
- Misses      32522    32523       +1     
- Partials      926      927       +1     
Flag Coverage Δ
admin-tests 53.44% <ø> (-0.02%) ⬇️
e2e-tests 73.76% <ø> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@9larsons 9larsons enabled auto-merge (squash) May 13, 2026 16:18
@9larsons 9larsons merged commit db2beff into main May 13, 2026
46 checks passed
@9larsons 9larsons deleted the security/vite-bump-step2 branch May 13, 2026 16:23
9larsons added a commit that referenced this pull request May 13, 2026
The final two public/UMD apps move from vite@5.4.21 + vitest@1.6.1 to
vite@7.3.2 + vitest@4.1.2, finishing the unification across apps/* that
started with PR #27731 and continued through #27867 and #27869. Every
workspace under apps/ now runs the same vite + vitest pair.

@vitest/coverage-v8 in comments-ui jumped from 0.34.6 to 4.1.2 - it had
been pinned to a version compatible with vitest 0.x and was years out
of date.

Substantive changes beyond the bumps:

1. vite-plugin-svgr v3 -> v4 changed the SVG import API (./foo.svg
   now gives the URL, ./foo.svg?react gives the React component as
   default export). Migrated 16 imports across:
   - 14 in comments-ui (12 files)
   - 2 in signup-form (2 files)
   Both typings.d.ts files were split into *.svg (URL default) and
   *.svg?react (component default), using `import type` instead of
   the legacy `import React = require('react')` pattern.

2. vitest 1 -> 4 had two test-side issues in comments-ui:

   a. setup-tests.ts: `global.ResizeObserver = vi.fn()
      .mockImplementation(() => ({...}))` - vitest 4 enforces that
      mocks called with `new` have constructable implementations, and
      arrow functions are not constructable. Switched to function
      expression form.

   b. test/unit/utils/api.test.ts: three tests each called
      vi.spyOn(window, 'fetch') with no teardown. The spy persisted
      across tests, accumulating call counts. Added an afterEach
      with vi.restoreAllMocks().

3. vite 5 -> 7 build performance fix in all four public UMD apps
   (signup-form, comments-ui, plus portal and sodo-search already on
   vite 7 from #27869). Each app's vite.config used
   `commonjsOptions.dynamicRequireTargets: SUPPORTED_LOCALES.map(
   locale => '../../ghost/i18n/locales/' + locale + '/X.json')`,
   producing an array of ~60 explicit paths. Under vite 7's bundled
   @rollup/plugin-commonjs, each entry in that array triggers a full
   directory crawl from `dynamicRequireRoot` (= repo root, including
   node_modules) at the start of every build. The result was a ~58s
   delay before rollup's buildStart hook fired, which under
   `vite build && vite preview` blew playwright's webServer timeout
   (10s for signup-form, 20s for comments-ui).

   Replaced the N-entries-per-locale array with a single glob -
   `['../../ghost/i18n/locales/*/X.json']` - which the plugin
   resolves with one crawl. Same bundle output (verified byte-for-byte
   for portal/sodo-search/comments-ui; signup-form bundle size
   unchanged at 301817 bytes). Build time drops from ~60s to ~2s.

   Also fixed for portal and sodo-search in this PR even though
   they're not on the failing branch - they were already shipping
   the slow pattern on main via #27869 and the savings apply.

Patch version bumps on both new packages (CI version-bump check
requires this whenever source files change in a monitored app).

Verification:
- comments-ui: 207/207 unit tests, build (2.7s, was 60s), lint
- signup-form: build (2.0s, was 60s), lint
- portal: build (3.6s, was 60s+) - bundle byte-identical
- sodo-search: build (4.7s, was 60s+) - bundle byte-identical
- Local repro: dev:test webServer ready in 4s (was timing out)
9larsons added a commit that referenced this pull request May 13, 2026
The final two public/UMD apps move from vite@5.4.21 + vitest@1.6.1 to
vite@7.3.2 + vitest@4.1.2, finishing the unification across apps/* that
started with PR #27731 and continued through #27867 and #27869. Every
workspace under apps/ now runs the same vite + vitest pair.

@vitest/coverage-v8 in comments-ui jumped from 0.34.6 to 4.1.2 - it had
been pinned to a version compatible with vitest 0.x and was years out
of date.

Substantive changes beyond the bumps:

1. vite-plugin-svgr v3 -> v4 changed the SVG import API (./foo.svg
   now gives the URL, ./foo.svg?react gives the React component as
   default export). Migrated 16 imports across:
   - 14 in comments-ui (12 files)
   - 2 in signup-form (2 files)
   Both typings.d.ts files were split into *.svg (URL default) and
   *.svg?react (component default), using `import type` instead of
   the legacy `import React = require('react')` pattern.

2. vitest 1 -> 4 had two test-side issues in comments-ui:

   a. setup-tests.ts: `global.ResizeObserver = vi.fn()
      .mockImplementation(() => ({...}))` - vitest 4 enforces that
      mocks called with `new` have constructable implementations, and
      arrow functions are not constructable. Switched to function
      expression form.

   b. test/unit/utils/api.test.ts: three tests each called
      vi.spyOn(window, 'fetch') with no teardown. The spy persisted
      across tests, accumulating call counts. Added an afterEach
      with vi.restoreAllMocks().

3. vite 5 -> 7 build performance fix in all four public UMD apps
   (signup-form, comments-ui, plus portal and sodo-search already on
   vite 7 from #27869). Each app's vite.config used
   `commonjsOptions.dynamicRequireTargets: SUPPORTED_LOCALES.map(
   locale => '../../ghost/i18n/locales/' + locale + '/X.json')`,
   producing an array of ~60 explicit paths. Under vite 7's bundled
   @rollup/plugin-commonjs, each entry in that array triggers a full
   directory crawl from `dynamicRequireRoot` (= repo root, including
   node_modules) at the start of every build. The result was a ~58s
   delay before rollup's buildStart hook fired, which under
   `vite build && vite preview` blew playwright's webServer timeout
   (10s for signup-form, 20s for comments-ui).

   Replaced the N-entries-per-locale array with a single glob -
   `['../../ghost/i18n/locales/*/X.json']` - which the plugin
   resolves with one crawl. Same bundle output (verified byte-for-byte
   for portal/sodo-search/comments-ui; signup-form bundle size
   unchanged at 301817 bytes). Build time drops from ~60s to ~2s.

   Also fixed for portal and sodo-search in this PR even though
   they're not on the failing branch - they were already shipping
   the slow pattern on main via #27869 and the savings apply.

Patch version bumps on both new packages (CI version-bump check
requires this whenever source files change in a monitored app).

Verification:
- comments-ui: 207/207 unit tests, build (2.7s, was 60s), lint
- signup-form: build (2.0s, was 60s), lint
- portal: build (3.6s, was 60s+) - bundle byte-identical
- sodo-search: build (4.7s, was 60s+) - bundle byte-identical
- Local repro: dev:test webServer ready in 4s (was timing out)
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