mirror of
https://github.com/lobehub/lobehub.git
synced 2026-03-26 13:19:34 +07:00
🐛 fix(desktop): add missing Stats and Creds tabs to Electron componentMap (#13243)
This commit is contained in:
@@ -37,6 +37,10 @@ description: 'Code review checklist for LobeHub. Use when reviewing PRs, diffs,
|
||||
- Keys added to `src/locales/default/{namespace}.ts` with `{feature}.{context}.{action|status}` naming
|
||||
- For PRs: `locales/` translations for all languages updated (`pnpm i18n`)
|
||||
|
||||
### SPA / routing
|
||||
|
||||
- **`desktopRouter` pair:** If the diff touches `src/spa/router/desktopRouter.config.tsx`, does it also update `src/spa/router/desktopRouter.config.desktop.tsx` with the same route paths and nesting? Single-file edits often cause drift and blank screens.
|
||||
|
||||
### Reuse
|
||||
|
||||
- Newly written code duplicates existing utilities in `packages/utils` or shared modules?
|
||||
|
||||
@@ -32,15 +32,28 @@ Hybrid routing: Next.js App Router (static pages) + React Router DOM (main SPA).
|
||||
| Route Type | Use Case | Implementation |
|
||||
| ------------------ | --------------------------------- | ---------------------------- |
|
||||
| Next.js App Router | Auth pages (login, signup, oauth) | `src/app/[variants]/(auth)/` |
|
||||
| React Router DOM | Main SPA (chat, settings) | `desktopRouter.config.tsx` |
|
||||
| React Router DOM | Main SPA (chat, settings) | `desktopRouter.config.tsx` + `desktopRouter.config.desktop.tsx` (must match) |
|
||||
|
||||
### Key Files
|
||||
|
||||
- Entry: `src/spa/entry.web.tsx` (web), `src/spa/entry.mobile.tsx`, `src/spa/entry.desktop.tsx`
|
||||
- Desktop router: `src/spa/router/desktopRouter.config.tsx`
|
||||
- Desktop router (pair — **always edit both** when changing routes): `src/spa/router/desktopRouter.config.tsx` (dynamic imports) and `src/spa/router/desktopRouter.config.desktop.tsx` (sync imports). Drift can cause unregistered routes / blank screen.
|
||||
- Mobile router: `src/spa/router/mobileRouter.config.tsx`
|
||||
- Router utilities: `src/utils/router.tsx`
|
||||
|
||||
### `.desktop.{ts,tsx}` File Sync Rule
|
||||
|
||||
**CRITICAL**: Some files have a `.desktop.ts(x)` variant that Electron uses instead of the base file. When editing a base file, **always check** if a `.desktop` counterpart exists and update it in sync. Drift causes blank pages or missing features in Electron.
|
||||
|
||||
Known pairs that must stay in sync:
|
||||
|
||||
| Base file (web, dynamic imports) | Desktop file (Electron, sync imports) |
|
||||
| --- | --- |
|
||||
| `src/spa/router/desktopRouter.config.tsx` | `src/spa/router/desktopRouter.config.desktop.tsx` |
|
||||
| `src/routes/(main)/settings/features/componentMap.ts` | `src/routes/(main)/settings/features/componentMap.desktop.ts` |
|
||||
|
||||
**How to check**: After editing any `.ts` / `.tsx` file, run `Glob` for `<filename>.desktop.{ts,tsx}` in the same directory. If a match exists, update it with the equivalent sync-import change.
|
||||
|
||||
### Router Utilities
|
||||
|
||||
```tsx
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: spa-routes
|
||||
description: SPA route and feature structure. Use when adding or modifying SPA routes in src/routes, defining new route segments, or moving route logic into src/features. Covers how to keep routes thin and how to divide files between routes and features.
|
||||
description: MUST use when editing src/routes/ segments, src/spa/router/desktopRouter.config.tsx or desktopRouter.config.desktop.tsx (always change both together), mobileRouter.config.tsx, or when moving UI/logic between routes and src/features/.
|
||||
---
|
||||
|
||||
# SPA Routes and Features Guide
|
||||
@@ -13,6 +13,8 @@ SPA structure:
|
||||
|
||||
This project uses a **roots vs features** split: `src/routes/` only holds page segments; business logic and UI live in `src/features/` by domain.
|
||||
|
||||
**Agent constraint — desktop router parity:** Edits to the desktop route tree must update **both** `src/spa/router/desktopRouter.config.tsx` and `src/spa/router/desktopRouter.config.desktop.tsx` in the same change (same paths, nesting, index routes, and segment registration). Updating only one causes drift; the missing tree can fail to register routes and surface as a **blank screen** or broken navigation on the affected build.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
- Adding a new SPA route or route segment
|
||||
@@ -73,8 +75,21 @@ Each feature should:
|
||||
- Layout: `export { default } from '@/features/MyFeature/MyLayout'` or compose a few feature components + `<Outlet />`.
|
||||
- Page: import from `@/features/MyFeature` (or a specific subpath) and render; no business logic in the route file.
|
||||
|
||||
5. **Register the route**
|
||||
- Add the segment to `src/spa/router/desktopRouter.config.tsx` (or the right router config) with `dynamicElement` / `dynamicLayout` pointing at the new route paths (e.g. `@/routes/(main)/my-feature`).
|
||||
5. **Register the route (desktop — two files, always)**
|
||||
- **`desktopRouter.config.tsx`:** Add the segment with `dynamicElement` / `dynamicLayout` pointing at route modules (e.g. `@/routes/(main)/my-feature`).
|
||||
- **`desktopRouter.config.desktop.tsx`:** Mirror the **same** `RouteObject` shape: identical `path` / `index` / parent-child structure. Use the static imports and elements already used in that file (see neighboring routes). Do **not** register in only one of these files.
|
||||
- **Mobile-only flows:** use `mobileRouter.config.tsx` instead (no need to duplicate into the desktop pair unless the route truly exists on both).
|
||||
|
||||
---
|
||||
|
||||
## 3a. Desktop router pair (`desktopRouter.config` × 2)
|
||||
|
||||
| File | Role |
|
||||
|------|------|
|
||||
| `desktopRouter.config.tsx` | Dynamic imports via `dynamicElement` / `dynamicLayout` — code-splitting; used by `entry.web.tsx` and `entry.desktop.tsx`. |
|
||||
| `desktopRouter.config.desktop.tsx` | Same route tree with **synchronous** imports — kept for Electron / local parity and predictable bundling. |
|
||||
|
||||
Anything that changes the tree (new segment, renamed `path`, moved layout, new child route) must be reflected in **both** files in one PR or commit. Remove routes from both when deleting.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@ const Installation = memo<{ mobile?: boolean }>(({ mobile }) => {
|
||||
|
||||
return (
|
||||
<Platform
|
||||
downloadUrl={downloadUrl}
|
||||
expandCodeByDefault
|
||||
downloadUrl={downloadUrl}
|
||||
identifier={identifier}
|
||||
mobile={mobile}
|
||||
/>
|
||||
|
||||
@@ -22,6 +22,9 @@ const Versions = memo(() => {
|
||||
<Title>{t('skills.details.versions.title')}</Title>
|
||||
<Block variant={'outlined'}>
|
||||
<InlineTable
|
||||
dataSource={versions}
|
||||
rowKey={'version'}
|
||||
size={'middle'}
|
||||
columns={[
|
||||
{
|
||||
dataIndex: 'version',
|
||||
@@ -35,7 +38,7 @@ const Versions = memo(() => {
|
||||
url: pathname,
|
||||
})}
|
||||
>
|
||||
<Flexbox align={'center'} gap={8} horizontal>
|
||||
<Flexbox horizontal align={'center'} gap={8}>
|
||||
<code style={{ fontSize: 14 }}>{record.version}</code>
|
||||
{record.isLatest && (
|
||||
<Tag color={'info'}>{t('skills.details.versions.table.isLatest')}</Tag>
|
||||
@@ -52,9 +55,6 @@ const Versions = memo(() => {
|
||||
title: t('skills.details.versions.table.publishAt'),
|
||||
},
|
||||
]}
|
||||
dataSource={versions}
|
||||
rowKey={'version'}
|
||||
size={'middle'}
|
||||
/>
|
||||
</Block>
|
||||
</Flexbox>
|
||||
|
||||
@@ -80,8 +80,8 @@ const Header = memo<{ mobile?: boolean }>(({ mobile }) => {
|
||||
const resourcesCount = (Object.values(resources || {})?.length || 0) + 1;
|
||||
|
||||
const scores = (
|
||||
<Flexbox align={'center'} className={styles.extraTag} gap={16} horizontal>
|
||||
<Flexbox align={'center'} className={styles.extraTagActive} gap={8} horizontal>
|
||||
<Flexbox horizontal align={'center'} className={styles.extraTag} gap={16}>
|
||||
<Flexbox horizontal align={'center'} className={styles.extraTagActive} gap={8}>
|
||||
<Icon icon={FileTextIcon} size={14} />
|
||||
{resourcesCount}
|
||||
</Flexbox>
|
||||
@@ -103,7 +103,7 @@ const Header = memo<{ mobile?: boolean }>(({ mobile }) => {
|
||||
|
||||
return (
|
||||
<Flexbox gap={12}>
|
||||
<Flexbox align={'flex-start'} gap={16} horizontal width={'100%'}>
|
||||
<Flexbox horizontal align={'flex-start'} gap={16} width={'100%'}>
|
||||
<Avatar avatar={icon || name} size={mobile ? 48 : 64} />
|
||||
<Flexbox
|
||||
flex={1}
|
||||
@@ -113,9 +113,9 @@ const Header = memo<{ mobile?: boolean }>(({ mobile }) => {
|
||||
}}
|
||||
>
|
||||
<Flexbox
|
||||
horizontal
|
||||
align={'center'}
|
||||
gap={8}
|
||||
horizontal
|
||||
justify={'space-between'}
|
||||
style={{
|
||||
overflow: 'hidden',
|
||||
@@ -123,18 +123,18 @@ const Header = memo<{ mobile?: boolean }>(({ mobile }) => {
|
||||
}}
|
||||
>
|
||||
<Flexbox
|
||||
horizontal
|
||||
align={'center'}
|
||||
flex={1}
|
||||
gap={12}
|
||||
horizontal
|
||||
style={{
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
as={'h1'}
|
||||
ellipsis
|
||||
as={'h1'}
|
||||
style={{ fontSize: mobile ? 18 : 24, margin: 0 }}
|
||||
title={identifier}
|
||||
>
|
||||
@@ -142,22 +142,22 @@ const Header = memo<{ mobile?: boolean }>(({ mobile }) => {
|
||||
</Text>
|
||||
{!mobile && scores}
|
||||
</Flexbox>
|
||||
<Flexbox align={'center'} gap={6} horizontal>
|
||||
<Flexbox horizontal align={'center'} gap={6}>
|
||||
{homepage && (
|
||||
<a
|
||||
href={homepage}
|
||||
onClick={stopPropagation}
|
||||
rel="noopener noreferrer"
|
||||
target={'_blank'}
|
||||
onClick={stopPropagation}
|
||||
>
|
||||
<ActionIcon fill={cssVar.colorTextDescription} icon={Github} />
|
||||
</a>
|
||||
)}
|
||||
</Flexbox>
|
||||
</Flexbox>
|
||||
<Flexbox align={'center'} gap={4} horizontal>
|
||||
<Flexbox horizontal align={'center'} gap={4}>
|
||||
{Boolean(ratingAverage) ? (
|
||||
<Flexbox align={'center'} gap={8} horizontal>
|
||||
<Flexbox horizontal align={'center'} gap={8}>
|
||||
<Icon fill={cssVar.colorWarning} icon={StarIcon} size={14} />
|
||||
<Text weight={500}>{ratingAverage?.toFixed(1)}</Text>
|
||||
</Flexbox>
|
||||
@@ -182,37 +182,37 @@ const Header = memo<{ mobile?: boolean }>(({ mobile }) => {
|
||||
</Flexbox>
|
||||
</Flexbox>
|
||||
<Flexbox
|
||||
horizontal
|
||||
align={'center'}
|
||||
gap={mobile ? 12 : 24}
|
||||
horizontal
|
||||
wrap={'wrap'}
|
||||
style={{
|
||||
color: cssVar.colorTextSecondary,
|
||||
}}
|
||||
wrap={'wrap'}
|
||||
>
|
||||
{mobile && scores}
|
||||
{!mobile && cateButton}
|
||||
<Flexbox align={'center'} gap={mobile ? 12 : 24} horizontal wrap={'wrap'}>
|
||||
<Flexbox horizontal align={'center'} gap={mobile ? 12 : 24} wrap={'wrap'}>
|
||||
{Boolean(license?.name) && (
|
||||
<Flexbox align={'center'} gap={6} horizontal>
|
||||
<Flexbox horizontal align={'center'} gap={6}>
|
||||
<Icon icon={ScaleIcon} size={14} />
|
||||
{license?.name}
|
||||
</Flexbox>
|
||||
)}
|
||||
{Boolean(installCount) && (
|
||||
<Flexbox align={'center'} gap={6} horizontal>
|
||||
<Flexbox horizontal align={'center'} gap={6}>
|
||||
<Icon icon={DownloadIcon} size={14} />
|
||||
{formatCompactNumber(installCount)}
|
||||
</Flexbox>
|
||||
)}
|
||||
{Boolean(github?.stars) && (
|
||||
<Flexbox align={'center'} gap={6} horizontal>
|
||||
<Flexbox horizontal align={'center'} gap={6}>
|
||||
<Icon icon={StarIcon} size={14} />
|
||||
{formatCompactNumber(github?.stars)}
|
||||
</Flexbox>
|
||||
)}
|
||||
{Boolean(comments?.totalCount) && (
|
||||
<Flexbox align={'center'} gap={6} horizontal>
|
||||
<Flexbox horizontal align={'center'} gap={6}>
|
||||
<Icon icon={MessageSquare} size={14} />
|
||||
{formatCompactNumber(comments?.totalCount)}
|
||||
</Flexbox>
|
||||
|
||||
@@ -27,7 +27,7 @@ const InstallationConfig = memo(() => {
|
||||
<Title more={t('mcp.details.sidebar.moreServerConfig')} moreLink={installLink}>
|
||||
{t('skills.details.sidebar.installationConfig')}
|
||||
</Title>
|
||||
<Platform downloadUrl={downloadUrl} expandCodeByDefault identifier={identifier} lite />
|
||||
<Platform expandCodeByDefault lite downloadUrl={downloadUrl} identifier={identifier} />
|
||||
</Flexbox>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -9,6 +9,7 @@ import About from '../about';
|
||||
import Advanced from '../advanced';
|
||||
import APIKey from '../apikey';
|
||||
import Appearance from '../appearance';
|
||||
import Creds from '../creds';
|
||||
import Hotkey from '../hotkey';
|
||||
import Memory from '../memory';
|
||||
import Profile from '../profile';
|
||||
@@ -17,6 +18,7 @@ import Proxy from '../proxy';
|
||||
import Security from '../security';
|
||||
import ServiceModel from '../service-model';
|
||||
import Skill from '../skill';
|
||||
import Stats from '../stats';
|
||||
import Storage from '../storage';
|
||||
import SystemTools from '../system-tools';
|
||||
|
||||
@@ -33,8 +35,10 @@ export const componentMap = {
|
||||
[SettingsTabs.Storage]: Storage,
|
||||
// Profile related tabs
|
||||
[SettingsTabs.Profile]: Profile,
|
||||
[SettingsTabs.Stats]: Stats,
|
||||
[SettingsTabs.Usage]: Usage,
|
||||
[SettingsTabs.APIKey]: APIKey,
|
||||
[SettingsTabs.Creds]: Creds,
|
||||
[SettingsTabs.Security]: Security,
|
||||
[SettingsTabs.Skill]: Skill,
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { componentMap as webMap } from './componentMap';
|
||||
import { componentMap as desktopMap } from './componentMap.desktop';
|
||||
|
||||
describe('componentMap desktop sync', () => {
|
||||
it('desktop keys must match web keys', () => {
|
||||
const webKeys = Object.keys(webMap).sort();
|
||||
const desktopKeys = Object.keys(desktopMap).sort();
|
||||
|
||||
const missingInDesktop = webKeys.filter((k) => !desktopKeys.includes(k));
|
||||
const extraInDesktop = desktopKeys.filter((k) => !webKeys.includes(k));
|
||||
|
||||
expect(
|
||||
missingInDesktop,
|
||||
`Missing in componentMap.desktop: ${missingInDesktop.join(', ')}`,
|
||||
).toEqual([]);
|
||||
expect(extraInDesktop, `Extra in componentMap.desktop: ${extraInDesktop.join(', ')}`).toEqual(
|
||||
[],
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -104,12 +104,12 @@ const UsernameRow = ({ mobile }: UsernameRowProps) => {
|
||||
)}
|
||||
{dirty && !saving && (
|
||||
<Button
|
||||
size="small"
|
||||
variant="outlined"
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault();
|
||||
handleCancel();
|
||||
}}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
>
|
||||
{t('profile.cancel')}
|
||||
</Button>
|
||||
@@ -125,13 +125,13 @@ const UsernameRow = ({ mobile }: UsernameRowProps) => {
|
||||
variant="filled"
|
||||
onBlur={handleSave}
|
||||
onChange={handleChange}
|
||||
onPressEnter={handleSave}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
handleCancel();
|
||||
}
|
||||
}}
|
||||
onPressEnter={handleSave}
|
||||
/>
|
||||
</Flexbox>
|
||||
</Flexbox>
|
||||
|
||||
@@ -462,3 +462,10 @@ desktopRoutes.push({
|
||||
errorElement: <ErrorBoundary resetPath="/" />,
|
||||
path: '/desktop-onboarding',
|
||||
});
|
||||
|
||||
// Web onboarding aliases redirect to the desktop-specific onboarding flow.
|
||||
desktopRoutes.push({
|
||||
element: redirectElement('/desktop-onboarding'),
|
||||
errorElement: <ErrorBoundary resetPath="/" />,
|
||||
path: '/onboarding',
|
||||
});
|
||||
|
||||
51
src/spa/router/desktopRouter.sync.test.tsx
Normal file
51
src/spa/router/desktopRouter.sync.test.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
/**
|
||||
* Known path pairs that intentionally differ between web and desktop (Electron).
|
||||
* Map: desktop path → web path
|
||||
*/
|
||||
const KNOWN_DIVERGENCES: Record<string, string> = {
|
||||
'/desktop-onboarding': '/onboarding',
|
||||
};
|
||||
|
||||
function extractIndexCount(source: string) {
|
||||
return [...source.matchAll(/index:\s*true/g)].length;
|
||||
}
|
||||
|
||||
function extractPaths(source: string) {
|
||||
return [...source.matchAll(/path:\s*'([^']+)'/g)].map((match) => match[1]);
|
||||
}
|
||||
|
||||
function normalizePaths(paths: string[]) {
|
||||
return [...new Set(paths.map((path) => KNOWN_DIVERGENCES[path] ?? path))].sort();
|
||||
}
|
||||
|
||||
describe('desktopRouter config sync', () => {
|
||||
it('desktop (sync) route paths must match web (async) route paths', async () => {
|
||||
const asyncSource = await readFile(
|
||||
join(process.cwd(), 'src/spa/router/desktopRouter.config.tsx'),
|
||||
'utf8',
|
||||
);
|
||||
const syncSource = await readFile(
|
||||
join(process.cwd(), 'src/spa/router/desktopRouter.config.desktop.tsx'),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
const asyncPaths = normalizePaths(extractPaths(asyncSource));
|
||||
const syncPaths = normalizePaths(extractPaths(syncSource));
|
||||
|
||||
const missingInSync = asyncPaths.filter((p) => !syncPaths.includes(p));
|
||||
const extraInSync = syncPaths.filter((p) => !asyncPaths.includes(p));
|
||||
const asyncIndexCount = extractIndexCount(asyncSource);
|
||||
const syncIndexCount = extractIndexCount(syncSource);
|
||||
|
||||
expect(missingInSync, `Missing in desktop config: ${missingInSync.join(', ')}`).toEqual([]);
|
||||
expect(extraInSync, `Extra in desktop config: ${extraInSync.join(', ')}`).toEqual([]);
|
||||
expect(syncIndexCount, 'Desktop config index route count must match async config').toBe(
|
||||
asyncIndexCount,
|
||||
);
|
||||
});
|
||||
});
|
||||
3
tests/mocks/emojiMartData.ts
Normal file
3
tests/mocks/emojiMartData.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
const emojiMartData = {};
|
||||
|
||||
export default emojiMartData;
|
||||
3
tests/mocks/emojiMartReact.tsx
Normal file
3
tests/mocks/emojiMartReact.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
const EmojiMartPicker = () => null;
|
||||
|
||||
export default EmojiMartPicker;
|
||||
@@ -1,12 +1,32 @@
|
||||
import { dirname, join, resolve } from 'node:path';
|
||||
import tsconfigPaths from 'vite-tsconfig-paths';
|
||||
import { coverageConfigDefaults, defineConfig } from 'vitest/config';
|
||||
|
||||
const alias = {
|
||||
'@emoji-mart/data': resolve(__dirname, './tests/mocks/emojiMartData.ts'),
|
||||
'@emoji-mart/react': resolve(__dirname, './tests/mocks/emojiMartReact.tsx'),
|
||||
'@/database/_deprecated': resolve(__dirname, './src/database/_deprecated'),
|
||||
'@/utils/client/switchLang': resolve(__dirname, './src/utils/client/switchLang'),
|
||||
'@/const/locale': resolve(__dirname, './src/const/locale'),
|
||||
// TODO: after refactor the errorResponse, we can remove it
|
||||
'@/utils/errorResponse': resolve(__dirname, './src/utils/errorResponse'),
|
||||
'@/utils/unzipFile': resolve(__dirname, './src/utils/unzipFile'),
|
||||
'@/utils/server': resolve(__dirname, './src/utils/server'),
|
||||
'@/utils/identifier': resolve(__dirname, './src/utils/identifier'),
|
||||
'@/utils/electron': resolve(__dirname, './src/utils/electron'),
|
||||
'@/utils/markdownToTxt': resolve(__dirname, './src/utils/markdownToTxt'),
|
||||
'@/utils/sanitizeFileName': resolve(__dirname, './src/utils/sanitizeFileName'),
|
||||
'~test-utils': resolve(__dirname, './tests/utils.tsx'),
|
||||
'lru_map': resolve(__dirname, './tests/mocks/lru_map'),
|
||||
};
|
||||
|
||||
export default defineConfig({
|
||||
optimizeDeps: {
|
||||
exclude: ['crypto', 'util', 'tty'],
|
||||
include: ['@lobehub/tts'],
|
||||
},
|
||||
plugins: [
|
||||
tsconfigPaths({ projects: ['.'] }),
|
||||
/**
|
||||
* @lobehub/fluent-emoji@4.0.0 ships `es/FluentEmoji/style.js` but its `es/FluentEmoji/index.js`
|
||||
* imports `./style/index.js` which doesn't exist.
|
||||
@@ -38,28 +58,11 @@ export default defineConfig({
|
||||
},
|
||||
},
|
||||
],
|
||||
resolve: {
|
||||
alias,
|
||||
},
|
||||
test: {
|
||||
alias: {
|
||||
'@/database/_deprecated': resolve(__dirname, './src/database/_deprecated'),
|
||||
'@/database': resolve(__dirname, './packages/database/src'),
|
||||
'@/utils/client/switchLang': resolve(__dirname, './src/utils/client/switchLang'),
|
||||
'@/const/locale': resolve(__dirname, './src/const/locale'),
|
||||
// TODO: after refactor the errorResponse, we can remove it
|
||||
'@/utils/errorResponse': resolve(__dirname, './src/utils/errorResponse'),
|
||||
'@/utils/unzipFile': resolve(__dirname, './src/utils/unzipFile'),
|
||||
'@/utils/server': resolve(__dirname, './src/utils/server'),
|
||||
'@/utils/identifier': resolve(__dirname, './src/utils/identifier'),
|
||||
'@/utils/electron': resolve(__dirname, './src/utils/electron'),
|
||||
'@/utils/markdownToTxt': resolve(__dirname, './src/utils/markdownToTxt'),
|
||||
'@/utils/sanitizeFileName': resolve(__dirname, './src/utils/sanitizeFileName'),
|
||||
'@/utils': resolve(__dirname, './packages/utils/src'),
|
||||
'@/types': resolve(__dirname, './packages/types/src'),
|
||||
'@/const': resolve(__dirname, './packages/const/src'),
|
||||
'@': resolve(__dirname, './src'),
|
||||
'~test-utils': resolve(__dirname, './tests/utils.tsx'),
|
||||
'lru_map': resolve(__dirname, './tests/mocks/lru_map'),
|
||||
|
||||
},
|
||||
alias,
|
||||
coverage: {
|
||||
all: false,
|
||||
exclude: [
|
||||
@@ -99,6 +102,7 @@ export default defineConfig({
|
||||
deps: {
|
||||
inline: [
|
||||
'vitest-canvas-mock',
|
||||
/@emoji-mart/,
|
||||
'@lobehub/ui',
|
||||
'@lobehub/fluent-emoji',
|
||||
'@pierre/diffs',
|
||||
|
||||
Reference in New Issue
Block a user