Feature/upgrade (#1170)

* chore: align workspace tooling and refresh docs

Unify pnpm workspace configuration and CI versioning, upgrade create package dependencies, and update README guidance for basic and with-lingui templates.

Made-with: Cursor

* chore: regenerate route tree files

Update generated route tree outputs after recent formatting and script changes.

Made-with: Cursor

* ci: fix with-lingui e2e script name

Use the existing test:e2e script in CI to avoid missing-script failures.

Made-with: Cursor
This commit is contained in:
zuiidea
2026-04-28 18:52:45 +08:00
committed by GitHub
parent c18d288a4e
commit f28bae8f3e
11 changed files with 8397 additions and 7970 deletions
@@ -40,7 +40,7 @@ jobs:
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@v4 uses: pnpm/action-setup@v4
with: with:
version: 8.15.6 version: 10.33.2
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v4
+2 -2
View File
@@ -20,7 +20,7 @@ jobs:
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v4
with: with:
version: 10 version: 10.33.2
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
@@ -44,7 +44,7 @@ jobs:
- name: E2E (core) - name: E2E (core)
working-directory: apps/with-lingui working-directory: apps/with-lingui
run: pnpm run test:e2e:core run: pnpm run test:e2e
- name: Build - name: Build
working-directory: apps/with-lingui working-directory: apps/with-lingui
+7 -3
View File
@@ -5,10 +5,12 @@
Antd Admin provides production-style admin templates with modern frontend tooling, mock-first development, and practical testing coverage. Antd Admin provides production-style admin templates with modern frontend tooling, mock-first development, and practical testing coverage.
Available templates: Available templates:
- `basic`: English-only setup - `basic`: English-only setup
- `with-lingui`: Bilingual setup (`en` + `zh`) with Lingui - `with-lingui`: Bilingual setup (`en` + `zh`) with Lingui
Scaffolding CLI: Scaffolding CLI:
- `init-antd-admin` (from `packages/create`) - `init-antd-admin` (from `packages/create`)
## Key Features ## Key Features
@@ -45,17 +47,19 @@ pnpm dlx init-antd-admin@latest my-app --example with-lingui --skip-install
### Common options ### Common options
| Option | Description | | Option | Description |
| --- | --- | | ---------------------------- | -------------------------------- |
| `[project-directory]` | Target folder | | `[project-directory]` | Target folder |
| `-e, --example <name|url>` | Template source | | `-e, --example <name | url>` |
| `--example-path <path>` | Subpath inside remote repository | | `--example-path <path>` | Subpath inside remote repository |
| `-m, --package-manager <pm>` | `npm` / `pnpm` / `yarn` / `bun` | | `-m, --package-manager <pm>` | `npm` / `pnpm` / `yarn` / `bun` |
| `--skip-install` | Skip dependency install | | `--skip-install` | Skip dependency install |
| `--skip-transforms` | Skip rewrite transforms | | `--skip-transforms` | Skip rewrite transforms |
| `--no-git` | Skip git initialization | | `--no-git` | Skip git initialization |
For detailed CLI behavior, see [`packages/create/README.md`](./packages/create/README.md).
For detailed CLI behavior, see `[packages/create/README.md](./packages/create/README.md)`.
Production docs: [https://antd-admin-doc.zuiidea.top](https://antd-admin-doc.zuiidea.top) Production docs: [https://antd-admin-doc.zuiidea.top](https://antd-admin-doc.zuiidea.top)
+68 -54
View File
@@ -56,12 +56,9 @@ English-only admin scaffold (no Lingui): MSW mocks, minimal RBAC, full CRUD, and
### Install & Run ### Install & Run
From the monorepo root, or after `cd apps/basic`:
```bash ```bash
cd apps/basic pnpm install
vp install pnpm run dev
vp dev
``` ```
Open [http://localhost:5173](http://localhost:5173) — you'll be redirected to the login page. Open [http://localhost:5173](http://localhost:5173) — you'll be redirected to the login page.
@@ -71,75 +68,92 @@ Open [http://localhost:5173](http://localhost:5173) — you'll be redirected to
### Build ### Build
```bash ```bash
vp build pnpm run build
vp preview pnpm run preview
```
### Project Setup
```bash
pnpm run prepare
```
### Code Quality
```bash
pnpm run fmt
pnpm run lint
pnpm run check
```
### Unit Tests
```bash
pnpm run test:unit
``` ```
### E2E Tests ### E2E Tests
```bash ```bash
cd apps/basic
pnpm run test:e2e pnpm run test:e2e
pnpm run test:e2e:core pnpm run test:e2e:core
pnpm run test:e2e:ui # interactive UI mode pnpm run test:e2e:ui # interactive UI mode
``` ```
`test:e2e:core` runs the scaffold's highest-value flows only: login and users CRUD.
## Project Structure ## Project Structure
``` ```text
src/ src/
├── api/ # Zod schemas, endpoint constants, type exports ├── api/ # Zod models + endpoint contracts
│ ├── schemas.ts # Domain models (User, AuthTokens, MenuItem, etc.) ├── components/ # Reusable UI primitives and layout shells
│ ├── auth.ts # Auth endpoint constants │ ├── Layout/ # MainLayout, Header, Sidebar, UserMenu, AppFooter
│ └── user.ts # User CRUD endpoint constants │ ├── DataTable/ # Shared table frame/skeleton/empty state
├── components/ │ ├── FormModal/ # Generic modal form wrapper
│ ├── Aurora/ # Login background effect │ ├── FilterToolbar/ # Reusable list page toolbar
│ ├── Auth/ # Auth (permission gate): index.tsx │ ├── Auth/ # Permission gate component
│ ├── DataTable/ # Shared table shell, skeleton, empty state │ ├── Aurora/ # Login page background effect
│ ├── FilterToolbar/ # Shared filter/action toolbar │ ├── Icon/ # Shared icons (GitHub, Theme, etc.)
│ ├── FormModal/ # Reusable modal + form shell │ ├── NotFound/ # Not-found visual component
│ ├── Icon/ # Shared icons and theme toggle icon │ └── RouteError.tsx # Route-level error boundary UI
│ └── Layout/ # Admin shell (sidebar, header, main) ├── hooks/ # Shared hooks (theme, CRUD, URL state, permissions)
│ ├── MainLayout/ # Main layout shell: index.tsx │ ├── useAppTheme.ts
│ ├── AppFooter/ # Login footer: Powered by + GitHub → zuiidea/antd-admin │ ├── usePermission.ts
│ ├── Sidebar/ # Dynamic menu sidebar: index.tsx │ ├── useResourceCRUD.ts
│ ├── UserMenu/ # User dropdown in sidebar: index.tsx + index.css │ ├── useCrudToasts.ts
│ └── Header/ # Top bar: index.tsx │ ├── useUrlSearchState.ts
├── hooks/ │ ├── useTableFitHeight.ts
│ ├── tokenBuilders.ts # Shared Ant Design token/config builders │ └── tokenBuilders.ts
│ ├── useAppTheme.ts # Theme selection hook (ConfigProvider) ├── mocks/ # MSW bootstrap, handlers, seed data, test helpers
│ ├── usePermission.ts # Permission check hook │ ├── browser.ts
│ └── useResourceCRUD.ts # Shared CRUD query/mutation wiring │ ├── createHandler.ts
├── utils/ │ ├── createHandler.test.ts
│ ├── constants.ts # API base URL, static assets │ ├── data.ts
│ └── http.ts # HTTP client with JWT injection & error handling │ ├── utils.ts
├── mocks/ │ └── handlers/
│ ├── browser.ts # MSW worker setup ├── routes/ # TanStack Router file routes
│ ├── createHandler.ts # Shared MSW success/error/delay helpers │ ├── __root.tsx # QueryClient + ConfigProvider (en_US) wiring
│ ├── data.ts # Mock seed data (users, menus) │ ├── _auth.tsx # Protected route layout
│ ├── utils.ts # Mock-only helpers (filters, pagination, demo avatar URLs)
│ └── handlers/ # Request handlers (auth, user CRUD)
├── routes/ # TanStack Router file-based routes
│ ├── __root.tsx # Root layout (QueryClient, ConfigProvider, en_US)
│ ├── _auth.tsx # Auth guard layout (redirects to /login)
│ ├── _auth/dashboard/index.tsx │ ├── _auth/dashboard/index.tsx
│ ├── _auth/users/index.tsx # Full CRUD with URL-synced search params │ ├── _auth/users/index.tsx
│ ├── _auth/users/-Toolbar.tsx
│ ├── _auth/users/-FormModal.tsx
│ ├── _auth/403/index.tsx │ ├── _auth/403/index.tsx
│ ├── login/index.tsx │ ├── login/index.tsx
│ ├── register/index.tsx
│ ├── 404/index.tsx │ ├── 404/index.tsx
│ └── index.tsx # Redirects / → /login │ └── index.tsx
├── stores/ ├── stores/ # Persisted auth/settings stores
│ ├── auth.ts # Auth store (tokens, user, menus, permissions) ├── utils/ # HTTP client, constants, session/menu helpers
│ ├── createPersistentStore.ts # Shared persisted-store factory ├── main.tsx # App bootstrap (MSW init + React render)
│ └── settings.ts # Settings store (darkMode, sidebar) └── routeTree.gen.ts # Generated TanStack route tree
└── main.tsx # Entry point (MSW init → React render)
e2e/ # Playwright E2E tests e2e/ # Playwright E2E suites
├── helpers.ts ├── helpers.ts
├── login.spec.ts ├── login.spec.ts
└── users.spec.ts ├── users.spec.ts
├── auth-refresh.spec.ts
├── rbac.spec.ts
└── url-state.spec.ts
``` ```
## Language & i18n ## Language & i18n
-11
View File
@@ -13,7 +13,6 @@
"check": "vp check --no-fmt", "check": "vp check --no-fmt",
"test:unit": "vitest run", "test:unit": "vitest run",
"test:e2e": "playwright test", "test:e2e": "playwright test",
"test:e2e:core": "playwright test e2e/login.spec.ts e2e/users.spec.ts e2e/auth-refresh.spec.ts e2e/rbac.spec.ts e2e/url-state.spec.ts",
"test:e2e:ui": "playwright test --ui" "test:e2e:ui": "playwright test --ui"
}, },
"dependencies": { "dependencies": {
@@ -39,16 +38,6 @@
"vitest": "3.0.5" "vitest": "3.0.5"
}, },
"packageManager": "pnpm@10.32.1", "packageManager": "pnpm@10.32.1",
"pnpm": {
"overrides": {
"vite": "npm:@voidzero-dev/vite-plus-core@latest",
"vitest": "npm:@voidzero-dev/vite-plus-test@latest"
},
"onlyBuiltDependencies": [
"esbuild",
"msw"
]
},
"msw": { "msw": {
"workerDirectory": [ "workerDirectory": [
"public" "public"
+145 -138
View File
@@ -8,184 +8,191 @@
// You should NOT make any changes in this file as it will be overwritten. // You should NOT make any changes in this file as it will be overwritten.
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
import { Route as rootRouteImport } from "./routes/__root"; import { Route as rootRouteImport } from './routes/__root'
import { Route as AuthRouteImport } from "./routes/_auth"; import { Route as AuthRouteImport } from './routes/_auth'
import { Route as IndexRouteImport } from "./routes/index"; import { Route as IndexRouteImport } from './routes/index'
import { Route as RegisterIndexRouteImport } from "./routes/register/index"; import { Route as RegisterIndexRouteImport } from './routes/register/index'
import { Route as LoginIndexRouteImport } from "./routes/login/index"; import { Route as LoginIndexRouteImport } from './routes/login/index'
import { Route as R404IndexRouteImport } from "./routes/404/index"; import { Route as R404IndexRouteImport } from './routes/404/index'
import { Route as AuthUsersIndexRouteImport } from "./routes/_auth/users/index"; import { Route as AuthUsersIndexRouteImport } from './routes/_auth/users/index'
import { Route as AuthDashboardIndexRouteImport } from "./routes/_auth/dashboard/index"; import { Route as AuthDashboardIndexRouteImport } from './routes/_auth/dashboard/index'
import { Route as Auth403IndexRouteImport } from "./routes/_auth/403/index"; import { Route as Auth403IndexRouteImport } from './routes/_auth/403/index'
const AuthRoute = AuthRouteImport.update({ const AuthRoute = AuthRouteImport.update({
id: "/_auth", id: '/_auth',
getParentRoute: () => rootRouteImport, getParentRoute: () => rootRouteImport,
} as any); } as any)
const IndexRoute = IndexRouteImport.update({ const IndexRoute = IndexRouteImport.update({
id: "/", id: '/',
path: "/", path: '/',
getParentRoute: () => rootRouteImport, getParentRoute: () => rootRouteImport,
} as any); } as any)
const RegisterIndexRoute = RegisterIndexRouteImport.update({ const RegisterIndexRoute = RegisterIndexRouteImport.update({
id: "/register/", id: '/register/',
path: "/register/", path: '/register/',
getParentRoute: () => rootRouteImport, getParentRoute: () => rootRouteImport,
} as any); } as any)
const LoginIndexRoute = LoginIndexRouteImport.update({ const LoginIndexRoute = LoginIndexRouteImport.update({
id: "/login/", id: '/login/',
path: "/login/", path: '/login/',
getParentRoute: () => rootRouteImport, getParentRoute: () => rootRouteImport,
} as any); } as any)
const R404IndexRoute = R404IndexRouteImport.update({ const R404IndexRoute = R404IndexRouteImport.update({
id: "/404/", id: '/404/',
path: "/404/", path: '/404/',
getParentRoute: () => rootRouteImport, getParentRoute: () => rootRouteImport,
} as any); } as any)
const AuthUsersIndexRoute = AuthUsersIndexRouteImport.update({ const AuthUsersIndexRoute = AuthUsersIndexRouteImport.update({
id: "/users/", id: '/users/',
path: "/users/", path: '/users/',
getParentRoute: () => AuthRoute, getParentRoute: () => AuthRoute,
} as any); } as any)
const AuthDashboardIndexRoute = AuthDashboardIndexRouteImport.update({ const AuthDashboardIndexRoute = AuthDashboardIndexRouteImport.update({
id: "/dashboard/", id: '/dashboard/',
path: "/dashboard/", path: '/dashboard/',
getParentRoute: () => AuthRoute, getParentRoute: () => AuthRoute,
} as any); } as any)
const Auth403IndexRoute = Auth403IndexRouteImport.update({ const Auth403IndexRoute = Auth403IndexRouteImport.update({
id: "/403/", id: '/403/',
path: "/403/", path: '/403/',
getParentRoute: () => AuthRoute, getParentRoute: () => AuthRoute,
} as any); } as any)
export interface FileRoutesByFullPath { export interface FileRoutesByFullPath {
"/": typeof IndexRoute; '/': typeof IndexRoute
"/404/": typeof R404IndexRoute; '/404/': typeof R404IndexRoute
"/login/": typeof LoginIndexRoute; '/login/': typeof LoginIndexRoute
"/register/": typeof RegisterIndexRoute; '/register/': typeof RegisterIndexRoute
"/403/": typeof Auth403IndexRoute; '/403/': typeof Auth403IndexRoute
"/dashboard/": typeof AuthDashboardIndexRoute; '/dashboard/': typeof AuthDashboardIndexRoute
"/users/": typeof AuthUsersIndexRoute; '/users/': typeof AuthUsersIndexRoute
} }
export interface FileRoutesByTo { export interface FileRoutesByTo {
"/": typeof IndexRoute; '/': typeof IndexRoute
"/404": typeof R404IndexRoute; '/404': typeof R404IndexRoute
"/login": typeof LoginIndexRoute; '/login': typeof LoginIndexRoute
"/register": typeof RegisterIndexRoute; '/register': typeof RegisterIndexRoute
"/403": typeof Auth403IndexRoute; '/403': typeof Auth403IndexRoute
"/dashboard": typeof AuthDashboardIndexRoute; '/dashboard': typeof AuthDashboardIndexRoute
"/users": typeof AuthUsersIndexRoute; '/users': typeof AuthUsersIndexRoute
} }
export interface FileRoutesById { export interface FileRoutesById {
__root__: typeof rootRouteImport; __root__: typeof rootRouteImport
"/": typeof IndexRoute; '/': typeof IndexRoute
"/_auth": typeof AuthRouteWithChildren; '/_auth': typeof AuthRouteWithChildren
"/404/": typeof R404IndexRoute; '/404/': typeof R404IndexRoute
"/login/": typeof LoginIndexRoute; '/login/': typeof LoginIndexRoute
"/register/": typeof RegisterIndexRoute; '/register/': typeof RegisterIndexRoute
"/_auth/403/": typeof Auth403IndexRoute; '/_auth/403/': typeof Auth403IndexRoute
"/_auth/dashboard/": typeof AuthDashboardIndexRoute; '/_auth/dashboard/': typeof AuthDashboardIndexRoute
"/_auth/users/": typeof AuthUsersIndexRoute; '/_auth/users/': typeof AuthUsersIndexRoute
} }
export interface FileRouteTypes { export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath; fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: "/" | "/404/" | "/login/" | "/register/" | "/403/" | "/dashboard/" | "/users/"; fullPaths:
fileRoutesByTo: FileRoutesByTo; | '/'
to: "/" | "/404" | "/login" | "/register" | "/403" | "/dashboard" | "/users"; | '/404/'
| '/login/'
| '/register/'
| '/403/'
| '/dashboard/'
| '/users/'
fileRoutesByTo: FileRoutesByTo
to: '/' | '/404' | '/login' | '/register' | '/403' | '/dashboard' | '/users'
id: id:
| "__root__" | '__root__'
| "/" | '/'
| "/_auth" | '/_auth'
| "/404/" | '/404/'
| "/login/" | '/login/'
| "/register/" | '/register/'
| "/_auth/403/" | '/_auth/403/'
| "/_auth/dashboard/" | '/_auth/dashboard/'
| "/_auth/users/"; | '/_auth/users/'
fileRoutesById: FileRoutesById; fileRoutesById: FileRoutesById
} }
export interface RootRouteChildren { export interface RootRouteChildren {
IndexRoute: typeof IndexRoute; IndexRoute: typeof IndexRoute
AuthRoute: typeof AuthRouteWithChildren; AuthRoute: typeof AuthRouteWithChildren
R404IndexRoute: typeof R404IndexRoute; R404IndexRoute: typeof R404IndexRoute
LoginIndexRoute: typeof LoginIndexRoute; LoginIndexRoute: typeof LoginIndexRoute
RegisterIndexRoute: typeof RegisterIndexRoute; RegisterIndexRoute: typeof RegisterIndexRoute
} }
declare module "@tanstack/react-router" { declare module '@tanstack/react-router' {
interface FileRoutesByPath { interface FileRoutesByPath {
"/_auth": { '/_auth': {
id: "/_auth"; id: '/_auth'
path: ""; path: ''
fullPath: "/"; fullPath: '/'
preLoaderRoute: typeof AuthRouteImport; preLoaderRoute: typeof AuthRouteImport
parentRoute: typeof rootRouteImport; parentRoute: typeof rootRouteImport
}; }
"/": { '/': {
id: "/"; id: '/'
path: "/"; path: '/'
fullPath: "/"; fullPath: '/'
preLoaderRoute: typeof IndexRouteImport; preLoaderRoute: typeof IndexRouteImport
parentRoute: typeof rootRouteImport; parentRoute: typeof rootRouteImport
}; }
"/register/": { '/register/': {
id: "/register/"; id: '/register/'
path: "/register"; path: '/register'
fullPath: "/register/"; fullPath: '/register/'
preLoaderRoute: typeof RegisterIndexRouteImport; preLoaderRoute: typeof RegisterIndexRouteImport
parentRoute: typeof rootRouteImport; parentRoute: typeof rootRouteImport
}; }
"/login/": { '/login/': {
id: "/login/"; id: '/login/'
path: "/login"; path: '/login'
fullPath: "/login/"; fullPath: '/login/'
preLoaderRoute: typeof LoginIndexRouteImport; preLoaderRoute: typeof LoginIndexRouteImport
parentRoute: typeof rootRouteImport; parentRoute: typeof rootRouteImport
}; }
"/404/": { '/404/': {
id: "/404/"; id: '/404/'
path: "/404"; path: '/404'
fullPath: "/404/"; fullPath: '/404/'
preLoaderRoute: typeof R404IndexRouteImport; preLoaderRoute: typeof R404IndexRouteImport
parentRoute: typeof rootRouteImport; parentRoute: typeof rootRouteImport
}; }
"/_auth/users/": { '/_auth/users/': {
id: "/_auth/users/"; id: '/_auth/users/'
path: "/users"; path: '/users'
fullPath: "/users/"; fullPath: '/users/'
preLoaderRoute: typeof AuthUsersIndexRouteImport; preLoaderRoute: typeof AuthUsersIndexRouteImport
parentRoute: typeof AuthRoute; parentRoute: typeof AuthRoute
}; }
"/_auth/dashboard/": { '/_auth/dashboard/': {
id: "/_auth/dashboard/"; id: '/_auth/dashboard/'
path: "/dashboard"; path: '/dashboard'
fullPath: "/dashboard/"; fullPath: '/dashboard/'
preLoaderRoute: typeof AuthDashboardIndexRouteImport; preLoaderRoute: typeof AuthDashboardIndexRouteImport
parentRoute: typeof AuthRoute; parentRoute: typeof AuthRoute
}; }
"/_auth/403/": { '/_auth/403/': {
id: "/_auth/403/"; id: '/_auth/403/'
path: "/403"; path: '/403'
fullPath: "/403/"; fullPath: '/403/'
preLoaderRoute: typeof Auth403IndexRouteImport; preLoaderRoute: typeof Auth403IndexRouteImport
parentRoute: typeof AuthRoute; parentRoute: typeof AuthRoute
}; }
} }
} }
interface AuthRouteChildren { interface AuthRouteChildren {
Auth403IndexRoute: typeof Auth403IndexRoute; Auth403IndexRoute: typeof Auth403IndexRoute
AuthDashboardIndexRoute: typeof AuthDashboardIndexRoute; AuthDashboardIndexRoute: typeof AuthDashboardIndexRoute
AuthUsersIndexRoute: typeof AuthUsersIndexRoute; AuthUsersIndexRoute: typeof AuthUsersIndexRoute
} }
const AuthRouteChildren: AuthRouteChildren = { const AuthRouteChildren: AuthRouteChildren = {
Auth403IndexRoute: Auth403IndexRoute, Auth403IndexRoute: Auth403IndexRoute,
AuthDashboardIndexRoute: AuthDashboardIndexRoute, AuthDashboardIndexRoute: AuthDashboardIndexRoute,
AuthUsersIndexRoute: AuthUsersIndexRoute, AuthUsersIndexRoute: AuthUsersIndexRoute,
}; }
const AuthRouteWithChildren = AuthRoute._addFileChildren(AuthRouteChildren); const AuthRouteWithChildren = AuthRoute._addFileChildren(AuthRouteChildren)
const rootRouteChildren: RootRouteChildren = { const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute, IndexRoute: IndexRoute,
@@ -193,7 +200,7 @@ const rootRouteChildren: RootRouteChildren = {
R404IndexRoute: R404IndexRoute, R404IndexRoute: R404IndexRoute,
LoginIndexRoute: LoginIndexRoute, LoginIndexRoute: LoginIndexRoute,
RegisterIndexRoute: RegisterIndexRoute, RegisterIndexRoute: RegisterIndexRoute,
}; }
export const routeTree = rootRouteImport export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren) ._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>(); ._addFileTypes<FileRouteTypes>()
+86 -54
View File
@@ -49,8 +49,8 @@ Balanced admin scaffold: i18n (Lingui), MSW mocks, minimal RBAC, full CRUD, and
### Install & Run ### Install & Run
```bash ```bash
vp install pnpm install
vp dev pnpm run dev
``` ```
Open [http://localhost:5173](http://localhost:5173) — you'll be redirected to the login page. Open [http://localhost:5173](http://localhost:5173) — you'll be redirected to the login page.
@@ -60,78 +60,110 @@ Open [http://localhost:5173](http://localhost:5173) — you'll be redirected to
### Build ### Build
```bash ```bash
vp build pnpm run build
vp preview pnpm run preview
```
### Project Setup
```bash
pnpm run prepare
```
### Code Quality
```bash
pnpm run fmt
pnpm run lint
pnpm run check
```
### Unit Tests
```bash
pnpm run test:unit
``` ```
### E2E Tests ### E2E Tests
```bash ```bash
vp run test:e2e pnpm run test:e2e
vp run test:e2e:core pnpm run test:e2e:core
vp run test:e2e:ui # interactive UI mode pnpm run test:e2e:ui # interactive UI mode
``` ```
`test:e2e:core` runs the scaffold's highest-value flows: login, users CRUD, auth refresh, RBAC, URL state (see `package.json` script list). ### Lingui Scripts
```bash
# extract messages from source to .po catalogs
vp exec lingui extract -- --clean
# compile catalogs to runtime messages
vp exec lingui compile
# CI-oriented check: extract + ensure no pending locale diff
pnpm run i18n:extract && git diff --exit-code -- src/locales
```
## Project Structure ## Project Structure
``` ```
.github/instructions/ # Scoped AI recipes (mirrored in ../../.cursor/rules/with-lingui-*.mdc) .github/instructions/ # Scoped AI guidance (frontend/testing/api/refactor/add-resource)
src/ src/
├── api/ # Zod schemas, endpoint constants, type exports ├── api/ # Zod models + endpoint contracts
│ ├── schemas.ts # Domain models (User, AuthTokens, MenuItem, etc.) ├── components/ # Reusable UI primitives and layout shells
│ ├── auth.ts # Auth endpoint constants │ ├── Layout/ # MainLayout, Header, Sidebar, UserMenu, AppFooter
│ └── user.ts # User CRUD endpoint constants │ ├── DataTable/ # Shared table frame/skeleton/empty state
├── components/ │ ├── FormModal/ # Generic modal form wrapper
│ ├── Aurora/ # Login background effect │ ├── FilterToolbar/ # Reusable list page toolbar
│ ├── Auth/ # Auth (permission gate): index.tsx │ ├── Auth/ # Permission gate component
│ ├── DataTable/ # Shared table shell, skeleton, empty state │ ├── Aurora/ # Login page background effect
│ ├── FilterToolbar/ # Shared filter/action toolbar │ ├── Icon/ # Shared icons (GitHub, Theme, etc.)
│ ├── FormModal/ # Reusable modal + form shell │ ├── NotFound/ # Not-found visual component
│ ├── Icon/ # Shared icons and theme toggle icon │ └── RouteError.tsx # Route-level error boundary UI
│ └── Layout/ # Admin shell (sidebar, header, main) ├── hooks/ # Shared hooks (theme, CRUD, URL state, permissions)
│ ├── MainLayout/ # Main layout shell: index.tsx │ ├── useAppTheme.ts
│ ├── AppFooter/ # Login footer: Powered by + GitHub → zuiidea/antd-admin │ ├── usePermission.ts
│ ├── Sidebar/ # Dynamic menu sidebar: index.tsx │ ├── useResourceCRUD.ts
│ ├── UserMenu/ # User dropdown in sidebar: index.tsx + index.css │ ├── useCrudToasts.ts
│ └── Header/ # Top bar: index.tsx │ ├── useUrlSearchState.ts
├── hooks/ │ ├── useTableFitHeight.ts
│ ├── tokenBuilders.ts # Shared Ant Design token/config builders │ └── tokenBuilders.ts
│ ├── useAppTheme.ts # Theme selection hook (ConfigProvider) ├── locales/ # Lingui catalogs + locale loader
│ ├── usePermission.ts # Permission check hook
│ └── useResourceCRUD.ts # Shared CRUD query/mutation wiring
├── utils/
│ ├── constants.ts # API base URL, static assets
│ └── http.ts # HTTP client with JWT injection & error handling
├── locales/ # LinguiJS catalogs (.po + compiled .js)
│ ├── en/messages.po │ ├── en/messages.po
│ └── zh/messages.po │ ├── zh/messages.po
├── mocks/ │ └── loadLocaleCatalog.ts
│ ├── browser.ts # MSW worker setup ├── mocks/ # MSW bootstrap, handlers, seed data, test helpers
│ ├── createHandler.ts # Shared MSW success/error/delay helpers │ ├── browser.ts
│ ├── data.ts # Mock seed data (users, menus) │ ├── createHandler.ts
│ ├── utils.ts # Mock-only helpers (filters, pagination, demo avatar URLs) │ ├── createHandler.test.ts
│ └── handlers/ # Request handlers (auth, users, …) │ ├── data.ts
├── routes/ # TanStack Router file-based routes │ ├── utils.ts
│ ├── __root.tsx # Root layout (QueryClient, ConfigProvider, I18n) │ └── handlers/
│ ├── _auth.tsx # Auth guard layout (redirects to /login) ├── routes/ # TanStack Router file routes
│ ├── __root.tsx # QueryClient + ConfigProvider + I18n wiring
│ ├── _auth.tsx # Protected route layout
│ ├── _auth/dashboard/index.tsx │ ├── _auth/dashboard/index.tsx
│ ├── _auth/users/index.tsx # Full CRUD with URL-synced search params │ ├── _auth/users/index.tsx
│ ├── _auth/users/-Toolbar.tsx
│ ├── _auth/users/-FormModal.tsx
│ ├── _auth/403/index.tsx │ ├── _auth/403/index.tsx
│ ├── login/index.tsx │ ├── login/index.tsx
│ ├── register/index.tsx
│ ├── 404/index.tsx │ ├── 404/index.tsx
│ └── index.tsx # Redirects / → /login │ └── index.tsx
├── stores/ ├── stores/ # Persisted auth/settings stores
│ ├── auth.ts # Auth store (tokens, user, menus, permissions) ├── utils/ # HTTP client, constants, session/menu helpers
│ ├── createPersistentStore.ts # Shared persisted-store factory ├── main.tsx # App bootstrap (MSW init + React render)
│ └── settings.ts # Settings store (darkMode, locale, sidebar) └── routeTree.gen.ts # Generated TanStack route tree
└── main.tsx # Entry point (MSW init → React render)
e2e/ # Playwright E2E tests e2e/ # Playwright E2E suites
├── helpers.ts ├── helpers.ts
├── login.spec.ts ├── login.spec.ts
└── users.spec.ts ├── users.spec.ts
├── auth-refresh.spec.ts
├── rbac.spec.ts
└── url-state.spec.ts
``` ```
-11
View File
@@ -13,7 +13,6 @@
"check": "vp check --no-fmt", "check": "vp check --no-fmt",
"test:unit": "vitest run", "test:unit": "vitest run",
"test:e2e": "playwright test", "test:e2e": "playwright test",
"test:e2e:core": "playwright test e2e/login.spec.ts e2e/users.spec.ts e2e/auth-refresh.spec.ts e2e/rbac.spec.ts e2e/url-state.spec.ts",
"test:e2e:ui": "playwright test --ui", "test:e2e:ui": "playwright test --ui",
"i18n:extract": "vp exec lingui extract -- --clean", "i18n:extract": "vp exec lingui extract -- --clean",
"i18n:compile": "vp exec lingui compile", "i18n:compile": "vp exec lingui compile",
@@ -48,16 +47,6 @@
"vitest": "4.1.5" "vitest": "4.1.5"
}, },
"packageManager": "pnpm@10.32.1", "packageManager": "pnpm@10.32.1",
"pnpm": {
"overrides": {
"vite": "npm:@voidzero-dev/vite-plus-core@latest",
"vitest": "npm:@voidzero-dev/vite-plus-test@latest"
},
"onlyBuiltDependencies": [
"esbuild",
"msw"
]
},
"msw": { "msw": {
"workerDirectory": [ "workerDirectory": [
"public" "public"
+11 -1
View File
@@ -15,5 +15,15 @@
"prettier": "^3.2.5", "prettier": "^3.2.5",
"turbo": "^2.9.6" "turbo": "^2.9.6"
}, },
"packageManager": "pnpm@8.15.6" "packageManager": "pnpm@10.33.2",
"pnpm": {
"overrides": {
"vite": "npm:@voidzero-dev/vite-plus-core@latest",
"vitest": "npm:@voidzero-dev/vite-plus-test@latest"
},
"onlyBuiltDependencies": [
"esbuild",
"msw"
]
}
} }
+3 -3
View File
@@ -26,15 +26,15 @@
"node": ">=20" "node": ">=20"
}, },
"dependencies": { "dependencies": {
"@inquirer/prompts": "^7.10.1", "@inquirer/prompts": "^8.4.2",
"commander": "^14.0.0", "commander": "^14.0.0",
"giget": "^3.2.0", "giget": "^3.2.0",
"picocolors": "^1.0.1" "picocolors": "^1.0.1"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^22.10.0", "@types/node": "^25.6.0",
"tsup": "^8.3.5", "tsup": "^8.3.5",
"typescript": "~5.9.3", "typescript": "~6.0.3",
"vitest": "^3.0.5" "vitest": "^3.0.5"
} }
} }
+8051 -7669
View File
File diff suppressed because it is too large Load Diff