9.4 KiB
9.4 KiB
AGENTS.md
Kintone Customize Manager - Electron + React 应用,用于管理 Kintone 自定义资源。
1. 构建命令
# 开发(HMR + 热重载)
npm run dev
# 类型检查
npx tsc --noEmit
# 构建
npm run build
# 打包
npm run package:win # Windows
npm run package:mac # macOS
npm run package:linux # Linux
# 代码质量
npm run lint # ESLint 检查
npm run format # Prettier 格式化
# 测试
npm test # 运行测试
npm run test:watch # 监听模式
npm run test:coverage # 测试覆盖率
重要: 修改 src/main/ 目录下的文件后,必须运行 npm test 确保测试通过。
2. 项目架构
src/
├── main/ # Electron 主进程
│ ├── index.ts # 入口,创建窗口
│ ├── ipc-handlers.ts # IPC 处理器(所有通信入口)
│ ├── storage.ts # 文件存储 + 密码加密
│ ├── kintone-api.ts # Kintone REST API 封装
│ └── __tests__/ # 主进程测试
├── preload/ # Preload 脚本
│ ├── index.ts # 暴露 API 到渲染进程
│ └── index.d.ts # 类型声明
├── shared/ # 跨进程共享代码
│ └── types/ # 共享类型定义
├── renderer/ # React 渲染进程
│ └── src/
│ ├── main.tsx # React 入口
│ ├── App.tsx # 根组件
│ ├── components/ # React 组件
│ └── stores/ # Zustand Stores
└── tests/ # 测试配置
├── setup.ts # 测试环境设置
└── mocks/ # Mock 文件
3. 路径别名
| 别名 | 路径 |
|---|---|
@renderer/* |
src/renderer/src/* |
@main/* |
src/main/* |
@preload/* |
src/preload/* |
@shared/* |
src/shared/* |
4. 代码风格
| 别名 | 路径 |
|---|---|
@renderer/* |
src/renderer/src/* |
@main/* |
src/main/* |
@preload/* |
src/preload/* |
@shared/* |
src/shared/* |
4. 代码风格
导入顺序
// 1. Node.js 内置模块
import { join } from "path";
import { app, BrowserWindow } from "electron";
// 2. 第三方库
import React, { useState, useEffect } from "react";
import { Button, Layout } from "antd";
// 3. 项目内部模块(别名)
import { useDomainStore } from "@renderer/stores";
// 4. 相对导入
import "./styles.css";
命名规范
- 组件文件/名:
PascalCase(e.g.,DomainManager.tsx) - 工具函数文件:
camelCase(e.g.,formatDate.ts) - Store 文件:
camelCase + Store(e.g.,domainStore.ts) - 函数/变量:
camelCase(e.g.,handleSubmit) - 常量:
UPPER_SNAKE_CASE(e.g.,MAX_FILE_SIZE) - 类型/接口:
PascalCase(e.g.,DomainConfig)
TypeScript 规范
// 显式类型定义,避免 any
interface DomainConfig {
id: string;
name: string;
authType: "password" | "api_token"; // 使用字面量联合类型
}
// 异步函数返回 Promise
async function fetchDomains(): Promise<Domain[]> {}
// 使用类型守卫处理 unknown
function parseResponse(data: unknown): DomainConfig {
if (typeof data !== "object" || data === null) {
throw new Error("Invalid response");
}
return data as DomainConfig;
}
React 组件规范
interface DomainListProps {
domains: Domain[]
onSelect: (domain: Domain) => void
}
function DomainList({ domains, onSelect }: DomainListProps) {
const [selectedId, setSelectedId] = useState<string | null>(null)
// Hooks 放在组件顶部
// 事件处理函数使用 useCallback
const handleClick = useCallback((domain: Domain) => {
setSelectedId(domain.id)
onSelect(domain)
}, [onSelect])
return <div>...</div>
}
export default DomainList
Zustand Store 规范
import { create } from "zustand";
import { persist } from "zustand/middleware";
interface DomainState {
domains: Domain[];
addDomain: (domain: Domain) => void;
}
export const useDomainStore = create<DomainState>()(
persist(
(set) => ({
domains: [],
addDomain: (domain) =>
set((state) => ({
domains: [...state.domains, domain],
})),
}),
{ name: "domain-storage" },
),
);
5. IPC 通信规范
Result 模式(所有 IPC 返回)
type Result<T> = { success: true; data: T } | { success: false; error: string };
Preload 暴露 API
const api = {
// 双向通信使用 invoke
fetchDomains: () => ipcRenderer.invoke("fetch-domains"),
// 单向通信使用 send
notify: (message: string) => ipcRenderer.send("notify", message),
};
contextBridge.exposeInMainWorld("api", api);
类型定义
// preload/index.d.ts
declare global {
interface Window {
api: ElectronAPI;
}
}
6. UI 组件规范
使用 Ant Design 6 + antd-style(不用 Tailwind):
import { createStyles } from 'antd-style'
const useStyles = createStyles(({ token, css }) => ({
container: css`
padding: ${token.paddingLG}px;
background: ${token.colorBgContainer};
border-radius: ${token.borderRadiusLG}px;
`
}))
function MyComponent() {
const { styles } = useStyles()
return <div className={styles.container}>...</div>
}
国际化:使用中文默认 import zhCN from 'antd/locale/zh_CN'
7. 安全规范
密码存储
import { safeStorage } from "electron";
// 加密
const encrypted = safeStorage.encryptString(password);
// 解密
const decrypted = safeStorage.decryptString(encrypted);
WebPreferences
必须启用:contextIsolation: true, nodeIntegration: false, sandbox: false
8. 错误处理
// 主进程 IPC
ipcMain.handle("fetch-domains", async () => {
try {
const domains = await fetchDomainsFromApi();
return { success: true, data: domains };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : "Unknown error",
};
}
});
// 渲染进程调用
const result = await window.api.fetchDomains();
if (!result.success) {
message.error(result.error);
}
9. fnm 环境配置
所有 npm/npx 命令需加载 fnm 环境:
eval "$(fnm env --use-on-cd)" && npm run dev
10. 注意事项
- ESM Only: LobeHub UI 仅支持 ESM
- React 19: 使用
@types/react@^19.0.0 - CSS 方案: 使用
antd-style,禁止 Tailwind - 禁止
as any: 使用类型守卫或unknown - 函数组件优先: 禁止 class 组件
11. MVP Phase - Breaking Changes
This is MVP phase - breaking changes are acceptable for better design. However, you MUST:
- Explain what will break and why: Document which components/APIs/workflows will be affected
- Compare old vs new approach: Show the differences and improvements
- Document the tradeoffs: What are the pros and cons of this change
- Ask for confirmation: If the change is significant (affects multiple modules or core architecture)
Examples of acceptable breaking changes during MVP:
- Refactoring data structures for better type safety
- Changing IPC communication patterns
- Restructuring component hierarchy
- Modifying store architecture
- Updating API interfaces
Process for breaking changes:
- Identify the change and its impact scope
- Document the breaking change in code comments
- Explain the reasoning and benefits
- If significant, ask user for confirmation before implementing
- Update related documentation after implementation
12. 测试规范
测试框架
使用 Vitest 作为测试框架,与 Vite 原生集成。
测试文件结构
src/main/__tests__/ # 主进程测试
tests/
├── setup.ts # 全局测试设置
└── mocks/
└── electron.ts # Electron API Mocks
测试要求
- 修改
src/main/目录下的文件后,必须运行npm test确保测试通过 - 新增 API 功能时,应在
src/main/__tests__/中添加对应测试 - 测试文件命名:
*.test.ts
运行测试
# 运行所有测试
npm test
# 监听模式
npm run test:watch
# 测试覆盖率
npm run test:coverage
Mock Electron API
测试中需要 Mock Electron 的 API(如 safeStorage、app 等),已在 tests/mocks/electron.ts 中提供。
// tests/setup.ts 中自动 Mock
vi.mock("electron", () => import("./mocks/electron"));
测试示例
import { describe, expect, it, beforeAll } from "vitest";
import { SelfKintoneClient, createKintoneClient } from "@main/kintone-api";
import type { DomainWithPassword } from "@shared/types/domain";
describe("SelfKintoneClient", () => {
let client: SelfKintoneClient;
beforeAll(() => {
client = createKintoneClient({
id: "test",
name: "Test",
domain: "example.cybozu.com",
username: "user",
password: "pass",
authType: "password",
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
});
});
it("should return the correct domain", () => {
expect(client.getDomain()).toBe("example.cybozu.com");
});
it("should test connection successfully", async () => {
const result = await client.testConnection();
expect(result.success).toBe(true);
});
});