@dyyz1993/create-agent 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/package.json +1 -1
  2. package/src/commands/create.ts +31 -31
  3. package/src/commands/workspace.ts +112 -105
  4. package/src/lib/copy.ts +24 -1
  5. package/templates/agent/electron/main.js +46 -0
  6. package/templates/agent/electron/preload.js +5 -0
  7. package/templates/agent/electron-builder.json +40 -0
  8. package/templates/agent/eslint.config.mjs +17 -2
  9. package/templates/agent/package.json +69 -3
  10. package/templates/agent/src/__tests__/hybrid-mode.test.ts +126 -0
  11. package/templates/agent/src/__tests__/server-config-security.test.ts +55 -0
  12. package/templates/agent/src/bun/index.ts +96 -41
  13. package/templates/agent/src/bun/three.d.ts +1 -0
  14. package/templates/agent/src/gateway/__tests__/ws-handler-token.test.ts +118 -0
  15. package/templates/agent/src/gateway/http-routes.ts +1 -1
  16. package/templates/agent/src/gateway/ws-handler.ts +84 -56
  17. package/templates/agent/src/mainview/App.tsx +29 -26
  18. package/templates/agent/src/mainview/__tests__/i18n/i18n.test.ts +1 -0
  19. package/templates/agent/src/mainview/__tests__/setup.ts +32 -29
  20. package/templates/agent/src/mainview/__tests__/theme-variables.test.ts +36 -0
  21. package/templates/agent/src/mainview/components/chat/ChatPanel.tsx +88 -88
  22. package/templates/agent/src/mainview/components/common/ErrorBoundary.tsx +1 -58
  23. package/templates/agent/src/mainview/components/common/LanguageSwitcher.tsx +1 -0
  24. package/templates/agent/src/mainview/components/common/ThemeToggle.tsx +1 -0
  25. package/templates/agent/src/mainview/components/feed/FeedPanel.tsx +232 -210
  26. package/templates/agent/src/mainview/components/file-preview/VirtualizedCodeView.tsx +105 -81
  27. package/templates/agent/src/mainview/components/search/SearchPanel.tsx +427 -378
  28. package/templates/agent/src/mainview/components/todo/TodoPanel.tsx +149 -122
  29. package/templates/agent/src/mainview/hooks/use-input-history.ts +70 -61
  30. package/templates/agent/src/mainview/lib/api-client.ts +179 -172
  31. package/templates/agent/src/mainview/main.tsx +4 -10
  32. package/templates/agent/src/mainview/stores/use-feed-store.ts +107 -107
  33. package/templates/agent/src/mainview/utils/drop-handler.ts +114 -115
  34. package/templates/agent/src/server-config.ts +34 -32
  35. package/templates/agent/src/server.ts +42 -54
  36. package/templates/agent/src/shared/handlers/__tests__/chat-concurrency.test.ts +127 -0
  37. package/templates/agent/src/shared/handlers/__tests__/handler-isolation.test.ts +244 -0
  38. package/templates/agent/src/shared/handlers/bash.ts +65 -65
  39. package/templates/agent/src/shared/handlers/chat.ts +189 -155
  40. package/templates/agent/src/shared/handlers/debug.ts +12 -0
  41. package/templates/agent/src/shared/handlers/feed.ts +31 -32
  42. package/templates/agent/src/shared/handlers/git.ts +286 -243
  43. package/templates/agent/src/shared/handlers/index.ts +1 -0
  44. package/templates/agent/src/shared/handlers/rules.ts +31 -31
  45. package/templates/agent/src/shared/handlers/todo.ts +31 -31
  46. package/templates/agent/src/shared/http-routes.ts +250 -0
  47. package/templates/agent/src/shared/lib/__tests__/web-server.test.ts +190 -0
  48. package/templates/agent/src/shared/lib/bash-security.ts +43 -43
  49. package/templates/agent/src/shared/lib/logger.ts +117 -95
  50. package/templates/agent/src/shared/lib/web-server.ts +128 -0
  51. package/templates/agent/src/shared/modules/debug.ts +8 -0
  52. package/templates/agent/src/shared/rpc-schema.ts +16 -3
  53. package/templates/agent/test-upload.txt +0 -0
  54. package/templates/agent/tsconfig.ipc.json +14 -0
  55. package/templates/agent/tsconfig.json +13 -3
  56. package/templates/chat/eslint.config.mjs +15 -2
  57. package/templates/chat/package.json +13 -2
  58. package/templates/chat/src/__tests__/hybrid-mode.test.ts +125 -0
  59. package/templates/chat/src/__tests__/server-config-security.test.ts +55 -0
  60. package/templates/chat/src/__tests__/server-config.test.ts +46 -44
  61. package/templates/chat/src/bun/index.ts +94 -44
  62. package/templates/chat/src/bun/three.d.ts +1 -0
  63. package/templates/chat/src/gateway/http-routes.ts +1 -1
  64. package/templates/chat/src/gateway/ws-handler.ts +84 -56
  65. package/templates/chat/src/mainview/App.tsx +6 -0
  66. package/templates/chat/src/mainview/__tests__/i18n/i18n.test.ts +1 -0
  67. package/templates/chat/src/mainview/__tests__/theme-variables.test.ts +36 -0
  68. package/templates/chat/src/mainview/components/common/LanguageSwitcher.tsx +1 -0
  69. package/templates/chat/src/mainview/components/common/ThemeToggle.tsx +1 -0
  70. package/templates/chat/src/mainview/hooks/use-input-history.ts +70 -61
  71. package/templates/chat/src/mainview/lib/api-client.ts +180 -156
  72. package/templates/chat/src/mainview/lib/rpc-cache.ts +84 -0
  73. package/templates/chat/src/mainview/main.tsx +10 -6
  74. package/templates/chat/src/server-config.ts +33 -31
  75. package/templates/chat/src/server.ts +42 -60
  76. package/templates/chat/src/shared/handlers/chat.ts +196 -162
  77. package/templates/chat/src/shared/handlers/debug.ts +12 -0
  78. package/templates/chat/src/shared/handlers/index.ts +1 -0
  79. package/templates/chat/src/shared/http-routes.ts +250 -0
  80. package/templates/chat/src/shared/lib/__tests__/logger.test.ts +92 -0
  81. package/templates/chat/src/shared/lib/logger.ts +108 -95
  82. package/templates/chat/src/shared/lib/web-server.ts +128 -0
  83. package/templates/chat/src/shared/modules/debug.ts +8 -0
  84. package/templates/chat/src/shared/rpc-schema.ts +3 -2
  85. package/templates/chat/test-upload.txt +0 -0
  86. package/templates/chat/tsconfig.ipc.json +14 -0
  87. package/templates/chat/tsconfig.json +12 -2
  88. package/templates/general/eslint.config.mjs +15 -2
  89. package/templates/general/package.json +13 -2
  90. package/templates/general/src/__tests__/hybrid-mode.test.ts +125 -0
  91. package/templates/general/src/__tests__/server-config-security.test.ts +55 -0
  92. package/templates/general/src/__tests__/server-config.test.ts +46 -44
  93. package/templates/general/src/bun/index.ts +94 -44
  94. package/templates/general/src/bun/three.d.ts +1 -0
  95. package/templates/general/src/gateway/http-routes.ts +1 -1
  96. package/templates/general/src/gateway/ws-handler.ts +84 -56
  97. package/templates/general/src/mainview/__tests__/i18n/i18n.test.ts +1 -0
  98. package/templates/general/src/mainview/__tests__/theme-variables.test.ts +36 -0
  99. package/templates/general/src/mainview/components/common/LanguageSwitcher.tsx +1 -0
  100. package/templates/general/src/mainview/components/common/ThemeToggle.tsx +1 -0
  101. package/templates/general/src/mainview/components/feed/FeedPanel.tsx +241 -219
  102. package/templates/general/src/mainview/components/file-preview/VirtualizedCodeView.tsx +101 -81
  103. package/templates/general/src/mainview/components/layout/AppLayout.tsx +6 -0
  104. package/templates/general/src/mainview/components/search/SearchPanel.tsx +429 -378
  105. package/templates/general/src/mainview/hooks/use-input-history.ts +70 -61
  106. package/templates/general/src/mainview/lib/api-client.ts +180 -156
  107. package/templates/general/src/mainview/lib/rpc-cache.ts +84 -0
  108. package/templates/general/src/mainview/main.tsx +10 -6
  109. package/templates/general/src/mainview/stores/use-feed-store.ts +107 -107
  110. package/templates/general/src/mainview/utils/drop-handler.ts +114 -115
  111. package/templates/general/src/server-config.ts +33 -31
  112. package/templates/general/src/server.ts +42 -60
  113. package/templates/general/src/shared/handlers/__tests__/chat.test.ts +165 -160
  114. package/templates/general/src/shared/handlers/chat.ts +189 -155
  115. package/templates/general/src/shared/handlers/debug.ts +12 -0
  116. package/templates/general/src/shared/handlers/feed.ts +38 -40
  117. package/templates/general/src/shared/handlers/git.ts +286 -243
  118. package/templates/general/src/shared/handlers/index.ts +1 -0
  119. package/templates/general/src/shared/http-routes.ts +250 -0
  120. package/templates/general/src/shared/lib/__tests__/logger.test.ts +92 -0
  121. package/templates/general/src/shared/lib/logger.ts +117 -95
  122. package/templates/general/src/shared/lib/web-server.ts +128 -0
  123. package/templates/general/src/shared/modules/debug.ts +8 -0
  124. package/templates/general/src/shared/rpc-schema.ts +12 -2
  125. package/templates/general/test-upload.txt +0 -0
  126. package/templates/general/tsconfig.ipc.json +14 -0
  127. package/templates/general/tsconfig.json +12 -2
  128. package/templates/shared/__tests__/cors-security.test.ts +165 -0
  129. package/templates/shared/__tests__/file-path-security.test.ts +260 -0
  130. package/templates/shared/__tests__/http-routes.test.ts +174 -0
  131. package/templates/shared/__tests__/logger.test.ts +186 -0
  132. package/templates/shared/components/ErrorBoundary.tsx +59 -0
  133. package/templates/shared/env.d.ts +11 -0
  134. package/templates/shared/http-routes.ts +250 -0
  135. package/templates/shared/logger.ts +129 -0
  136. package/templates/shared/test-upload.txt +0 -0
  137. package/templates/shared/vite-base.config.ts +92 -0
  138. package/templates/shared/vitest-base.config.ts +42 -0
  139. package/templates/agent/eslint-plugin-rpc/index.js +0 -50
  140. package/templates/agent/eslint-plugin-rpc/package.json +0 -6
  141. package/templates/agent/eslint-plugin-rpc/rules/module-file-naming.js +0 -99
  142. package/templates/agent/eslint-plugin-rpc/rules/no-bare-method.js +0 -78
  143. package/templates/agent/eslint-plugin-rpc/rules/no-direct-register.js +0 -65
  144. package/templates/agent/eslint-plugin-rpc/rules/no-hardcoded-strings.js +0 -69
  145. package/templates/agent/eslint-plugin-rpc/rules/require-api-client.js +0 -58
  146. package/templates/agent/eslint-plugin-rpc/rules/require-typed-register.js +0 -78
  147. package/templates/agent/eslint-plugin-rpc/rules/schema-merge-only.js +0 -55
  148. package/templates/chat/eslint-plugin-rpc/index.js +0 -50
  149. package/templates/chat/eslint-plugin-rpc/package.json +0 -6
  150. package/templates/chat/eslint-plugin-rpc/rules/module-file-naming.js +0 -99
  151. package/templates/chat/eslint-plugin-rpc/rules/no-bare-method.js +0 -78
  152. package/templates/chat/eslint-plugin-rpc/rules/no-direct-register.js +0 -65
  153. package/templates/chat/eslint-plugin-rpc/rules/no-hardcoded-strings.js +0 -69
  154. package/templates/chat/eslint-plugin-rpc/rules/require-api-client.js +0 -58
  155. package/templates/chat/eslint-plugin-rpc/rules/require-typed-register.js +0 -78
  156. package/templates/chat/eslint-plugin-rpc/rules/schema-merge-only.js +0 -55
  157. package/templates/general/eslint-plugin-rpc/index.js +0 -50
  158. package/templates/general/eslint-plugin-rpc/package.json +0 -6
  159. package/templates/general/eslint-plugin-rpc/rules/module-file-naming.js +0 -99
  160. package/templates/general/eslint-plugin-rpc/rules/no-bare-method.js +0 -78
  161. package/templates/general/eslint-plugin-rpc/rules/no-direct-register.js +0 -65
  162. package/templates/general/eslint-plugin-rpc/rules/no-hardcoded-strings.js +0 -69
  163. package/templates/general/eslint-plugin-rpc/rules/require-api-client.js +0 -58
  164. package/templates/general/eslint-plugin-rpc/rules/require-typed-register.js +0 -78
  165. package/templates/general/eslint-plugin-rpc/rules/schema-merge-only.js +0 -55
