From 97776eabffff2dbed44517a4206b259a2b592a08 Mon Sep 17 00:00:00 2001 From: Joel Date: Fri, 13 Mar 2026 15:29:24 +0800 Subject: [PATCH 1/8] chore: add tracking info of in site message (#33394) Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../app/in-site-message/index.spec.tsx | 20 ++++++++++++++----- .../components/app/in-site-message/index.tsx | 16 ++++++++++++++- .../app/in-site-message/notification.spec.tsx | 15 +++++++++----- .../app/in-site-message/notification.tsx | 2 ++ 4 files changed, 42 insertions(+), 11 deletions(-) diff --git a/web/app/components/app/in-site-message/index.spec.tsx b/web/app/components/app/in-site-message/index.spec.tsx index 69f036da17..530084074d 100644 --- a/web/app/components/app/in-site-message/index.spec.tsx +++ b/web/app/components/app/in-site-message/index.spec.tsx @@ -1,7 +1,13 @@ +import type { ComponentProps } from 'react' import type { InSiteMessageActionItem } from './index' import { fireEvent, render, screen } from '@testing-library/react' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import InSiteMessage from './index' +vi.mock('@/app/components/base/amplitude', () => ({ + trackEvent: vi.fn(), +})) + describe('InSiteMessage', () => { const originalLocation = window.location @@ -18,9 +24,10 @@ describe('InSiteMessage', () => { vi.unstubAllGlobals() }) - const renderComponent = (actions: InSiteMessageActionItem[], props?: Partial>) => { + const renderComponent = (actions: InSiteMessageActionItem[], props?: Partial>) => { return render( { describe('Rendering', () => { it('should render title, subtitle, markdown content, and action buttons', () => { const actions: InSiteMessageActionItem[] = [ - { action: 'close', text: 'Close', type: 'default' }, - { action: 'link', text: 'Learn more', type: 'primary', data: 'https://example.com' }, + { action: 'close', action_name: 'dismiss', text: 'Close', type: 'default' }, + { action: 'link', action_name: 'learn_more', text: 'Learn more', type: 'primary', data: 'https://example.com' }, ] renderComponent(actions, { className: 'custom-message' }) @@ -56,7 +63,7 @@ describe('InSiteMessage', () => { }) it('should fallback to default header background when headerBgUrl is empty string', () => { - const actions: InSiteMessageActionItem[] = [{ action: 'close', text: 'Close', type: 'default' }] + const actions: InSiteMessageActionItem[] = [{ action: 'close', action_name: 'dismiss', text: 'Close', type: 'default' }] const { container } = renderComponent(actions, { headerBgUrl: '' }) const header = container.querySelector('div[style]') @@ -68,7 +75,7 @@ describe('InSiteMessage', () => { describe('Actions', () => { it('should call onAction and hide component when close action is clicked', () => { const onAction = vi.fn() - const closeAction: InSiteMessageActionItem = { action: 'close', text: 'Close', type: 'default' } + const closeAction: InSiteMessageActionItem = { action: 'close', action_name: 'dismiss', text: 'Close', type: 'default' } renderComponent([closeAction], { onAction }) fireEvent.click(screen.getByRole('button', { name: 'Close' })) @@ -80,6 +87,7 @@ describe('InSiteMessage', () => { it('should open a new tab when link action data is a string', () => { const linkAction: InSiteMessageActionItem = { action: 'link', + action_name: 'confirm', text: 'Open link', type: 'primary', data: 'https://example.com', @@ -103,6 +111,7 @@ describe('InSiteMessage', () => { const linkAction: InSiteMessageActionItem = { action: 'link', + action_name: 'confirm', text: 'Open self', type: 'primary', data: { href: 'https://example.com/self', target: '_self' }, @@ -118,6 +127,7 @@ describe('InSiteMessage', () => { it('should not trigger navigation when link data is invalid', () => { const linkAction: InSiteMessageActionItem = { action: 'link', + action_name: 'confirm', text: 'Broken link', type: 'primary', data: { rel: 'noopener' }, diff --git a/web/app/components/app/in-site-message/index.tsx b/web/app/components/app/in-site-message/index.tsx index 9225eb8a15..0276257860 100644 --- a/web/app/components/app/in-site-message/index.tsx +++ b/web/app/components/app/in-site-message/index.tsx @@ -1,6 +1,7 @@ 'use client' -import { useMemo, useState } from 'react' +import { useEffect, useMemo, useState } from 'react' +import { trackEvent } from '@/app/components/base/amplitude' import Button from '@/app/components/base/button' import { MarkdownWithDirective } from '@/app/components/base/markdown-with-directive' import { cn } from '@/utils/classnames' @@ -10,12 +11,14 @@ type InSiteMessageButtonType = 'primary' | 'default' export type InSiteMessageActionItem = { action: InSiteMessageAction + action_name: string // for tracing and analytics data?: unknown text: string type: InSiteMessageButtonType } type InSiteMessageProps = { + notificationId: string actions: InSiteMessageActionItem[] className?: string headerBgUrl?: string @@ -52,6 +55,7 @@ function normalizeLinkData(data: unknown): { href: string, rel?: string, target? const DEFAULT_HEADER_BG_URL = '/in-site-message/header-bg.svg' function InSiteMessage({ + notificationId, actions, className, headerBgUrl = DEFAULT_HEADER_BG_URL, @@ -70,7 +74,17 @@ function InSiteMessage({ } }, [headerBgUrl]) + useEffect(() => { + trackEvent('in_site_message_show', { + notification_id: notificationId, + }) + }, [notificationId]) + const handleAction = (item: InSiteMessageActionItem) => { + trackEvent('in_site_message_action', { + notification_id: notificationId, + action: item.action_name, + }) onAction?.(item) if (item.action === 'close') { diff --git a/web/app/components/app/in-site-message/notification.spec.tsx b/web/app/components/app/in-site-message/notification.spec.tsx index 84fe3aebc7..0d86d8a91c 100644 --- a/web/app/components/app/in-site-message/notification.spec.tsx +++ b/web/app/components/app/in-site-message/notification.spec.tsx @@ -15,11 +15,16 @@ const { mockNotificationDismiss: vi.fn(), })) -vi.mock('@/config', () => ({ - get IS_CLOUD_EDITION() { - return mockConfig.isCloudEdition - }, -})) +vi.mock(import('@/config'), async (importOriginal) => { + const actual = await importOriginal() + + return { + ...actual, + get IS_CLOUD_EDITION() { + return mockConfig.isCloudEdition + }, + } +}) vi.mock('@/service/client', () => ({ consoleQuery: { diff --git a/web/app/components/app/in-site-message/notification.tsx b/web/app/components/app/in-site-message/notification.tsx index de256a4663..cebf6ffd91 100644 --- a/web/app/components/app/in-site-message/notification.tsx +++ b/web/app/components/app/in-site-message/notification.tsx @@ -75,6 +75,7 @@ function InSiteMessageNotification() { const fallbackActions: InSiteMessageActionItem[] = [ { type: 'default', + action_name: 'dismiss', text: t('operation.close', { ns: 'common' }), action: 'close', }, @@ -96,6 +97,7 @@ function InSiteMessageNotification() { return ( Date: Fri, 13 Mar 2026 15:35:02 +0800 Subject: [PATCH 2/8] fix: with_debug_recipient() silently drops debug emails when user_id is None or empty string (#33373) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Crazywoola <100913391+crazywoola@users.noreply.github.com> --- api/dify_graph/nodes/human_input/entities.py | 8 ++++---- api/services/workflow_service.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/api/dify_graph/nodes/human_input/entities.py b/api/dify_graph/nodes/human_input/entities.py index 82a8f32053..642c2143e5 100644 --- a/api/dify_graph/nodes/human_input/entities.py +++ b/api/dify_graph/nodes/human_input/entities.py @@ -72,8 +72,8 @@ class EmailDeliveryConfig(BaseModel): body: str debug_mode: bool = False - def with_debug_recipient(self, user_id: str) -> "EmailDeliveryConfig": - if not user_id: + def with_debug_recipient(self, user_id: str | None) -> "EmailDeliveryConfig": + if user_id is None: debug_recipients = EmailRecipients(whole_workspace=False, items=[]) return self.model_copy(update={"recipients": debug_recipients}) debug_recipients = EmailRecipients(whole_workspace=False, items=[MemberRecipient(user_id=user_id)]) @@ -141,7 +141,7 @@ def apply_debug_email_recipient( method: DeliveryChannelConfig, *, enabled: bool, - user_id: str, + user_id: str | None, ) -> DeliveryChannelConfig: if not enabled: return method @@ -149,7 +149,7 @@ def apply_debug_email_recipient( return method if not method.config.debug_mode: return method - debug_config = method.config.with_debug_recipient(user_id or "") + debug_config = method.config.with_debug_recipient(user_id) return method.model_copy(update={"config": debug_config}) diff --git a/api/services/workflow_service.py b/api/services/workflow_service.py index ec37fbeb6b..2549cdf180 100644 --- a/api/services/workflow_service.py +++ b/api/services/workflow_service.py @@ -952,7 +952,7 @@ class WorkflowService: delivery_method = apply_debug_email_recipient( delivery_method, enabled=True, - user_id=account.id or "", + user_id=account.id, ) variable_pool = self._build_human_input_variable_pool( From 00eda73ad109c024ca79384f69c8f64320c21464 Mon Sep 17 00:00:00 2001 From: Coding On Star <447357187@qq.com> Date: Fri, 13 Mar 2026 16:31:05 +0800 Subject: [PATCH 3/8] test: enforce app/components coverage gates in web tests (#33395) Co-authored-by: CodingOnStar --- .github/workflows/main-ci.yml | 3 + .github/workflows/web-tests.yml | 18 + .../component-coverage-filters.test.ts | 115 ++++ .../check-components-diff-coverage.mjs | 560 ++++++++++++++++++ web/scripts/component-coverage-filters.mjs | 316 ++++++++++ .../components-coverage-thresholds.mjs | 128 ++++ web/vite.config.ts | 24 + 7 files changed, 1164 insertions(+) create mode 100644 web/__tests__/component-coverage-filters.test.ts create mode 100644 web/scripts/check-components-diff-coverage.mjs create mode 100644 web/scripts/component-coverage-filters.mjs create mode 100644 web/scripts/components-coverage-thresholds.mjs diff --git a/.github/workflows/main-ci.yml b/.github/workflows/main-ci.yml index ef2e3c7bb4..fd104e9496 100644 --- a/.github/workflows/main-ci.yml +++ b/.github/workflows/main-ci.yml @@ -62,6 +62,9 @@ jobs: needs: check-changes if: needs.check-changes.outputs.web-changed == 'true' uses: ./.github/workflows/web-tests.yml + with: + base_sha: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }} + head_sha: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} style-check: name: Style Check diff --git a/.github/workflows/web-tests.yml b/.github/workflows/web-tests.yml index 7d2977245f..47cced863a 100644 --- a/.github/workflows/web-tests.yml +++ b/.github/workflows/web-tests.yml @@ -2,6 +2,13 @@ name: Web Tests on: workflow_call: + inputs: + base_sha: + required: false + type: string + head_sha: + required: false + type: string permissions: contents: read @@ -14,6 +21,8 @@ jobs: test: name: Web Tests (${{ matrix.shardIndex }}/${{ matrix.shardTotal }}) runs-on: ubuntu-latest + env: + VITEST_COVERAGE_SCOPE: app-components strategy: fail-fast: false matrix: @@ -50,6 +59,8 @@ jobs: if: ${{ !cancelled() }} needs: [test] runs-on: ubuntu-latest + env: + VITEST_COVERAGE_SCOPE: app-components defaults: run: shell: bash @@ -59,6 +70,7 @@ jobs: - name: Checkout code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: + fetch-depth: 0 persist-credentials: false - name: Setup web environment @@ -74,6 +86,12 @@ jobs: - name: Merge reports run: pnpm vitest --merge-reports --reporter=json --reporter=agent --coverage + - name: Check app/components diff coverage + env: + BASE_SHA: ${{ inputs.base_sha }} + HEAD_SHA: ${{ inputs.head_sha }} + run: node ./scripts/check-components-diff-coverage.mjs + - name: Coverage Summary if: always() id: coverage-summary diff --git a/web/__tests__/component-coverage-filters.test.ts b/web/__tests__/component-coverage-filters.test.ts new file mode 100644 index 0000000000..cacc1e2142 --- /dev/null +++ b/web/__tests__/component-coverage-filters.test.ts @@ -0,0 +1,115 @@ +import fs from 'node:fs' +import os from 'node:os' +import path from 'node:path' +import { afterEach, describe, expect, it } from 'vitest' +import { + collectComponentCoverageExcludedFiles, + COMPONENT_COVERAGE_EXCLUDE_LABEL, + getComponentCoverageExclusionReasons, +} from '../scripts/component-coverage-filters.mjs' + +describe('component coverage filters', () => { + describe('getComponentCoverageExclusionReasons', () => { + it('should exclude type-only files by basename', () => { + expect( + getComponentCoverageExclusionReasons( + 'web/app/components/share/text-generation/types.ts', + 'export type ShareMode = "run-once" | "run-batch"', + ), + ).toContain('type-only') + }) + + it('should exclude pure barrel files', () => { + expect( + getComponentCoverageExclusionReasons( + 'web/app/components/base/amplitude/index.ts', + [ + 'export { default } from "./AmplitudeProvider"', + 'export { resetUser, trackEvent } from "./utils"', + ].join('\n'), + ), + ).toContain('pure-barrel') + }) + + it('should exclude generated files from marker comments', () => { + expect( + getComponentCoverageExclusionReasons( + 'web/app/components/base/icons/src/vender/workflow/Answer.tsx', + [ + '// GENERATE BY script', + '// DON NOT EDIT IT MANUALLY', + 'export default function Icon() {', + ' return null', + '}', + ].join('\n'), + ), + ).toContain('generated') + }) + + it('should exclude pure static files with exported constants only', () => { + expect( + getComponentCoverageExclusionReasons( + 'web/app/components/workflow/note-node/constants.ts', + [ + 'import { NoteTheme } from "./types"', + 'export const CUSTOM_NOTE_NODE = "custom-note"', + 'export const THEME_MAP = {', + ' [NoteTheme.blue]: { title: "bg-blue-100" },', + '}', + ].join('\n'), + ), + ).toContain('pure-static') + }) + + it('should keep runtime logic files tracked', () => { + expect( + getComponentCoverageExclusionReasons( + 'web/app/components/workflow/nodes/trigger-schedule/default.ts', + [ + 'const validate = (value: string) => value.trim()', + 'export const nodeDefault = {', + ' value: validate("x"),', + '}', + ].join('\n'), + ), + ).toEqual([]) + }) + }) + + describe('collectComponentCoverageExcludedFiles', () => { + const tempDirs: string[] = [] + + afterEach(() => { + for (const dir of tempDirs) + fs.rmSync(dir, { recursive: true, force: true }) + tempDirs.length = 0 + }) + + it('should collect excluded files for coverage config and keep runtime files out', () => { + const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), 'component-coverage-filters-')) + tempDirs.push(rootDir) + + fs.mkdirSync(path.join(rootDir, 'barrel'), { recursive: true }) + fs.mkdirSync(path.join(rootDir, 'icons'), { recursive: true }) + fs.mkdirSync(path.join(rootDir, 'static'), { recursive: true }) + fs.mkdirSync(path.join(rootDir, 'runtime'), { recursive: true }) + + fs.writeFileSync(path.join(rootDir, 'barrel', 'index.ts'), 'export { default } from "./Button"\n') + fs.writeFileSync(path.join(rootDir, 'icons', 'generated-icon.tsx'), '// @generated\nexport default function Icon() { return null }\n') + fs.writeFileSync(path.join(rootDir, 'static', 'constants.ts'), 'export const COLORS = { primary: "#fff" }\n') + fs.writeFileSync(path.join(rootDir, 'runtime', 'config.ts'), 'export const config = makeConfig()\n') + fs.writeFileSync(path.join(rootDir, 'runtime', 'types.ts'), 'export type Config = { value: string }\n') + + expect(collectComponentCoverageExcludedFiles(rootDir, { pathPrefix: 'app/components' })).toEqual([ + 'app/components/barrel/index.ts', + 'app/components/icons/generated-icon.tsx', + 'app/components/runtime/types.ts', + 'app/components/static/constants.ts', + ]) + }) + }) + + it('should describe the excluded coverage categories', () => { + expect(COMPONENT_COVERAGE_EXCLUDE_LABEL).toBe('type-only files, pure barrel files, generated files, pure static files') + }) +}) diff --git a/web/scripts/check-components-diff-coverage.mjs b/web/scripts/check-components-diff-coverage.mjs new file mode 100644 index 0000000000..429f97cb99 --- /dev/null +++ b/web/scripts/check-components-diff-coverage.mjs @@ -0,0 +1,560 @@ +import { execFileSync } from 'node:child_process' +import fs from 'node:fs' +import path from 'node:path' +import { + collectComponentCoverageExcludedFiles, + COMPONENT_COVERAGE_EXCLUDE_LABEL, +} from './component-coverage-filters.mjs' +import { + COMPONENTS_GLOBAL_THRESHOLDS, + EXCLUDED_COMPONENT_MODULES, + getComponentModuleThreshold, +} from './components-coverage-thresholds.mjs' + +const APP_COMPONENTS_PREFIX = 'web/app/components/' +const APP_COMPONENTS_COVERAGE_PREFIX = 'app/components/' +const SHARED_TEST_PREFIX = 'web/__tests__/' +const STRICT_TEST_FILE_TOUCH = process.env.STRICT_COMPONENT_TEST_TOUCH === 'true' +const EXCLUDED_MODULES_LABEL = [...EXCLUDED_COMPONENT_MODULES].sort().join(', ') + +const repoRoot = repoRootFromCwd() +const webRoot = path.join(repoRoot, 'web') +const excludedComponentCoverageFiles = new Set( + collectComponentCoverageExcludedFiles(path.join(webRoot, 'app/components'), { pathPrefix: 'web/app/components' }), +) +const baseSha = process.env.BASE_SHA?.trim() +const headSha = process.env.HEAD_SHA?.trim() || 'HEAD' +const coverageFinalPath = path.join(webRoot, 'coverage', 'coverage-final.json') + +if (!baseSha || /^0+$/.test(baseSha)) { + appendSummary([ + '### app/components Diff Coverage', + '', + 'Skipped diff coverage check because `BASE_SHA` was not available.', + ]) + process.exit(0) +} + +if (!fs.existsSync(coverageFinalPath)) { + console.error(`Coverage report not found at ${coverageFinalPath}`) + process.exit(1) +} + +const coverage = JSON.parse(fs.readFileSync(coverageFinalPath, 'utf8')) +const changedFiles = getChangedFiles(baseSha, headSha) +const changedComponentSourceFiles = changedFiles.filter(isAnyComponentSourceFile) +const changedSourceFiles = changedComponentSourceFiles.filter(isTrackedComponentSourceFile) +const changedExcludedSourceFiles = changedComponentSourceFiles.filter(isExcludedComponentSourceFile) +const changedTestFiles = changedFiles.filter(isRelevantTestFile) + +if (changedSourceFiles.length === 0) { + appendSummary(buildSkipSummary(changedExcludedSourceFiles)) + process.exit(0) +} + +const coverageEntries = new Map() +for (const [file, entry] of Object.entries(coverage)) { + const repoRelativePath = normalizeToRepoRelative(entry.path ?? file) + if (!isTrackedComponentSourceFile(repoRelativePath)) + continue + + coverageEntries.set(repoRelativePath, entry) +} + +const fileCoverageRows = [] +const moduleCoverageMap = new Map() + +for (const [file, entry] of coverageEntries.entries()) { + const stats = getCoverageStats(entry) + const moduleName = getModuleName(file) + fileCoverageRows.push({ file, moduleName, ...stats }) + mergeCoverageStats(moduleCoverageMap, moduleName, stats) +} + +const overallCoverage = sumCoverageStats(fileCoverageRows) +const diffChanges = getChangedLineMap(baseSha, headSha) +const diffRows = [] + +for (const [file, changedLines] of diffChanges.entries()) { + if (!isTrackedComponentSourceFile(file)) + continue + + const entry = coverageEntries.get(file) + const lineHits = entry ? getLineHits(entry) : {} + const executableChangedLines = [...changedLines] + .filter(line => !entry || lineHits[line] !== undefined) + .sort((a, b) => a - b) + + if (executableChangedLines.length === 0) { + diffRows.push({ + file, + moduleName: getModuleName(file), + total: 0, + covered: 0, + uncoveredLines: [], + }) + continue + } + + const uncoveredLines = executableChangedLines.filter(line => (lineHits[line] ?? 0) === 0) + diffRows.push({ + file, + moduleName: getModuleName(file), + total: executableChangedLines.length, + covered: executableChangedLines.length - uncoveredLines.length, + uncoveredLines, + }) +} + +const diffTotals = diffRows.reduce((acc, row) => { + acc.total += row.total + acc.covered += row.covered + return acc +}, { total: 0, covered: 0 }) + +const diffCoveragePct = percentage(diffTotals.covered, diffTotals.total) +const diffFailures = diffRows.filter(row => row.uncoveredLines.length > 0) +const overallThresholdFailures = getThresholdFailures(overallCoverage, COMPONENTS_GLOBAL_THRESHOLDS) +const moduleCoverageRows = [...moduleCoverageMap.entries()] + .map(([moduleName, stats]) => ({ + moduleName, + stats, + thresholds: getComponentModuleThreshold(moduleName), + })) + .map(row => ({ + ...row, + failures: row.thresholds ? getThresholdFailures(row.stats, row.thresholds) : [], + })) +const moduleThresholdFailures = moduleCoverageRows + .filter(row => row.failures.length > 0) + .flatMap(row => row.failures.map(failure => ({ + moduleName: row.moduleName, + ...failure, + }))) +const hasRelevantTestChanges = changedTestFiles.length > 0 +const missingTestTouch = !hasRelevantTestChanges + +appendSummary(buildSummary({ + overallCoverage, + overallThresholdFailures, + moduleCoverageRows, + moduleThresholdFailures, + diffRows, + diffFailures, + diffCoveragePct, + changedSourceFiles, + changedTestFiles, + missingTestTouch, +})) + +if (diffFailures.length > 0 && process.env.CI) { + for (const failure of diffFailures.slice(0, 20)) { + const firstLine = failure.uncoveredLines[0] ?? 1 + console.log(`::error file=${failure.file},line=${firstLine}::Uncovered changed lines: ${formatLineRanges(failure.uncoveredLines)}`) + } +} + +if ( + overallThresholdFailures.length > 0 + || moduleThresholdFailures.length > 0 + || diffFailures.length > 0 + || (STRICT_TEST_FILE_TOUCH && missingTestTouch) +) { + process.exit(1) +} + +function buildSummary({ + overallCoverage, + overallThresholdFailures, + moduleCoverageRows, + moduleThresholdFailures, + diffRows, + diffFailures, + diffCoveragePct, + changedSourceFiles, + changedTestFiles, + missingTestTouch, +}) { + const lines = [ + '### app/components Diff Coverage', + '', + `Compared \`${baseSha.slice(0, 12)}\` -> \`${headSha.slice(0, 12)}\``, + '', + `Excluded modules: \`${EXCLUDED_MODULES_LABEL}\``, + `Excluded file kinds: \`${COMPONENT_COVERAGE_EXCLUDE_LABEL}\``, + '', + '| Check | Result | Details |', + '|---|---:|---|', + `| Overall tracked lines | ${formatPercent(overallCoverage.lines)} | ${overallCoverage.lines.covered}/${overallCoverage.lines.total}; threshold ${COMPONENTS_GLOBAL_THRESHOLDS.lines}% |`, + `| Overall tracked statements | ${formatPercent(overallCoverage.statements)} | ${overallCoverage.statements.covered}/${overallCoverage.statements.total}; threshold ${COMPONENTS_GLOBAL_THRESHOLDS.statements}% |`, + `| Overall tracked functions | ${formatPercent(overallCoverage.functions)} | ${overallCoverage.functions.covered}/${overallCoverage.functions.total}; threshold ${COMPONENTS_GLOBAL_THRESHOLDS.functions}% |`, + `| Overall tracked branches | ${formatPercent(overallCoverage.branches)} | ${overallCoverage.branches.covered}/${overallCoverage.branches.total}; threshold ${COMPONENTS_GLOBAL_THRESHOLDS.branches}% |`, + `| Changed executable lines | ${formatPercent({ covered: diffTotals.covered, total: diffTotals.total })} | ${diffTotals.covered}/${diffTotals.total} |`, + '', + ] + + if (overallThresholdFailures.length > 0) { + lines.push('Overall thresholds failed:') + for (const failure of overallThresholdFailures) + lines.push(`- ${failure.metric}: ${failure.actual.toFixed(2)}% < ${failure.expected}%`) + lines.push('') + } + + if (moduleThresholdFailures.length > 0) { + lines.push('Module thresholds failed:') + for (const failure of moduleThresholdFailures) + lines.push(`- ${failure.moduleName} ${failure.metric}: ${failure.actual.toFixed(2)}% < ${failure.expected}%`) + lines.push('') + } + + const moduleRows = moduleCoverageRows + .map(({ moduleName, stats, thresholds, failures }) => ({ + moduleName, + lines: percentage(stats.lines.covered, stats.lines.total), + statements: percentage(stats.statements.covered, stats.statements.total), + functions: percentage(stats.functions.covered, stats.functions.total), + branches: percentage(stats.branches.covered, stats.branches.total), + thresholds, + failures, + })) + .sort((a, b) => { + if (a.failures.length !== b.failures.length) + return b.failures.length - a.failures.length + + return a.lines - b.lines || a.moduleName.localeCompare(b.moduleName) + }) + + lines.push('
Module coverage') + lines.push('') + lines.push('| Module | Lines | Statements | Functions | Branches | Thresholds | Status |') + lines.push('|---|---:|---:|---:|---:|---|---|') + for (const row of moduleRows) { + const thresholdLabel = row.thresholds + ? `L${row.thresholds.lines}/S${row.thresholds.statements}/F${row.thresholds.functions}/B${row.thresholds.branches}` + : 'n/a' + const status = row.thresholds ? (row.failures.length > 0 ? 'fail' : 'pass') : 'info' + lines.push(`| ${row.moduleName} | ${row.lines.toFixed(2)}% | ${row.statements.toFixed(2)}% | ${row.functions.toFixed(2)}% | ${row.branches.toFixed(2)}% | ${thresholdLabel} | ${status} |`) + } + lines.push('
') + lines.push('') + + const changedRows = diffRows + .filter(row => row.total > 0) + .sort((a, b) => { + const aPct = percentage(rowCovered(a), rowTotal(a)) + const bPct = percentage(rowCovered(b), rowTotal(b)) + return aPct - bPct || a.file.localeCompare(b.file) + }) + + lines.push('
Changed file coverage') + lines.push('') + lines.push('| File | Module | Changed executable lines | Coverage | Uncovered lines |') + lines.push('|---|---|---:|---:|---|') + for (const row of changedRows) { + const rowPct = percentage(row.covered, row.total) + lines.push(`| ${row.file.replace('web/', '')} | ${row.moduleName} | ${row.total} | ${rowPct.toFixed(2)}% | ${formatLineRanges(row.uncoveredLines)} |`) + } + lines.push('
') + lines.push('') + + if (missingTestTouch) { + lines.push(`Warning: tracked source files changed under \`web/app/components/\`, but no test files changed under \`web/app/components/**\` or \`web/__tests__/\`.`) + if (STRICT_TEST_FILE_TOUCH) + lines.push('`STRICT_COMPONENT_TEST_TOUCH=true` is enabled, so this warning fails the check.') + lines.push('') + } + else { + lines.push(`Relevant test files changed: ${changedTestFiles.length}`) + lines.push('') + } + + if (diffFailures.length > 0) { + lines.push('Uncovered changed lines:') + for (const row of diffFailures) { + lines.push(`- ${row.file.replace('web/', '')}: ${formatLineRanges(row.uncoveredLines)}`) + } + lines.push('') + } + + lines.push(`Changed source files checked: ${changedSourceFiles.length}`) + lines.push(`Changed executable line coverage: ${diffCoveragePct.toFixed(2)}%`) + + return lines +} + +function buildSkipSummary(changedExcludedSourceFiles) { + const lines = [ + '### app/components Diff Coverage', + '', + `Excluded modules: \`${EXCLUDED_MODULES_LABEL}\``, + `Excluded file kinds: \`${COMPONENT_COVERAGE_EXCLUDE_LABEL}\``, + '', + ] + + if (changedExcludedSourceFiles.length > 0) { + lines.push('Only excluded component modules or type-only files changed, so diff coverage check was skipped.') + lines.push(`Skipped files: ${changedExcludedSourceFiles.length}`) + } + else { + lines.push('No source changes under tracked `web/app/components/`. Diff coverage check skipped.') + } + + return lines +} + +function getChangedFiles(base, head) { + const output = execGit(['diff', '--name-only', '--diff-filter=ACMR', `${base}...${head}`, '--', 'web/app/components', 'web/__tests__']) + return output + .split('\n') + .map(line => line.trim()) + .filter(Boolean) +} + +function getChangedLineMap(base, head) { + const diff = execGit(['diff', '--unified=0', '--no-color', '--diff-filter=ACMR', `${base}...${head}`, '--', 'web/app/components']) + const lineMap = new Map() + let currentFile = null + + for (const line of diff.split('\n')) { + if (line.startsWith('+++ b/')) { + currentFile = line.slice(6).trim() + continue + } + + if (!currentFile || !isTrackedComponentSourceFile(currentFile)) + continue + + const match = line.match(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/) + if (!match) + continue + + const start = Number(match[1]) + const count = match[2] ? Number(match[2]) : 1 + if (count === 0) + continue + + const linesForFile = lineMap.get(currentFile) ?? new Set() + for (let offset = 0; offset < count; offset += 1) + linesForFile.add(start + offset) + lineMap.set(currentFile, linesForFile) + } + + return lineMap +} + +function isAnyComponentSourceFile(filePath) { + return filePath.startsWith(APP_COMPONENTS_PREFIX) + && /\.(?:ts|tsx)$/.test(filePath) + && !isTestLikePath(filePath) +} + +function isTrackedComponentSourceFile(filePath) { + return isAnyComponentSourceFile(filePath) + && !isExcludedComponentSourceFile(filePath) +} + +function isExcludedComponentSourceFile(filePath) { + return isAnyComponentSourceFile(filePath) + && ( + EXCLUDED_COMPONENT_MODULES.has(getModuleName(filePath)) + || excludedComponentCoverageFiles.has(filePath) + ) +} + +function isRelevantTestFile(filePath) { + return filePath.startsWith(SHARED_TEST_PREFIX) + || (filePath.startsWith(APP_COMPONENTS_PREFIX) && isTestLikePath(filePath) && !isExcludedComponentTestFile(filePath)) +} + +function isExcludedComponentTestFile(filePath) { + if (!filePath.startsWith(APP_COMPONENTS_PREFIX)) + return false + + return EXCLUDED_COMPONENT_MODULES.has(getModuleName(filePath)) +} + +function isTestLikePath(filePath) { + return /(?:^|\/)__tests__\//.test(filePath) + || /(?:^|\/)__mocks__\//.test(filePath) + || /\.(?:spec|test)\.(?:ts|tsx)$/.test(filePath) + || /\.stories\.(?:ts|tsx)$/.test(filePath) + || /\.d\.ts$/.test(filePath) +} + +function getCoverageStats(entry) { + const lineHits = getLineHits(entry) + const statementHits = Object.values(entry.s ?? {}) + const functionHits = Object.values(entry.f ?? {}) + const branchHits = Object.values(entry.b ?? {}).flat() + + return { + lines: { + covered: Object.values(lineHits).filter(count => count > 0).length, + total: Object.keys(lineHits).length, + }, + statements: { + covered: statementHits.filter(count => count > 0).length, + total: statementHits.length, + }, + functions: { + covered: functionHits.filter(count => count > 0).length, + total: functionHits.length, + }, + branches: { + covered: branchHits.filter(count => count > 0).length, + total: branchHits.length, + }, + } +} + +function getLineHits(entry) { + if (entry.l && Object.keys(entry.l).length > 0) + return entry.l + + const lineHits = {} + for (const [statementId, statement] of Object.entries(entry.statementMap ?? {})) { + const line = statement?.start?.line + if (!line) + continue + + const hits = entry.s?.[statementId] ?? 0 + const previous = lineHits[line] + lineHits[line] = previous === undefined ? hits : Math.max(previous, hits) + } + + return lineHits +} + +function sumCoverageStats(rows) { + const total = createEmptyCoverageStats() + for (const row of rows) + addCoverageStats(total, row) + return total +} + +function mergeCoverageStats(map, moduleName, stats) { + const existing = map.get(moduleName) ?? createEmptyCoverageStats() + addCoverageStats(existing, stats) + map.set(moduleName, existing) +} + +function addCoverageStats(target, source) { + for (const metric of ['lines', 'statements', 'functions', 'branches']) { + target[metric].covered += source[metric].covered + target[metric].total += source[metric].total + } +} + +function createEmptyCoverageStats() { + return { + lines: { covered: 0, total: 0 }, + statements: { covered: 0, total: 0 }, + functions: { covered: 0, total: 0 }, + branches: { covered: 0, total: 0 }, + } +} + +function getThresholdFailures(stats, thresholds) { + const failures = [] + for (const metric of ['lines', 'statements', 'functions', 'branches']) { + const actual = percentage(stats[metric].covered, stats[metric].total) + const expected = thresholds[metric] + if (actual < expected) { + failures.push({ + metric, + actual, + expected, + }) + } + } + return failures +} + +function getModuleName(filePath) { + const relativePath = filePath.slice(APP_COMPONENTS_PREFIX.length) + if (!relativePath) + return '(root)' + + const segments = relativePath.split('/') + return segments.length === 1 ? '(root)' : segments[0] +} + +function normalizeToRepoRelative(filePath) { + if (!filePath) + return '' + + if (filePath.startsWith(APP_COMPONENTS_PREFIX) || filePath.startsWith(SHARED_TEST_PREFIX)) + return filePath + + if (filePath.startsWith(APP_COMPONENTS_COVERAGE_PREFIX)) + return `web/${filePath}` + + const absolutePath = path.isAbsolute(filePath) + ? filePath + : path.resolve(webRoot, filePath) + + return path.relative(repoRoot, absolutePath).split(path.sep).join('/') +} + +function formatLineRanges(lines) { + if (!lines || lines.length === 0) + return '' + + const ranges = [] + let start = lines[0] + let end = lines[0] + + for (let index = 1; index < lines.length; index += 1) { + const current = lines[index] + if (current === end + 1) { + end = current + continue + } + + ranges.push(start === end ? `${start}` : `${start}-${end}`) + start = current + end = current + } + + ranges.push(start === end ? `${start}` : `${start}-${end}`) + return ranges.join(', ') +} + +function percentage(covered, total) { + if (total === 0) + return 100 + return (covered / total) * 100 +} + +function formatPercent(metric) { + return `${percentage(metric.covered, metric.total).toFixed(2)}%` +} + +function appendSummary(lines) { + const content = `${lines.join('\n')}\n` + if (process.env.GITHUB_STEP_SUMMARY) + fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY, content) + console.log(content) +} + +function execGit(args) { + return execFileSync('git', args, { + cwd: repoRoot, + encoding: 'utf8', + }) +} + +function repoRootFromCwd() { + return execFileSync('git', ['rev-parse', '--show-toplevel'], { + cwd: process.cwd(), + encoding: 'utf8', + }).trim() +} + +function rowCovered(row) { + return row.covered +} + +function rowTotal(row) { + return row.total +} diff --git a/web/scripts/component-coverage-filters.mjs b/web/scripts/component-coverage-filters.mjs new file mode 100644 index 0000000000..e33c843cb4 --- /dev/null +++ b/web/scripts/component-coverage-filters.mjs @@ -0,0 +1,316 @@ +import fs from 'node:fs' +import path from 'node:path' +import tsParser from '@typescript-eslint/parser' + +const TS_TSX_FILE_PATTERN = /\.(?:ts|tsx)$/ +const TYPE_COVERAGE_EXCLUDE_BASENAMES = new Set([ + 'type', + 'types', + 'declarations', +]) +const GENERATED_FILE_COMMENT_PATTERNS = [ + /@generated/i, + /\bauto-?generated\b/i, + /\bgenerated by\b/i, + /\bgenerate by\b/i, + /\bdo not edit\b/i, + /\bdon not edit\b/i, +] +const PARSER_OPTIONS = { + ecmaVersion: 'latest', + sourceType: 'module', + ecmaFeatures: { jsx: true }, +} + +const collectedExcludedFilesCache = new Map() + +export const COMPONENT_COVERAGE_EXCLUDE_LABEL = 'type-only files, pure barrel files, generated files, pure static files' + +export function isTypeCoverageExcludedComponentFile(filePath) { + return TYPE_COVERAGE_EXCLUDE_BASENAMES.has(getPathBaseNameWithoutExtension(filePath)) +} + +export function getComponentCoverageExclusionReasons(filePath, sourceCode) { + if (!isEligibleComponentSourceFilePath(filePath)) + return [] + + const reasons = [] + if (isTypeCoverageExcludedComponentFile(filePath)) + reasons.push('type-only') + + if (typeof sourceCode !== 'string' || sourceCode.length === 0) + return reasons + + if (isGeneratedComponentFile(sourceCode)) + reasons.push('generated') + + const ast = parseComponentFile(sourceCode) + if (!ast) + return reasons + + if (isPureBarrelComponentFile(ast)) + reasons.push('pure-barrel') + else if (isPureStaticComponentFile(ast)) + reasons.push('pure-static') + + return reasons +} + +export function collectComponentCoverageExcludedFiles(rootDir, options = {}) { + const normalizedRootDir = path.resolve(rootDir) + const pathPrefix = normalizePathPrefix(options.pathPrefix ?? '') + const cacheKey = `${normalizedRootDir}::${pathPrefix}` + const cached = collectedExcludedFilesCache.get(cacheKey) + if (cached) + return cached + + const files = [] + walkComponentFiles(normalizedRootDir, (absolutePath) => { + const relativePath = path.relative(normalizedRootDir, absolutePath).split(path.sep).join('/') + const prefixedPath = pathPrefix ? `${pathPrefix}/${relativePath}` : relativePath + const sourceCode = fs.readFileSync(absolutePath, 'utf8') + if (getComponentCoverageExclusionReasons(prefixedPath, sourceCode).length > 0) + files.push(prefixedPath) + }) + + files.sort((a, b) => a.localeCompare(b)) + collectedExcludedFilesCache.set(cacheKey, files) + return files +} + +function normalizePathPrefix(pathPrefix) { + return pathPrefix.replace(/\\/g, '/').replace(/\/$/, '') +} + +function walkComponentFiles(currentDir, onFile) { + if (!fs.existsSync(currentDir)) + return + + const entries = fs.readdirSync(currentDir, { withFileTypes: true }) + for (const entry of entries) { + const entryPath = path.join(currentDir, entry.name) + if (entry.isDirectory()) { + if (entry.name === '__tests__' || entry.name === '__mocks__') + continue + walkComponentFiles(entryPath, onFile) + continue + } + + if (!isEligibleComponentSourceFilePath(entry.name)) + continue + + onFile(entryPath) + } +} + +function isEligibleComponentSourceFilePath(filePath) { + return TS_TSX_FILE_PATTERN.test(filePath) + && !isTestLikePath(filePath) +} + +function isTestLikePath(filePath) { + return /(?:^|\/)__tests__\//.test(filePath) + || /(?:^|\/)__mocks__\//.test(filePath) + || /\.(?:spec|test)\.(?:ts|tsx)$/.test(filePath) + || /\.stories\.(?:ts|tsx)$/.test(filePath) + || /\.d\.ts$/.test(filePath) +} + +function getPathBaseNameWithoutExtension(filePath) { + if (!filePath) + return '' + + const normalizedPath = filePath.replace(/\\/g, '/') + const fileName = normalizedPath.split('/').pop() ?? '' + return fileName.replace(TS_TSX_FILE_PATTERN, '') +} + +function isGeneratedComponentFile(sourceCode) { + const leadingText = sourceCode.split('\n').slice(0, 5).join('\n') + return GENERATED_FILE_COMMENT_PATTERNS.some(pattern => pattern.test(leadingText)) +} + +function parseComponentFile(sourceCode) { + try { + return tsParser.parse(sourceCode, PARSER_OPTIONS) + } + catch { + return null + } +} + +function isPureBarrelComponentFile(ast) { + let hasRuntimeReExports = false + + for (const statement of ast.body) { + if (statement.type === 'ExportAllDeclaration') { + hasRuntimeReExports = true + continue + } + + if (statement.type === 'ExportNamedDeclaration' && statement.source) { + hasRuntimeReExports = hasRuntimeReExports || statement.exportKind !== 'type' + continue + } + + if (statement.type === 'TSInterfaceDeclaration' || statement.type === 'TSTypeAliasDeclaration') + continue + + return false + } + + return hasRuntimeReExports +} + +function isPureStaticComponentFile(ast) { + const importedStaticBindings = collectImportedStaticBindings(ast.body) + const staticBindings = new Set() + let hasRuntimeValue = false + + for (const statement of ast.body) { + if (statement.type === 'ImportDeclaration') + continue + + if (statement.type === 'TSInterfaceDeclaration' || statement.type === 'TSTypeAliasDeclaration') + continue + + if (statement.type === 'ExportAllDeclaration') + return false + + if (statement.type === 'ExportNamedDeclaration' && statement.source) + return false + + if (statement.type === 'ExportDefaultDeclaration') { + if (!isStaticExpression(statement.declaration, staticBindings, importedStaticBindings)) + return false + hasRuntimeValue = true + continue + } + + if (statement.type === 'ExportNamedDeclaration' && statement.declaration) { + if (!handleStaticDeclaration(statement.declaration, staticBindings, importedStaticBindings)) + return false + hasRuntimeValue = true + continue + } + + if (statement.type === 'ExportNamedDeclaration' && statement.specifiers.length > 0) { + const allStaticSpecifiers = statement.specifiers.every((specifier) => { + if (specifier.type !== 'ExportSpecifier' || specifier.exportKind === 'type') + return false + return specifier.local.type === 'Identifier' && staticBindings.has(specifier.local.name) + }) + if (!allStaticSpecifiers) + return false + hasRuntimeValue = true + continue + } + + if (!handleStaticDeclaration(statement, staticBindings, importedStaticBindings)) + return false + hasRuntimeValue = true + } + + return hasRuntimeValue +} + +function handleStaticDeclaration(statement, staticBindings, importedStaticBindings) { + if (statement.type !== 'VariableDeclaration' || statement.kind !== 'const') + return false + + for (const declarator of statement.declarations) { + if (declarator.id.type !== 'Identifier' || !declarator.init) + return false + + if (!isStaticExpression(declarator.init, staticBindings, importedStaticBindings)) + return false + + staticBindings.add(declarator.id.name) + } + + return true +} + +function collectImportedStaticBindings(statements) { + const importedBindings = new Set() + + for (const statement of statements) { + if (statement.type !== 'ImportDeclaration') + continue + + const importSource = String(statement.source.value ?? '') + const isTypeLikeSource = isTypeCoverageExcludedComponentFile(importSource) + const importIsStatic = statement.importKind === 'type' || isTypeLikeSource + if (!importIsStatic) + continue + + for (const specifier of statement.specifiers) { + if (specifier.local?.type === 'Identifier') + importedBindings.add(specifier.local.name) + } + } + + return importedBindings +} + +function isStaticExpression(node, staticBindings, importedStaticBindings) { + switch (node.type) { + case 'Literal': + return true + case 'Identifier': + return staticBindings.has(node.name) || importedStaticBindings.has(node.name) + case 'TemplateLiteral': + return node.expressions.every(expression => isStaticExpression(expression, staticBindings, importedStaticBindings)) + case 'ArrayExpression': + return node.elements.every(element => !element || isStaticExpression(element, staticBindings, importedStaticBindings)) + case 'ObjectExpression': + return node.properties.every((property) => { + if (property.type === 'SpreadElement') + return isStaticExpression(property.argument, staticBindings, importedStaticBindings) + + if (property.type !== 'Property' || property.method) + return false + + if (property.computed && !isStaticExpression(property.key, staticBindings, importedStaticBindings)) + return false + + if (property.shorthand) + return property.value.type === 'Identifier' && staticBindings.has(property.value.name) + + return isStaticExpression(property.value, staticBindings, importedStaticBindings) + }) + case 'UnaryExpression': + return isStaticExpression(node.argument, staticBindings, importedStaticBindings) + case 'BinaryExpression': + case 'LogicalExpression': + return isStaticExpression(node.left, staticBindings, importedStaticBindings) + && isStaticExpression(node.right, staticBindings, importedStaticBindings) + case 'ConditionalExpression': + return isStaticExpression(node.test, staticBindings, importedStaticBindings) + && isStaticExpression(node.consequent, staticBindings, importedStaticBindings) + && isStaticExpression(node.alternate, staticBindings, importedStaticBindings) + case 'MemberExpression': + return isStaticMemberExpression(node, staticBindings, importedStaticBindings) + case 'ChainExpression': + return isStaticExpression(node.expression, staticBindings, importedStaticBindings) + case 'TSAsExpression': + case 'TSSatisfiesExpression': + case 'TSTypeAssertion': + case 'TSNonNullExpression': + return isStaticExpression(node.expression, staticBindings, importedStaticBindings) + case 'ParenthesizedExpression': + return isStaticExpression(node.expression, staticBindings, importedStaticBindings) + default: + return false + } +} + +function isStaticMemberExpression(node, staticBindings, importedStaticBindings) { + if (!isStaticExpression(node.object, staticBindings, importedStaticBindings)) + return false + + if (!node.computed) + return node.property.type === 'Identifier' + + return isStaticExpression(node.property, staticBindings, importedStaticBindings) +} diff --git a/web/scripts/components-coverage-thresholds.mjs b/web/scripts/components-coverage-thresholds.mjs new file mode 100644 index 0000000000..d61a6ad814 --- /dev/null +++ b/web/scripts/components-coverage-thresholds.mjs @@ -0,0 +1,128 @@ +// Floors were set from the app/components baseline captured on 2026-03-13, +// with a small buffer to avoid CI noise on existing code. +export const EXCLUDED_COMPONENT_MODULES = new Set([ + 'devtools', + 'provider', +]) + +export const COMPONENTS_GLOBAL_THRESHOLDS = { + lines: 58, + statements: 58, + functions: 58, + branches: 54, +} + +export const COMPONENT_MODULE_THRESHOLDS = { + 'app': { + lines: 45, + statements: 45, + functions: 50, + branches: 35, + }, + 'app-sidebar': { + lines: 95, + statements: 95, + functions: 95, + branches: 90, + }, + 'apps': { + lines: 90, + statements: 90, + functions: 85, + branches: 80, + }, + 'base': { + lines: 95, + statements: 95, + functions: 90, + branches: 95, + }, + 'billing': { + lines: 95, + statements: 95, + functions: 95, + branches: 95, + }, + 'custom': { + lines: 70, + statements: 70, + functions: 70, + branches: 80, + }, + 'datasets': { + lines: 95, + statements: 95, + functions: 95, + branches: 90, + }, + 'develop': { + lines: 95, + statements: 95, + functions: 95, + branches: 90, + }, + 'explore': { + lines: 95, + statements: 95, + functions: 95, + branches: 85, + }, + 'goto-anything': { + lines: 90, + statements: 90, + functions: 90, + branches: 90, + }, + 'header': { + lines: 95, + statements: 95, + functions: 95, + branches: 95, + }, + 'plugins': { + lines: 90, + statements: 90, + functions: 90, + branches: 85, + }, + 'rag-pipeline': { + lines: 95, + statements: 95, + functions: 95, + branches: 90, + }, + 'share': { + lines: 15, + statements: 15, + functions: 20, + branches: 20, + }, + 'signin': { + lines: 95, + statements: 95, + functions: 95, + branches: 95, + }, + 'tools': { + lines: 95, + statements: 95, + functions: 90, + branches: 90, + }, + 'workflow': { + lines: 15, + statements: 15, + functions: 10, + branches: 10, + }, + 'workflow-app': { + lines: 20, + statements: 20, + functions: 25, + branches: 15, + }, +} + +export function getComponentModuleThreshold(moduleName) { + return COMPONENT_MODULE_THRESHOLDS[moduleName] ?? null +} diff --git a/web/vite.config.ts b/web/vite.config.ts index d0c7e947a2..3a61264e3c 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -8,15 +8,24 @@ import { defineConfig } from 'vite' import Inspect from 'vite-plugin-inspect' import { createCodeInspectorPlugin, createForceInspectorClientInjectionPlugin } from './plugins/vite/code-inspector' import { customI18nHmrPlugin } from './plugins/vite/custom-i18n-hmr' +import { collectComponentCoverageExcludedFiles } from './scripts/component-coverage-filters.mjs' +import { EXCLUDED_COMPONENT_MODULES } from './scripts/components-coverage-thresholds.mjs' const projectRoot = path.dirname(fileURLToPath(import.meta.url)) const isCI = !!process.env.CI +const coverageScope = process.env.VITEST_COVERAGE_SCOPE const browserInitializerInjectTarget = path.resolve(projectRoot, 'app/components/browser-initializer.tsx') +const excludedAppComponentsCoveragePaths = [...EXCLUDED_COMPONENT_MODULES] + .map(moduleName => `app/components/${moduleName}/**`) export default defineConfig(({ mode }) => { const isTest = mode === 'test' const isStorybook = process.env.STORYBOOK === 'true' || process.argv.some(arg => arg.toLowerCase().includes('storybook')) + const isAppComponentsCoverage = coverageScope === 'app-components' + const excludedComponentCoverageFiles = isAppComponentsCoverage + ? collectComponentCoverageExcludedFiles(path.join(projectRoot, 'app/components'), { pathPrefix: 'app/components' }) + : [] return { plugins: isTest @@ -82,6 +91,21 @@ export default defineConfig(({ mode }) => { coverage: { provider: 'v8', reporter: isCI ? ['json', 'json-summary'] : ['text', 'json', 'json-summary'], + ...(isAppComponentsCoverage + ? { + include: ['app/components/**/*.{ts,tsx}'], + exclude: [ + 'app/components/**/*.d.ts', + 'app/components/**/*.spec.{ts,tsx}', + 'app/components/**/*.test.{ts,tsx}', + 'app/components/**/__tests__/**', + 'app/components/**/__mocks__/**', + 'app/components/**/*.stories.{ts,tsx}', + ...excludedComponentCoverageFiles, + ...excludedAppComponentsCoveragePaths, + ], + } + : {}), }, }, } From f38e8cca521093f2ae1225c6161f8261a51ee8c2 Mon Sep 17 00:00:00 2001 From: Asuka Minato Date: Fri, 13 Mar 2026 17:32:39 +0900 Subject: [PATCH 4/8] test: [Refactor/Chore] use Testcontainers to do sql test #32454 (#32460) --- .../models/test_app_model_config.py | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 api/tests/test_containers_integration_tests/models/test_app_model_config.py diff --git a/api/tests/test_containers_integration_tests/models/test_app_model_config.py b/api/tests/test_containers_integration_tests/models/test_app_model_config.py new file mode 100644 index 0000000000..e8b36097e1 --- /dev/null +++ b/api/tests/test_containers_integration_tests/models/test_app_model_config.py @@ -0,0 +1,32 @@ +""" +Integration tests for AppModelConfig using testcontainers. + +These tests validate database-backed model behavior without mocking SQLAlchemy queries. +""" + +from uuid import uuid4 + +from sqlalchemy.orm import Session + +from models.model import AppModelConfig + + +class TestAppModelConfig: + """Integration tests for AppModelConfig.""" + + def test_annotation_reply_dict_disabled_without_setting(self, db_session_with_containers: Session) -> None: + """Return disabled annotation reply dict when no AppAnnotationSetting exists.""" + # Arrange + config = AppModelConfig(app_id=str(uuid4())) + db_session_with_containers.add(config) + db_session_with_containers.commit() + + # Act + result = config.annotation_reply_dict + + # Assert + assert result == {"enabled": False} + + # Cleanup + db_session_with_containers.delete(config) + db_session_with_containers.commit() From 20e91990bfbb0b665bd128cd8e0ca8d68e554726 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Mar 2026 17:33:33 +0900 Subject: [PATCH 5/8] chore(deps): bump orjson from 3.11.4 to 3.11.6 in /api (#33380) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- api/uv.lock | 64 ++++++++++++++++++++++++++--------------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/api/uv.lock b/api/uv.lock index 518668711d..1d03d5a360 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -4461,40 +4461,40 @@ wheels = [ [[package]] name = "orjson" -version = "3.11.4" +version = "3.11.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c6/fe/ed708782d6709cc60eb4c2d8a361a440661f74134675c72990f2c48c785f/orjson-3.11.4.tar.gz", hash = "sha256:39485f4ab4c9b30a3943cfe99e1a213c4776fb69e8abd68f66b83d5a0b0fdc6d", size = 5945188, upload-time = "2025-10-24T15:50:38.027Z" } +sdist = { url = "https://files.pythonhosted.org/packages/70/a3/4e09c61a5f0c521cba0bb433639610ae037437669f1a4cbc93799e731d78/orjson-3.11.6.tar.gz", hash = "sha256:0a54c72259f35299fd033042367df781c2f66d10252955ca1efb7db309b954cb", size = 6175856, upload-time = "2026-01-29T15:13:07.942Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/63/1d/1ea6005fffb56715fd48f632611e163d1604e8316a5bad2288bee9a1c9eb/orjson-3.11.4-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:5e59d23cd93ada23ec59a96f215139753fbfe3a4d989549bcb390f8c00370b39", size = 243498, upload-time = "2025-10-24T15:48:48.101Z" }, - { url = "https://files.pythonhosted.org/packages/37/d7/ffed10c7da677f2a9da307d491b9eb1d0125b0307019c4ad3d665fd31f4f/orjson-3.11.4-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:5c3aedecfc1beb988c27c79d52ebefab93b6c3921dbec361167e6559aba2d36d", size = 128961, upload-time = "2025-10-24T15:48:49.571Z" }, - { url = "https://files.pythonhosted.org/packages/a2/96/3e4d10a18866d1368f73c8c44b7fe37cc8a15c32f2a7620be3877d4c55a3/orjson-3.11.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da9e5301f1c2caa2a9a4a303480d79c9ad73560b2e7761de742ab39fe59d9175", size = 130321, upload-time = "2025-10-24T15:48:50.713Z" }, - { url = "https://files.pythonhosted.org/packages/eb/1f/465f66e93f434f968dd74d5b623eb62c657bdba2332f5a8be9f118bb74c7/orjson-3.11.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8873812c164a90a79f65368f8f96817e59e35d0cc02786a5356f0e2abed78040", size = 129207, upload-time = "2025-10-24T15:48:52.193Z" }, - { url = "https://files.pythonhosted.org/packages/28/43/d1e94837543321c119dff277ae8e348562fe8c0fafbb648ef7cb0c67e521/orjson-3.11.4-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5d7feb0741ebb15204e748f26c9638e6665a5fa93c37a2c73d64f1669b0ddc63", size = 136323, upload-time = "2025-10-24T15:48:54.806Z" }, - { url = "https://files.pythonhosted.org/packages/bf/04/93303776c8890e422a5847dd012b4853cdd88206b8bbd3edc292c90102d1/orjson-3.11.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01ee5487fefee21e6910da4c2ee9eef005bee568a0879834df86f888d2ffbdd9", size = 137440, upload-time = "2025-10-24T15:48:56.326Z" }, - { url = "https://files.pythonhosted.org/packages/1e/ef/75519d039e5ae6b0f34d0336854d55544ba903e21bf56c83adc51cd8bf82/orjson-3.11.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d40d46f348c0321df01507f92b95a377240c4ec31985225a6668f10e2676f9a", size = 136680, upload-time = "2025-10-24T15:48:57.476Z" }, - { url = "https://files.pythonhosted.org/packages/b5/18/bf8581eaae0b941b44efe14fee7b7862c3382fbc9a0842132cfc7cf5ecf4/orjson-3.11.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95713e5fc8af84d8edc75b785d2386f653b63d62b16d681687746734b4dfc0be", size = 136160, upload-time = "2025-10-24T15:48:59.631Z" }, - { url = "https://files.pythonhosted.org/packages/c4/35/a6d582766d351f87fc0a22ad740a641b0a8e6fc47515e8614d2e4790ae10/orjson-3.11.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ad73ede24f9083614d6c4ca9a85fe70e33be7bf047ec586ee2363bc7418fe4d7", size = 140318, upload-time = "2025-10-24T15:49:00.834Z" }, - { url = "https://files.pythonhosted.org/packages/76/b3/5a4801803ab2e2e2d703bce1a56540d9f99a9143fbec7bf63d225044fef8/orjson-3.11.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:842289889de515421f3f224ef9c1f1efb199a32d76d8d2ca2706fa8afe749549", size = 406330, upload-time = "2025-10-24T15:49:02.327Z" }, - { url = "https://files.pythonhosted.org/packages/80/55/a8f682f64833e3a649f620eafefee175cbfeb9854fc5b710b90c3bca45df/orjson-3.11.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:3b2427ed5791619851c52a1261b45c233930977e7de8cf36de05636c708fa905", size = 149580, upload-time = "2025-10-24T15:49:03.517Z" }, - { url = "https://files.pythonhosted.org/packages/ad/e4/c132fa0c67afbb3eb88274fa98df9ac1f631a675e7877037c611805a4413/orjson-3.11.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c36e524af1d29982e9b190573677ea02781456b2e537d5840e4538a5ec41907", size = 139846, upload-time = "2025-10-24T15:49:04.761Z" }, - { url = "https://files.pythonhosted.org/packages/54/06/dc3491489efd651fef99c5908e13951abd1aead1257c67f16135f95ce209/orjson-3.11.4-cp311-cp311-win32.whl", hash = "sha256:87255b88756eab4a68ec61837ca754e5d10fa8bc47dc57f75cedfeaec358d54c", size = 135781, upload-time = "2025-10-24T15:49:05.969Z" }, - { url = "https://files.pythonhosted.org/packages/79/b7/5e5e8d77bd4ea02a6ac54c42c818afb01dd31961be8a574eb79f1d2cfb1e/orjson-3.11.4-cp311-cp311-win_amd64.whl", hash = "sha256:e2d5d5d798aba9a0e1fede8d853fa899ce2cb930ec0857365f700dffc2c7af6a", size = 131391, upload-time = "2025-10-24T15:49:07.355Z" }, - { url = "https://files.pythonhosted.org/packages/0f/dc/9484127cc1aa213be398ed735f5f270eedcb0c0977303a6f6ddc46b60204/orjson-3.11.4-cp311-cp311-win_arm64.whl", hash = "sha256:6bb6bb41b14c95d4f2702bce9975fda4516f1db48e500102fc4d8119032ff045", size = 126252, upload-time = "2025-10-24T15:49:08.869Z" }, - { url = "https://files.pythonhosted.org/packages/63/51/6b556192a04595b93e277a9ff71cd0cc06c21a7df98bcce5963fa0f5e36f/orjson-3.11.4-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:d4371de39319d05d3f482f372720b841c841b52f5385bd99c61ed69d55d9ab50", size = 243571, upload-time = "2025-10-24T15:49:10.008Z" }, - { url = "https://files.pythonhosted.org/packages/1c/2c/2602392ddf2601d538ff11848b98621cd465d1a1ceb9db9e8043181f2f7b/orjson-3.11.4-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:e41fd3b3cac850eaae78232f37325ed7d7436e11c471246b87b2cd294ec94853", size = 128891, upload-time = "2025-10-24T15:49:11.297Z" }, - { url = "https://files.pythonhosted.org/packages/4e/47/bf85dcf95f7a3a12bf223394a4f849430acd82633848d52def09fa3f46ad/orjson-3.11.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:600e0e9ca042878c7fdf189cf1b028fe2c1418cc9195f6cb9824eb6ed99cb938", size = 130137, upload-time = "2025-10-24T15:49:12.544Z" }, - { url = "https://files.pythonhosted.org/packages/b4/4d/a0cb31007f3ab6f1fd2a1b17057c7c349bc2baf8921a85c0180cc7be8011/orjson-3.11.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7bbf9b333f1568ef5da42bc96e18bf30fd7f8d54e9ae066d711056add508e415", size = 129152, upload-time = "2025-10-24T15:49:13.754Z" }, - { url = "https://files.pythonhosted.org/packages/f7/ef/2811def7ce3d8576b19e3929fff8f8f0d44bc5eb2e0fdecb2e6e6cc6c720/orjson-3.11.4-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4806363144bb6e7297b8e95870e78d30a649fdc4e23fc84daa80c8ebd366ce44", size = 136834, upload-time = "2025-10-24T15:49:15.307Z" }, - { url = "https://files.pythonhosted.org/packages/00/d4/9aee9e54f1809cec8ed5abd9bc31e8a9631d19460e3b8470145d25140106/orjson-3.11.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad355e8308493f527d41154e9053b86a5be892b3b359a5c6d5d95cda23601cb2", size = 137519, upload-time = "2025-10-24T15:49:16.557Z" }, - { url = "https://files.pythonhosted.org/packages/db/ea/67bfdb5465d5679e8ae8d68c11753aaf4f47e3e7264bad66dc2f2249e643/orjson-3.11.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8a7517482667fb9f0ff1b2f16fe5829296ed7a655d04d68cd9711a4d8a4e708", size = 136749, upload-time = "2025-10-24T15:49:17.796Z" }, - { url = "https://files.pythonhosted.org/packages/01/7e/62517dddcfce6d53a39543cd74d0dccfcbdf53967017c58af68822100272/orjson-3.11.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97eb5942c7395a171cbfecc4ef6701fc3c403e762194683772df4c54cfbb2210", size = 136325, upload-time = "2025-10-24T15:49:19.347Z" }, - { url = "https://files.pythonhosted.org/packages/18/ae/40516739f99ab4c7ec3aaa5cc242d341fcb03a45d89edeeaabc5f69cb2cf/orjson-3.11.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:149d95d5e018bdd822e3f38c103b1a7c91f88d38a88aada5c4e9b3a73a244241", size = 140204, upload-time = "2025-10-24T15:49:20.545Z" }, - { url = "https://files.pythonhosted.org/packages/82/18/ff5734365623a8916e3a4037fcef1cd1782bfc14cf0992afe7940c5320bf/orjson-3.11.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:624f3951181eb46fc47dea3d221554e98784c823e7069edb5dbd0dc826ac909b", size = 406242, upload-time = "2025-10-24T15:49:21.884Z" }, - { url = "https://files.pythonhosted.org/packages/e1/43/96436041f0a0c8c8deca6a05ebeaf529bf1de04839f93ac5e7c479807aec/orjson-3.11.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:03bfa548cf35e3f8b3a96c4e8e41f753c686ff3d8e182ce275b1751deddab58c", size = 150013, upload-time = "2025-10-24T15:49:23.185Z" }, - { url = "https://files.pythonhosted.org/packages/1b/48/78302d98423ed8780479a1e682b9aecb869e8404545d999d34fa486e573e/orjson-3.11.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:525021896afef44a68148f6ed8a8bf8375553d6066c7f48537657f64823565b9", size = 139951, upload-time = "2025-10-24T15:49:24.428Z" }, - { url = "https://files.pythonhosted.org/packages/4a/7b/ad613fdcdaa812f075ec0875143c3d37f8654457d2af17703905425981bf/orjson-3.11.4-cp312-cp312-win32.whl", hash = "sha256:b58430396687ce0f7d9eeb3dd47761ca7d8fda8e9eb92b3077a7a353a75efefa", size = 136049, upload-time = "2025-10-24T15:49:25.973Z" }, - { url = "https://files.pythonhosted.org/packages/b9/3c/9cf47c3ff5f39b8350fb21ba65d789b6a1129d4cbb3033ba36c8a9023520/orjson-3.11.4-cp312-cp312-win_amd64.whl", hash = "sha256:c6dbf422894e1e3c80a177133c0dda260f81428f9de16d61041949f6a2e5c140", size = 131461, upload-time = "2025-10-24T15:49:27.259Z" }, - { url = "https://files.pythonhosted.org/packages/c6/3b/e2425f61e5825dc5b08c2a5a2b3af387eaaca22a12b9c8c01504f8614c36/orjson-3.11.4-cp312-cp312-win_arm64.whl", hash = "sha256:d38d2bc06d6415852224fcc9c0bfa834c25431e466dc319f0edd56cca81aa96e", size = 126167, upload-time = "2025-10-24T15:49:28.511Z" }, + { url = "https://files.pythonhosted.org/packages/f3/fd/d6b0a36854179b93ed77839f107c4089d91cccc9f9ba1b752b6e3bac5f34/orjson-3.11.6-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:e259e85a81d76d9665f03d6129e09e4435531870de5961ddcd0bf6e3a7fde7d7", size = 250029, upload-time = "2026-01-29T15:11:35.942Z" }, + { url = "https://files.pythonhosted.org/packages/a3/bb/22902619826641cf3b627c24aab62e2ad6b571bdd1d34733abb0dd57f67a/orjson-3.11.6-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:52263949f41b4a4822c6b1353bcc5ee2f7109d53a3b493501d3369d6d0e7937a", size = 134518, upload-time = "2026-01-29T15:11:37.347Z" }, + { url = "https://files.pythonhosted.org/packages/72/90/7a818da4bba1de711a9653c420749c0ac95ef8f8651cbc1dca551f462fe0/orjson-3.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6439e742fa7834a24698d358a27346bb203bff356ae0402e7f5df8f749c621a8", size = 137917, upload-time = "2026-01-29T15:11:38.511Z" }, + { url = "https://files.pythonhosted.org/packages/59/0f/02846c1cac8e205cb3822dd8aa8f9114acda216f41fd1999ace6b543418d/orjson-3.11.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b81ffd68f084b4e993e3867acb554a049fa7787cc8710bbcc1e26965580d99be", size = 134923, upload-time = "2026-01-29T15:11:39.711Z" }, + { url = "https://files.pythonhosted.org/packages/94/cf/aeaf683001b474bb3c3c757073a4231dfdfe8467fceaefa5bfd40902c99f/orjson-3.11.6-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5a5468e5e60f7ef6d7f9044b06c8f94a3c56ba528c6e4f7f06ae95164b595ec", size = 140752, upload-time = "2026-01-29T15:11:41.347Z" }, + { url = "https://files.pythonhosted.org/packages/fc/fe/dad52d8315a65f084044a0819d74c4c9daf9ebe0681d30f525b0d29a31f0/orjson-3.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:72c5005eb45bd2535632d4f3bec7ad392832cfc46b62a3021da3b48a67734b45", size = 144201, upload-time = "2026-01-29T15:11:42.537Z" }, + { url = "https://files.pythonhosted.org/packages/36/bc/ab070dd421565b831801077f1e390c4d4af8bfcecafc110336680a33866b/orjson-3.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0b14dd49f3462b014455a28a4d810d3549bf990567653eb43765cd847df09145", size = 142380, upload-time = "2026-01-29T15:11:44.309Z" }, + { url = "https://files.pythonhosted.org/packages/e6/d8/4b581c725c3a308717f28bf45a9fdac210bca08b67e8430143699413ff06/orjson-3.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e0bb2c1ea30ef302f0f89f9bf3e7f9ab5e2af29dc9f80eb87aa99788e4e2d65", size = 145582, upload-time = "2026-01-29T15:11:45.506Z" }, + { url = "https://files.pythonhosted.org/packages/5b/a2/09aab99b39f9a7f175ea8fa29adb9933a3d01e7d5d603cdee7f1c40c8da2/orjson-3.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:825e0a85d189533c6bff7e2fc417a28f6fcea53d27125c4551979aecd6c9a197", size = 147270, upload-time = "2026-01-29T15:11:46.782Z" }, + { url = "https://files.pythonhosted.org/packages/b8/2f/5ef8eaf7829dc50da3bf497c7775b21ee88437bc8c41f959aa3504ca6631/orjson-3.11.6-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:b04575417a26530637f6ab4b1f7b4f666eb0433491091da4de38611f97f2fcf3", size = 421222, upload-time = "2026-01-29T15:11:48.106Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b0/dd6b941294c2b5b13da5fdc7e749e58d0c55a5114ab37497155e83050e95/orjson-3.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b83eb2e40e8c4da6d6b340ee6b1d6125f5195eb1b0ebb7eac23c6d9d4f92d224", size = 155562, upload-time = "2026-01-29T15:11:49.408Z" }, + { url = "https://files.pythonhosted.org/packages/8e/09/43924331a847476ae2f9a16bd6d3c9dab301265006212ba0d3d7fd58763a/orjson-3.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1f42da604ee65a6b87eef858c913ce3e5777872b19321d11e6fc6d21de89b64f", size = 147432, upload-time = "2026-01-29T15:11:50.635Z" }, + { url = "https://files.pythonhosted.org/packages/5d/e9/d9865961081816909f6b49d880749dbbd88425afd7c5bbce0549e2290d77/orjson-3.11.6-cp311-cp311-win32.whl", hash = "sha256:5ae45df804f2d344cffb36c43fdf03c82fb6cd247f5faa41e21891b40dfbf733", size = 139623, upload-time = "2026-01-29T15:11:51.82Z" }, + { url = "https://files.pythonhosted.org/packages/b4/f9/6836edb92f76eec1082919101eb1145d2f9c33c8f2c5e6fa399b82a2aaa8/orjson-3.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:f4295948d65ace0a2d8f2c4ccc429668b7eb8af547578ec882e16bf79b0050b2", size = 136647, upload-time = "2026-01-29T15:11:53.454Z" }, + { url = "https://files.pythonhosted.org/packages/b3/0c/4954082eea948c9ae52ee0bcbaa2f99da3216a71bcc314ab129bde22e565/orjson-3.11.6-cp311-cp311-win_arm64.whl", hash = "sha256:314e9c45e0b81b547e3a1cfa3df3e07a815821b3dac9fe8cb75014071d0c16a4", size = 135327, upload-time = "2026-01-29T15:11:56.616Z" }, + { url = "https://files.pythonhosted.org/packages/14/ba/759f2879f41910b7e5e0cdbd9cf82a4f017c527fb0e972e9869ca7fe4c8e/orjson-3.11.6-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:6f03f30cd8953f75f2a439070c743c7336d10ee940da918d71c6f3556af3ddcf", size = 249988, upload-time = "2026-01-29T15:11:58.294Z" }, + { url = "https://files.pythonhosted.org/packages/f0/70/54cecb929e6c8b10104fcf580b0cc7dc551aa193e83787dd6f3daba28bb5/orjson-3.11.6-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:af44baae65ef386ad971469a8557a0673bb042b0b9fd4397becd9c2dfaa02588", size = 134445, upload-time = "2026-01-29T15:11:59.819Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6f/ec0309154457b9ba1ad05f11faa4441f76037152f75e1ac577db3ce7ca96/orjson-3.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c310a48542094e4f7dbb6ac076880994986dda8ca9186a58c3cb70a3514d3231", size = 137708, upload-time = "2026-01-29T15:12:01.488Z" }, + { url = "https://files.pythonhosted.org/packages/20/52/3c71b80840f8bab9cb26417302707b7716b7d25f863f3a541bcfa232fe6e/orjson-3.11.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d8dfa7a5d387f15ecad94cb6b2d2d5f4aeea64efd8d526bfc03c9812d01e1cc0", size = 134798, upload-time = "2026-01-29T15:12:02.705Z" }, + { url = "https://files.pythonhosted.org/packages/30/51/b490a43b22ff736282360bd02e6bded455cf31dfc3224e01cd39f919bbd2/orjson-3.11.6-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba8daee3e999411b50f8b50dbb0a3071dd1845f3f9a1a0a6fa6de86d1689d84d", size = 140839, upload-time = "2026-01-29T15:12:03.956Z" }, + { url = "https://files.pythonhosted.org/packages/95/bc/4bcfe4280c1bc63c5291bb96f98298845b6355da2226d3400e17e7b51e53/orjson-3.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f89d104c974eafd7436d7a5fdbc57f7a1e776789959a2f4f1b2eab5c62a339f4", size = 144080, upload-time = "2026-01-29T15:12:05.151Z" }, + { url = "https://files.pythonhosted.org/packages/01/74/22970f9ead9ab1f1b5f8c227a6c3aa8d71cd2c5acd005868a1d44f2362fa/orjson-3.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2e2e2456788ca5ea75616c40da06fc885a7dc0389780e8a41bf7c5389ba257b", size = 142435, upload-time = "2026-01-29T15:12:06.641Z" }, + { url = "https://files.pythonhosted.org/packages/29/34/d564aff85847ab92c82ee43a7a203683566c2fca0723a5f50aebbe759603/orjson-3.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a42efebc45afabb1448001e90458c4020d5c64fbac8a8dc4045b777db76cb5a", size = 145631, upload-time = "2026-01-29T15:12:08.351Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ef/016957a3890752c4aa2368326ea69fa53cdc1fdae0a94a542b6410dbdf52/orjson-3.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:71b7cbef8471324966c3738c90ba38775563ef01b512feb5ad4805682188d1b9", size = 147058, upload-time = "2026-01-29T15:12:10.023Z" }, + { url = "https://files.pythonhosted.org/packages/56/cc/9a899c3972085645b3225569f91a30e221f441e5dc8126e6d060b971c252/orjson-3.11.6-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:f8515e5910f454fe9a8e13c2bb9dc4bae4c1836313e967e72eb8a4ad874f0248", size = 421161, upload-time = "2026-01-29T15:12:11.308Z" }, + { url = "https://files.pythonhosted.org/packages/21/a8/767d3fbd6d9b8fdee76974db40619399355fd49bf91a6dd2c4b6909ccf05/orjson-3.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:300360edf27c8c9bf7047345a94fddf3a8b8922df0ff69d71d854a170cb375cf", size = 155757, upload-time = "2026-01-29T15:12:12.776Z" }, + { url = "https://files.pythonhosted.org/packages/ad/0b/205cd69ac87e2272e13ef3f5f03a3d4657e317e38c1b08aaa2ef97060bbc/orjson-3.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:caaed4dad39e271adfadc106fab634d173b2bb23d9cf7e67bd645f879175ebfc", size = 147446, upload-time = "2026-01-29T15:12:14.166Z" }, + { url = "https://files.pythonhosted.org/packages/de/c5/dd9f22aa9f27c54c7d05cc32f4580c9ac9b6f13811eeb81d6c4c3f50d6b1/orjson-3.11.6-cp312-cp312-win32.whl", hash = "sha256:955368c11808c89793e847830e1b1007503a5923ddadc108547d3b77df761044", size = 139717, upload-time = "2026-01-29T15:12:15.7Z" }, + { url = "https://files.pythonhosted.org/packages/23/a1/e62fc50d904486970315a1654b8cfb5832eb46abb18cd5405118e7e1fc79/orjson-3.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:2c68de30131481150073d90a5d227a4a421982f42c025ecdfb66157f9579e06f", size = 136711, upload-time = "2026-01-29T15:12:17.055Z" }, + { url = "https://files.pythonhosted.org/packages/04/3d/b4fefad8bdf91e0fe212eb04975aeb36ea92997269d68857efcc7eb1dda3/orjson-3.11.6-cp312-cp312-win_arm64.whl", hash = "sha256:65dfa096f4e3a5e02834b681f539a87fbe85adc82001383c0db907557f666bfc", size = 135212, upload-time = "2026-01-29T15:12:18.3Z" }, ] [[package]] From 4203647c32f96fb0edaa6e66facbc7f79e6301ca Mon Sep 17 00:00:00 2001 From: Stephen Zhou Date: Fri, 13 Mar 2026 18:18:44 +0800 Subject: [PATCH 6/8] chore: use vite plus (#33407) --- .github/actions/setup-web/action.yml | 34 +- .github/workflows/autofix.yml | 4 +- .github/workflows/style.yml | 8 +- .github/workflows/translate-i18n-claude.yml | 2 - .github/workflows/web-tests.yml | 6 +- web/knip.config.ts | 2 + web/package.json | 14 +- web/pnpm-lock.yaml | 1293 ++++++++++++------- web/vite.config.ts | 4 +- 9 files changed, 865 insertions(+), 502 deletions(-) diff --git a/.github/actions/setup-web/action.yml b/.github/actions/setup-web/action.yml index c57da7cb5f..54702c914a 100644 --- a/.github/actions/setup-web/action.yml +++ b/.github/actions/setup-web/action.yml @@ -1,33 +1,13 @@ name: Setup Web Environment -description: Setup pnpm, Node.js, and install web dependencies. - -inputs: - node-version: - description: Node.js version to use - required: false - default: "22" - install-dependencies: - description: Whether to install web dependencies after setting up Node.js - required: false - default: "true" runs: using: composite steps: - - name: Install pnpm - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 + - name: Setup Vite+ + uses: voidzero-dev/setup-vp@b5d848f5a62488f3d3d920f8aa6ac318a60c5f07 # v1 with: - package_json_file: web/package.json - run_install: false - - - name: Setup Node.js - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 - with: - node-version: ${{ inputs.node-version }} - cache: pnpm - cache-dependency-path: ./web/pnpm-lock.yaml - - - name: Install dependencies - if: ${{ inputs.install-dependencies == 'true' }} - shell: bash - run: pnpm --dir web install --frozen-lockfile + node-version-file: "./web/.nvmrc" + cache: true + run-install: | + - cwd: ./web + args: ['--frozen-lockfile'] diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml index 2af3b130ad..80f892589d 100644 --- a/.github/workflows/autofix.yml +++ b/.github/workflows/autofix.yml @@ -102,13 +102,11 @@ jobs: - name: Setup web environment if: steps.web-changes.outputs.any_changed == 'true' uses: ./.github/actions/setup-web - with: - node-version: "24" - name: ESLint autofix if: steps.web-changes.outputs.any_changed == 'true' run: | cd web - pnpm eslint --concurrency=2 --prune-suppressions --quiet || true + vp exec eslint --concurrency=2 --prune-suppressions --quiet || true - uses: autofix-ci/action@7a166d7532b277f34e16238930461bf77f9d7ed8 # v1.3.3 diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml index b284694530..868bacc6e5 100644 --- a/.github/workflows/style.yml +++ b/.github/workflows/style.yml @@ -88,7 +88,7 @@ jobs: if: steps.changed-files.outputs.any_changed == 'true' working-directory: ./web run: | - pnpm run lint:ci + vp run lint:ci # pnpm run lint:report # continue-on-error: true @@ -102,17 +102,17 @@ jobs: - name: Web tsslint if: steps.changed-files.outputs.any_changed == 'true' working-directory: ./web - run: pnpm run lint:tss + run: vp run lint:tss - name: Web type check if: steps.changed-files.outputs.any_changed == 'true' working-directory: ./web - run: pnpm run type-check + run: vp run type-check - name: Web dead code check if: steps.changed-files.outputs.any_changed == 'true' working-directory: ./web - run: pnpm run knip + run: vp run knip superlinter: name: SuperLinter diff --git a/.github/workflows/translate-i18n-claude.yml b/.github/workflows/translate-i18n-claude.yml index ff07313ebe..62724c84e5 100644 --- a/.github/workflows/translate-i18n-claude.yml +++ b/.github/workflows/translate-i18n-claude.yml @@ -50,8 +50,6 @@ jobs: - name: Setup web environment uses: ./.github/actions/setup-web - with: - install-dependencies: "false" - name: Detect changed files and generate diff id: detect_changes diff --git a/.github/workflows/web-tests.yml b/.github/workflows/web-tests.yml index 47cced863a..fd2b941ce3 100644 --- a/.github/workflows/web-tests.yml +++ b/.github/workflows/web-tests.yml @@ -43,7 +43,7 @@ jobs: uses: ./.github/actions/setup-web - name: Run tests - run: pnpm vitest run --reporter=blob --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --coverage + run: vp test run --reporter=blob --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --coverage - name: Upload blob report if: ${{ !cancelled() }} @@ -84,7 +84,7 @@ jobs: merge-multiple: true - name: Merge reports - run: pnpm vitest --merge-reports --reporter=json --reporter=agent --coverage + run: vp test --merge-reports --reporter=json --reporter=agent --coverage - name: Check app/components diff coverage env: @@ -447,4 +447,4 @@ jobs: - name: Web build check if: steps.changed-files.outputs.any_changed == 'true' working-directory: ./web - run: pnpm run build + run: vp run build diff --git a/web/knip.config.ts b/web/knip.config.ts index fffb057b41..b7090ef8b1 100644 --- a/web/knip.config.ts +++ b/web/knip.config.ts @@ -20,6 +20,8 @@ const config: KnipConfig = { '@iconify-json/*', '@storybook/addon-onboarding', + + '@voidzero-dev/vite-plus-core', ], rules: { files: 'warn', diff --git a/web/package.json b/web/package.json index 40659c1085..e4115db287 100644 --- a/web/package.json +++ b/web/package.json @@ -50,9 +50,9 @@ "start:vinext": "vinext start", "storybook": "storybook dev -p 6006", "storybook:build": "storybook build", - "test": "vitest run", - "test:coverage": "vitest run --coverage", - "test:watch": "vitest --watch", + "test": "vp test", + "test:coverage": "vp test --coverage", + "test:watch": "vp test --watch", "type-check": "tsc --noEmit", "type-check:tsgo": "tsgo --noEmit", "uglify-embed": "node ./bin/uglify-embed" @@ -213,6 +213,7 @@ "@vitejs/plugin-react": "6.0.0", "@vitejs/plugin-rsc": "0.5.21", "@vitest/coverage-v8": "4.1.0", + "@voidzero-dev/vite-plus-core": "0.1.11", "agentation": "2.3.2", "autoprefixer": "10.4.27", "code-inspector-plugin": "1.4.4", @@ -241,9 +242,10 @@ "typescript": "5.9.3", "uglify-js": "3.19.3", "vinext": "https://pkg.pr.new/vinext@18fe3ea", - "vite": "8.0.0", + "vite": "npm:@voidzero-dev/vite-plus-core@0.1.11", "vite-plugin-inspect": "11.3.3", - "vitest": "4.1.0", + "vite-plus": "0.1.11", + "vitest": "npm:@voidzero-dev/vite-plus-test@0.1.11", "vitest-canvas-mock": "1.1.3" }, "pnpm": { @@ -293,6 +295,8 @@ "svgo@>=3.0.0,<3.3.3": "3.3.3", "tar@<=7.5.10": "7.5.11", "typed-array-buffer": "npm:@nolyfill/typed-array-buffer@^1.0.44", + "vite": "npm:@voidzero-dev/vite-plus-core@0.1.11", + "vitest": "npm:@voidzero-dev/vite-plus-test@0.1.11", "which-typed-array": "npm:@nolyfill/which-typed-array@^1.0.44" }, "ignoredBuiltDependencies": [ diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 2231d6c8ad..b6dbedb067 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -50,6 +50,8 @@ overrides: svgo@>=3.0.0,<3.3.3: 3.3.3 tar@<=7.5.10: 7.5.11 typed-array-buffer: npm:@nolyfill/typed-array-buffer@^1.0.44 + vite: npm:@voidzero-dev/vite-plus-core@0.1.11 + vitest: npm:@voidzero-dev/vite-plus-test@0.1.11 which-typed-array: npm:@nolyfill/which-typed-array@^1.0.44 importers: @@ -377,7 +379,7 @@ importers: devDependencies: '@antfu/eslint-config': specifier: 7.7.2 - version: 7.7.2(@eslint-react/eslint-plugin@2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@next/eslint-plugin-next@16.1.6)(@typescript-eslint/rule-tester@8.57.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@typescript-eslint/typescript-estree@8.57.0(typescript@5.9.3))(@typescript-eslint/utils@8.57.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@vue/compiler-sfc@3.5.30)(eslint-plugin-react-hooks@7.0.1(eslint@10.0.3(jiti@1.21.7)))(eslint-plugin-react-refresh@0.5.2(eslint@10.0.3(jiti@1.21.7)))(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3)(vitest@4.1.0(@types/node@25.5.0)(jsdom@28.1.0(canvas@3.2.1))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))) + version: 7.7.2(@eslint-react/eslint-plugin@2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@next/eslint-plugin-next@16.1.6)(@typescript-eslint/rule-tester@8.57.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@typescript-eslint/typescript-estree@8.57.0(typescript@5.9.3))(@typescript-eslint/utils@8.57.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@voidzero-dev/vite-plus-test@0.1.11(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(@vue/compiler-sfc@3.5.30)(eslint-plugin-react-hooks@7.0.1(eslint@10.0.3(jiti@1.21.7)))(eslint-plugin-react-refresh@0.5.2(eslint@10.0.3(jiti@1.21.7)))(eslint@10.0.3(jiti@1.21.7))(oxlint@1.55.0(oxlint-tsgolint@0.16.0))(typescript@5.9.3) '@chromatic-com/storybook': specifier: 5.0.1 version: 5.0.1(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) @@ -413,7 +415,7 @@ importers: version: 4.2.0 '@storybook/addon-docs': specifier: 10.2.17 - version: 10.2.17(@types/react@19.2.14)(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) + version: 10.2.17(@types/react@19.2.14)(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) '@storybook/addon-links': specifier: 10.2.17 version: 10.2.17(react@19.2.4)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) @@ -425,7 +427,7 @@ importers: version: 10.2.17(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) '@storybook/nextjs-vite': specifier: 10.2.17 - version: 10.2.17(@babel/core@7.29.0)(esbuild@0.27.2)(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) + version: 10.2.17(@babel/core@7.29.0)(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) '@storybook/react': specifier: 10.2.17 version: 10.2.17(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) @@ -509,13 +511,16 @@ importers: version: 7.0.0-dev.20260312.1 '@vitejs/plugin-react': specifier: 6.0.0 - version: 6.0.0(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + version: 6.0.0(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) '@vitejs/plugin-rsc': specifier: 0.5.21 - version: 0.5.21(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4) '@vitest/coverage-v8': specifier: 4.1.0 - version: 4.1.0(vitest@4.1.0(@types/node@25.5.0)(jsdom@28.1.0(canvas@3.2.1))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))) + version: 4.1.0(@voidzero-dev/vite-plus-test@0.1.11(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) + '@voidzero-dev/vite-plus-core': + specifier: 0.1.11 + version: 0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) agentation: specifier: 2.3.2 version: 2.3.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -530,7 +535,7 @@ importers: version: 10.0.3(jiti@1.21.7) eslint-plugin-better-tailwindcss: specifier: 4.3.2 - version: 4.3.2(eslint@10.0.3(jiti@1.21.7))(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.2))(typescript@5.9.3) + version: 4.3.2(eslint@10.0.3(jiti@1.21.7))(oxlint@1.55.0(oxlint-tsgolint@0.16.0))(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.2))(typescript@5.9.3) eslint-plugin-hyoban: specifier: 0.14.1 version: 0.14.1(eslint@10.0.3(jiti@1.21.7)) @@ -599,19 +604,22 @@ importers: version: 3.19.3 vinext: specifier: https://pkg.pr.new/vinext@18fe3ea - version: https://pkg.pr.new/vinext@18fe3ea(@mdx-js/rollup@3.1.1(rollup@4.59.0))(@vitejs/plugin-rsc@0.5.21(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)))(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4)(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + version: https://pkg.pr.new/vinext@18fe3ea(@mdx-js/rollup@3.1.1(rollup@4.59.0))(@vitejs/plugin-rsc@0.5.21(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4))(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4)(typescript@5.9.3) vite: - specifier: 8.0.0 - version: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + specifier: npm:@voidzero-dev/vite-plus-core@0.1.11 + version: '@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' vite-plugin-inspect: specifier: 11.3.3 - version: 11.3.3(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + version: 11.3.3(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) + vite-plus: + specifier: 0.1.11 + version: 0.1.11(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) vitest: - specifier: 4.1.0 - version: 4.1.0(@types/node@25.5.0)(jsdom@28.1.0(canvas@3.2.1))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + specifier: npm:@voidzero-dev/vite-plus-test@0.1.11 + version: '@voidzero-dev/vite-plus-test@0.1.11(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' vitest-canvas-mock: specifier: 1.1.3 - version: 1.1.3(vitest@4.1.0(@types/node@25.5.0)(jsdom@28.1.0(canvas@3.2.1))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))) + version: 1.1.3(@voidzero-dev/vite-plus-test@0.1.11(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) packages: @@ -1977,6 +1985,280 @@ packages: cpu: [x64] os: [win32] + '@oxfmt/binding-android-arm-eabi@0.40.0': + resolution: {integrity: sha512-S6zd5r1w/HmqR8t0CTnGjFTBLDq2QKORPwriCHxo4xFNuhmOTABGjPaNvCJJVnrKBLsohOeiDX3YqQfJPF+FXw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxfmt/binding-android-arm64@0.40.0': + resolution: {integrity: sha512-/mbS9UUP/5Vbl2D6osIdcYiP0oie63LKMoTyGj5hyMCK/SFkl3EhtyRAfdjPvuvHC0SXdW6ePaTKkBSq1SNcIw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxfmt/binding-darwin-arm64@0.40.0': + resolution: {integrity: sha512-wRt8fRdfLiEhnRMBonlIbKrJWixoEmn6KCjKE9PElnrSDSXETGZfPb8ee+nQNTobXkCVvVLytp2o0obAsxl78Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxfmt/binding-darwin-x64@0.40.0': + resolution: {integrity: sha512-fzowhqbOE/NRy+AE5ob0+Y4X243WbWzDb00W+pKwD7d9tOqsAFbtWUwIyqqCoCLxj791m2xXIEeLH/3uz7zCCg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxfmt/binding-freebsd-x64@0.40.0': + resolution: {integrity: sha512-agZ9ITaqdBjcerRRFEHB8s0OyVcQW8F9ZxsszjxzeSthQ4fcN2MuOtQFWec1ed8/lDa50jSLHVE2/xPmTgtCfQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxfmt/binding-linux-arm-gnueabihf@0.40.0': + resolution: {integrity: sha512-ZM2oQ47p28TP1DVIp7HL1QoMUgqlBFHey0ksHct7tMXoU5BqjNvPWw7888azzMt25lnyPODVuye1wvNbvVUFOA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxfmt/binding-linux-arm-musleabihf@0.40.0': + resolution: {integrity: sha512-RBFPAxRAIsMisKM47Oe6Lwdv6agZYLz02CUhVCD1sOv5ajAcRMrnwCFBPWwGXpazToW2mjnZxFos8TuFjTU15A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxfmt/binding-linux-arm64-gnu@0.40.0': + resolution: {integrity: sha512-Nb2XbQ+wV3W2jSIihXdPj7k83eOxeSgYP3N/SRXvQ6ZYPIk6Q86qEh5Gl/7OitX3bQoQrESqm1yMLvZV8/J7dA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-arm64-musl@0.40.0': + resolution: {integrity: sha512-tGmWhLD/0YMotCdfezlT6tC/MJG/wKpo4vnQ3Cq+4eBk/BwNv7EmkD0VkD5F/dYkT3b8FNU01X2e8vvJuWoM1w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-linux-ppc64-gnu@0.40.0': + resolution: {integrity: sha512-rVbFyM3e7YhkVnp0IVYjaSHfrBWcTRWb60LEcdNAJcE2mbhTpbqKufx0FrhWfoxOrW/+7UJonAOShoFFLigDqQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-riscv64-gnu@0.40.0': + resolution: {integrity: sha512-3ZqBw14JtWeEoLiioJcXSJz8RQyPE+3jLARnYM1HdPzZG4vk+Ua8CUupt2+d+vSAvMyaQBTN2dZK+kbBS/j5mA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-riscv64-musl@0.40.0': + resolution: {integrity: sha512-JJ4PPSdcbGBjPvb+O7xYm2FmAsKCyuEMYhqatBAHMp/6TA6rVlf9Z/sYPa4/3Bommb+8nndm15SPFRHEPU5qFA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-linux-s390x-gnu@0.40.0': + resolution: {integrity: sha512-Kp0zNJoX9Ik77wUya2tpBY3W9f40VUoMQLWVaob5SgCrblH/t2xr/9B2bWHfs0WCefuGmqXcB+t0Lq77sbBmZw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-x64-gnu@0.40.0': + resolution: {integrity: sha512-7YTCNzleWTaQTqNGUNQ66qVjpoV6DjbCOea+RnpMBly2bpzrI/uu7Rr+2zcgRfNxyjXaFTVQKaRKjqVdeUfeVA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-x64-musl@0.40.0': + resolution: {integrity: sha512-hWnSzJ0oegeOwfOEeejYXfBqmnRGHusgtHfCPzmvJvHTwy1s3Neo59UKc1CmpE3zxvrCzJoVHos0rr97GHMNPw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-openharmony-arm64@0.40.0': + resolution: {integrity: sha512-28sJC1lR4qtBJGzSRRbPnSW3GxU2+4YyQFE6rCmsUYqZ5XYH8jg0/w+CvEzQ8TuAQz5zLkcA25nFQGwoU0PT3Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxfmt/binding-win32-arm64-msvc@0.40.0': + resolution: {integrity: sha512-cDkRnyT0dqwF5oIX1Cv59HKCeZQFbWWdUpXa3uvnHFT2iwYSSZspkhgjXjU6iDp5pFPaAEAe9FIbMoTgkTmKPg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxfmt/binding-win32-ia32-msvc@0.40.0': + resolution: {integrity: sha512-7rPemBJjqm5Gkv6ZRCPvK8lE6AqQ/2z31DRdWazyx2ZvaSgL7QGofHXHNouRpPvNsT9yxRNQJgigsWkc+0qg4w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxfmt/binding-win32-x64-msvc@0.40.0': + resolution: {integrity: sha512-/Zmj0yTYSvmha6TG1QnoLqVT7ZMRDqXvFXXBQpIjteEwx9qvUYMBH2xbiOFhDeMUJkGwC3D6fdKsFtaqUvkwNA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@oxlint-tsgolint/darwin-arm64@0.16.0': + resolution: {integrity: sha512-WQt5lGwRPJBw7q2KNR0mSPDAaMmZmVvDlEEti96xLO7ONhyomQc6fBZxxwZ4qTFedjJnrHX94sFelZ4OKzS7UQ==} + cpu: [arm64] + os: [darwin] + + '@oxlint-tsgolint/darwin-x64@0.16.0': + resolution: {integrity: sha512-VJo29XOzdkalvCTiE2v6FU3qZlgHaM8x8hUEVJGPU2i5W+FlocPpmn00+Ld2n7Q0pqIjyD5EyvZ5UmoIEJMfqg==} + cpu: [x64] + os: [darwin] + + '@oxlint-tsgolint/linux-arm64@0.16.0': + resolution: {integrity: sha512-MPfqRt1+XRHv9oHomcBMQ3KpTE+CSkZz14wUxDQoqTNdUlV0HWdzwIE9q65I3D9YyxEnqpM7j4qtDQ3apqVvbQ==} + cpu: [arm64] + os: [linux] + + '@oxlint-tsgolint/linux-x64@0.16.0': + resolution: {integrity: sha512-XQSwVUsnwLokMhe1TD6IjgvW5WMTPzOGGkdFDtXWQmlN2YeTw94s/NN0KgDrn2agM1WIgAenEkvnm0u7NgwEyw==} + cpu: [x64] + os: [linux] + + '@oxlint-tsgolint/win32-arm64@0.16.0': + resolution: {integrity: sha512-EWdlspQiiFGsP2AiCYdhg5dTYyAlj6y1nRyNI2dQWq4Q/LITFHiSRVPe+7m7K7lcsZCEz2icN/bCeSkZaORqIg==} + cpu: [arm64] + os: [win32] + + '@oxlint-tsgolint/win32-x64@0.16.0': + resolution: {integrity: sha512-1ufk8cgktXJuJZHKF63zCHAkaLMwZrEXnZ89H2y6NO85PtOXqu4zbdNl0VBpPP3fCUuUBu9RvNqMFiv0VsbXWA==} + cpu: [x64] + os: [win32] + + '@oxlint/binding-android-arm-eabi@1.55.0': + resolution: {integrity: sha512-NhvgAhncTSOhRahQSCnkK/4YIGPjTmhPurQQ2dwt2IvwCMTvZRW5vF2K10UBOxFve4GZDMw6LtXZdC2qeuYIVQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxlint/binding-android-arm64@1.55.0': + resolution: {integrity: sha512-P9iWRh+Ugqhg+D7rkc7boHX8o3H2h7YPcZHQIgvVBgnua5tk4LR2L+IBlreZs58/95cd2x3/004p5VsQM9z4SA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxlint/binding-darwin-arm64@1.55.0': + resolution: {integrity: sha512-esakkJIt7WFAhT30P/Qzn96ehFpzdZ1mNuzpOb8SCW7lI4oB8VsyQnkSHREM671jfpuBb/o2ppzBCx5l0jpgMA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxlint/binding-darwin-x64@1.55.0': + resolution: {integrity: sha512-xDMFRCCAEK9fOH6As2z8ELsC+VDGSFRHwIKVSilw+xhgLwTDFu37rtmRbmUlx8rRGS6cWKQPTc47AVxAZEVVPQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxlint/binding-freebsd-x64@1.55.0': + resolution: {integrity: sha512-mYZqnwUD7ALCRxGenyLd1uuG+rHCL+OTT6S8FcAbVm/ZT2AZMGjvibp3F6k1SKOb2aeqFATmwRykrE41Q0GWVw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxlint/binding-linux-arm-gnueabihf@1.55.0': + resolution: {integrity: sha512-LcX6RYcF9vL9ESGwJW3yyIZ/d/ouzdOKXxCdey1q0XJOW1asrHsIg5MmyKdEBR4plQx+shvYeQne7AzW5f3T1w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm-musleabihf@1.55.0': + resolution: {integrity: sha512-C+8GS1rPtK+dI7mJFkqoRBkDuqbrNihnyYQsJPS9ez+8zF9JzfvU19lawqt4l/Y23o5uQswE/DORa8aiXUih3w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm64-gnu@1.55.0': + resolution: {integrity: sha512-ErLE4XbmcCopA4/CIDiH6J1IAaDOMnf/KSx/aFObs4/OjAAM3sFKWGZ57pNOMxhhyBdcmcXwYymph9GwcpcqgQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-arm64-musl@1.55.0': + resolution: {integrity: sha512-/kp65avi6zZfqEng56TTuhiy3P/3pgklKIdf38yvYeJ9/PgEeRA2A2AqKAKbZBNAqUzrzHhz9jF6j/PZvhJzTQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxlint/binding-linux-ppc64-gnu@1.55.0': + resolution: {integrity: sha512-A6pTdXwcEEwL/nmz0eUJ6WxmxcoIS+97GbH96gikAyre3s5deC7sts38ZVVowjS2QQFuSWkpA4ZmQC0jZSNvJQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-riscv64-gnu@1.55.0': + resolution: {integrity: sha512-clj0lnIN+V52G9tdtZl0LbdTSurnZ1NZj92Je5X4lC7gP5jiCSW+Y/oiDiSauBAD4wrHt2S7nN3pA0zfKYK/6Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-riscv64-musl@1.55.0': + resolution: {integrity: sha512-NNu08pllN5x/O94/sgR3DA8lbrGBnTHsINZZR0hcav1sj79ksTiKKm1mRzvZvacwQ0hUnGinFo+JO75ok2PxYg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxlint/binding-linux-s390x-gnu@1.55.0': + resolution: {integrity: sha512-BvfQz3PRlWZRoEZ17dZCqgQsMRdpzGZomJkVATwCIGhHVVeHJMQdmdXPSjcT1DCNUrOjXnVyj1RGDj5+/Je2+Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-x64-gnu@1.55.0': + resolution: {integrity: sha512-ngSOoFCSBMKVQd24H8zkbcBNc7EHhjnF1sv3mC9NNXQ/4rRjI/4Dj9+9XoDZeFEkF1SX1COSBXF1b2Pr9rqdEw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-x64-musl@1.55.0': + resolution: {integrity: sha512-BDpP7W8GlaG7BR6QjGZAleYzxoyKc/D24spZIF2mB3XsfALQJJT/OBmP8YpeTb1rveFSBHzl8T7l0aqwkWNdGA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxlint/binding-openharmony-arm64@1.55.0': + resolution: {integrity: sha512-PS6GFvmde/pc3fCA2Srt51glr8Lcxhpf6WIBFfLphndjRrD34NEcses4TSxQrEcxYo6qVywGfylM0ZhSCF2gGA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxlint/binding-win32-arm64-msvc@1.55.0': + resolution: {integrity: sha512-P6JcLJGs/q1UOvDLzN8otd9JsH4tsuuPDv+p7aHqHM3PrKmYdmUvkNj4K327PTd35AYcznOCN+l4ZOaq76QzSw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxlint/binding-win32-ia32-msvc@1.55.0': + resolution: {integrity: sha512-gzkk4zE2zsE+WmRxFOiAZHpCpUNDFytEakqNXoNHW+PnYEOTPKDdW6nrzgSeTbGKVPXNAKQnRnMgrh7+n3Xueg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxlint/binding-win32-x64-msvc@1.55.0': + resolution: {integrity: sha512-ZFALNow2/og75gvYzNP7qe+rREQ5xunktwA+lgykoozHZ6hw9bqg4fn5j2UvG4gIn1FXqrZHkOAXuPf5+GOYTQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@parcel/watcher-android-arm64@2.5.6': resolution: {integrity: sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==} engines: {node: '>= 10.0.0'} @@ -2357,101 +2639,6 @@ packages: resolution: {integrity: sha512-UuBOt7BOsKVOkFXRe4Ypd/lADuNIfqJXv8GvHqtXaTYXPPKkj2nS2zPllVsrtRjcomDhIJVBnZwfmlI222WH8g==} engines: {node: '>=14.0.0'} - '@rolldown/binding-android-arm64@1.0.0-rc.9': - resolution: {integrity: sha512-lcJL0bN5hpgJfSIz/8PIf02irmyL43P+j1pTCfbD1DbLkmGRuFIA4DD3B3ZOvGqG0XiVvRznbKtN0COQVaKUTg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [android] - - '@rolldown/binding-darwin-arm64@1.0.0-rc.9': - resolution: {integrity: sha512-J7Zk3kLYFsLtuH6U+F4pS2sYVzac0qkjcO5QxHS7OS7yZu2LRs+IXo+uvJ/mvpyUljDJ3LROZPoQfgBIpCMhdQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [darwin] - - '@rolldown/binding-darwin-x64@1.0.0-rc.9': - resolution: {integrity: sha512-iwtmmghy8nhfRGeNAIltcNXzD0QMNaaA5U/NyZc1Ia4bxrzFByNMDoppoC+hl7cDiUq5/1CnFthpT9n+UtfFyg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [darwin] - - '@rolldown/binding-freebsd-x64@1.0.0-rc.9': - resolution: {integrity: sha512-DLFYI78SCiZr5VvdEplsVC2Vx53lnA4/Ga5C65iyldMVaErr86aiqCoNBLl92PXPfDtUYjUh+xFFor40ueNs4Q==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [freebsd] - - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.9': - resolution: {integrity: sha512-CsjTmTwd0Hri6iTw/DRMK7kOZ7FwAkrO4h8YWKoX/kcj833e4coqo2wzIFywtch/8Eb5enQ/lwLM7w6JX1W5RQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] - - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.9': - resolution: {integrity: sha512-2x9O2JbSPxpxMDhP9Z74mahAStibTlrBMW0520+epJH5sac7/LwZW5Bmg/E6CXuEF53JJFW509uP+lSedaUNxg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.9': - resolution: {integrity: sha512-JA1QRW31ogheAIRhIg9tjMfsYbglXXYGNPLdPEYrwFxdbkQCAzvpSCSHCDWNl4hTtrol8WeboCSEpjdZK8qrCg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.9': - resolution: {integrity: sha512-aOKU9dJheda8Kj8Y3w9gnt9QFOO+qKPAl8SWd7JPHP+Cu0EuDAE5wokQubLzIDQWg2myXq2XhTpOVS07qqvT+w==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.9': - resolution: {integrity: sha512-OalO94fqj7IWRn3VdXWty75jC5dk4C197AWEuMhIpvVv2lw9fiPhud0+bW2ctCxb3YoBZor71QHbY+9/WToadA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.9': - resolution: {integrity: sha512-cVEl1vZtBsBZna3YMjGXNvnYYrOJ7RzuWvZU0ffvJUexWkukMaDuGhUXn0rjnV0ptzGVkvc+vW9Yqy6h8YX4pg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-x64-musl@1.0.0-rc.9': - resolution: {integrity: sha512-UzYnKCIIc4heAKgI4PZ3dfBGUZefGCJ1TPDuLHoCzgrMYPb5Rv6TLFuYtyM4rWyHM7hymNdsg5ik2C+UD9VDbA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [musl] - - '@rolldown/binding-openharmony-arm64@1.0.0-rc.9': - resolution: {integrity: sha512-+6zoiF+RRyf5cdlFQP7nm58mq7+/2PFaY2DNQeD4B87N36JzfF/l9mdBkkmTvSYcYPE8tMh/o3cRlsx1ldLfog==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [openharmony] - - '@rolldown/binding-wasm32-wasi@1.0.0-rc.9': - resolution: {integrity: sha512-rgFN6sA/dyebil3YTlL2evvi/M+ivhfnyxec7AccTpRPccno/rPoNlqybEZQBkcbZu8Hy+eqNJCqfBR8P7Pg8g==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] - - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.9': - resolution: {integrity: sha512-lHVNUG/8nlF1IQk1C0Ci574qKYyty2goMiPlRqkC5R+3LkXDkL5Dhx8ytbxq35m+pkHVIvIxviD+TWLdfeuadA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [win32] - - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.9': - resolution: {integrity: sha512-G0oA4+w1iY5AGi5HcDTxWsoxF509hrFIPB2rduV5aDqS9FtDg1CAfa7V34qImbjfhIcA8C+RekocJZA96EarwQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [win32] - '@rolldown/pluginutils@1.0.0-rc.3': resolution: {integrity: sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==} @@ -2461,9 +2648,6 @@ packages: '@rolldown/pluginutils@1.0.0-rc.7': resolution: {integrity: sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==} - '@rolldown/pluginutils@1.0.0-rc.9': - resolution: {integrity: sha512-w6oiRWgEBl04QkFZgmW+jnU1EC9b57Oihi2ot3HNWIQRqgHp5PnYDia5iZ5FF7rpa4EQdiqMDXjlqKGXBhsoXw==} - '@rollup/plugin-replace@6.0.3': resolution: {integrity: sha512-J4RZarRvQAm5IF0/LwUUg+obsm+xZhYnbMXmXROyoSE1ATJe3oXSb9L5MMppdxP2ylNSjv6zFBwKYjcKMucVfA==} engines: {node: '>=14.0.0'} @@ -3448,44 +3632,144 @@ packages: '@vitest/expect@3.2.4': resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} - '@vitest/expect@4.1.0': - resolution: {integrity: sha512-EIxG7k4wlWweuCLG9Y5InKFwpMEOyrMb6ZJ1ihYu02LVj/bzUwn2VMU+13PinsjRW75XnITeFrQBMH5+dLvCDA==} - - '@vitest/mocker@4.1.0': - resolution: {integrity: sha512-evxREh+Hork43+Y4IOhTo+h5lGmVRyjqI739Rz4RlUPqwrkFFDF6EMvOOYjTx4E8Tl6gyCLRL8Mu7Ry12a13Tw==} - peerDependencies: - msw: ^2.4.9 - vite: ^6.0.0 || ^7.0.0 || ^8.0.0-0 - peerDependenciesMeta: - msw: - optional: true - vite: - optional: true - '@vitest/pretty-format@3.2.4': resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} '@vitest/pretty-format@4.1.0': resolution: {integrity: sha512-3RZLZlh88Ib0J7NQTRATfc/3ZPOnSUn2uDBUoGNn5T36+bALixmzphN26OUD3LRXWkJu4H0s5vvUeqBiw+kS0A==} - '@vitest/runner@4.1.0': - resolution: {integrity: sha512-Duvx2OzQ7d6OjchL+trw+aSrb9idh7pnNfxrklo14p3zmNL4qPCDeIJAK+eBKYjkIwG96Bc6vYuxhqDXQOWpoQ==} - - '@vitest/snapshot@4.1.0': - resolution: {integrity: sha512-0Vy9euT1kgsnj1CHttwi9i9o+4rRLEaPRSOJ5gyv579GJkNpgJK+B4HSv/rAWixx2wdAFci1X4CEPjiu2bXIMg==} - '@vitest/spy@3.2.4': resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} - '@vitest/spy@4.1.0': - resolution: {integrity: sha512-pz77k+PgNpyMDv2FV6qmk5ZVau6c3R8HC8v342T2xlFxQKTrSeYw9waIJG8KgV9fFwAtTu4ceRzMivPTH6wSxw==} - '@vitest/utils@3.2.4': resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} '@vitest/utils@4.1.0': resolution: {integrity: sha512-XfPXT6a8TZY3dcGY8EdwsBulFCIw+BeeX0RZn2x/BtiY/75YGh8FeWGG8QISN/WhaqSrE2OrlDgtF8q5uhOTmw==} + '@voidzero-dev/vite-plus-core@0.1.11': + resolution: {integrity: sha512-feyYRSg3u8acYNC1fF4EGfgYZm2efZB8YWTjz4NrU0Ulhlni1C6COMwHSDVpu9F4Jh+WcSsBWL3ZC1WvLa7jCw==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + '@arethetypeswrong/core': ^0.18.1 + '@tsdown/css': 0.21.2 + '@tsdown/exe': 0.21.2 + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.0.0-alpha.31 + esbuild: 0.27.2 + jiti: '>=1.21.0' + less: ^4.0.0 + publint: ^0.3.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + typescript: ^5.0.0 + unplugin-unused: ^0.5.0 + yaml: ^2.4.2 + peerDependenciesMeta: + '@arethetypeswrong/core': + optional: true + '@tsdown/css': + optional: true + '@tsdown/exe': + optional: true + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + publint: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + typescript: + optional: true + unplugin-unused: + optional: true + yaml: + optional: true + + '@voidzero-dev/vite-plus-darwin-arm64@0.1.11': + resolution: {integrity: sha512-ENokEkMhDMJ9nM/tUDAXvtah/P3cAnEbkeKCCxJgFvTTGnGM8eBvP2qpJeTrfhy9ndIWihcsfMufszinLsfhUg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@voidzero-dev/vite-plus-darwin-x64@0.1.11': + resolution: {integrity: sha512-gOSGYtXq5qigDsiW+oCrefv4K8WUSnZ5vH+kPHDvpsMXlqxR0rY6xrJgkJ2tCkWdCig8YHVDascSV/cj4nGwsw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@voidzero-dev/vite-plus-linux-arm64-gnu@0.1.11': + resolution: {integrity: sha512-aDVe1vvhtXBqZdmCiCSm3DUl5/O+x5CeAcjPPTLSsEX79cSfvkD0UU26lQ8eX+pr3xVDEocJTtTLmOMVImGlyA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@voidzero-dev/vite-plus-linux-x64-gnu@0.1.11': + resolution: {integrity: sha512-rkaKCGq/CFML2M7c0ixUOuhE6qi961x84/ZFQhkUy2MJw3RP7R/M1BDyWr2qEq20SgRWLkffcWMni3P2JnmrBw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@voidzero-dev/vite-plus-test@0.1.11': + resolution: {integrity: sha512-3kBfi/LyPOGnLCmvYtgM5GZVAyiJiYjgdm9Fu9WLLl56zcSljj0TBG19eaKY6v/j2VJ+7o80n/A/MPz46lzMFA==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/ui': 4.1.0 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0-0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + '@voidzero-dev/vite-plus-win32-arm64-msvc@0.1.11': + resolution: {integrity: sha512-MerozzH8QYY+V5l6ZQq+vrtx75rnPlmc+TauH5hL08oEWx7ScwfrNKyamnv5rg7HWBx/ryuaYaJCjODOu7MjSg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@voidzero-dev/vite-plus-win32-x64-msvc@0.1.11': + resolution: {integrity: sha512-ubGlfvkfWT4Eivg3O2lxMyA6h7u1XZm4XdW3MUZIXXd9Q/iIRVJdSsEg78C/OZ3e8Qofszsro6P8ZrQo8ROQxg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@volar/language-core@2.4.28': resolution: {integrity: sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==} @@ -3803,6 +4087,10 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + cac@7.0.0: resolution: {integrity: sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==} engines: {node: '>=20.19.0'} @@ -3832,10 +4120,6 @@ packages: resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} engines: {node: '>=18'} - chai@6.2.2: - resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} - engines: {node: '>=18'} - chalk@4.1.1: resolution: {integrity: sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==} engines: {node: '>=10'} @@ -4817,10 +5101,6 @@ packages: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} - expect-type@1.3.0: - resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} - engines: {node: '>=12.0.0'} - exsolve@1.0.8: resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} @@ -6077,6 +6357,25 @@ packages: oxc-resolver@11.19.1: resolution: {integrity: sha512-qE/CIg/spwrTBFt5aKmwe3ifeDdLfA2NESN30E42X/lII5ClF8V7Wt6WIJhcGZjp0/Q+nQ+9vgxGk//xZNX2hg==} + oxfmt@0.40.0: + resolution: {integrity: sha512-g0C3I7xUj4b4DcagevM9kgH6+pUHytikxUcn3/VUkvzTNaaXBeyZqb7IBsHwojeXm4mTBEC/aBjBTMVUkZwWUQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + oxlint-tsgolint@0.16.0: + resolution: {integrity: sha512-4RuJK2jP08XwqtUu+5yhCbxEauCm6tv2MFHKEMsjbosK2+vy5us82oI3VLuHwbNyZG7ekZA26U2LLHnGR4frIA==} + hasBin: true + + oxlint@1.55.0: + resolution: {integrity: sha512-T+FjepiyWpaZMhekqRpH8Z3I4vNM610p6w+Vjfqgj5TZUxHXl7N8N5IPvmOU8U4XdTRxqtNNTh9Y4hLtr7yvFg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + oxlint-tsgolint: '>=0.15.0' + peerDependenciesMeta: + oxlint-tsgolint: + optional: true + p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} @@ -6196,6 +6495,10 @@ packages: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} + pixelmatch@7.1.0: + resolution: {integrity: sha512-1wrVzJ2STrpmONHKBy228LM1b84msXDUoAzVEl0R8Mz4Ce6EPr+IVtxm8+yvrqLYMHswREkjYFaMxnyGnaY3Ng==} + hasBin: true + pkg-types@1.3.1: resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} @@ -6206,6 +6509,10 @@ packages: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} + pngjs@7.0.0: + resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==} + engines: {node: '>=14.19.0'} + pnpm-workspace-yaml@1.6.0: resolution: {integrity: sha512-uUy4dK3E11sp7nK+hnT7uAWfkBMe00KaUw8OG3NuNlYQoTk4sc9pcdIy1+XIP85v9Tvr02mK3JPaNNrP0QyRaw==} @@ -6664,11 +6971,6 @@ packages: robust-predicates@3.0.2: resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} - rolldown@1.0.0-rc.9: - resolution: {integrity: sha512-9EbgWge7ZH+yqb4d2EnELAntgPTWbfL8ajiTW+SyhJEC4qhBbkCKbqFV4Ge4zmu5ziQuVbWxb/XwLZ+RIO7E8Q==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - rollup@4.59.0: resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -6762,9 +7064,6 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} @@ -6838,9 +7137,6 @@ packages: engines: {node: '>=20.16.0'} hasBin: true - stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - std-env@4.0.0: resolution: {integrity: sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==} @@ -7053,6 +7349,10 @@ packages: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} + tinypool@2.1.0: + resolution: {integrity: sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==} + engines: {node: ^20.0.0 || >=22.0.0} + tinyrainbow@2.0.0: resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} engines: {node: '>=14.0.0'} @@ -7404,6 +7704,11 @@ packages: storybook: ^0.0.0-0 || ^9.0.0 || ^10.0.0 || ^10.0.0-0 || ^10.1.0-0 || ^10.2.0-0 || ^10.3.0-0 vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + vite-plus@0.1.11: + resolution: {integrity: sha512-mDUbWirSUWtS/diQiq1QkHGsMNQWu90kSH5s7RWqVnV9s1PRxQ1IcH6mIeOG7YzPJlfO1vQbONZRsOfdyj9IKw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + vite-tsconfig-paths@5.1.4: resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==} peerDependencies: @@ -7417,49 +7722,6 @@ packages: peerDependencies: vite: '*' - vite@8.0.0: - resolution: {integrity: sha512-fPGaRNj9Zytaf8LEiBhY7Z6ijnFKdzU/+mL8EFBaKr7Vw1/FWcTBAMW0wLPJAGMPX38ZPVCVgLceWiEqeoqL2Q==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.0.0-alpha.31 - esbuild: 0.27.2 - jiti: '>=1.21.0' - less: ^4.0.0 - sass: ^1.70.0 - sass-embedded: ^1.70.0 - stylus: '>=0.54.8' - sugarss: ^5.0.0 - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - '@types/node': - optional: true - '@vitejs/devtools': - optional: true - esbuild: - optional: true - jiti: - optional: true - less: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - vitefu@1.1.2: resolution: {integrity: sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw==} peerDependencies: @@ -7473,41 +7735,6 @@ packages: peerDependencies: vitest: ^3.0.0 || ^4.0.0 - vitest@4.1.0: - resolution: {integrity: sha512-YbDrMF9jM2Lqc++2530UourxZHmkKLxrs4+mYhEwqWS97WJ7wOYEkcr+QfRgJ3PW9wz3odRijLZjHEaRLTNbqw==} - engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@opentelemetry/api': ^1.9.0 - '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.1.0 - '@vitest/browser-preview': 4.1.0 - '@vitest/browser-webdriverio': 4.1.0 - '@vitest/ui': 4.1.0 - happy-dom: '*' - jsdom: '*' - vite: ^6.0.0 || ^7.0.0 || ^8.0.0-0 - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@opentelemetry/api': - optional: true - '@types/node': - optional: true - '@vitest/browser-playwright': - optional: true - '@vitest/browser-preview': - optional: true - '@vitest/browser-webdriverio': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true - void-elements@3.1.0: resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} engines: {node: '>=0.10.0'} @@ -7599,11 +7826,6 @@ packages: engines: {node: '>= 8'} hasBin: true - why-is-node-running@2.3.0: - resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} - engines: {node: '>=8'} - hasBin: true - word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -7881,17 +8103,17 @@ snapshots: idb: 8.0.0 tslib: 2.8.1 - '@antfu/eslint-config@7.7.2(@eslint-react/eslint-plugin@2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@next/eslint-plugin-next@16.1.6)(@typescript-eslint/rule-tester@8.57.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@typescript-eslint/typescript-estree@8.57.0(typescript@5.9.3))(@typescript-eslint/utils@8.57.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@vue/compiler-sfc@3.5.30)(eslint-plugin-react-hooks@7.0.1(eslint@10.0.3(jiti@1.21.7)))(eslint-plugin-react-refresh@0.5.2(eslint@10.0.3(jiti@1.21.7)))(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3)(vitest@4.1.0(@types/node@25.5.0)(jsdom@28.1.0(canvas@3.2.1))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)))': + '@antfu/eslint-config@7.7.2(@eslint-react/eslint-plugin@2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@next/eslint-plugin-next@16.1.6)(@typescript-eslint/rule-tester@8.57.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@typescript-eslint/typescript-estree@8.57.0(typescript@5.9.3))(@typescript-eslint/utils@8.57.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@voidzero-dev/vite-plus-test@0.1.11(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(@vue/compiler-sfc@3.5.30)(eslint-plugin-react-hooks@7.0.1(eslint@10.0.3(jiti@1.21.7)))(eslint-plugin-react-refresh@0.5.2(eslint@10.0.3(jiti@1.21.7)))(eslint@10.0.3(jiti@1.21.7))(oxlint@1.55.0(oxlint-tsgolint@0.16.0))(typescript@5.9.3)': dependencies: '@antfu/install-pkg': 1.1.0 '@clack/prompts': 1.1.0 - '@e18e/eslint-plugin': 0.2.0(eslint@10.0.3(jiti@1.21.7)) + '@e18e/eslint-plugin': 0.2.0(eslint@10.0.3(jiti@1.21.7))(oxlint@1.55.0(oxlint-tsgolint@0.16.0)) '@eslint-community/eslint-plugin-eslint-comments': 4.7.1(eslint@10.0.3(jiti@1.21.7)) '@eslint/markdown': 7.5.1 '@stylistic/eslint-plugin': 5.10.0(eslint@10.0.3(jiti@1.21.7)) '@typescript-eslint/eslint-plugin': 8.57.0(@typescript-eslint/parser@8.57.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) '@typescript-eslint/parser': 8.57.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - '@vitest/eslint-plugin': 1.6.10(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3)(vitest@4.1.0(@types/node@25.5.0)(jsdom@28.1.0(canvas@3.2.1))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))) + '@vitest/eslint-plugin': 1.6.10(@voidzero-dev/vite-plus-test@0.1.11(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) ansis: 4.2.0 cac: 7.0.0 eslint: 10.0.3(jiti@1.21.7) @@ -8228,11 +8450,12 @@ snapshots: '@csstools/css-tokenizer@4.0.0': {} - '@e18e/eslint-plugin@0.2.0(eslint@10.0.3(jiti@1.21.7))': + '@e18e/eslint-plugin@0.2.0(eslint@10.0.3(jiti@1.21.7))(oxlint@1.55.0(oxlint-tsgolint@0.16.0))': dependencies: eslint-plugin-depend: 1.5.0(eslint@10.0.3(jiti@1.21.7)) optionalDependencies: eslint: 10.0.3(jiti@1.21.7) + oxlint: 1.55.0(oxlint-tsgolint@0.16.0) '@egoist/tailwindcss-icons@1.9.2(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.2))': dependencies: @@ -8753,11 +8976,11 @@ snapshots: dependencies: minipass: 7.1.3 - '@joshwooding/vite-plugin-react-docgen-typescript@0.6.4(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': + '@joshwooding/vite-plugin-react-docgen-typescript@0.6.4(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(typescript@5.9.3)': dependencies: glob: 13.0.6 react-docgen-typescript: 2.4.0(typescript@5.9.3) - vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: '@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' optionalDependencies: typescript: 5.9.3 @@ -9251,6 +9474,138 @@ snapshots: '@oxc-resolver/binding-win32-x64-msvc@11.19.1': optional: true + '@oxfmt/binding-android-arm-eabi@0.40.0': + optional: true + + '@oxfmt/binding-android-arm64@0.40.0': + optional: true + + '@oxfmt/binding-darwin-arm64@0.40.0': + optional: true + + '@oxfmt/binding-darwin-x64@0.40.0': + optional: true + + '@oxfmt/binding-freebsd-x64@0.40.0': + optional: true + + '@oxfmt/binding-linux-arm-gnueabihf@0.40.0': + optional: true + + '@oxfmt/binding-linux-arm-musleabihf@0.40.0': + optional: true + + '@oxfmt/binding-linux-arm64-gnu@0.40.0': + optional: true + + '@oxfmt/binding-linux-arm64-musl@0.40.0': + optional: true + + '@oxfmt/binding-linux-ppc64-gnu@0.40.0': + optional: true + + '@oxfmt/binding-linux-riscv64-gnu@0.40.0': + optional: true + + '@oxfmt/binding-linux-riscv64-musl@0.40.0': + optional: true + + '@oxfmt/binding-linux-s390x-gnu@0.40.0': + optional: true + + '@oxfmt/binding-linux-x64-gnu@0.40.0': + optional: true + + '@oxfmt/binding-linux-x64-musl@0.40.0': + optional: true + + '@oxfmt/binding-openharmony-arm64@0.40.0': + optional: true + + '@oxfmt/binding-win32-arm64-msvc@0.40.0': + optional: true + + '@oxfmt/binding-win32-ia32-msvc@0.40.0': + optional: true + + '@oxfmt/binding-win32-x64-msvc@0.40.0': + optional: true + + '@oxlint-tsgolint/darwin-arm64@0.16.0': + optional: true + + '@oxlint-tsgolint/darwin-x64@0.16.0': + optional: true + + '@oxlint-tsgolint/linux-arm64@0.16.0': + optional: true + + '@oxlint-tsgolint/linux-x64@0.16.0': + optional: true + + '@oxlint-tsgolint/win32-arm64@0.16.0': + optional: true + + '@oxlint-tsgolint/win32-x64@0.16.0': + optional: true + + '@oxlint/binding-android-arm-eabi@1.55.0': + optional: true + + '@oxlint/binding-android-arm64@1.55.0': + optional: true + + '@oxlint/binding-darwin-arm64@1.55.0': + optional: true + + '@oxlint/binding-darwin-x64@1.55.0': + optional: true + + '@oxlint/binding-freebsd-x64@1.55.0': + optional: true + + '@oxlint/binding-linux-arm-gnueabihf@1.55.0': + optional: true + + '@oxlint/binding-linux-arm-musleabihf@1.55.0': + optional: true + + '@oxlint/binding-linux-arm64-gnu@1.55.0': + optional: true + + '@oxlint/binding-linux-arm64-musl@1.55.0': + optional: true + + '@oxlint/binding-linux-ppc64-gnu@1.55.0': + optional: true + + '@oxlint/binding-linux-riscv64-gnu@1.55.0': + optional: true + + '@oxlint/binding-linux-riscv64-musl@1.55.0': + optional: true + + '@oxlint/binding-linux-s390x-gnu@1.55.0': + optional: true + + '@oxlint/binding-linux-x64-gnu@1.55.0': + optional: true + + '@oxlint/binding-linux-x64-musl@1.55.0': + optional: true + + '@oxlint/binding-openharmony-arm64@1.55.0': + optional: true + + '@oxlint/binding-win32-arm64-msvc@1.55.0': + optional: true + + '@oxlint/binding-win32-ia32-msvc@1.55.0': + optional: true + + '@oxlint/binding-win32-x64-msvc@1.55.0': + optional: true + '@parcel/watcher-android-arm64@2.5.6': optional: true @@ -9616,61 +9971,12 @@ snapshots: '@rgrove/parse-xml@4.2.0': {} - '@rolldown/binding-android-arm64@1.0.0-rc.9': - optional: true - - '@rolldown/binding-darwin-arm64@1.0.0-rc.9': - optional: true - - '@rolldown/binding-darwin-x64@1.0.0-rc.9': - optional: true - - '@rolldown/binding-freebsd-x64@1.0.0-rc.9': - optional: true - - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.9': - optional: true - - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.9': - optional: true - - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.9': - optional: true - - '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.9': - optional: true - - '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.9': - optional: true - - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.9': - optional: true - - '@rolldown/binding-linux-x64-musl@1.0.0-rc.9': - optional: true - - '@rolldown/binding-openharmony-arm64@1.0.0-rc.9': - optional: true - - '@rolldown/binding-wasm32-wasi@1.0.0-rc.9': - dependencies: - '@napi-rs/wasm-runtime': 1.1.1 - optional: true - - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.9': - optional: true - - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.9': - optional: true - '@rolldown/pluginutils@1.0.0-rc.3': {} '@rolldown/pluginutils@1.0.0-rc.5': {} '@rolldown/pluginutils@1.0.0-rc.7': {} - '@rolldown/pluginutils@1.0.0-rc.9': {} - '@rollup/plugin-replace@6.0.3(rollup@4.59.0)': dependencies: '@rollup/pluginutils': 5.3.0(rollup@4.59.0) @@ -9840,10 +10146,10 @@ snapshots: '@standard-schema/spec@1.1.0': {} - '@storybook/addon-docs@10.2.17(@types/react@19.2.14)(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3))': + '@storybook/addon-docs@10.2.17(@types/react@19.2.14)(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3))': dependencies: '@mdx-js/react': 3.1.1(@types/react@19.2.14)(react@19.2.4) - '@storybook/csf-plugin': 10.2.17(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) + '@storybook/csf-plugin': 10.2.17(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) '@storybook/icons': 2.0.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@storybook/react-dom-shim': 10.2.17(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) react: 19.2.4 @@ -9873,25 +10179,25 @@ snapshots: storybook: 10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) ts-dedent: 2.2.0 - '@storybook/builder-vite@10.2.17(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3))': + '@storybook/builder-vite@10.2.17(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3))': dependencies: - '@storybook/csf-plugin': 10.2.17(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) + '@storybook/csf-plugin': 10.2.17(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) storybook: 10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) ts-dedent: 2.2.0 - vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: '@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' transitivePeerDependencies: - esbuild - rollup - webpack - '@storybook/csf-plugin@10.2.17(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3))': + '@storybook/csf-plugin@10.2.17(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3))': dependencies: storybook: 10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) unplugin: 2.3.11 optionalDependencies: esbuild: 0.27.2 rollup: 4.59.0 - vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: '@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' webpack: 5.105.4(esbuild@0.27.2)(uglify-js@3.19.3) '@storybook/global@5.0.0': {} @@ -9901,18 +10207,18 @@ snapshots: react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@storybook/nextjs-vite@10.2.17(@babel/core@7.29.0)(esbuild@0.27.2)(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3))': + '@storybook/nextjs-vite@10.2.17(@babel/core@7.29.0)(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3))': dependencies: - '@storybook/builder-vite': 10.2.17(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) + '@storybook/builder-vite': 10.2.17(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) '@storybook/react': 10.2.17(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) - '@storybook/react-vite': 10.2.17(esbuild@0.27.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) + '@storybook/react-vite': 10.2.17(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) next: 16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) storybook: 10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) styled-jsx: 5.1.6(@babel/core@7.29.0)(react@19.2.4) - vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) - vite-plugin-storybook-nextjs: 3.2.2(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + vite: '@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' + vite-plugin-storybook-nextjs: 3.2.2(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: @@ -9929,11 +10235,11 @@ snapshots: react-dom: 19.2.4(react@19.2.4) storybook: 10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@storybook/react-vite@10.2.17(esbuild@0.27.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3))': + '@storybook/react-vite@10.2.17(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3))': dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.6.4(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + '@joshwooding/vite-plugin-react-docgen-typescript': 0.6.4(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(typescript@5.9.3) '@rollup/pluginutils': 5.3.0(rollup@4.59.0) - '@storybook/builder-vite': 10.2.17(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) + '@storybook/builder-vite': 10.2.17(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) '@storybook/react': 10.2.17(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) empathic: 2.0.0 magic-string: 0.30.21 @@ -9943,7 +10249,7 @@ snapshots: resolve: 1.22.11 storybook: 10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) tsconfig-paths: 4.2.0 - vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: '@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' transitivePeerDependencies: - esbuild - rollup @@ -10669,7 +10975,7 @@ snapshots: '@resvg/resvg-wasm': 2.4.0 satori: 0.16.0 - '@vitejs/plugin-react@5.1.4(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': + '@vitejs/plugin-react@5.1.4(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) @@ -10677,16 +10983,16 @@ snapshots: '@rolldown/pluginutils': 1.0.0-rc.3 '@types/babel__core': 7.20.5 react-refresh: 0.18.0 - vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: '@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' transitivePeerDependencies: - supports-color - '@vitejs/plugin-react@6.0.0(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': + '@vitejs/plugin-react@6.0.0(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))': dependencies: '@rolldown/pluginutils': 1.0.0-rc.7 - vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: '@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' - '@vitejs/plugin-rsc@0.5.21(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': + '@vitejs/plugin-rsc@0.5.21(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4)': dependencies: '@rolldown/pluginutils': 1.0.0-rc.5 es-module-lexer: 2.0.0 @@ -10698,12 +11004,12 @@ snapshots: srvx: 0.11.9 strip-literal: 3.1.0 turbo-stream: 3.2.0 - vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) - vitefu: 1.1.2(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + vite: '@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' + vitefu: 1.1.2(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) optionalDependencies: react-server-dom-webpack: 19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) - '@vitest/coverage-v8@4.1.0(vitest@4.1.0(@types/node@25.5.0)(jsdom@28.1.0(canvas@3.2.1))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)))': + '@vitest/coverage-v8@4.1.0(@voidzero-dev/vite-plus-test@0.1.11(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))': dependencies: '@bcoe/v8-coverage': 1.0.2 '@vitest/utils': 4.1.0 @@ -10715,16 +11021,16 @@ snapshots: obug: 2.1.1 std-env: 4.0.0 tinyrainbow: 3.0.3 - vitest: 4.1.0(@types/node@25.5.0)(jsdom@28.1.0(canvas@3.2.1))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + vitest: '@voidzero-dev/vite-plus-test@0.1.11(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' - '@vitest/eslint-plugin@1.6.10(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3)(vitest@4.1.0(@types/node@25.5.0)(jsdom@28.1.0(canvas@3.2.1))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)))': + '@vitest/eslint-plugin@1.6.10(@voidzero-dev/vite-plus-test@0.1.11(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3)': dependencies: '@typescript-eslint/scope-manager': 8.57.0 '@typescript-eslint/utils': 8.57.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) eslint: 10.0.3(jiti@1.21.7) optionalDependencies: typescript: 5.9.3 - vitest: 4.1.0(@types/node@25.5.0)(jsdom@28.1.0(canvas@3.2.1))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + vitest: '@voidzero-dev/vite-plus-test@0.1.11(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' transitivePeerDependencies: - supports-color @@ -10736,23 +11042,6 @@ snapshots: chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/expect@4.1.0': - dependencies: - '@standard-schema/spec': 1.1.0 - '@types/chai': 5.2.3 - '@vitest/spy': 4.1.0 - '@vitest/utils': 4.1.0 - chai: 6.2.2 - tinyrainbow: 3.0.3 - - '@vitest/mocker@4.1.0(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': - dependencies: - '@vitest/spy': 4.1.0 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) - '@vitest/pretty-format@3.2.4': dependencies: tinyrainbow: 2.0.0 @@ -10761,24 +11050,10 @@ snapshots: dependencies: tinyrainbow: 3.0.3 - '@vitest/runner@4.1.0': - dependencies: - '@vitest/utils': 4.1.0 - pathe: 2.0.3 - - '@vitest/snapshot@4.1.0': - dependencies: - '@vitest/pretty-format': 4.1.0 - '@vitest/utils': 4.1.0 - magic-string: 0.30.21 - pathe: 2.0.3 - '@vitest/spy@3.2.4': dependencies: tinyspy: 4.0.4 - '@vitest/spy@4.1.0': {} - '@vitest/utils@3.2.4': dependencies: '@vitest/pretty-format': 3.2.4 @@ -10791,6 +11066,81 @@ snapshots: convert-source-map: 2.0.0 tinyrainbow: 3.0.3 + '@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)': + dependencies: + '@oxc-project/runtime': 0.115.0 + '@oxc-project/types': 0.115.0 + lightningcss: 1.32.0 + postcss: 8.5.8 + optionalDependencies: + '@types/node': 25.5.0 + esbuild: 0.27.2 + fsevents: 2.3.3 + jiti: 1.21.7 + sass: 1.98.0 + terser: 5.46.0 + tsx: 4.21.0 + typescript: 5.9.3 + yaml: 2.8.2 + + '@voidzero-dev/vite-plus-darwin-arm64@0.1.11': + optional: true + + '@voidzero-dev/vite-plus-darwin-x64@0.1.11': + optional: true + + '@voidzero-dev/vite-plus-linux-arm64-gnu@0.1.11': + optional: true + + '@voidzero-dev/vite-plus-linux-x64-gnu@0.1.11': + optional: true + + '@voidzero-dev/vite-plus-test@0.1.11(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@voidzero-dev/vite-plus-core': 0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) + es-module-lexer: 1.7.0 + obug: 2.1.1 + pixelmatch: 7.1.0 + pngjs: 7.0.0 + sirv: 3.0.2 + std-env: 4.0.0 + tinybench: 2.9.0 + tinyexec: 1.0.2 + tinyglobby: 0.2.15 + vite: '@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' + ws: 8.19.0 + optionalDependencies: + '@types/node': 25.5.0 + jsdom: 28.1.0(canvas@3.2.1) + transitivePeerDependencies: + - '@arethetypeswrong/core' + - '@tsdown/css' + - '@tsdown/exe' + - '@vitejs/devtools' + - bufferutil + - esbuild + - jiti + - less + - publint + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - typescript + - unplugin-unused + - utf-8-validate + - yaml + + '@voidzero-dev/vite-plus-win32-arm64-msvc@0.1.11': + optional: true + + '@voidzero-dev/vite-plus-win32-x64-msvc@0.1.11': + optional: true + '@volar/language-core@2.4.28': dependencies: '@volar/source-map': 2.4.28 @@ -11124,6 +11474,8 @@ snapshots: bytes@3.1.2: {} + cac@6.7.14: {} + cac@7.0.0: {} callsites@3.1.0: {} @@ -11150,8 +11502,6 @@ snapshots: loupe: 3.2.1 pathval: 2.0.1 - chai@6.2.2: {} - chalk@4.1.1: dependencies: ansi-styles: 4.3.0 @@ -11843,7 +12193,7 @@ snapshots: dependencies: eslint: 10.0.3(jiti@1.21.7) - eslint-plugin-better-tailwindcss@4.3.2(eslint@10.0.3(jiti@1.21.7))(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.2))(typescript@5.9.3): + eslint-plugin-better-tailwindcss@4.3.2(eslint@10.0.3(jiti@1.21.7))(oxlint@1.55.0(oxlint-tsgolint@0.16.0))(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.2))(typescript@5.9.3): dependencies: '@eslint/css-tree': 3.6.9 '@valibot/to-json-schema': 1.5.0(valibot@1.2.0(typescript@5.9.3)) @@ -11856,6 +12206,7 @@ snapshots: valibot: 1.2.0(typescript@5.9.3) optionalDependencies: eslint: 10.0.3(jiti@1.21.7) + oxlint: 1.55.0(oxlint-tsgolint@0.16.0) transitivePeerDependencies: - typescript @@ -12359,8 +12710,6 @@ snapshots: expand-template@2.0.3: optional: true - expect-type@1.3.0: {} - exsolve@1.0.8: {} extend@3.0.2: {} @@ -13954,6 +14303,62 @@ snapshots: '@oxc-resolver/binding-win32-ia32-msvc': 11.19.1 '@oxc-resolver/binding-win32-x64-msvc': 11.19.1 + oxfmt@0.40.0: + dependencies: + tinypool: 2.1.0 + optionalDependencies: + '@oxfmt/binding-android-arm-eabi': 0.40.0 + '@oxfmt/binding-android-arm64': 0.40.0 + '@oxfmt/binding-darwin-arm64': 0.40.0 + '@oxfmt/binding-darwin-x64': 0.40.0 + '@oxfmt/binding-freebsd-x64': 0.40.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.40.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.40.0 + '@oxfmt/binding-linux-arm64-gnu': 0.40.0 + '@oxfmt/binding-linux-arm64-musl': 0.40.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.40.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.40.0 + '@oxfmt/binding-linux-riscv64-musl': 0.40.0 + '@oxfmt/binding-linux-s390x-gnu': 0.40.0 + '@oxfmt/binding-linux-x64-gnu': 0.40.0 + '@oxfmt/binding-linux-x64-musl': 0.40.0 + '@oxfmt/binding-openharmony-arm64': 0.40.0 + '@oxfmt/binding-win32-arm64-msvc': 0.40.0 + '@oxfmt/binding-win32-ia32-msvc': 0.40.0 + '@oxfmt/binding-win32-x64-msvc': 0.40.0 + + oxlint-tsgolint@0.16.0: + optionalDependencies: + '@oxlint-tsgolint/darwin-arm64': 0.16.0 + '@oxlint-tsgolint/darwin-x64': 0.16.0 + '@oxlint-tsgolint/linux-arm64': 0.16.0 + '@oxlint-tsgolint/linux-x64': 0.16.0 + '@oxlint-tsgolint/win32-arm64': 0.16.0 + '@oxlint-tsgolint/win32-x64': 0.16.0 + + oxlint@1.55.0(oxlint-tsgolint@0.16.0): + optionalDependencies: + '@oxlint/binding-android-arm-eabi': 1.55.0 + '@oxlint/binding-android-arm64': 1.55.0 + '@oxlint/binding-darwin-arm64': 1.55.0 + '@oxlint/binding-darwin-x64': 1.55.0 + '@oxlint/binding-freebsd-x64': 1.55.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.55.0 + '@oxlint/binding-linux-arm-musleabihf': 1.55.0 + '@oxlint/binding-linux-arm64-gnu': 1.55.0 + '@oxlint/binding-linux-arm64-musl': 1.55.0 + '@oxlint/binding-linux-ppc64-gnu': 1.55.0 + '@oxlint/binding-linux-riscv64-gnu': 1.55.0 + '@oxlint/binding-linux-riscv64-musl': 1.55.0 + '@oxlint/binding-linux-s390x-gnu': 1.55.0 + '@oxlint/binding-linux-x64-gnu': 1.55.0 + '@oxlint/binding-linux-x64-musl': 1.55.0 + '@oxlint/binding-openharmony-arm64': 1.55.0 + '@oxlint/binding-win32-arm64-msvc': 1.55.0 + '@oxlint/binding-win32-ia32-msvc': 1.55.0 + '@oxlint/binding-win32-x64-msvc': 1.55.0 + oxlint-tsgolint: 0.16.0 + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 @@ -14070,6 +14475,10 @@ snapshots: pirates@4.0.7: {} + pixelmatch@7.1.0: + dependencies: + pngjs: 7.0.0 + pkg-types@1.3.1: dependencies: confbox: 0.1.8 @@ -14084,6 +14493,8 @@ snapshots: pluralize@8.0.0: {} + pngjs@7.0.0: {} + pnpm-workspace-yaml@1.6.0: dependencies: yaml: 2.8.2 @@ -14635,27 +15046,6 @@ snapshots: robust-predicates@3.0.2: {} - rolldown@1.0.0-rc.9: - dependencies: - '@oxc-project/types': 0.115.0 - '@rolldown/pluginutils': 1.0.0-rc.9 - optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.0-rc.9 - '@rolldown/binding-darwin-arm64': 1.0.0-rc.9 - '@rolldown/binding-darwin-x64': 1.0.0-rc.9 - '@rolldown/binding-freebsd-x64': 1.0.0-rc.9 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.9 - '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.9 - '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.9 - '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.9 - '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.9 - '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.9 - '@rolldown/binding-linux-x64-musl': 1.0.0-rc.9 - '@rolldown/binding-openharmony-arm64': 1.0.0-rc.9 - '@rolldown/binding-wasm32-wasi': 1.0.0-rc.9 - '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.9 - '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.9 - rollup@4.59.0: dependencies: '@types/estree': 1.0.8 @@ -14803,8 +15193,6 @@ snapshots: shebang-regex@3.0.0: {} - siginfo@2.0.0: {} - signal-exit@4.1.0: {} simple-concat@1.0.1: @@ -14873,8 +15261,6 @@ snapshots: srvx@0.11.9: {} - stackback@0.0.2: {} - std-env@4.0.0: {} storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): @@ -15141,6 +15527,8 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 + tinypool@2.1.0: {} + tinyrainbow@2.0.0: {} tinyrainbow@3.0.3: {} @@ -15419,36 +15807,36 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vinext@https://pkg.pr.new/vinext@18fe3ea(@mdx-js/rollup@3.1.1(rollup@4.59.0))(@vitejs/plugin-rsc@0.5.21(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)))(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4)(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): + vinext@https://pkg.pr.new/vinext@18fe3ea(@mdx-js/rollup@3.1.1(rollup@4.59.0))(@vitejs/plugin-rsc@0.5.21(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4))(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4)(typescript@5.9.3): dependencies: '@unpic/react': 1.0.2(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@vercel/og': 0.8.6 - '@vitejs/plugin-react': 5.1.4(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + '@vitejs/plugin-react': 5.1.4(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) magic-string: 0.30.21 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) rsc-html-stream: 0.0.7 - vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: '@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' vite-plugin-commonjs: 0.10.4 - vite-tsconfig-paths: 6.1.1(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + vite-tsconfig-paths: 6.1.1(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(typescript@5.9.3) optionalDependencies: '@mdx-js/rollup': 3.1.1(rollup@4.59.0) - '@vitejs/plugin-rsc': 0.5.21(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + '@vitejs/plugin-rsc': 0.5.21(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4) react-server-dom-webpack: 19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) transitivePeerDependencies: - next - supports-color - typescript - vite-dev-rpc@1.1.0(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): + vite-dev-rpc@1.1.0(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)): dependencies: birpc: 2.9.0 - vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) - vite-hot-client: 2.1.0(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + vite: '@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' + vite-hot-client: 2.1.0(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) - vite-hot-client@2.1.0(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): + vite-hot-client@2.1.0(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)): dependencies: - vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: '@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' vite-plugin-commonjs@0.10.4: dependencies: @@ -15463,7 +15851,7 @@ snapshots: fast-glob: 3.3.3 magic-string: 0.30.21 - vite-plugin-inspect@11.3.3(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): + vite-plugin-inspect@11.3.3(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)): dependencies: ansis: 4.2.0 debug: 4.4.3 @@ -15473,12 +15861,12 @@ snapshots: perfect-debounce: 2.1.0 sirv: 3.0.2 unplugin-utils: 0.3.1 - vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) - vite-dev-rpc: 1.1.0(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + vite: '@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' + vite-dev-rpc: 1.1.0(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) transitivePeerDependencies: - supports-color - vite-plugin-storybook-nextjs@3.2.2(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): + vite-plugin-storybook-nextjs@3.2.2(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3): dependencies: '@next/env': 16.0.0 image-size: 2.0.2 @@ -15487,88 +15875,88 @@ snapshots: next: 16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0) storybook: 10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) ts-dedent: 2.2.0 - vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) - vite-tsconfig-paths: 5.1.4(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + vite: '@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' + vite-tsconfig-paths: 5.1.4(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(typescript@5.9.3) transitivePeerDependencies: - supports-color - typescript - vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): + vite-plus@0.1.11(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2): + dependencies: + '@oxc-project/types': 0.115.0 + '@voidzero-dev/vite-plus-core': 0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) + '@voidzero-dev/vite-plus-test': 0.1.11(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) + cac: 6.7.14 + cross-spawn: 7.0.6 + oxfmt: 0.40.0 + oxlint: 1.55.0(oxlint-tsgolint@0.16.0) + oxlint-tsgolint: 0.16.0 + picocolors: 1.1.1 + optionalDependencies: + '@voidzero-dev/vite-plus-darwin-arm64': 0.1.11 + '@voidzero-dev/vite-plus-darwin-x64': 0.1.11 + '@voidzero-dev/vite-plus-linux-arm64-gnu': 0.1.11 + '@voidzero-dev/vite-plus-linux-x64-gnu': 0.1.11 + '@voidzero-dev/vite-plus-win32-arm64-msvc': 0.1.11 + '@voidzero-dev/vite-plus-win32-x64-msvc': 0.1.11 + transitivePeerDependencies: + - '@arethetypeswrong/core' + - '@edge-runtime/vm' + - '@opentelemetry/api' + - '@tsdown/css' + - '@tsdown/exe' + - '@types/node' + - '@vitejs/devtools' + - '@vitest/ui' + - bufferutil + - esbuild + - happy-dom + - jiti + - jsdom + - less + - publint + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - typescript + - unplugin-unused + - utf-8-validate + - vite + - yaml + + vite-tsconfig-paths@5.1.4(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(typescript@5.9.3): dependencies: debug: 4.4.3 globrex: 0.1.2 tsconfck: 3.1.6(typescript@5.9.3) optionalDependencies: - vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: '@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' transitivePeerDependencies: - supports-color - typescript - vite-tsconfig-paths@6.1.1(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): + vite-tsconfig-paths@6.1.1(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(typescript@5.9.3): dependencies: debug: 4.4.3 globrex: 0.1.2 tsconfck: 3.1.6(typescript@5.9.3) - vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: '@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' transitivePeerDependencies: - supports-color - typescript - vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2): - dependencies: - '@oxc-project/runtime': 0.115.0 - lightningcss: 1.32.0 - picomatch: 4.0.3 - postcss: 8.5.8 - rolldown: 1.0.0-rc.9 - tinyglobby: 0.2.15 + vitefu@1.1.2(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)): optionalDependencies: - '@types/node': 25.5.0 - esbuild: 0.27.2 - fsevents: 2.3.3 - jiti: 1.21.7 - sass: 1.98.0 - terser: 5.46.0 - tsx: 4.21.0 - yaml: 2.8.2 + vite: '@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' - vitefu@1.1.2(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): - optionalDependencies: - vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) - - vitest-canvas-mock@1.1.3(vitest@4.1.0(@types/node@25.5.0)(jsdom@28.1.0(canvas@3.2.1))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))): + vitest-canvas-mock@1.1.3(@voidzero-dev/vite-plus-test@0.1.11(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)): dependencies: cssfontparser: 1.2.1 moo-color: 1.0.3 - vitest: 4.1.0(@types/node@25.5.0)(jsdom@28.1.0(canvas@3.2.1))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) - - vitest@4.1.0(@types/node@25.5.0)(jsdom@28.1.0(canvas@3.2.1))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): - dependencies: - '@vitest/expect': 4.1.0 - '@vitest/mocker': 4.1.0(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) - '@vitest/pretty-format': 4.1.0 - '@vitest/runner': 4.1.0 - '@vitest/snapshot': 4.1.0 - '@vitest/spy': 4.1.0 - '@vitest/utils': 4.1.0 - es-module-lexer: 2.0.0 - expect-type: 1.3.0 - magic-string: 0.30.21 - obug: 2.1.1 - pathe: 2.0.3 - picomatch: 4.0.3 - std-env: 4.0.0 - tinybench: 2.9.0 - tinyexec: 1.0.2 - tinyglobby: 0.2.15 - tinyrainbow: 3.0.3 - vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/node': 25.5.0 - jsdom: 28.1.0(canvas@3.2.1) - transitivePeerDependencies: - - msw + vitest: '@voidzero-dev/vite-plus-test@0.1.11(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' void-elements@3.1.0: {} @@ -15674,11 +16062,6 @@ snapshots: dependencies: isexe: 2.0.0 - why-is-node-running@2.3.0: - dependencies: - siginfo: 2.0.0 - stackback: 0.0.2 - word-wrap@1.2.5: {} wrap-ansi@9.0.2: diff --git a/web/vite.config.ts b/web/vite.config.ts index 3a61264e3c..39dff85dae 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -1,11 +1,9 @@ -/// - import path from 'node:path' import { fileURLToPath } from 'node:url' import react from '@vitejs/plugin-react' import vinext from 'vinext' -import { defineConfig } from 'vite' import Inspect from 'vite-plugin-inspect' +import { defineConfig } from 'vite-plus' import { createCodeInspectorPlugin, createForceInspectorClientInjectionPlugin } from './plugins/vite/code-inspector' import { customI18nHmrPlugin } from './plugins/vite/custom-i18n-hmr' import { collectComponentCoverageExcludedFiles } from './scripts/component-coverage-filters.mjs' From 03c58d151a4c5a6c2a0b33687007b456a9afce1a Mon Sep 17 00:00:00 2001 From: yyh Date: Fri, 13 Mar 2026 18:40:29 +0800 Subject: [PATCH 7/8] test: strengthen model provider header coverage --- .../use-change-provider-priority.spec.ts | 208 ++++++++++++++++++ .../use-trial-credits.spec.ts | 88 ++++++++ .../model-provider-page/utils.spec.ts | 11 + 3 files changed, 307 insertions(+) create mode 100644 web/app/components/header/account-setting/model-provider-page/provider-added-card/use-change-provider-priority.spec.ts create mode 100644 web/app/components/header/account-setting/model-provider-page/provider-added-card/use-trial-credits.spec.ts diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/use-change-provider-priority.spec.ts b/web/app/components/header/account-setting/model-provider-page/provider-added-card/use-change-provider-priority.spec.ts new file mode 100644 index 0000000000..45fb6e1803 --- /dev/null +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/use-change-provider-priority.spec.ts @@ -0,0 +1,208 @@ +import type { ReactNode } from 'react' +import type { ModelProvider } from '../declarations' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { act, renderHook, waitFor } from '@testing-library/react' +import * as React from 'react' +import { ConfigurationMethodEnum, ModelTypeEnum, PreferredProviderTypeEnum } from '../declarations' +import { useChangeProviderPriority } from './use-change-provider-priority' + +const mockUpdateModelList = vi.fn() +const mockUpdateModelProviders = vi.fn() +const mockNotify = vi.fn() +const mockQueryKey = vi.fn(({ input }: { input: { params: { provider: string } } }) => ['model-providers', 'models', input.params.provider]) +const mockChangePreferredProviderType = vi.fn() +const mockMutationOptions = vi.fn((options: Record) => ({ + mutationFn: (variables: unknown) => mockChangePreferredProviderType(variables), + ...options, +})) + +vi.mock('@/app/components/base/toast', () => ({ + default: { + notify: (...args: unknown[]) => mockNotify(...args), + }, +})) + +vi.mock('@/service/client', () => ({ + consoleQuery: { + modelProviders: { + models: { + queryKey: (options: { input: { params: { provider: string } } }) => mockQueryKey(options), + }, + changePreferredProviderType: { + mutationOptions: (options: Record) => mockMutationOptions(options), + }, + }, + }, +})) + +vi.mock('../hooks', () => ({ + useUpdateModelList: () => mockUpdateModelList, + useUpdateModelProviders: () => mockUpdateModelProviders, +})) + +const createProvider = (overrides: Partial = {}): ModelProvider => ({ + provider: 'langgenius/openai/openai', + configurate_methods: [ + ConfigurationMethodEnum.customizableModel, + ConfigurationMethodEnum.predefinedModel, + ], + supported_model_types: [ModelTypeEnum.textGeneration, ModelTypeEnum.textEmbedding], + label: { en_US: 'OpenAI' }, + icon_small: { en_US: 'https://example.com/icon.png' }, + provider_credential_schema: { credential_form_schemas: [] }, + model_credential_schema: { + model: { + label: { en_US: 'Model' }, + placeholder: { en_US: 'Select model' }, + }, + credential_form_schemas: [], + }, + ...overrides, +} as ModelProvider) + +const createTestQueryClient = () => new QueryClient({ + defaultOptions: { + queries: { retry: false, gcTime: 0 }, + mutations: { retry: false }, + }, +}) + +const createWrapper = (queryClient: QueryClient) => { + return ({ children }: { children: ReactNode }) => ( + React.createElement(QueryClientProvider, { client: queryClient }, children) + ) +} + +describe('useChangeProviderPriority', () => { + beforeEach(() => { + vi.clearAllMocks() + mockChangePreferredProviderType.mockResolvedValue(undefined) + }) + + describe('when changing provider priority', () => { + it('should submit the selected preferred provider type for the current provider', async () => { + const queryClient = createTestQueryClient() + const invalidateQueries = vi.spyOn(queryClient, 'invalidateQueries').mockResolvedValue(undefined) + const provider = createProvider() + const { result } = renderHook(() => useChangeProviderPriority(provider), { + wrapper: createWrapper(queryClient), + }) + + act(() => { + result.current.handleChangePriority(PreferredProviderTypeEnum.custom) + }) + + await waitFor(() => { + expect(mockChangePreferredProviderType).toHaveBeenCalledWith({ + params: { provider: 'langgenius/openai/openai' }, + body: { preferred_provider_type: PreferredProviderTypeEnum.custom }, + }) + }) + + expect(mockQueryKey).toHaveBeenCalledWith({ + input: { + params: { + provider: 'langgenius/openai/openai', + }, + }, + }) + expect(mockMutationOptions).toHaveBeenCalled() + expect(invalidateQueries).toHaveBeenCalledWith({ + queryKey: ['model-providers', 'models', 'langgenius/openai/openai'], + exact: true, + refetchType: 'none', + }) + expect(mockUpdateModelProviders).toHaveBeenCalledTimes(1) + expect(mockUpdateModelList).toHaveBeenCalledTimes(2) + expect(mockUpdateModelList).toHaveBeenNthCalledWith(1, ModelTypeEnum.textGeneration) + expect(mockUpdateModelList).toHaveBeenNthCalledWith(2, ModelTypeEnum.textEmbedding) + expect(mockNotify).toHaveBeenCalledWith({ + type: 'success', + message: 'common.actionMsg.modifiedSuccessfully', + }) + expect(result.current.isChangingPriority).toBe(false) + }) + + it('should tolerate an undefined provider and still submit a request without refreshing model lists', async () => { + const queryClient = createTestQueryClient() + const invalidateQueries = vi.spyOn(queryClient, 'invalidateQueries').mockResolvedValue(undefined) + const { result } = renderHook(() => useChangeProviderPriority(undefined), { + wrapper: createWrapper(queryClient), + }) + + act(() => { + result.current.handleChangePriority(PreferredProviderTypeEnum.system) + }) + + await waitFor(() => { + expect(mockChangePreferredProviderType).toHaveBeenCalledWith({ + params: { provider: '' }, + body: { preferred_provider_type: PreferredProviderTypeEnum.system }, + }) + }) + + expect(invalidateQueries).toHaveBeenCalledWith({ + queryKey: ['model-providers', 'models', ''], + exact: true, + refetchType: 'none', + }) + expect(mockUpdateModelProviders).toHaveBeenCalledTimes(1) + expect(mockUpdateModelList).not.toHaveBeenCalled() + }) + }) + + describe('when the mutation is not successful immediately', () => { + it('should show an error toast when the mutation fails', async () => { + const queryClient = createTestQueryClient() + const invalidateQueries = vi.spyOn(queryClient, 'invalidateQueries').mockResolvedValue(undefined) + mockChangePreferredProviderType.mockRejectedValueOnce(new Error('network error')) + const { result } = renderHook(() => useChangeProviderPriority(createProvider()), { + wrapper: createWrapper(queryClient), + }) + + act(() => { + result.current.handleChangePriority(PreferredProviderTypeEnum.custom) + }) + + await waitFor(() => { + expect(mockNotify).toHaveBeenCalledWith({ + type: 'error', + message: 'common.actionMsg.modifiedUnsuccessfully', + }) + }) + + expect(invalidateQueries).not.toHaveBeenCalled() + expect(mockUpdateModelProviders).not.toHaveBeenCalled() + expect(mockUpdateModelList).not.toHaveBeenCalled() + expect(result.current.isChangingPriority).toBe(false) + }) + + it('should expose the pending mutation state while the request is in flight', async () => { + let resolveMutation: (() => void) | undefined + mockChangePreferredProviderType.mockImplementationOnce(() => new Promise((resolve) => { + resolveMutation = resolve + })) + + const queryClient = createTestQueryClient() + const { result } = renderHook(() => useChangeProviderPriority(createProvider()), { + wrapper: createWrapper(queryClient), + }) + + act(() => { + result.current.handleChangePriority(PreferredProviderTypeEnum.custom) + }) + + await waitFor(() => { + expect(result.current.isChangingPriority).toBe(true) + }) + + await act(async () => { + resolveMutation?.() + }) + + await waitFor(() => { + expect(result.current.isChangingPriority).toBe(false) + }) + }) + }) +}) diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/use-trial-credits.spec.ts b/web/app/components/header/account-setting/model-provider-page/provider-added-card/use-trial-credits.spec.ts new file mode 100644 index 0000000000..fd7cc6d429 --- /dev/null +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/use-trial-credits.spec.ts @@ -0,0 +1,88 @@ +import { renderHook } from '@testing-library/react' +import { useTrialCredits } from './use-trial-credits' + +const mockUseCurrentWorkspace = vi.fn() + +vi.mock('@/service/use-common', () => ({ + useCurrentWorkspace: () => mockUseCurrentWorkspace(), +})) + +describe('useTrialCredits', () => { + beforeEach(() => { + vi.clearAllMocks() + mockUseCurrentWorkspace.mockReturnValue({ + data: { + trial_credits: 100, + trial_credits_used: 40, + next_credit_reset_date: '2026-04-01', + }, + isPending: false, + }) + }) + + describe('when workspace data is available', () => { + it('should return the remaining credits and reset date', () => { + const { result } = renderHook(() => useTrialCredits()) + + expect(result.current).toEqual({ + credits: 60, + totalCredits: 100, + isExhausted: false, + isLoading: false, + nextCreditResetDate: '2026-04-01', + }) + }) + + it('should keep the hook out of loading state during a background refetch', () => { + mockUseCurrentWorkspace.mockReturnValue({ + data: { + trial_credits: 80, + trial_credits_used: 20, + next_credit_reset_date: '2026-05-01', + }, + isPending: true, + }) + + const { result } = renderHook(() => useTrialCredits()) + + expect(result.current.isLoading).toBe(false) + expect(result.current.credits).toBe(60) + expect(result.current.isExhausted).toBe(false) + }) + }) + + describe('when workspace data is missing or exhausted', () => { + it('should report loading while the first workspace request is pending', () => { + mockUseCurrentWorkspace.mockReturnValue({ + data: undefined, + isPending: true, + }) + + const { result } = renderHook(() => useTrialCredits()) + + expect(result.current).toEqual({ + credits: 0, + totalCredits: 0, + isExhausted: true, + isLoading: true, + nextCreditResetDate: undefined, + }) + }) + + it('should clamp negative remaining credits to zero', () => { + mockUseCurrentWorkspace.mockReturnValue({ + data: { + trial_credits: 10, + trial_credits_used: 99, + next_credit_reset_date: undefined, + }, + isPending: false, + }) + + const { result } = renderHook(() => useTrialCredits()) + + expect(result.current.credits).toBe(0) + expect(result.current.isExhausted).toBe(true) + }) + }) +}) diff --git a/web/app/components/header/account-setting/model-provider-page/utils.spec.ts b/web/app/components/header/account-setting/model-provider-page/utils.spec.ts index 375ddc4457..0ffa512e1c 100644 --- a/web/app/components/header/account-setting/model-provider-page/utils.spec.ts +++ b/web/app/components/header/account-setting/model-provider-page/utils.spec.ts @@ -16,6 +16,7 @@ import { genModelNameFormSchema, genModelTypeFormSchema, modelTypeFormat, + providerToPluginId, removeCredentials, saveCredentials, savePredefinedLoadBalancingConfig, @@ -47,6 +48,16 @@ describe('utils', () => { }) }) + describe('providerToPluginId', () => { + it('should return the plugin id prefix when the provider key contains a provider segment', () => { + expect(providerToPluginId('langgenius/openai/openai')).toBe('langgenius/openai') + }) + + it('should return an empty string when the provider key has no plugin prefix', () => { + expect(providerToPluginId('openai')).toBe('') + }) + }) + describe('modelTypeFormat', () => { it('should format text embedding type', () => { expect(modelTypeFormat(ModelTypeEnum.textEmbedding)).toBe('TEXT EMBEDDING') From 0c42c11d288a06969dadf129b7ee708f88b5efa6 Mon Sep 17 00:00:00 2001 From: yyh Date: Fri, 13 Mar 2026 21:47:57 +0800 Subject: [PATCH 8/8] fix(workflow): scope llm model warning dot --- .../workflow/nodes/llm/panel.spec.tsx | 248 ++++++++++++++++++ .../components/workflow/nodes/llm/panel.tsx | 11 +- 2 files changed, 256 insertions(+), 3 deletions(-) create mode 100644 web/app/components/workflow/nodes/llm/panel.spec.tsx diff --git a/web/app/components/workflow/nodes/llm/panel.spec.tsx b/web/app/components/workflow/nodes/llm/panel.spec.tsx new file mode 100644 index 0000000000..109174e7d2 --- /dev/null +++ b/web/app/components/workflow/nodes/llm/panel.spec.tsx @@ -0,0 +1,248 @@ +import type { LLMNodeType } from './types' +import type { ModelProvider } from '@/app/components/header/account-setting/model-provider-page/declarations' +import type { ProviderContextState } from '@/context/provider-context' +import type { PanelProps } from '@/types/workflow' +import { render, screen } from '@testing-library/react' +import * as React from 'react' +import { defaultPlan } from '@/app/components/billing/config' +import { + ConfigurationMethodEnum, + CurrentSystemQuotaTypeEnum, + CustomConfigurationStatusEnum, + ModelTypeEnum, + PreferredProviderTypeEnum, +} from '@/app/components/header/account-setting/model-provider-page/declarations' +import { useProviderContextSelector } from '@/context/provider-context' +import { AppModeEnum } from '@/types/app' +import { BlockEnum } from '../../types' +import Panel from './panel' + +const mockUseConfig = vi.fn() + +vi.mock('@/context/provider-context', () => ({ + useProviderContextSelector: vi.fn(), +})) + +vi.mock('./use-config', () => ({ + default: (...args: unknown[]) => mockUseConfig(...args), +})) + +vi.mock('@/app/components/header/account-setting/model-provider-page/model-parameter-modal', () => ({ + default: () =>
, +})) + +vi.mock('./components/config-prompt', () => ({ + default: () =>
, +})) + +vi.mock('../_base/components/config-vision', () => ({ + default: () => null, +})) + +vi.mock('../_base/components/memory-config', () => ({ + default: () => null, +})) + +vi.mock('../_base/components/variable/var-reference-picker', () => ({ + default: () => null, +})) + +vi.mock('@/app/components/workflow/nodes/_base/components/prompt/editor', () => ({ + default: () => null, +})) + +vi.mock('@/app/components/workflow/nodes/_base/components/variable/var-list', () => ({ + default: () => null, +})) + +vi.mock('./components/reasoning-format-config', () => ({ + default: () => null, +})) + +vi.mock('./components/structure-output', () => ({ + default: () => null, +})) + +vi.mock('@/app/components/workflow/nodes/_base/components/output-vars', () => ({ + default: ({ children }: { children?: React.ReactNode }) =>
{children}
, + VarItem: () => null, +})) + +type MockUseConfigReturn = ReturnType + +const modelProviderSelector = vi.mocked(useProviderContextSelector) + +const createProviderContextState = (modelProviders: ModelProvider[]): ProviderContextState => ({ + modelProviders, + refreshModelProviders: vi.fn(), + textGenerationModelList: [], + supportRetrievalMethods: [], + isAPIKeySet: true, + plan: defaultPlan, + isFetchedPlan: true, + enableBilling: false, + onPlanInfoChanged: vi.fn(), + enableReplaceWebAppLogo: false, + modelLoadBalancingEnabled: false, + datasetOperatorEnabled: false, + enableEducationPlan: false, + isEducationWorkspace: false, + isEducationAccount: false, + allowRefreshEducationVerify: false, + educationAccountExpireAt: null, + isLoadingEducationAccountInfo: false, + isFetchingEducationAccountInfo: false, + webappCopyrightEnabled: false, + licenseLimit: { + workspace_members: { + size: 0, + limit: 0, + }, + }, + refreshLicenseLimit: vi.fn(), + isAllowTransferWorkspace: false, + isAllowPublishAsCustomKnowledgePipelineTemplate: false, + humanInputEmailDeliveryEnabled: false, +}) + +const createMockModelProvider = (provider: string): ModelProvider => ({ + provider, + label: { en_US: provider, zh_Hans: provider }, + help: { + title: { en_US: provider, zh_Hans: provider }, + url: { en_US: '', zh_Hans: '' }, + }, + icon_small: { en_US: '', zh_Hans: '' }, + supported_model_types: [ModelTypeEnum.textGeneration], + configurate_methods: [ConfigurationMethodEnum.predefinedModel], + provider_credential_schema: { + credential_form_schemas: [], + }, + model_credential_schema: { + model: { + label: { en_US: '', zh_Hans: '' }, + placeholder: { en_US: '', zh_Hans: '' }, + }, + credential_form_schemas: [], + }, + preferred_provider_type: PreferredProviderTypeEnum.system, + custom_configuration: { + status: CustomConfigurationStatusEnum.active, + }, + system_configuration: { + enabled: true, + current_quota_type: CurrentSystemQuotaTypeEnum.free, + quota_configurations: [], + }, +}) + +const baseNodeData: LLMNodeType = { + type: BlockEnum.LLM, + title: 'LLM', + desc: '', + model: { + provider: 'openai', + name: 'gpt-4o', + mode: AppModeEnum.CHAT, + completion_params: {}, + }, + prompt_template: [], + context: { + enabled: false, + variable_selector: [], + }, + vision: { + enabled: false, + }, +} + +const panelProps = {} as PanelProps + +const buildUseConfigResult = (overrides?: Partial) => ({ + readOnly: false, + inputs: baseNodeData, + isChatModel: true, + isChatMode: true, + isCompletionModel: false, + shouldShowContextTip: false, + isVisionModel: false, + handleModelChanged: vi.fn(), + hasSetBlockStatus: false, + handleCompletionParamsChange: vi.fn(), + handleContextVarChange: vi.fn(), + filterInputVar: vi.fn(), + filterVar: vi.fn(), + availableVars: [], + availableNodesWithParent: [], + isShowVars: false, + handlePromptChange: vi.fn(), + handleAddEmptyVariable: vi.fn(), + handleAddVariable: vi.fn(), + handleVarListChange: vi.fn(), + handleVarNameChange: vi.fn(), + handleSyeQueryChange: vi.fn(), + handleMemoryChange: vi.fn(), + handleVisionResolutionEnabledChange: vi.fn(), + handleVisionResolutionChange: vi.fn(), + isModelSupportStructuredOutput: false, + structuredOutputCollapsed: false, + setStructuredOutputCollapsed: vi.fn(), + handleStructureOutputEnableChange: vi.fn(), + handleStructureOutputChange: vi.fn(), + filterJinja2InputVar: vi.fn(), + handleReasoningFormatChange: vi.fn(), + ...overrides, +}) + +const renderPanel = (data?: Partial) => { + return render( + , + ) +} + +describe('LLM Panel', () => { + beforeEach(() => { + vi.clearAllMocks() + modelProviderSelector.mockImplementation(selector => selector( + createProviderContextState([createMockModelProvider('openai')]), + )) + mockUseConfig.mockReturnValue(buildUseConfigResult()) + }) + + describe('Model Warning Dot', () => { + it('should not show the model warning dot when the node only has a connection checklist issue', () => { + renderPanel() + + const modelField = screen.getByText('workflow.nodes.llm.model').parentElement + expect(modelField?.querySelector('.bg-text-warning-secondary')).not.toBeInTheDocument() + }) + + it('should show the model warning dot when the model is not configured', () => { + mockUseConfig.mockReturnValue(buildUseConfigResult({ + inputs: { + ...baseNodeData, + model: { + ...baseNodeData.model, + provider: '', + name: '', + }, + }, + })) + + renderPanel({ + model: { + ...baseNodeData.model, + provider: '', + name: '', + }, + }) + + const modelField = screen.getByText('workflow.nodes.llm.model').parentElement + expect(modelField?.querySelector('.bg-text-warning-secondary')).toBeInTheDocument() + }) + }) +}) diff --git a/web/app/components/workflow/nodes/llm/panel.tsx b/web/app/components/workflow/nodes/llm/panel.tsx index 9796f1bc66..9ed6e81e3f 100644 --- a/web/app/components/workflow/nodes/llm/panel.tsx +++ b/web/app/components/workflow/nodes/llm/panel.tsx @@ -15,8 +15,9 @@ import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/compo import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor' import Split from '@/app/components/workflow/nodes/_base/components/split' import VarList from '@/app/components/workflow/nodes/_base/components/variable/var-list' -import { useStore } from '@/app/components/workflow/store' +import { useProviderContextSelector } from '@/context/provider-context' import { fetchAndMergeValidCompletionParams } from '@/utils/completion-params' +import { extractPluginId } from '../../utils/plugin' import ConfigVision from '../_base/components/config-vision' import MemoryConfig from '../_base/components/memory-config' import VarReferencePicker from '../_base/components/variable/var-reference-picker' @@ -32,7 +33,7 @@ const Panel: FC> = ({ data, }) => { const { t } = useTranslation() - const hasChecklistWarning = useStore(s => s.checklistItems.some(item => item.id === id)) + const modelProviders = useProviderContextSelector(s => s.modelProviders) const { readOnly, inputs, @@ -69,6 +70,10 @@ const Panel: FC> = ({ } = useConfig(id, data) const model = inputs.model + const installedPluginIds = new Set(modelProviders.map(provider => extractPluginId(provider.provider))) + const hasModelWarning = !model?.provider + || !model?.name + || (Boolean(model.provider) && !installedPluginIds.has(extractPluginId(model.provider))) const handleModelChange = useCallback((model: { provider: string @@ -104,7 +109,7 @@ const Panel: FC> = ({