@lark-apaas/fullstack-presets 1.1.5-beta.0 → 1.1.5-beta.2
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.
- package/lib/custom-eslint-rules/index.d.ts +0 -1
- package/lib/custom-eslint-rules/index.js +0 -2
- package/lib/custom-eslint-rules/no-direct-capability-api.d.ts +16 -0
- package/lib/custom-eslint-rules/no-direct-capability-api.js +119 -0
- package/lib/custom-eslint-rules/require-app-container.d.ts +7 -1
- package/lib/custom-eslint-rules/require-app-container.js +48 -14
- package/lib/recommend/eslint/eslint-client.d.ts +0 -6
- package/lib/recommend/eslint/eslint-client.js +0 -6
- package/lib/recommend/eslint/index.d.ts +0 -6
- package/lib/simple/recommend/eslint/eslint-client.d.ts +4 -5
- package/lib/simple/recommend/eslint/eslint-client.js +4 -3
- package/lib/simple/recommend/eslint/index.d.ts +4 -5
- package/package.json +1 -1
|
@@ -5,8 +5,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.customRules = void 0;
|
|
7
7
|
const no_nested_styled_jsx_1 = __importDefault(require("./no-nested-styled-jsx"));
|
|
8
|
-
const require_app_container_1 = __importDefault(require("./require-app-container"));
|
|
9
8
|
exports.customRules = {
|
|
10
9
|
'no-nested-styled-jsx': no_nested_styled_jsx_1.default,
|
|
11
|
-
'require-app-container': require_app_container_1.default,
|
|
12
10
|
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESLint 规则:禁止直接使用 axios 调用 /__innerapi__/capability/ 路径
|
|
3
|
+
*
|
|
4
|
+
* 此规则确保开发者使用 capabilityClient.load().call() 方式调用能力,
|
|
5
|
+
* 而不是直接通过 axios 调用内部 API 路径。
|
|
6
|
+
*
|
|
7
|
+
* 错误示例:
|
|
8
|
+
* axios.post('/__innerapi__/capability/xxx')
|
|
9
|
+
* axiosForBackend.get('/__innerapi__/capability/list')
|
|
10
|
+
*
|
|
11
|
+
* 正确示例:
|
|
12
|
+
* capabilityClient.load('capabilityId').call('action', params)
|
|
13
|
+
*/
|
|
14
|
+
import type { Rule } from 'eslint';
|
|
15
|
+
declare const rule: Rule.RuleModule;
|
|
16
|
+
export default rule;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const DEFAULT_MESSAGE = "Direct API calls to /__innerapi__/capability/ are not allowed. Use capabilityClient.load('capabilityId').call('action', params) instead.";
|
|
4
|
+
// 匹配 capability 内部 API 路径的正则表达式
|
|
5
|
+
const CAPABILITY_PATH_PATTERN = /__innerapi__\/capability\//;
|
|
6
|
+
// HTTP 方法列表
|
|
7
|
+
const HTTP_METHODS = [
|
|
8
|
+
'get',
|
|
9
|
+
'post',
|
|
10
|
+
'put',
|
|
11
|
+
'delete',
|
|
12
|
+
'patch',
|
|
13
|
+
'request',
|
|
14
|
+
'head',
|
|
15
|
+
'options',
|
|
16
|
+
];
|
|
17
|
+
// 需要检测的 axios 标识符
|
|
18
|
+
const AXIOS_IDENTIFIERS = ['axios', 'axiosForBackend'];
|
|
19
|
+
/**
|
|
20
|
+
* 从 AST 节点中提取 URL 字符串值
|
|
21
|
+
* 支持:字符串字面量、模板字符串、字符串拼接
|
|
22
|
+
*/
|
|
23
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
24
|
+
function extractUrlValue(node) {
|
|
25
|
+
if (!node)
|
|
26
|
+
return null;
|
|
27
|
+
switch (node.type) {
|
|
28
|
+
case 'Literal':
|
|
29
|
+
return typeof node.value === 'string' ? node.value : null;
|
|
30
|
+
case 'TemplateLiteral':
|
|
31
|
+
// 拼接所有静态部分(quasis)
|
|
32
|
+
return node.quasis.map((q) => q.value.raw).join('');
|
|
33
|
+
case 'BinaryExpression':
|
|
34
|
+
// 处理字符串拼接 'a' + 'b'
|
|
35
|
+
if (node.operator === '+') {
|
|
36
|
+
const left = extractUrlValue(node.left);
|
|
37
|
+
const right = extractUrlValue(node.right);
|
|
38
|
+
if (left !== null || right !== null) {
|
|
39
|
+
return (left || '') + (right || '');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
default:
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const rule = {
|
|
48
|
+
meta: {
|
|
49
|
+
type: 'problem',
|
|
50
|
+
docs: {
|
|
51
|
+
description: '禁止直接使用 axios 调用 /__innerapi__/capability/ 路径',
|
|
52
|
+
category: 'Best Practices',
|
|
53
|
+
recommended: true,
|
|
54
|
+
},
|
|
55
|
+
schema: [
|
|
56
|
+
{
|
|
57
|
+
type: 'object',
|
|
58
|
+
properties: {
|
|
59
|
+
message: {
|
|
60
|
+
type: 'string',
|
|
61
|
+
description: '自定义错误信息',
|
|
62
|
+
},
|
|
63
|
+
axiosIdentifiers: {
|
|
64
|
+
type: 'array',
|
|
65
|
+
items: { type: 'string' },
|
|
66
|
+
description: '额外需要检测的 axios 实例标识符',
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
additionalProperties: false,
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
messages: {
|
|
73
|
+
noDirectCapabilityApi: DEFAULT_MESSAGE,
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
create(context) {
|
|
77
|
+
const options = context.options[0];
|
|
78
|
+
const customMessage = options?.message;
|
|
79
|
+
const additionalIdentifiers = options?.axiosIdentifiers || [];
|
|
80
|
+
// 合并默认和自定义的 axios 标识符
|
|
81
|
+
const allAxiosIdentifiers = [
|
|
82
|
+
...AXIOS_IDENTIFIERS,
|
|
83
|
+
...additionalIdentifiers,
|
|
84
|
+
];
|
|
85
|
+
return {
|
|
86
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
87
|
+
CallExpression(node) {
|
|
88
|
+
// 1. 检查是否为 MemberExpression 调用 (xxx.method())
|
|
89
|
+
if (node.callee.type !== 'MemberExpression')
|
|
90
|
+
return;
|
|
91
|
+
// 2. 检查方法名是否为 HTTP 方法
|
|
92
|
+
const methodName = node.callee.property?.name;
|
|
93
|
+
if (!methodName || !HTTP_METHODS.includes(methodName.toLowerCase())) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
// 3. 检查调用者是否为 axios 相关标识符
|
|
97
|
+
const objectName = node.callee.object?.name;
|
|
98
|
+
if (!objectName || !allAxiosIdentifiers.includes(objectName)) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
// 4. 获取第一个参数(URL)
|
|
102
|
+
const urlArg = node.arguments[0];
|
|
103
|
+
if (!urlArg)
|
|
104
|
+
return;
|
|
105
|
+
// 5. 提取 URL 并检查是否包含 capability 路径
|
|
106
|
+
const urlValue = extractUrlValue(urlArg);
|
|
107
|
+
if (urlValue && CAPABILITY_PATH_PATTERN.test(urlValue)) {
|
|
108
|
+
context.report({
|
|
109
|
+
node,
|
|
110
|
+
...(customMessage
|
|
111
|
+
? { message: customMessage }
|
|
112
|
+
: { messageId: 'noDirectCapabilityApi' }),
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
exports.default = rule;
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ESLint 规则:要求入口文件中必须包含 AppContainer 组件
|
|
3
3
|
*
|
|
4
|
-
* 此规则确保 client/src/
|
|
4
|
+
* 此规则确保 client/src/app.tsx 或 client/src/index.tsx 中
|
|
5
5
|
* 包含 AppContainer 组件,以保证平台功能正常运行。
|
|
6
|
+
*
|
|
7
|
+
* 跨文件检查逻辑:
|
|
8
|
+
* - 只在 app.tsx 上执行检查和报错
|
|
9
|
+
* - 如果 app.tsx 没有 AppContainer,会检查 index.tsx
|
|
10
|
+
* - 只要任一文件使用了 AppContainer 就算通过
|
|
11
|
+
* - 只有两个文件都没有时才在 app.tsx 上报错
|
|
6
12
|
*/
|
|
7
13
|
import type { Rule } from 'eslint';
|
|
8
14
|
declare const rule: Rule.RuleModule;
|
|
@@ -34,12 +34,33 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
const path = __importStar(require("path"));
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
//
|
|
40
|
-
const
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const DEFAULT_MESSAGE = 'Missing platform component AppContainer. Please ensure that App is wrapped with AppContainer (in app.tsx or index.tsx) to enable platform features.';
|
|
39
|
+
// 主检查文件(报错在此文件上)
|
|
40
|
+
const PRIMARY_FILE = 'app.tsx';
|
|
41
|
+
// 备选检查文件
|
|
42
|
+
const SECONDARY_FILE = 'index.tsx';
|
|
41
43
|
// 匹配 client/src/ 目录的正则表达式
|
|
42
44
|
const CLIENT_SRC_PATTERN = /[/\\]client[/\\]src[/\\]$/;
|
|
45
|
+
// 检测文件内容是否包含 <AppContainer 的正则(比完整 AST 解析更快)
|
|
46
|
+
const APP_CONTAINER_PATTERN = /<AppContainer[\s/>]/;
|
|
47
|
+
/**
|
|
48
|
+
* 同步检查文件是否包含 AppContainer 使用
|
|
49
|
+
* 使用正则匹配,性能优于完整 AST 解析
|
|
50
|
+
*/
|
|
51
|
+
function checkFileForAppContainer(filePath) {
|
|
52
|
+
try {
|
|
53
|
+
if (!fs.existsSync(filePath)) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
57
|
+
return APP_CONTAINER_PATTERN.test(content);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// 文件不存在或无法读取,视为没有使用
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
43
64
|
const rule = {
|
|
44
65
|
meta: {
|
|
45
66
|
type: 'problem',
|
|
@@ -69,17 +90,18 @@ const rule = {
|
|
|
69
90
|
const customMessage = options?.message;
|
|
70
91
|
const filename = context.filename || context.getFilename();
|
|
71
92
|
// 获取文件名和目录名
|
|
72
|
-
const basename = path.basename(filename);
|
|
93
|
+
const basename = path.basename(filename).toLowerCase();
|
|
73
94
|
const dirname = path.dirname(filename);
|
|
74
|
-
//
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
//
|
|
78
|
-
|
|
95
|
+
// 判断是否在 client/src/ 目录下
|
|
96
|
+
const isInClientSrc = CLIENT_SRC_PATTERN.test(dirname + path.sep);
|
|
97
|
+
// 只在 app.tsx 上执行检查和报错
|
|
98
|
+
// index.tsx 不执行检查(避免重复报错)
|
|
99
|
+
const isPrimaryFile = basename === PRIMARY_FILE && isInClientSrc;
|
|
100
|
+
if (!isPrimaryFile) {
|
|
79
101
|
return {};
|
|
80
102
|
}
|
|
81
|
-
//
|
|
82
|
-
let
|
|
103
|
+
// 标记当前文件是否使用了 AppContainer
|
|
104
|
+
let hasAppContainerInCurrentFile = false;
|
|
83
105
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
84
106
|
let programNode = null;
|
|
85
107
|
return {
|
|
@@ -90,7 +112,7 @@ const rule = {
|
|
|
90
112
|
const elementName = node.name;
|
|
91
113
|
if (elementName?.type === 'JSXIdentifier' &&
|
|
92
114
|
elementName?.name === 'AppContainer') {
|
|
93
|
-
|
|
115
|
+
hasAppContainerInCurrentFile = true;
|
|
94
116
|
}
|
|
95
117
|
},
|
|
96
118
|
// 保存 Program 节点,用于在文件末尾报错时定位
|
|
@@ -99,7 +121,19 @@ const rule = {
|
|
|
99
121
|
},
|
|
100
122
|
// 文件解析完成后,检查是否在 JSX 中使用了 AppContainer
|
|
101
123
|
'Program:exit'() {
|
|
102
|
-
|
|
124
|
+
// 如果当前文件(app.tsx)有 AppContainer,通过
|
|
125
|
+
if (hasAppContainerInCurrentFile) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
// 当前文件没有,检查 index.tsx
|
|
129
|
+
const secondaryFilePath = path.join(dirname, SECONDARY_FILE);
|
|
130
|
+
const hasAppContainerInSecondary = checkFileForAppContainer(secondaryFilePath);
|
|
131
|
+
// 如果 index.tsx 有 AppContainer,也算通过
|
|
132
|
+
if (hasAppContainerInSecondary) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
// 两个文件都没有,报错
|
|
136
|
+
if (programNode) {
|
|
103
137
|
context.report({
|
|
104
138
|
node: programNode,
|
|
105
139
|
...(customMessage
|
|
@@ -13,12 +13,6 @@ declare const _default: {
|
|
|
13
13
|
plugins: {
|
|
14
14
|
'react-hooks': any;
|
|
15
15
|
import: any;
|
|
16
|
-
'@lark-apaas': {
|
|
17
|
-
rules: {
|
|
18
|
-
'no-nested-styled-jsx': import("eslint").Rule.RuleModule;
|
|
19
|
-
'require-app-container': import("eslint").Rule.RuleModule;
|
|
20
|
-
};
|
|
21
|
-
};
|
|
22
16
|
};
|
|
23
17
|
settings: {
|
|
24
18
|
'import/resolver': {
|
|
@@ -4,7 +4,6 @@ const globals = require('globals');
|
|
|
4
4
|
const reactHooks = require('eslint-plugin-react-hooks');
|
|
5
5
|
const tseslint = require('typescript-eslint');
|
|
6
6
|
const importPlugin = require('eslint-plugin-import');
|
|
7
|
-
const custom_eslint_rules_1 = require("../../custom-eslint-rules");
|
|
8
7
|
exports.default = {
|
|
9
8
|
name: 'fullstack-presets/client-recommend',
|
|
10
9
|
languageOptions: {
|
|
@@ -23,9 +22,6 @@ exports.default = {
|
|
|
23
22
|
plugins: {
|
|
24
23
|
'react-hooks': reactHooks,
|
|
25
24
|
import: importPlugin,
|
|
26
|
-
'@lark-apaas': {
|
|
27
|
-
rules: custom_eslint_rules_1.customRules,
|
|
28
|
-
},
|
|
29
25
|
},
|
|
30
26
|
settings: {
|
|
31
27
|
'import/resolver': {
|
|
@@ -40,8 +36,6 @@ exports.default = {
|
|
|
40
36
|
rules: {
|
|
41
37
|
// React Hooks 推荐规则
|
|
42
38
|
...reactHooks.configs.recommended.rules,
|
|
43
|
-
// 平台规则:确保入口文件包含 AppContainer 组件
|
|
44
|
-
'@lark-apaas/require-app-container': 'error',
|
|
45
39
|
// TypeScript 规则
|
|
46
40
|
'@typescript-eslint/no-unused-vars': 'off', // 未使用变量检查关闭
|
|
47
41
|
'@typescript-eslint/no-explicit-any': 'off', // 允许使用 any 类型
|
|
@@ -16,12 +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
|
-
};
|
|
24
|
-
};
|
|
25
19
|
};
|
|
26
20
|
settings: {
|
|
27
21
|
'import/resolver': {
|
|
@@ -15,14 +15,13 @@ declare const _default: ({
|
|
|
15
15
|
globals: any;
|
|
16
16
|
};
|
|
17
17
|
plugins: {
|
|
18
|
-
'
|
|
19
|
-
import: any;
|
|
20
|
-
'@lark-apaas': {
|
|
18
|
+
'@lark-apaas'?: {
|
|
21
19
|
rules: {
|
|
22
20
|
'no-nested-styled-jsx': import("eslint").Rule.RuleModule;
|
|
23
|
-
'require-app-container': import("eslint").Rule.RuleModule;
|
|
24
21
|
};
|
|
25
|
-
};
|
|
22
|
+
} | undefined;
|
|
23
|
+
'react-hooks': any;
|
|
24
|
+
import: any;
|
|
26
25
|
};
|
|
27
26
|
settings: {
|
|
28
27
|
'import/resolver': {
|
|
@@ -94,6 +94,9 @@ const strictSyntaxRules = [
|
|
|
94
94
|
message: 'Classname "text-accent" would cause visibility issues. Consider using proper semantic color tokens from `client/src/tailwind-theme.css`',
|
|
95
95
|
},
|
|
96
96
|
];
|
|
97
|
+
const looseSpecificPlugins = {
|
|
98
|
+
'@lark-apaas': { rules: custom_eslint_rules_1.customRules }
|
|
99
|
+
};
|
|
97
100
|
const looseSpecificRules = {
|
|
98
101
|
'@lark-apaas/no-nested-styled-jsx': 'error'
|
|
99
102
|
};
|
|
@@ -125,7 +128,7 @@ const baseConfig = {
|
|
|
125
128
|
plugins: {
|
|
126
129
|
'react-hooks': reactHooks,
|
|
127
130
|
import: importPlugin,
|
|
128
|
-
|
|
131
|
+
...(isLooseMode ? looseSpecificPlugins : {}),
|
|
129
132
|
},
|
|
130
133
|
settings: {
|
|
131
134
|
'import/resolver': {
|
|
@@ -140,8 +143,6 @@ const baseConfig = {
|
|
|
140
143
|
rules: {
|
|
141
144
|
// React Hooks 推荐规则
|
|
142
145
|
...reactHooks.configs.recommended.rules,
|
|
143
|
-
// 平台规则:确保入口文件包含 AppContainer 组件
|
|
144
|
-
'@lark-apaas/require-app-container': 'error',
|
|
145
146
|
// TypeScript 规则
|
|
146
147
|
'@typescript-eslint/no-unused-vars': 'off', // 未使用变量检查关闭
|
|
147
148
|
'@typescript-eslint/no-explicit-any': 'off', // 允许使用 any 类型
|
|
@@ -14,14 +14,13 @@ export declare const eslintPresets: {
|
|
|
14
14
|
globals: any;
|
|
15
15
|
};
|
|
16
16
|
plugins: {
|
|
17
|
-
'
|
|
18
|
-
import: any;
|
|
19
|
-
'@lark-apaas': {
|
|
17
|
+
'@lark-apaas'?: {
|
|
20
18
|
rules: {
|
|
21
19
|
'no-nested-styled-jsx': import("eslint").Rule.RuleModule;
|
|
22
|
-
'require-app-container': import("eslint").Rule.RuleModule;
|
|
23
20
|
};
|
|
24
|
-
};
|
|
21
|
+
} | undefined;
|
|
22
|
+
'react-hooks': any;
|
|
23
|
+
import: any;
|
|
25
24
|
};
|
|
26
25
|
settings: {
|
|
27
26
|
'import/resolver': {
|