@@ -0,0 +1,129 @@
1
+ import { appendFile, mkdir as mkdirAsync, readdir as readdirAsync, unlink as unlinkAsync, stat as statAsync } from "fs/promises";
2
+ import { existsSync, mkdirSync } from "fs";
3
+ import { join } from "path";
4
+
5
+ export type LogLevel = "debug" | "info" | "warn" | "error";
6
+
7
+ interface LogEntry {
8
+ timestamp: string;
9
+ level: LogLevel;
10
+ module: string;
11
+ message: string;
12
+ data?: Record<string, unknown>;
13
+ }
14
+
15
+ let _logDir: string | null = null;
16
+ let _maxAgeDays = 30;
17
+
18
+ export function configureLogDir(dir: string, options?: { maxAgeDays?: number }): void {
19
+ _logDir = dir;
20
+ if (options?.maxAgeDays !== undefined) {
21
+ _maxAgeDays = options.maxAgeDays;
22
+ }
23
+ if (!existsSync(dir)) {
24
+ mkdirSync(dir, { recursive: true });
25
+ }
26
+ cleanOldLogs(dir, _maxAgeDays);
27
+ }
28
+
29
+ function getLogDir(): string {
30
+ if (!_logDir) {
31
+ _logDir = "logs";
32
+ if (!existsSync(_logDir)) {
33
+ mkdirSync(_logDir, { recursive: true });
34
+ }
35
+ }
36
+ return _logDir;
37
+ }
38
+
39
+ async function cleanOldLogs(dir: string, maxAgeDays: number): Promise<void> {
40
+ try {
41
+ if (!existsSync(dir)) return;
42
+ const files = await readdirAsync(dir);
43
+ const cutoff = Date.now() - maxAgeDays * 86400000;
44
+ for (const f of files) {
45
+ if (!f.endsWith(".log")) continue;
46
+ const filePath = join(dir, f);
47
+ const s = await statAsync(filePath);
48
+ if (s.mtimeMs < cutoff) {
49
+ await unlinkAsync(filePath);
50
+ }
51
+ }
52
+ } catch { /* ignore */ }
53
+ }
54
+
55
+ function formatLine(entry: LogEntry): string {
56
+ const base = `[${entry.timestamp}] [${entry.level.toUpperCase()}] [${entry.module}] ${entry.message}`;
57
+ return entry.data ? `${base} ${JSON.stringify(entry.data)}` : base;
58
+ }
59
+
60
+ let writeQueue: Promise<void> = Promise.resolve();
61
+
62
+ function writeToFile(line: string): void {
63
+ writeQueue = writeQueue.then(async () => {
64
+ try {
65
+ const date = new Date().toISOString().slice(0, 10);
66
+ const dir = getLogDir();
67
+ if (!existsSync(dir)) {
68
+ await mkdirAsync(dir, { recursive: true });
69
+ }
70
+ await appendFile(join(dir, `${date}.log`), `${line}\n`);
71
+ } catch { /* ignore */ }
72
+ });
73
+ }
74
+
75
+ export class Logger {
76
+ private readonly module: string;
77
+
78
+ constructor(module: string) {
79
+ this.module = module;
80
+ }
81
+
82
+ debug(message: string, data?: Record<string, unknown>): void {
83
+ this.write("debug", message, data);
84
+ }
85
+
86
+ info(message: string, data?: Record<string, unknown>): void {
87
+ this.write("info", message, data);
88
+ }
89
+
90
+ warn(message: string, data?: Record<string, unknown>): void {
91
+ this.write("warn", message, data);
92
+ }
93
+
94
+ error(message: string, data?: Record<string, unknown>): void {
95
+ this.write("error", message, data);
96
+ }
97
+
98
+ private write(level: LogLevel, message: string, data?: Record<string, unknown>): void {
99
+ const entry: LogEntry = {
100
+ timestamp: new Date().toISOString(),
101
+ level,
102
+ module: this.module,
103
+ message,
104
+ ...(data ? { data } : {}),
105
+ };
106
+
107
+ const line = formatLine(entry);
108
+
109
+ if (level === "error") {
110
+ console.error(line);
111
+ } else if (level === "warn") {
112
+ console.warn(line);
113
+ } else {
114
+ console.log(line);
115
+ }
116
+
117
+ if (process.env.NODE_ENV !== "production" || level !== "debug") {
118
+ writeToFile(line);
119
+ }
120
+ }
121
+ }
122
+
123
+ export function createLogger(module: string): Logger {
124
+ return new Logger(module);
125
+ }
126
+
127
+ export function flushLogs(): Promise<void> {
128
+ return writeQueue;
129
+ }
File without changes
@@ -0,0 +1,92 @@
1
+ import { defineConfig } from "vite";
2
+ import react from "@vitejs/plugin-react";
3
+ import { resolve } from "path";
4
+ import { readFileSync, existsSync } from "fs";
5
+ export function createViteConfig(dirname: string) {
6
+ const PORT_FILE = resolve(dirname, ".server-port");
7
+
8
+ function getBackendPort(): number {
9
+ if (existsSync(PORT_FILE)) {
10
+ try {
11
+ return parseInt(readFileSync(PORT_FILE, "utf-8").trim());
12
+ } catch {
13
+ /* fall through */
14
+ }
15
+ }
16
+ return parseInt(process.env.PORT || "3100");
17
+ }
18
+
19
+ const backendPort = getBackendPort();
20
+
21
+ return defineConfig({
22
+ plugins: [react()],
23
+ root: "src/mainview",
24
+ build: {
25
+ outDir: "../../dist",
26
+ emptyOutDir: true,
27
+ target: "es2020",
28
+ cssMinify: true,
29
+ minify: "esbuild",
30
+ reportCompressedSize: true,
31
+ chunkSizeWarningLimit: 1000,
32
+ rollupOptions: {
33
+ output: {
34
+ manualChunks: {
35
+ "vendor-react": ["react", "react-dom"],
36
+ "vendor-state": ["zustand"],
37
+ "vendor-i18n": ["i18next", "react-i18next"],
38
+ },
39
+ compact: true,
40
+ },
41
+ },
42
+ },
43
+ css: {
44
+ devSourcemap: false,
45
+ },
46
+ server: {
47
+ port: parseInt(process.env.VITE_PORT || "5173"),
48
+ strictPort: false,
49
+ watch: {
50
+ ignored: [".workspace/**"],
51
+ },
52
+ proxy: {
53
+ "/health": {
54
+ target: `http://localhost:${backendPort}`,
55
+ changeOrigin: true,
56
+ },
57
+ "/info": {
58
+ target: `http://localhost:${backendPort}`,
59
+ changeOrigin: true,
60
+ },
61
+ "/file": {
62
+ target: `http://localhost:${backendPort}`,
63
+ changeOrigin: true,
64
+ },
65
+ "/upload": {
66
+ target: `http://localhost:${backendPort}`,
67
+ changeOrigin: true,
68
+ },
69
+ "/api": {
70
+ target: `http://localhost:${backendPort}`,
71
+ changeOrigin: true,
72
+ ws: true,
73
+ },
74
+ "/ws": { target: `ws://localhost:${backendPort}`, ws: true },
75
+ },
76
+ },
77
+ resolve: {
78
+ alias: {
79
+ "@dyyz1993/rpc-core": resolve(
80
+ dirname,
81
+ "..",
82
+ "..",
83
+ "packages",
84
+ "rpc-core",
85
+ "src",
86
+ "index.ts"
87
+ ),
88
+ "@shared": resolve(dirname, "..", "shared"),
89
+ },
90
+ },
91
+ });
92
+ }
@@ -0,0 +1,42 @@
1
+ import { defineConfig } from "vitest/config";
2
+ import react from "@vitejs/plugin-react";
3
+ import { resolve } from "path";
4
+
5
+ export function createVitestConfig(dirname: string) {
6
+ return defineConfig({
7
+ plugins: [react()],
8
+ test: {
9
+ globals: true,
10
+ environment: "happy-dom",
11
+ setupFiles: ["./src/mainview/__tests__/setup.ts"],
12
+ include: [
13
+ "src/mainview/**/*.{test,spec}.{ts,tsx}",
14
+ "src/shared/**/*.{test,spec}.{ts,tsx}",
15
+ "src/__tests__/**/*.{test,spec}.{ts,tsx}",
16
+ "src/gateway/**/*.{test,spec}.{ts,tsx}",
17
+ "../shared/**/*.{test,spec}.{ts,tsx}",
18
+ ],
19
+ css: false,
20
+ coverage: {
21
+ provider: "v8",
22
+ reporter: ["text", "lcov"],
23
+ include: ["src/mainview/**/*.{ts,tsx}"],
24
+ exclude: ["src/mainview/**/types/**", "src/mainview/**/*.d.ts", "src/mainview/main.tsx"],
25
+ },
26
+ },
27
+ resolve: {
28
+ alias: {
29
+ "@dyyz1993/rpc-core": resolve(
30
+ dirname,
31
+ "..",
32
+ "..",
33
+ "packages",
34
+ "rpc-core",
35
+ "src",
36
+ "index.ts"
37
+ ),
38
+ "@shared": resolve(dirname, "..", "shared"),
39
+ },
40
+ },
41
+ });
42
+ }
@@ -1,50 +0,0 @@
1
- /**
2
- * eslint-plugin-rpc — RPC 模块化规范 ESLint 插件(单注册架构)
3
- *
4
- * 规则列表:
5
- * - rpc/no-bare-method : 方法名必须使用 module.action 格式
6
- * - rpc/no-direct-register : server.register() 只允许在 handlers/ 目录内使用
7
- * - rpc/schema-merge-only : rpc-schema.ts 禁止直接定义方法
8
- * - rpc/module-file-naming : 模块文件命名、导出、方法前缀强制规范
9
- * - rpc/require-typed-register : 入口文件必须导入 registerAllHandlers
10
- * - rpc/require-api-client : 前端必须通过 apiClient 调用 RPC
11
- */
12
- "use strict";
13
-
14
- const noBareMethod = require("./rules/no-bare-method");
15
- const noDirectRegister = require("./rules/no-direct-register");
16
- const schemaMergeOnly = require("./rules/schema-merge-only");
17
- const moduleFileNaming = require("./rules/module-file-naming");
18
- const requireTypedRegister = require("./rules/require-typed-register");
19
- const requireApiClient = require("./rules/require-api-client");
20
- const noHardcodedStrings = require("./rules/no-hardcoded-strings");
21
-
22
- module.exports = {
23
- meta: {
24
- name: "eslint-plugin-rpc",
25
- version: "1.0.0",
26
- },
27
- rules: {
28
- "no-bare-method": noBareMethod,
29
- "no-direct-register": noDirectRegister,
30
- "schema-merge-only": schemaMergeOnly,
31
- "module-file-naming": moduleFileNaming,
32
- "require-typed-register": requireTypedRegister,
33
- "require-api-client": requireApiClient,
34
- "no-hardcoded-strings": noHardcodedStrings,
35
- },
36
- configs: {
37
- recommended: {
38
- plugins: ["rpc"],
39
- rules: {
40
- "rpc/no-bare-method": "error",
41
- "rpc/no-direct-register": "error",
42
- "rpc/schema-merge-only": "error",
43
- "rpc/module-file-naming": "error",
44
- "rpc/require-typed-register": "error",
45
- "rpc/require-api-client": "error",
46
- "rpc/no-hardcoded-strings": "warn",
47
- },
48
- },
49
- },
50
- };
@@ -1,6 +0,0 @@
1
- {
2
- "name": "@dyyz1993/eslint-plugin-rpc",
3
- "version": "1.0.0",
4
- "private": true,
5
- "main": "index.js"
6
- }
@@ -1,99 +0,0 @@
1
- /**
2
- * @fileoverview 强制 RPC 模块文件的命名和导出规范
3
- */
4
- "use strict";
5
-
6
- function getModuleName(filename) {
7
- const match = filename.match(/\/modules\/([^/]+)\.[jt]sx?$/);
8
- return match ? match[1] : null;
9
- }
10
-
11
- module.exports = {
12
- meta: {
13
- type: "problem",
14
- docs: {
15
- description: "RPC 模块文件必须遵循命名和导出规范",
16
- category: "RPC Conventions",
17
- recommended: "error",
18
- },
19
- messages: {
20
- missingMethodsExport:
21
- '模块文件 "{{file}}" 必须导出 "{{ModuleName}}Methods" 接口。',
22
- methodPrefixMismatch:
23
- '方法 "{{method}}" 的前缀不匹配模块 "{{module}}"。必须以 "{{module}}." 开头。',
24
- eventPrefixMismatch:
25
- '事件 "{{event}}" 的前缀不匹配模块 "{{module}}"。必须以 "{{module}}." 开头。',
26
- },
27
- schema: [],
28
- },
29
-
30
- create(context) {
31
- const filename = context.getFilename();
32
- const moduleName = getModuleName(filename);
33
- if (!moduleName) return {};
34
-
35
- const moduleNamePascal = moduleName
36
- .split(/[-_]/)
37
- .map((s) => s.charAt(0).toUpperCase() + s.slice(1))
38
- .join("");
39
-
40
- let hasMethodsExport = false;
41
-
42
- return {
43
- TSInterfaceDeclaration(node) {
44
- const name = node.id.name;
45
-
46
- if (name === `${moduleNamePascal}Methods`) {
47
- hasMethodsExport = true;
48
-
49
- if (node.body && node.body.body) {
50
- for (const member of node.body.body) {
51
- if (member.type === "TSPropertySignature" && member.key) {
52
- const key =
53
- member.key.type === "Literal" ? String(member.key.value) : null;
54
- if (key && !key.startsWith(`${moduleName}.`)) {
55
- context.report({
56
- node: member,
57
- messageId: "methodPrefixMismatch",
58
- data: { method: key, module: moduleName },
59
- });
60
- }
61
- }
62
- }
63
- }
64
- }
65
-
66
- if (name === `${moduleNamePascal}Events`) {
67
- if (node.body && node.body.body) {
68
- for (const member of node.body.body) {
69
- if (member.type === "TSPropertySignature" && member.key) {
70
- const key =
71
- member.key.type === "Literal" ? String(member.key.value) : null;
72
- if (key && !key.startsWith(`${moduleName}.`)) {
73
- context.report({
74
- node: member,
75
- messageId: "eventPrefixMismatch",
76
- data: { event: key, module: moduleName },
77
- });
78
- }
79
- }
80
- }
81
- }
82
- }
83
- },
84
-
85
- "Program:exit"() {
86
- if (!hasMethodsExport) {
87
- context.report({
88
- loc: { line: 1, column: 0 },
89
- messageId: "missingMethodsExport",
90
- data: {
91
- file: filename.split("/").pop(),
92
- ModuleName: moduleNamePascal,
93
- },
94
- });
95
- }
96
- },
97
- };
98
- },
99
- };
@@ -1,78 +0,0 @@
1
- /**
2
- * @fileoverview 禁止使用裸方法名(无模块前缀)
3
- */
4
- "use strict";
5
-
6
- const METHOD_CALL_PATTERNS = [
7
- { calleePattern: /^register$/, argIndex: 0 },
8
- { calleePattern: /\.call$/, argIndex: 0 },
9
- { calleePattern: /\.subscribe$/, argIndex: 0 },
10
- { calleePattern: /\.emitEvent$/, argIndex: 0 },
11
- ];
12
-
13
- function isModuleMethodName(name) {
14
- const parts = name.split(".");
15
- return parts.length === 2 && parts[0].length > 0 && parts[1].length > 0;
16
- }
17
-
18
- module.exports = {
19
- meta: {
20
- type: "problem",
21
- docs: {
22
- description: "RPC 方法名必须使用 module.action 格式(禁止裸方法名)",
23
- category: "RPC Conventions",
24
- recommended: "error",
25
- },
26
- messages: {
27
- bareMethod:
28
- 'RPC 方法名 "{{name}}" 缺少模块前缀。必须使用 "module.action" 格式,如 "{{suggestion}}"。',
29
- invalidFormat:
30
- 'RPC 方法名 "{{name}}" 格式错误。必须使用 "module.action" 格式(单一 "." 分隔)。',
31
- },
32
- schema: [],
33
- },
34
-
35
- create(context) {
36
- const filename = context.getFilename();
37
-
38
- if (!/\.[jt]sx?$/.test(filename)) return {};
39
-
40
- return {
41
- CallExpression(node) {
42
- const { callee, arguments: args } = node;
43
-
44
- let calleeText = "";
45
- if (callee.type === "Identifier") {
46
- calleeText = callee.name;
47
- } else if (callee.type === "MemberExpression" && callee.property.type === "Identifier") {
48
- calleeText = `.${callee.property.name}`;
49
- }
50
-
51
- for (const pattern of METHOD_CALL_PATTERNS) {
52
- if (!pattern.calleePattern.test(calleeText)) continue;
53
- if (args.length <= pattern.argIndex) continue;
54
-
55
- const arg = args[pattern.argIndex];
56
- if (arg.type !== "Literal" || typeof arg.value !== "string") continue;
57
-
58
- const methodName = arg.value;
59
-
60
- if (!methodName.includes(".")) {
61
- const suggestion = `module.${methodName}`;
62
- context.report({
63
- node: arg,
64
- messageId: "bareMethod",
65
- data: { name: methodName, suggestion },
66
- });
67
- } else if (!isModuleMethodName(methodName)) {
68
- context.report({
69
- node: arg,
70
- messageId: "invalidFormat",
71
- data: { name: methodName },
72
- });
73
- }
74
- }
75
- },
76
- };
77
- },
78
- };
@@ -1,65 +0,0 @@
1
- /**
2
- * @fileoverview server.register() 只允许在 shared/handlers/ 目录内使用
3
- */
4
- "use strict";
5
-
6
- module.exports = {
7
- meta: {
8
- type: "problem",
9
- docs: {
10
- description:
11
- "server.register() 只允许在 shared/handlers/ 目录内使用,入口文件和其他文件禁止直接调用",
12
- category: "RPC Conventions",
13
- recommended: "error",
14
- },
15
- messages: {
16
- noDirectRegisterInEntry:
17
- "入口文件禁止直接调用 server.register()。请导入并使用 registerAllHandlers() 统一注册 handler。",
18
- noDirectRegisterGeneral:
19
- "禁止直接调用 server.register()。Handler 注册只能在 shared/handlers/ 目录内的文件中进行。",
20
- },
21
- schema: [],
22
- },
23
-
24
- create(context) {
25
- const filename = context.getFilename();
26
-
27
- if (!/src\/.*\.[jt]sx?$/.test(filename)) return {};
28
-
29
- if (filename.includes("shared/handlers/")) return {};
30
-
31
- const isEntryPoint =
32
- filename.endsWith("bun/index.ts") || filename.endsWith("server.ts");
33
-
34
- return {
35
- CallExpression(node) {
36
- const { callee } = node;
37
-
38
- if (
39
- callee.type === "MemberExpression" &&
40
- callee.property.type === "Identifier" &&
41
- callee.property.name === "register" &&
42
- callee.object.type === "Identifier"
43
- ) {
44
- const objectName = callee.object.name;
45
-
46
- const serverVarNames = [
47
- "server",
48
- "rpcServer",
49
- "rpc",
50
- "rpc_server",
51
- ];
52
-
53
- if (serverVarNames.includes(objectName)) {
54
- context.report({
55
- node,
56
- messageId: isEntryPoint
57
- ? "noDirectRegisterInEntry"
58
- : "noDirectRegisterGeneral",
59
- });
60
- }
61
- }
62
- },
63
- };
64
- },
65
- };
@@ -1,69 +0,0 @@
1
- "use strict";
2
-
3
- module.exports = {
4
- meta: {
5
- type: "suggestion",
6
- docs: {
7
- description: "disallow hardcoded strings in JSX (use i18n t() instead)",
8
- category: "Best Practices",
9
- recommended: false,
10
- },
11
- fixable: null,
12
- schema: [
13
- {
14
- type: "object",
15
- properties: {
16
- ignoreAttributes: {
17
- type: "array",
18
- items: { type: "string" },
19
- },
20
- ignoreComponents: {
21
- type: "array",
22
- items: { type: "string" },
23
- },
24
- },
25
- additionalProperties: false,
26
- },
27
- ],
28
- messages: {
29
- hardcodedString:
30
- 'Hardcoded string "{{text}}" should use i18n translation (t()).',
31
- },
32
- },
33
-
34
- create(context) {
35
- const options = context.options[0] || {};
36
- const ignoreAttributes = new Set(options.ignoreAttributes || ["className", "style", "id", "key", "ref", "type", "name", "data-testid", "viewBox", "d", "points", "x1", "x2", "y1", "y2", "strokeWidth", "strokeLinecap", "strokeLinejoin", "fill", "stroke", "xmlns"]);
37
- new Set(options.ignoreComponents || []);
38
-
39
- return {
40
- JSXText(node) {
41
- const text = node.value.trim();
42
- if (text.length > 1 && /[a-zA-Z]{2,}/.test(text)) {
43
- context.report({
44
- node,
45
- messageId: "hardcodedString",
46
- data: { text },
47
- });
48
- }
49
- },
50
- JSXAttribute(node) {
51
- if (ignoreAttributes.has(node.name.name)) return;
52
- if (
53
- node.value &&
54
- node.value.type === "Literal" &&
55
- typeof node.value.value === "string"
56
- ) {
57
- const text = node.value.value.trim();
58
- if (text.length > 3 && /[a-zA-Z]{3,}/.test(text) && !text.startsWith("var(") && !text.startsWith("bg-") && !text.startsWith("text-") && !text.startsWith("border-")) {
59
- context.report({
60
- node,
61
- messageId: "hardcodedString",
62
- data: { text },
63
- });
64
- }
65
- }
66
- },
67
- };
68
- },
69
- };
@@ -1,58 +0,0 @@
1
- /**
2
- * @fileoverview 强制前端使用 apiClient 进行 RPC 调用
3
- */
4
- "use strict";
5
-
6
- module.exports = {
7
- meta: {
8
- type: "problem",
9
- docs: {
10
- description: "前端 RPC 调用必须通过 apiClient",
11
- category: "RPC Conventions",
12
- recommended: "error",
13
- },
14
- messages: {
15
- useApiClient:
16
- "前端 RPC 调用必须通过 apiClient.call() / apiClient.subscribe()。禁止直接操作 WebSocket 或其他传输层。",
17
- },
18
- schema: [],
19
- },
20
-
21
- create(context) {
22
- const filename = context.getFilename();
23
-
24
- if (!/mainview\/.*\.[jt]sx?$/.test(filename)) return {};
25
-
26
- return {
27
- NewExpression(node) {
28
- if (
29
- node.callee.type === "Identifier" &&
30
- node.callee.name === "WebSocket"
31
- ) {
32
- context.report({
33
- node,
34
- messageId: "useApiClient",
35
- });
36
- }
37
- },
38
-
39
- CallExpression(node) {
40
- if (
41
- node.callee.type === "MemberExpression" &&
42
- node.callee.property.type === "Identifier" &&
43
- node.callee.property.name === "send"
44
- ) {
45
- if (node.callee.object.type === "Identifier") {
46
- const objName = node.callee.object.name.toLowerCase();
47
- if (objName.includes("websocket") || objName.includes("ws")) {
48
- context.report({
49
- node,
50
- messageId: "useApiClient",
51
- });
52
- }
53
- }
54
- }
55
- },
56
- };
57
- },
58
- };