From 6550275c8302cb5c86e1821090e560e2e6ad53eb Mon Sep 17 00:00:00 2001 From: Alex Zhu Date: Thu, 30 Apr 2026 12:19:54 +0800 Subject: [PATCH] fix: footer version extra quotes, add Umi/Utoo versions and commit hash fallback (#11728) Co-authored-by: Claude Opus 4.7 --- CLAUDE.md | 145 ++++++++++++++++++ README.md | 1 + README.zh-CN.md | 1 + config/config.ts | 24 ++- jest.config.ts | 8 + src/components/Footer/index.tsx | 17 +- .../login/__snapshots__/login.test.tsx.snap | 36 +++++ src/typings.d.ts | 2 + 8 files changed, 228 insertions(+), 6 deletions(-) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..9e27038b --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,145 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Ant Design Pro — an enterprise-class React UI solution built on Umi Max (v4), antd v6, and ProComponents v3. It serves as both a production boilerplate and a live demo of Ant Design's capabilities. + +## Commands + +```bash +# Development +npm start # Dev server with mock (UMI_ENV=dev) +npm run dev # Dev server without mock +npm run start:no-mock # Start with MOCK=none, proxy to real backend +npm run start:pre # Pre-production environment +npm run start:test # Test environment + +# Build & Preview +npm run build # Production build (utoopack, Turbopack-based) +npm run preview # Preview production build on port 8000 +npm run preview:build # Build and preview on port 8000 +npm run deploy # Build and deploy to GitHub Pages (gh-pages branch) +npm run analyze # Bundle size analysis + +# Lint & Type Check +npm run lint # Biome lint + tsc --noEmit +npm run biome # Biome check --write (auto-fix) +npm run tsc # Type check only (tsc --noEmit) + +# Test +npm run test # Run all Jest tests +npm run test:coverage # Jest with coverage +npm run test:update # Update test snapshots +# Run a single test: npx jest src/pages/user/login/login.test.tsx + +# Code Generation +npm run openapi # Regenerate services from config/oneapi.json (overwrites src/services/) + +# Other +npm run simple # Irreversible: removes most page blocks for minimal version +npm run i18n-remove # Remove i18n, replace with zh-CN +npm run record # Record request data for mock replay +``` + +## Documentation + +- `docs/cheatsheet.zh-CN.md` / `docs/cheatsheet.en-US.md` — comprehensive cheatsheet covering routes, layout, data flow, requests, permissions, i18n, styling, and testing with code examples. Rendered in the Welcome page via `@ant-design/x-markdown`. + +## Architecture + +### Framework: Umi Max + +Umi Max (`@umijs/max`) is the meta-framework. It wraps the build pipeline and provides conventions: + +- **Build tool**: utoopack (Turbopack-based, Webpack-compatible). Configured via `utoopack` field in `config/config.ts`, supports `module.rules` for custom loaders +- **Central config**: `config/config.ts` (defineConfig) controls all plugins, theme, proxy, and build settings +- **Routes**: Defined declaratively in `config/routes.ts`, not convention-based. Each route maps `component` to a file under `src/pages/`. Route `name` auto-maps to `menu.xxx` i18n key. Route `access` field controls menu visibility (unauthorized routes hidden from menu) +- **Convention files in `src/`**: `app.tsx` (runtime config), `access.ts` (permissions), `global.tsx` (side effects), `loading.tsx` (route transitions), `typings.d.ts` (global types) +- **Umi plugins** are enabled via config flags in `config/config.ts` (e.g., `model`, `initialState`, `access`, `locale`, `qiankun`) + +### Authentication Flow + +1. `app.tsx` exports `getInitialState()` → calls `GET /api/currentUser` +2. If 401 and not on login page → redirect to `/user/login?redirect=...` +3. `access.ts` defines permissions: `canAdmin` = `currentUser.access === 'admin'` +4. Routes use `access: 'canAdmin'` in `config/routes.ts` for permission gating +5. Login mock credentials: `admin`/`ant.design` (admin) or `user`/`ant.design` (user) + +### API & Request Layer + +- **Auto-generated services** in `src/services/ant-design-pro/` — do NOT edit manually; regenerate with `npm run openapi` +- **Per-page services**: many pages have co-located `service.ts` files +- **Request config**: centralized in `src/requestErrorConfig.ts` (error handler, interceptors, base URL). The `request` export in `app.tsx` sets global `RequestConfig` +- Built-in `request` function from `@umijs/max` — no manual axios wrapping needed +- Production API: `https://pro-api.ant-design-demo.workers.dev` +- Dev proxy config: `config/proxy.ts` (keyed by `UMI_ENV`) + +### Mock System + +- Global mocks in `mock/` (Express-style handlers matching URL patterns like `'GET /api/currentUser'`) +- Co-located page mocks: `src/pages/**/_mock.ts` (Umi auto-discovers these) +- `requestRecord.mock.js` is gitignored + +### State Management + +- **Umi model plugin** (`useModel`): files in `src/models/` auto-register as global hooks. Use `useModel('filename')` in any component +- **`useModel('@@initialState')`** for global state (currentUser, settings). Initialized by `getInitialState()` in `app.tsx` which runs once on app startup +- **`useRequest`** from `@umijs/max` for simple data fetching +- **@tanstack/react-query** for complex server state (e.g., table-list uses `useMutation` + `useQuery`) +- Most pages use ProTable's built-in `request` prop for data loading + +### Styling: Three Systems Coexist + +1. **Tailwind CSS v4** — entry: `src/tailwind.css`, PostCSS plugin in `postcss.config.js`. Best for layout utilities +2. **antd-style v4** (`createStyles`) — CSS-in-JS with design token access (`{ token }`). Preferred when consuming theme tokens +3. **CSS Modules** (`*.module.less` / `*.module.css`) — for component-scoped styles +4. **Less** (`global.less`) — legacy, only for font-face declarations and base styles + +New code should prefer Tailwind for layout, antd-style for theme-aware styles, CSS Modules for component styles. Less is only for legacy global styles. + +Dynamic theme: configured in `config/config.ts` under `antd.configProvider.theme.token`. Dev mode `SettingDrawer` allows live theme switching. + +### Internationalization + +8 locales in `src/locales/` (zh-CN, en-US, zh-TW, ja-JP, pt-BR, id-ID, fa-IR, bn-BD). Pages use `useIntl().formatMessage()` with `id` and `defaultMessage`. Menu labels auto-resolve via route `name` key (e.g., `name: 'login'` → `menu.login`). + +### Layout + +ProLayout is configured via the `layout` export in `src/app.tsx` (`RunTimeLayoutConfig`). Layout settings (theme, color, layout mode) in `config/defaultSettings.ts`. Layout modes: `side` (sidebar), `top` (top nav), `mix` (mixed). Routes with `layout: false` (e.g., `/user/*`) render without the ProLayout shell. Use `` from `@ant-design/pro-components` for page-level headers and breadcrumbs. + +### Page Co-location Pattern + +Each page directory contains its own `index.tsx`, optional `service.ts`, `_mock.ts`, `data.d.ts` (types), and `style.style.ts` or CSS files. This is the primary organizational pattern — keep page-specific code with the page. + +### Cloudflare Worker Backend + +`cloudflare-worker/` is a separate deployable (Hono framework, own `package.json`/`tsconfig.json`). Not an npm workspace — manage independently. Provides the production demo API. + +## Ant Design CLI + +`@ant-design/cli` (antd) is installed as a dev dependency. It provides offline antd component metadata and project analysis. Run via `npx antd`. + +- **Always query before writing antd code** — use `npx antd info ` to check props/APIs rather than guessing from memory +- `npx antd demo ` — get working demo code +- `npx antd token ` — check design tokens +- `npx antd semantic ` — check semantic classNames +- `npx antd lint ./src` — find deprecated or problematic antd usage +- `npx antd doctor` — diagnose project configuration issues +- `npx antd migrate ` — migration checklist between versions +- Always use `--format json` for structured output + +The CLI also supports MCP server mode: `npx antd mcp` (for IDE integrations). + +## Key Conventions + +- **Biome** replaces ESLint + Prettier. Config in `biome.json`. Pre-commit hook runs `lint-staged` with Biome. +- **Commit messages**: must follow conventional commits (`commitlint` with `@commitlint/config-conventional`). +- **TypeScript strict mode** enabled. Path aliases: `@/*` → `./src/*`, `@@/*` → `./src/.umi/*`. +- **Node >= 20** required. +- **Markdown as raw strings**: `config/md-raw-loader.cjs` lets `.md` files be imported as strings (used in Welcome/cheatsheet pages with `@ant-design/x-markdown`). +- **Auto-generated code in `src/services/`** is excluded from Biome linting. Never edit manually — regenerate with `npm run openapi`. +- **Adding a new page**: 1) Create component in `src/pages/` 2) Add route in `config/routes.ts` 3) Add menu translation in `src/locales/` (route `name` maps to `menu.xxx` i18n key) +- **Adding global state**: Create a file in `src/models/` exporting a custom Hook, use `useModel('filename')` in components +- **Access control**: Route-level via `access` field in routes; component-level via `` or `useAccess()` hook from `@umijs/max` \ No newline at end of file diff --git a/README.md b/README.md index d20479ae..787cc538 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ An out-of-box UI solution for enterprise applications as a React boilerplate. [![CI](https://github.com/ant-design/ant-design-pro/actions/workflows/ci.yml/badge.svg)](https://github.com/ant-design/ant-design-pro/actions/workflows/ci.yml) +[![GitHub release](https://img.shields.io/github/v/release/ant-design/ant-design-pro.svg)](https://github.com/ant-design/ant-design-pro/releases) [![Build With Utoo](https://img.shields.io/badge/build%20with-utoo-028fe4.svg)](https://utoo.land) [![Build With Umi](https://img.shields.io/badge/build%20with-umi-028fe4.svg)](https://umijs.org/) [![Checked with Biome](https://img.shields.io/badge/Checked_with-Biome-60a5fa?style=flat&logo=biome)](https://biomejs.dev) diff --git a/README.zh-CN.md b/README.zh-CN.md index 1c8283c0..18aab847 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -7,6 +7,7 @@ Language : [🇺🇸](./README.md) | 🇨🇳 开箱即用的中台前端/设计解决方案。 [![CI](https://github.com/ant-design/ant-design-pro/actions/workflows/ci.yml/badge.svg)](https://github.com/ant-design/ant-design-pro/actions/workflows/ci.yml) +[![GitHub release](https://img.shields.io/github/v/release/ant-design/ant-design-pro.svg)](https://github.com/ant-design/ant-design-pro/releases) [![Build With Utoo](https://img.shields.io/badge/build%20with-utoo-028fe4.svg)](https://utoo.land) [![Build With Umi](https://img.shields.io/badge/build%20with-umi-028fe4.svg)](https://umijs.org/) [![Checked with Biome](https://img.shields.io/badge/Checked_with-Biome-60a5fa?style=flat&logo=biome)](https://biomejs.dev) diff --git a/config/config.ts b/config/config.ts index d5b0ee52..2a6e4bac 100644 --- a/config/config.ts +++ b/config/config.ts @@ -9,6 +9,23 @@ import routes from './routes'; const { UMI_ENV = 'dev' } = process.env; +// Compute commit hash: env vars take precedence, fall back to git at build time +const commitHash = + process.env.COMMIT_HASH || + process.env.CF_PAGES_COMMIT_SHA || + (() => { + try { + return require('child_process') + .execSync('git rev-parse HEAD', { + stdio: ['ignore', 'pipe', 'ignore'], + encoding: 'utf-8', + }) + .trim(); + } catch { + return ''; + } + })(); + /** * @name 使用公共路径 * @description 部署时的路径,如果部署在非根目录下,需要配置这个变量 @@ -207,8 +224,9 @@ export default defineConfig({ exportStatic: {}, define: { 'process.env.CI': process.env.CI, - 'process.env.COMMIT_HASH': process.env.COMMIT_HASH || '', - 'process.env.CF_PAGES_COMMIT_SHA': process.env.CF_PAGES_COMMIT_SHA || '', - __APP_VERSION__: JSON.stringify(require('./../package.json').version), + 'process.env.COMMIT_HASH': commitHash, + __APP_VERSION__: require('./../package.json').version, + __UMI_VERSION__: require('@umijs/max/package.json').version, + __UTOO_VERSION__: require('@utoo/pack/package.json').version, }, }); diff --git a/jest.config.ts b/jest.config.ts index 9a36fc1b..6d55e1e8 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -1,5 +1,11 @@ +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; import { configUmiAlias, createConfig } from '@umijs/max/test.js'; +const readPkgVersion = (pkg: string) => + JSON.parse(readFileSync(join('node_modules', pkg, 'package.json'), 'utf-8')) + .version; + export default async (): Promise => { const config = await configUmiAlias({ ...createConfig({ @@ -23,6 +29,8 @@ export default async (): Promise => { ...config.globals, localStorage: null, __APP_VERSION__: 'test', + __UMI_VERSION__: readPkgVersion('@umijs/max'), + __UTOO_VERSION__: readPkgVersion('@utoo/pack'), }, }; }; diff --git a/src/components/Footer/index.tsx b/src/components/Footer/index.tsx index d4d62a52..1b08bf6d 100644 --- a/src/components/Footer/index.tsx +++ b/src/components/Footer/index.tsx @@ -18,9 +18,8 @@ const getRepoUrl = () => { const REPO_URL = getRepoUrl(); -// Git commit hash, can be updated via CI/CD (GitHub Actions or Cloudflare Pages) -const COMMIT_HASH = - process.env.COMMIT_HASH || process.env.CF_PAGES_COMMIT_SHA || ''; +// Git commit hash, resolved at build time from env vars or git +const COMMIT_HASH = process.env.COMMIT_HASH || ''; const Footer: React.FC = () => { return ( @@ -36,6 +35,18 @@ const Footer: React.FC = () => { href: REPO_URL, blankTarget: true, }, + { + key: 'umi', + title: `Umi ${__UMI_VERSION__}`, + href: 'https://umijs.org/', + blankTarget: true, + }, + { + key: 'utoo', + title: `Utoo ${__UTOO_VERSION__}`, + href: 'https://utoo.land', + blankTarget: true, + }, ...(COMMIT_HASH ? [ { diff --git a/src/pages/user/login/__snapshots__/login.test.tsx.snap b/src/pages/user/login/__snapshots__/login.test.tsx.snap index 7eb5afaf..dfe198a7 100644 --- a/src/pages/user/login/__snapshots__/login.test.tsx.snap +++ b/src/pages/user/login/__snapshots__/login.test.tsx.snap @@ -475,6 +475,24 @@ exports[`Login Page should login success 1`] = ` > vtest + + Umi 4.6.49 + + + Utoo 1.4.2-alpha.0 + vtest + + Umi 4.6.49 + + + Utoo 1.4.2-alpha.0 +