@lark-apaas/fullstack-presets 1.1.15 → 1.1.16

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.
@@ -3,6 +3,9 @@ export declare const customRules: {
3
3
  'require-app-container': import("eslint").Rule.RuleModule;
4
4
  'no-direct-capability-api': import("eslint").Rule.RuleModule;
5
5
  'require-scroll-reveal-hook': import("eslint").Rule.RuleModule;
6
+ 'require-number-wrapped-count': import("eslint").Rule.RuleModule;
7
+ 'require-shared-response-type': import("eslint").Rule.RuleModule;
8
+ 'require-shared-request-type': import("eslint").Rule.RuleModule;
6
9
  'no-welcome-index-route': import("eslint").Rule.RuleModule;
7
10
  'require-index-route': import("eslint").Rule.RuleModule;
8
11
  'no-duplicate-route-component': import("eslint").Rule.RuleModule;
@@ -8,6 +8,9 @@ const no_nested_styled_jsx_1 = __importDefault(require("./no-nested-styled-jsx")
8
8
  const require_app_container_1 = __importDefault(require("./require-app-container"));
9
9
  const no_direct_capability_api_1 = __importDefault(require("./no-direct-capability-api"));
10
10
  const require_scroll_reveal_hook_1 = __importDefault(require("./require-scroll-reveal-hook"));
11
+ const require_number_wrapped_count_1 = __importDefault(require("./require-number-wrapped-count"));
12
+ const require_shared_response_type_1 = __importDefault(require("./require-shared-response-type"));
13
+ const require_shared_request_type_1 = __importDefault(require("./require-shared-request-type"));
11
14
  const no_welcome_index_route_1 = __importDefault(require("./no-welcome-index-route"));
12
15
  const require_index_route_1 = __importDefault(require("./require-index-route"));
13
16
  const no_duplicate_route_component_1 = __importDefault(require("./no-duplicate-route-component"));
@@ -16,6 +19,9 @@ exports.customRules = {
16
19
  'require-app-container': require_app_container_1.default,
17
20
  'no-direct-capability-api': no_direct_capability_api_1.default,
18
21
  'require-scroll-reveal-hook': require_scroll_reveal_hook_1.default,
22
+ 'require-number-wrapped-count': require_number_wrapped_count_1.default,
23
+ 'require-shared-response-type': require_shared_response_type_1.default,
24
+ 'require-shared-request-type': require_shared_request_type_1.default,
19
25
  'no-welcome-index-route': no_welcome_index_route_1.default,
20
26
  'require-index-route': require_index_route_1.default,
21
27
  'no-duplicate-route-component': no_duplicate_route_component_1.default,
@@ -0,0 +1,11 @@
1
+ /**
2
+ * ESLint 规则:Drizzle count() 返回值必须用 Number(...) 包裹
3
+ *
4
+ * postgres-js 驱动下 Drizzle 的 count() 返回 string 而非 number,
5
+ * 不包裹 Number() 会导致类型不符预期。
6
+ *
7
+ * 通过 import 追踪确认 count 来自 drizzle-orm,避免误匹配其他 count 函数。
8
+ */
9
+ import type { Rule } from 'eslint';
10
+ declare const rule: Rule.RuleModule;
11
+ export default rule;
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const rule = {
4
+ meta: {
5
+ type: 'problem',
6
+ docs: {
7
+ description: 'Require Drizzle count() to be wrapped with Number()',
8
+ category: 'Possible Errors',
9
+ recommended: true,
10
+ },
11
+ schema: [],
12
+ messages: {
13
+ unwrappedCount: 'Drizzle count() returns string in postgres-js. Wrap with Number(count(...)) to get a numeric value.',
14
+ },
15
+ },
16
+ create(context) {
17
+ // 追踪从 drizzle-orm 导入的 count 标识符名
18
+ const drizzleCountNames = new Set();
19
+ return {
20
+ // 收集 import { count } from 'drizzle-orm' 或 import { count as x } from 'drizzle-orm'
21
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
22
+ ImportDeclaration(node) {
23
+ if (node.source.value !== 'drizzle-orm' &&
24
+ !node.source.value.startsWith('drizzle-orm/')) {
25
+ return;
26
+ }
27
+ for (const specifier of node.specifiers) {
28
+ if (specifier.type === 'ImportSpecifier' &&
29
+ specifier.imported.name === 'count') {
30
+ drizzleCountNames.add(specifier.local.name);
31
+ }
32
+ }
33
+ },
34
+ // 检查 count() 调用是否被 Number(...) 包裹
35
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
36
+ CallExpression(node) {
37
+ // 是否是 count(...) 调用
38
+ if (node.callee.type !== 'Identifier' ||
39
+ !drizzleCountNames.has(node.callee.name)) {
40
+ return;
41
+ }
42
+ // 检查父节点是否是 Number(count(...))
43
+ const parent = node.parent;
44
+ if (parent &&
45
+ parent.type === 'CallExpression' &&
46
+ parent.callee.type === 'Identifier' &&
47
+ parent.callee.name === 'Number') {
48
+ return; // 已包裹
49
+ }
50
+ // 检查是否在对象属性值位置且 key 是 count(如 { count: count() })
51
+ // 这种场景下 count() 是 select 的字段定义,不是直接赋值,跳过
52
+ if (parent &&
53
+ parent.type === 'Property' &&
54
+ parent.value === node) {
55
+ // 但如果整个 select result 后来被直接当 number 用,那是使用侧的问题
56
+ // 这里只检查 count() 的直接调用位置
57
+ return;
58
+ }
59
+ context.report({
60
+ node,
61
+ messageId: 'unwrappedCount',
62
+ });
63
+ },
64
+ };
65
+ },
66
+ };
67
+ exports.default = rule;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * ESLint 规则:客户端 HTTP 调用必须使用 shared/ 目录下定义的响应类型
3
+ *
4
+ * 强制前端 API 调用声明响应类型,且类型必须从 shared/ 导入,
5
+ * 让前后端共享类型定义,避免类型不一致。
6
+ *
7
+ * 仅检查调用自有后端接口(URL 以 /api/ 开头)的请求。
8
+ * 三方接口(绝对 URL、其他前缀)跳过,不强制 shared 类型。
9
+ *
10
+ * 检查逻辑(纯 AST):
11
+ * 1. 匹配 axiosForBackend 的 HTTP 调用
12
+ * 2. 检查第一个参数 URL 是否以 /api/ 开头(自有接口)
13
+ * 3. 检查调用是否有泛型参数,且类型从 shared/ 路径导入
14
+ */
15
+ import type { Rule } from 'eslint';
16
+ declare const rule: Rule.RuleModule;
17
+ export default rule;
@@ -0,0 +1,141 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ // 匹配的 HTTP 客户端方法
4
+ const HTTP_METHODS = new Set(['get', 'post', 'put', 'patch', 'delete', 'request']);
5
+ // 只匹配 axiosForBackend —— 前端统一使用的 HTTP 客户端
6
+ const HTTP_CLIENTS = new Set(['axiosForBackend']);
7
+ const rule = {
8
+ meta: {
9
+ type: 'problem',
10
+ docs: {
11
+ description: 'Require HTTP calls to use response types defined in shared/',
12
+ category: 'Possible Errors',
13
+ recommended: true,
14
+ },
15
+ schema: [],
16
+ messages: {
17
+ missingResponseType: 'HTTP call must declare a response type via generic parameter (e.g. apiClient.get<MyResponse>(...)). Define the type in shared/api.interface.ts.',
18
+ responseTypeNotFromShared: 'HTTP response type "{{typeName}}" must be defined in shared/api.interface.ts. Import it from @shared/ or shared/ path.',
19
+ },
20
+ },
21
+ create(context) {
22
+ // 追踪从 shared/ 路径导入的类型名
23
+ const sharedTypeNames = new Set();
24
+ return {
25
+ // 收集从 shared/ 导入的类型名
26
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
27
+ ImportDeclaration(node) {
28
+ const source = node.source.value;
29
+ if (source.includes('shared/') ||
30
+ source.startsWith('@shared/')) {
31
+ for (const specifier of node.specifiers) {
32
+ if (specifier.local?.name) {
33
+ sharedTypeNames.add(specifier.local.name);
34
+ }
35
+ }
36
+ }
37
+ },
38
+ // 检查 HTTP 调用
39
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
40
+ CallExpression(node) {
41
+ if (!isHttpCall(node))
42
+ return;
43
+ // 只检查自有后端接口(URL 以 /api/ 开头),三方接口跳过
44
+ if (!isOwnApiCall(node))
45
+ return;
46
+ // 检查是否有泛型参数 axiosForBackend.get<Type>(...)
47
+ const typeArgs = node.typeArguments || node.typeParameters;
48
+ if (!typeArgs || typeArgs.params.length === 0) {
49
+ context.report({
50
+ node: node.callee,
51
+ messageId: 'missingResponseType',
52
+ });
53
+ return;
54
+ }
55
+ // 提取第一个泛型参数的类型名
56
+ const typeParam = typeArgs.params[0];
57
+ const typeName = extractTypeName(typeParam);
58
+ if (!typeName)
59
+ return; // 基本类型或无法解析,跳过
60
+ // 检查类型是否从 shared/ 导入
61
+ if (!sharedTypeNames.has(typeName)) {
62
+ context.report({
63
+ node: typeParam,
64
+ messageId: 'responseTypeNotFromShared',
65
+ data: { typeName },
66
+ });
67
+ }
68
+ },
69
+ };
70
+ },
71
+ };
72
+ /**
73
+ * 判断是否是 HTTP 调用:
74
+ * - axios.get/post/...()
75
+ * - apiClient.get/post/...()
76
+ * - fetch() (全局)
77
+ */
78
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
79
+ function isHttpCall(node) {
80
+ const callee = node.callee;
81
+ // axios.get(url) / apiClient.post(url) 等
82
+ // 只匹配已知的 HTTP 客户端对象,避免误匹配业务方法
83
+ // 不匹配 fetch()(原生 fetch 不支持泛型参数)
84
+ if (callee.type === 'MemberExpression' &&
85
+ callee.object.type === 'Identifier' &&
86
+ callee.property.type === 'Identifier' &&
87
+ HTTP_CLIENTS.has(callee.object.name) &&
88
+ HTTP_METHODS.has(callee.property.name)) {
89
+ return true;
90
+ }
91
+ return false;
92
+ }
93
+ /**
94
+ * 判断请求是否是自有后端接口(URL 以 /api/ 开头)。
95
+ * 三方接口(绝对 URL https://...、其他前缀 /open-api/... 等)跳过。
96
+ * 无法解析 URL(变量、模板表达式)时保守跳过,宁漏勿杀。
97
+ */
98
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
99
+ function isOwnApiCall(node) {
100
+ const firstArg = node.arguments?.[0];
101
+ if (!firstArg)
102
+ return false;
103
+ // 字符串字面量: '/api/users'
104
+ if (firstArg.type === 'Literal' && typeof firstArg.value === 'string') {
105
+ return firstArg.value.startsWith('/api/') || firstArg.value.startsWith('api/');
106
+ }
107
+ // 模板字面量无表达式: `/api/users`
108
+ if (firstArg.type === 'TemplateLiteral' && firstArg.expressions.length === 0) {
109
+ const raw = firstArg.quasis[0]?.value?.raw || '';
110
+ return raw.startsWith('/api/') || raw.startsWith('api/');
111
+ }
112
+ // 模板字面量有表达式: `/api/users/${id}` — 检查开头部分
113
+ if (firstArg.type === 'TemplateLiteral' && firstArg.quasis.length > 0) {
114
+ const raw = firstArg.quasis[0]?.value?.raw || '';
115
+ return raw.startsWith('/api/') || raw.startsWith('api/');
116
+ }
117
+ // 变量或其他动态表达式 — 无法判断,保守跳过
118
+ return false;
119
+ }
120
+ /**
121
+ * 从类型参数 AST 中提取主要类型名。
122
+ */
123
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
124
+ function extractTypeName(typeNode) {
125
+ // SomeType
126
+ if (typeNode.type === 'TSTypeReference' && typeNode.typeName?.type === 'Identifier') {
127
+ return typeNode.typeName.name;
128
+ }
129
+ // SomeType[]
130
+ if (typeNode.type === 'TSArrayType') {
131
+ return extractTypeName(typeNode.elementType);
132
+ }
133
+ // 基本类型 → 跳过
134
+ if (typeNode.type === 'TSVoidKeyword' || typeNode.type === 'TSStringKeyword' ||
135
+ typeNode.type === 'TSNumberKeyword' || typeNode.type === 'TSBooleanKeyword' ||
136
+ typeNode.type === 'TSAnyKeyword') {
137
+ return null;
138
+ }
139
+ return null;
140
+ }
141
+ exports.default = rule;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * ESLint 规则:Controller 路由方法的返回类型必须定义在 shared/ 目录下
3
+ *
4
+ * 强制 agent 把 Controller 方法的返回类型写到 shared/api.interface.ts,
5
+ * 让前后端共享类型定义,避免类型不一致。
6
+ *
7
+ * 检查逻辑(纯 AST,不需要 TypeChecker):
8
+ * 1. 找到有 @Controller 装饰器的 class
9
+ * 2. 找到有 @Get/@Post/@Put/@Patch/@Delete 装饰器的方法
10
+ * 3. 检查方法是否有显式返回类型注解
11
+ * 4. 如果有,追踪类型名是否通过 import 从 shared/ 路径导入
12
+ */
13
+ import type { Rule } from 'eslint';
14
+ declare const rule: Rule.RuleModule;
15
+ export default rule;
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const HTTP_DECORATORS = new Set(['Get', 'Post', 'Put', 'Patch', 'Delete']);
4
+ const rule = {
5
+ meta: {
6
+ type: 'problem',
7
+ docs: {
8
+ description: 'Require Controller route methods to use return types defined in shared/',
9
+ category: 'Possible Errors',
10
+ recommended: true,
11
+ },
12
+ schema: [],
13
+ messages: {
14
+ missingReturnType: 'Controller route method must have an explicit return type annotation.',
15
+ returnTypeNotFromShared: 'Controller return type "{{typeName}}" must be defined in shared/api.interface.ts. Import it from @shared/ or shared/ path.',
16
+ },
17
+ },
18
+ create(context) {
19
+ // 追踪从 shared/ 路径导入的类型名
20
+ const sharedTypeNames = new Set();
21
+ return {
22
+ // 收集从 shared/ 导入的类型名
23
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
24
+ ImportDeclaration(node) {
25
+ const source = node.source.value;
26
+ if (source.includes('shared/') ||
27
+ source.startsWith('@shared/')) {
28
+ for (const specifier of node.specifiers) {
29
+ if (specifier.local?.name) {
30
+ sharedTypeNames.add(specifier.local.name);
31
+ }
32
+ }
33
+ }
34
+ },
35
+ // 检查 Controller class 的路由方法
36
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
37
+ MethodDefinition(node) {
38
+ // 检查方法是否有 HTTP 装饰器
39
+ const decorators = node.decorators;
40
+ if (!decorators || decorators.length === 0)
41
+ return;
42
+ const hasHttpDecorator = decorators.some((d) => {
43
+ const expr = d.expression;
44
+ // @Get() / @Post() 等
45
+ if (expr.type === 'CallExpression' && expr.callee.type === 'Identifier') {
46
+ return HTTP_DECORATORS.has(expr.callee.name);
47
+ }
48
+ // @Get (无括号,不常见但合法)
49
+ if (expr.type === 'Identifier') {
50
+ return HTTP_DECORATORS.has(expr.name);
51
+ }
52
+ return false;
53
+ });
54
+ if (!hasHttpDecorator)
55
+ return;
56
+ // 检查父节点是否是 @Controller 装饰的 class
57
+ const classNode = node.parent?.parent;
58
+ if (!classNode || classNode.type !== 'ClassDeclaration')
59
+ return;
60
+ const classDecorators = classNode.decorators;
61
+ if (!classDecorators)
62
+ return;
63
+ const hasControllerDecorator = classDecorators.some((d) => {
64
+ const expr = d.expression;
65
+ if (expr.type === 'CallExpression' && expr.callee.type === 'Identifier') {
66
+ return expr.callee.name === 'Controller';
67
+ }
68
+ if (expr.type === 'Identifier') {
69
+ return expr.name === 'Controller';
70
+ }
71
+ return false;
72
+ });
73
+ if (!hasControllerDecorator)
74
+ return;
75
+ // 检查方法是否有显式返回类型
76
+ const returnType = node.value?.returnType?.typeAnnotation;
77
+ if (!returnType) {
78
+ context.report({
79
+ node: node.key,
80
+ messageId: 'missingReturnType',
81
+ });
82
+ return;
83
+ }
84
+ // 提取返回类型名(处理 Promise<T> 的情况)
85
+ const typeName = extractTypeName(returnType);
86
+ if (!typeName)
87
+ return; // 无法解析的复杂类型,跳过
88
+ // 检查类型是否从 shared/ 导入
89
+ if (!sharedTypeNames.has(typeName)) {
90
+ context.report({
91
+ node: node.key,
92
+ messageId: 'returnTypeNotFromShared',
93
+ data: { typeName },
94
+ });
95
+ }
96
+ },
97
+ };
98
+ },
99
+ };
100
+ /**
101
+ * 从返回类型 AST 中提取主要类型名。
102
+ * 处理:Promise<T> → T, T → T, T[] → T
103
+ */
104
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
105
+ function extractTypeName(typeNode) {
106
+ // Promise<SomeType> → 提取 SomeType
107
+ if (typeNode.type === 'TSTypeReference' &&
108
+ typeNode.typeName?.name === 'Promise' &&
109
+ typeNode.typeArguments?.params?.length === 1) {
110
+ return extractTypeName(typeNode.typeArguments.params[0]);
111
+ }
112
+ // SomeType → 直接返回名字
113
+ if (typeNode.type === 'TSTypeReference' && typeNode.typeName?.type === 'Identifier') {
114
+ return typeNode.typeName.name;
115
+ }
116
+ // SomeType[] → 提取 SomeType
117
+ if (typeNode.type === 'TSArrayType') {
118
+ return extractTypeName(typeNode.elementType);
119
+ }
120
+ // 基本类型 (string, number, void, boolean) → 跳过检查
121
+ if (typeNode.type === 'TSVoidKeyword' || typeNode.type === 'TSStringKeyword' ||
122
+ typeNode.type === 'TSNumberKeyword' || typeNode.type === 'TSBooleanKeyword' ||
123
+ typeNode.type === 'TSAnyKeyword') {
124
+ return null;
125
+ }
126
+ return null;
127
+ }
128
+ exports.default = rule;
@@ -19,6 +19,9 @@ declare const _default: {
19
19
  'require-app-container': import("eslint").Rule.RuleModule;
20
20
  'no-direct-capability-api': import("eslint").Rule.RuleModule;
21
21
  'require-scroll-reveal-hook': import("eslint").Rule.RuleModule;
22
+ 'require-number-wrapped-count': import("eslint").Rule.RuleModule;
23
+ 'require-shared-response-type': import("eslint").Rule.RuleModule;
24
+ 'require-shared-request-type': import("eslint").Rule.RuleModule;
22
25
  'no-welcome-index-route': import("eslint").Rule.RuleModule;
23
26
  'require-index-route': import("eslint").Rule.RuleModule;
24
27
  'no-duplicate-route-component': import("eslint").Rule.RuleModule;
@@ -22,6 +22,9 @@ export declare const eslintPresets: {
22
22
  'require-app-container': import("eslint").Rule.RuleModule;
23
23
  'no-direct-capability-api': import("eslint").Rule.RuleModule;
24
24
  'require-scroll-reveal-hook': import("eslint").Rule.RuleModule;
25
+ 'require-number-wrapped-count': import("eslint").Rule.RuleModule;
26
+ 'require-shared-response-type': import("eslint").Rule.RuleModule;
27
+ 'require-shared-request-type': import("eslint").Rule.RuleModule;
25
28
  'no-welcome-index-route': import("eslint").Rule.RuleModule;
26
29
  'require-index-route': import("eslint").Rule.RuleModule;
27
30
  'no-duplicate-route-component': import("eslint").Rule.RuleModule;
@@ -17,17 +17,6 @@ declare const _default: ({
17
17
  plugins: {
18
18
  'react-hooks': any;
19
19
  import: any;
20
- '@lark-apaas': {
21
- rules: {
22
- 'no-nested-styled-jsx': import("eslint").Rule.RuleModule;
23
- 'require-app-container': import("eslint").Rule.RuleModule;
24
- 'no-direct-capability-api': import("eslint").Rule.RuleModule;
25
- 'require-scroll-reveal-hook': import("eslint").Rule.RuleModule;
26
- 'no-welcome-index-route': import("eslint").Rule.RuleModule;
27
- 'require-index-route': import("eslint").Rule.RuleModule;
28
- 'no-duplicate-route-component': import("eslint").Rule.RuleModule;
29
- };
30
- };
31
20
  };
32
21
  settings: {
33
22
  'import/resolver': {
@@ -7,7 +7,6 @@ const tseslint = require('typescript-eslint');
7
7
  const importPlugin = require('eslint-plugin-import');
8
8
  const fs = require('fs');
9
9
  const path = require('path');
10
- const custom_eslint_rules_1 = require("../../../custom-eslint-rules");
11
10
  // 检查是否启用宽松 lint 模式
12
11
  const isLooseMode = process.env.FORCE_FRAMEWORK_LINT_LOOSE_MODE === 'true';
13
12
  // 读取项目 package.json 中的 flags 配置
@@ -115,6 +114,7 @@ const strictSyntaxRules = [
115
114
  },
116
115
  ];
117
116
  // 宽松模式专用,请勿删除
117
+ // 注意:@lark-apaas 插件在 index.ts 统一注册,这里不再重复
118
118
  const looseSpecificPlugins = {};
119
119
  // 宽松模式专用,请勿删除
120
120
  const looseSpecificRules = {};
@@ -149,7 +149,6 @@ const baseConfig = {
149
149
  plugins: {
150
150
  'react-hooks': reactHooks,
151
151
  import: importPlugin,
152
- '@lark-apaas': { rules: custom_eslint_rules_1.customRules },
153
152
  ...looseSpecificPlugins,
154
153
  },
155
154
  settings: {
@@ -170,6 +169,8 @@ const baseConfig = {
170
169
  // 平台规则:禁止直接调用 capability 内部 API,应使用 capabilityClient
171
170
  '@lark-apaas/no-direct-capability-api': 'error',
172
171
  '@lark-apaas/no-nested-styled-jsx': 'error',
172
+ // 自定义规则:HTTP 调用响应类型必须定义在 shared/
173
+ '@lark-apaas/require-shared-request-type': 'error',
173
174
  // TypeScript 规则
174
175
  '@typescript-eslint/no-unused-vars': 'off', // 未使用变量检查关闭
175
176
  '@typescript-eslint/no-explicit-any': 'off', // 允许使用 any 类型
@@ -203,6 +204,12 @@ const baseConfig = {
203
204
  message: "Importing from 'next/link' is prohibited. Please use the `Link` component from 'react-router-dom' for navigation.",
204
205
  },
205
206
  ],
207
+ patterns: [
208
+ {
209
+ group: ['antd/es/table', 'antd/es/table/*', 'antd/lib/table', 'antd/lib/table/*'],
210
+ message: "Importing Table from 'antd' is prohibited. Please use '@lark-apaas/client-toolkit/antd-table' instead.",
211
+ },
212
+ ],
206
213
  },
207
214
  ],
208
215
  'no-restricted-syntax': [
@@ -52,6 +52,12 @@ declare const _default: {
52
52
  '@darraghor/nestjs-typed/api-method-should-specify-api-operation': string;
53
53
  '@darraghor/nestjs-typed/api-enum-property-best-practices': string;
54
54
  '@darraghor/nestjs-typed/all-properties-are-whitelisted': string;
55
+ '@lark-apaas/require-number-wrapped-count': string;
56
+ '@lark-apaas/require-shared-response-type': string;
57
+ 'no-restricted-syntax': (string | {
58
+ selector: string;
59
+ message: string;
60
+ })[];
55
61
  };
56
62
  };
57
63
  export default _default;
@@ -73,5 +73,17 @@ exports.default = {
73
73
  '@darraghor/nestjs-typed/api-method-should-specify-api-operation': 'off',
74
74
  '@darraghor/nestjs-typed/api-enum-property-best-practices': 'off',
75
75
  '@darraghor/nestjs-typed/all-properties-are-whitelisted': 'off',
76
+ // 自定义规则:Drizzle count() 必须 Number() 包裹
77
+ '@lark-apaas/require-number-wrapped-count': 'error',
78
+ // 自定义规则:Controller 返回类型必须定义在 shared/
79
+ '@lark-apaas/require-shared-response-type': 'error',
80
+ // Drizzle ORM 规则
81
+ 'no-restricted-syntax': [
82
+ 'error',
83
+ {
84
+ selector: "CallExpression[callee.property.name=/^(values|set)$/] Property[key.name=/^(_created_at|_updated_at|_created_by|_updated_by)$/]",
85
+ message: 'Audit columns (_created_at/_updated_at/_created_by/_updated_by) are auto-populated by the platform. Do not set them in .values() or .set() calls.',
86
+ },
87
+ ],
76
88
  },
77
89
  };
@@ -16,17 +16,6 @@ export declare const eslintPresets: {
16
16
  plugins: {
17
17
  'react-hooks': any;
18
18
  import: any;
19
- '@lark-apaas': {
20
- rules: {
21
- 'no-nested-styled-jsx': import("eslint").Rule.RuleModule;
22
- 'require-app-container': import("eslint").Rule.RuleModule;
23
- 'no-direct-capability-api': import("eslint").Rule.RuleModule;
24
- 'require-scroll-reveal-hook': import("eslint").Rule.RuleModule;
25
- 'no-welcome-index-route': import("eslint").Rule.RuleModule;
26
- 'require-index-route': import("eslint").Rule.RuleModule;
27
- 'no-duplicate-route-component': import("eslint").Rule.RuleModule;
28
- };
29
- };
30
19
  };
31
20
  settings: {
32
21
  'import/resolver': {
@@ -52,9 +41,45 @@ export declare const eslintPresets: {
52
41
  '@lark-apaas/no-duplicate-route-component': string;
53
42
  };
54
43
  } | null)[] | {
44
+ name: string;
45
+ plugins: {
46
+ '@lark-apaas': {
47
+ rules: {
48
+ 'no-nested-styled-jsx': import("eslint").Rule.RuleModule;
49
+ 'require-app-container': import("eslint").Rule.RuleModule;
50
+ 'no-direct-capability-api': import("eslint").Rule.RuleModule;
51
+ 'require-scroll-reveal-hook': import("eslint").Rule.RuleModule;
52
+ 'require-number-wrapped-count': import("eslint").Rule.RuleModule;
53
+ 'require-shared-response-type': import("eslint").Rule.RuleModule;
54
+ 'require-shared-request-type': import("eslint").Rule.RuleModule;
55
+ 'no-welcome-index-route': import("eslint").Rule.RuleModule;
56
+ 'require-index-route': import("eslint").Rule.RuleModule;
57
+ 'no-duplicate-route-component': import("eslint").Rule.RuleModule;
58
+ };
59
+ };
60
+ };
61
+ } | {
55
62
  ignores: string[];
56
63
  })[];
57
64
  server: ({
58
65
  readonly rules: Readonly<import("eslint").Linter.RulesRecord>;
59
- } | import("typescript-eslint/dist/compatibility-types").CompatibleConfig)[];
66
+ } | import("typescript-eslint/dist/compatibility-types").CompatibleConfig | {
67
+ name: string;
68
+ plugins: {
69
+ '@lark-apaas': {
70
+ rules: {
71
+ 'no-nested-styled-jsx': import("eslint").Rule.RuleModule;
72
+ 'require-app-container': import("eslint").Rule.RuleModule;
73
+ 'no-direct-capability-api': import("eslint").Rule.RuleModule;
74
+ 'require-scroll-reveal-hook': import("eslint").Rule.RuleModule;
75
+ 'require-number-wrapped-count': import("eslint").Rule.RuleModule;
76
+ 'require-shared-response-type': import("eslint").Rule.RuleModule;
77
+ 'require-shared-request-type': import("eslint").Rule.RuleModule;
78
+ 'no-welcome-index-route': import("eslint").Rule.RuleModule;
79
+ 'require-index-route': import("eslint").Rule.RuleModule;
80
+ 'no-duplicate-route-component': import("eslint").Rule.RuleModule;
81
+ };
82
+ };
83
+ };
84
+ })[];
60
85
  };
@@ -9,6 +9,7 @@ const eslint_server_1 = __importDefault(require("./eslint-server"));
9
9
  const js_1 = __importDefault(require("@eslint/js"));
10
10
  const typescript_eslint_1 = __importDefault(require("typescript-eslint"));
11
11
  const eslint_plugin_nestjs_typed_1 = __importDefault(require("@darraghor/eslint-plugin-nestjs-typed"));
12
+ const custom_eslint_rules_1 = require("../../../custom-eslint-rules");
12
13
  const testFileIgnorePatterns = [
13
14
  '**/__tests__/**',
14
15
  '**/*.test.js',
@@ -22,6 +23,13 @@ const testFileIgnorePatterns = [
22
23
  ];
23
24
  const globalIgnoreServerPatterns = ['server/database/.introspect/**', ...testFileIgnorePatterns]; // 全局 eslint server ignore
24
25
  const globalIgnoreClientPatterns = ['dist', 'node_modules', 'client/src/api/gen', ...testFileIgnorePatterns]; // 全局 eslint client Ignore
26
+ // @lark-apaas 插件只注册一次,client 和 server config 共享
27
+ const larkApaasPlugin = {
28
+ name: '@lark-apaas/plugin-registration',
29
+ plugins: {
30
+ '@lark-apaas': { rules: custom_eslint_rules_1.customRules },
31
+ },
32
+ };
25
33
  // 只导出纯净的规则配置,不包含基础配置
26
34
  // 用户需要在项目配置中自己添加基础配置(eslintJs, tseslint, nestjs 等)
27
35
  exports.eslintPresets = {
@@ -29,6 +37,7 @@ exports.eslintPresets = {
29
37
  { ignores: globalIgnoreClientPatterns },
30
38
  js_1.default.configs.recommended,
31
39
  ...typescript_eslint_1.default.configs.recommended,
40
+ larkApaasPlugin,
32
41
  eslint_client_1.default
33
42
  ],
34
43
  server: [
@@ -36,6 +45,7 @@ exports.eslintPresets = {
36
45
  js_1.default.configs.recommended,
37
46
  ...typescript_eslint_1.default.configs.recommended,
38
47
  ...eslint_plugin_nestjs_typed_1.default.configs.flatRecommended,
48
+ larkApaasPlugin,
39
49
  eslint_server_1.default
40
50
  ],
41
51
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/fullstack-presets",
3
- "version": "1.1.15",
3
+ "version": "1.1.16",
4
4
  "files": [
5
5
  "lib"
6
6
  ],