@n8n/eslint-plugin-community-nodes 0.35.0 → 0.36.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.
- package/README.md +1 -1
- package/dist/plugin.d.ts +6 -6
- package/dist/plugin.js +2 -2
- package/dist/rules/n8n-object-validation.d.ts.map +1 -1
- package/dist/rules/n8n-object-validation.js +4 -6
- package/docs/rules/n8n-object-validation.md +4 -3
- package/docs/rules/resource-operation-pattern.md +2 -2
- package/package.json +4 -3
- package/src/plugin.ts +2 -2
- package/src/rules/n8n-object-validation.test.ts +30 -3
- package/src/rules/n8n-object-validation.ts +4 -7
- package/src/rules/resource-operation-pattern.test.ts +61 -0
package/README.md
CHANGED
|
@@ -83,7 +83,7 @@ export default [
|
|
|
83
83
|
| [require-node-api-error](docs/rules/require-node-api-error.md) | Require NodeApiError or NodeOperationError for error wrapping in catch blocks. Raw errors lose HTTP context in the n8n UI. | ✅ ☑️ | | | | |
|
|
84
84
|
| [require-node-description-fields](docs/rules/require-node-description-fields.md) | Node class description must define all required fields: icon, subtitle | ✅ ☑️ | | | | |
|
|
85
85
|
| [require-version](docs/rules/require-version.md) | Require a valid "version" field in community node package.json | ✅ ☑️ | | | | |
|
|
86
|
-
| [resource-operation-pattern](docs/rules/resource-operation-pattern.md) | Enforce proper resource/operation pattern for better UX in n8n nodes |
|
|
86
|
+
| [resource-operation-pattern](docs/rules/resource-operation-pattern.md) | Enforce proper resource/operation pattern for better UX in n8n nodes | ✅ ☑️ | | | | |
|
|
87
87
|
| [trigger-node-conventions](docs/rules/trigger-node-conventions.md) | Trigger nodes (class name ends with `Trigger`) must label themselves consistently as triggers | ✅ ☑️ | | | | |
|
|
88
88
|
| [valid-author](docs/rules/valid-author.md) | Require a non-empty author name and email in package.json | ✅ ☑️ | | | | |
|
|
89
89
|
| [valid-credential-references](docs/rules/valid-credential-references.md) | Ensure credentials referenced in node descriptions exist as credential classes in the package | ✅ ☑️ | | | 💡 | |
|
package/dist/plugin.d.ts
CHANGED
|
@@ -38,7 +38,7 @@ declare const configs: {
|
|
|
38
38
|
'@n8n/community-nodes/no-unsafe-connection-type-cast': "error";
|
|
39
39
|
'@n8n/community-nodes/icon-validation': "error";
|
|
40
40
|
'@n8n/community-nodes/icon-prefer-themed-variants': "warn";
|
|
41
|
-
'@n8n/community-nodes/resource-operation-pattern': "
|
|
41
|
+
'@n8n/community-nodes/resource-operation-pattern': "error";
|
|
42
42
|
'@n8n/community-nodes/trigger-node-conventions': "error";
|
|
43
43
|
'@n8n/community-nodes/credential-documentation-url': "error";
|
|
44
44
|
'@n8n/community-nodes/cred-class-field-icon-missing': "error";
|
|
@@ -96,7 +96,7 @@ declare const configs: {
|
|
|
96
96
|
'@n8n/community-nodes/icon-validation': "error";
|
|
97
97
|
'@n8n/community-nodes/icon-prefer-themed-variants': "warn";
|
|
98
98
|
'@n8n/community-nodes/credential-documentation-url': "error";
|
|
99
|
-
'@n8n/community-nodes/resource-operation-pattern': "
|
|
99
|
+
'@n8n/community-nodes/resource-operation-pattern': "error";
|
|
100
100
|
'@n8n/community-nodes/trigger-node-conventions': "error";
|
|
101
101
|
'@n8n/community-nodes/cred-class-field-icon-missing': "error";
|
|
102
102
|
'@n8n/community-nodes/cred-class-oauth2-naming': "error";
|
|
@@ -163,7 +163,7 @@ declare const pluginWithConfigs: {
|
|
|
163
163
|
'@n8n/community-nodes/no-unsafe-connection-type-cast': "error";
|
|
164
164
|
'@n8n/community-nodes/icon-validation': "error";
|
|
165
165
|
'@n8n/community-nodes/icon-prefer-themed-variants': "warn";
|
|
166
|
-
'@n8n/community-nodes/resource-operation-pattern': "
|
|
166
|
+
'@n8n/community-nodes/resource-operation-pattern': "error";
|
|
167
167
|
'@n8n/community-nodes/trigger-node-conventions': "error";
|
|
168
168
|
'@n8n/community-nodes/credential-documentation-url': "error";
|
|
169
169
|
'@n8n/community-nodes/cred-class-field-icon-missing': "error";
|
|
@@ -221,7 +221,7 @@ declare const pluginWithConfigs: {
|
|
|
221
221
|
'@n8n/community-nodes/icon-validation': "error";
|
|
222
222
|
'@n8n/community-nodes/icon-prefer-themed-variants': "warn";
|
|
223
223
|
'@n8n/community-nodes/credential-documentation-url': "error";
|
|
224
|
-
'@n8n/community-nodes/resource-operation-pattern': "
|
|
224
|
+
'@n8n/community-nodes/resource-operation-pattern': "error";
|
|
225
225
|
'@n8n/community-nodes/trigger-node-conventions': "error";
|
|
226
226
|
'@n8n/community-nodes/cred-class-field-icon-missing': "error";
|
|
227
227
|
'@n8n/community-nodes/cred-class-oauth2-naming': "error";
|
|
@@ -289,7 +289,7 @@ declare const n8nCommunityNodesPlugin: {
|
|
|
289
289
|
'@n8n/community-nodes/no-unsafe-connection-type-cast': "error";
|
|
290
290
|
'@n8n/community-nodes/icon-validation': "error";
|
|
291
291
|
'@n8n/community-nodes/icon-prefer-themed-variants': "warn";
|
|
292
|
-
'@n8n/community-nodes/resource-operation-pattern': "
|
|
292
|
+
'@n8n/community-nodes/resource-operation-pattern': "error";
|
|
293
293
|
'@n8n/community-nodes/trigger-node-conventions': "error";
|
|
294
294
|
'@n8n/community-nodes/credential-documentation-url': "error";
|
|
295
295
|
'@n8n/community-nodes/cred-class-field-icon-missing': "error";
|
|
@@ -347,7 +347,7 @@ declare const n8nCommunityNodesPlugin: {
|
|
|
347
347
|
'@n8n/community-nodes/icon-validation': "error";
|
|
348
348
|
'@n8n/community-nodes/icon-prefer-themed-variants': "warn";
|
|
349
349
|
'@n8n/community-nodes/credential-documentation-url': "error";
|
|
350
|
-
'@n8n/community-nodes/resource-operation-pattern': "
|
|
350
|
+
'@n8n/community-nodes/resource-operation-pattern': "error";
|
|
351
351
|
'@n8n/community-nodes/trigger-node-conventions': "error";
|
|
352
352
|
'@n8n/community-nodes/cred-class-field-icon-missing': "error";
|
|
353
353
|
'@n8n/community-nodes/cred-class-oauth2-naming': "error";
|
package/dist/plugin.js
CHANGED
|
@@ -40,7 +40,7 @@ const configs = {
|
|
|
40
40
|
'@n8n/community-nodes/no-unsafe-connection-type-cast': 'error',
|
|
41
41
|
'@n8n/community-nodes/icon-validation': 'error',
|
|
42
42
|
'@n8n/community-nodes/icon-prefer-themed-variants': 'warn',
|
|
43
|
-
'@n8n/community-nodes/resource-operation-pattern': '
|
|
43
|
+
'@n8n/community-nodes/resource-operation-pattern': 'error',
|
|
44
44
|
'@n8n/community-nodes/trigger-node-conventions': 'error',
|
|
45
45
|
'@n8n/community-nodes/credential-documentation-url': 'error',
|
|
46
46
|
'@n8n/community-nodes/cred-class-field-icon-missing': 'error',
|
|
@@ -91,7 +91,7 @@ const configs = {
|
|
|
91
91
|
'@n8n/community-nodes/icon-validation': 'error',
|
|
92
92
|
'@n8n/community-nodes/icon-prefer-themed-variants': 'warn',
|
|
93
93
|
'@n8n/community-nodes/credential-documentation-url': 'error',
|
|
94
|
-
'@n8n/community-nodes/resource-operation-pattern': '
|
|
94
|
+
'@n8n/community-nodes/resource-operation-pattern': 'error',
|
|
95
95
|
'@n8n/community-nodes/trigger-node-conventions': 'error',
|
|
96
96
|
'@n8n/community-nodes/cred-class-field-icon-missing': 'error',
|
|
97
97
|
'@n8n/community-nodes/cred-class-oauth2-naming': 'error',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"n8n-object-validation.d.ts","sourceRoot":"","sources":["../../src/rules/n8n-object-validation.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"n8n-object-validation.d.ts","sourceRoot":"","sources":["../../src/rules/n8n-object-validation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAY,MAAM,0BAA0B,CAAC;AAKnE,KAAK,UAAU,GACZ,kBAAkB,GAClB,yBAAyB,GACzB,wBAAwB,GACxB,wBAAwB,GACxB,iBAAiB,GACjB,kBAAkB,GAClB,eAAe,GACf,wBAAwB,GACxB,mBAAmB,GACnB,mBAAmB,GACnB,yBAAyB,GACzB,yBAAyB,GACzB,eAAe,CAAC;AAInB,eAAO,MAAM,uBAAuB;;CA8ElC,CAAC"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { parseNodesApiLevel } from '@n8n/utils/nodes-api-level';
|
|
1
2
|
import { AST_NODE_TYPES } from '@typescript-eslint/utils';
|
|
2
3
|
import { createRule, findJsonProperty, getTopLevelObjectInJson } from '../utils/index.js';
|
|
3
4
|
export const N8nObjectValidationRule = createRule({
|
|
@@ -10,8 +11,8 @@ export const N8nObjectValidationRule = createRule({
|
|
|
10
11
|
messages: {
|
|
11
12
|
missingN8nObject: 'Community node package.json must contain an "n8n" object describing the package.',
|
|
12
13
|
wrongLocationApiVersion: '"n8nNodesApiVersion" must be inside the "n8n" section, not at the root level of package.json.',
|
|
13
|
-
missingNodesApiVersion: 'The "n8n" object must declare "n8nNodesApiVersion" (a positive integer).',
|
|
14
|
-
invalidNodesApiVersion: '"n8n.n8nNodesApiVersion" must be a positive integer, got {{ value }}.',
|
|
14
|
+
missingNodesApiVersion: 'The "n8n" object must declare "n8nNodesApiVersion" (a positive integer, or a "<major>" or "<major>.<minor>" string).',
|
|
15
|
+
invalidNodesApiVersion: '"n8n.n8nNodesApiVersion" must be a positive integer, or a "<major>" or "<major>.<minor>" string, got {{ value }}.',
|
|
15
16
|
missingN8nNodes: 'The "n8n" object must declare "nodes" as an array of "dist/" paths.',
|
|
16
17
|
n8nNodesNotArray: '"n8n.nodes" must be an array of "dist/" paths.',
|
|
17
18
|
emptyN8nNodes: '"n8n.nodes" must contain at least one path.',
|
|
@@ -73,7 +74,7 @@ function validateApiVersion(context, n8nObject) {
|
|
|
73
74
|
return;
|
|
74
75
|
}
|
|
75
76
|
const valueNode = apiVersionProp.value;
|
|
76
|
-
if (valueNode.type !== AST_NODE_TYPES.Literal ||
|
|
77
|
+
if (valueNode.type !== AST_NODE_TYPES.Literal || parseNodesApiLevel(valueNode.value) === null) {
|
|
77
78
|
context.report({
|
|
78
79
|
node: apiVersionProp,
|
|
79
80
|
messageId: 'invalidNodesApiVersion',
|
|
@@ -142,7 +143,4 @@ function validatePathArray(context, elements, notStringMessageId, notInDistMessa
|
|
|
142
143
|
}
|
|
143
144
|
}
|
|
144
145
|
}
|
|
145
|
-
function isPositiveInteger(value) {
|
|
146
|
-
return typeof value === 'number' && Number.isInteger(value) && value > 0;
|
|
147
|
-
}
|
|
148
146
|
//# sourceMappingURL=n8n-object-validation.js.map
|
|
@@ -17,8 +17,9 @@ register at install time and the failure is opaque to the user.
|
|
|
17
17
|
This rule enforces the structural contract:
|
|
18
18
|
|
|
19
19
|
- `package.json` must contain an `n8n` object.
|
|
20
|
-
- `n8n.n8nNodesApiVersion` must be present and a positive integer
|
|
21
|
-
|
|
20
|
+
- `n8n.n8nNodesApiVersion` must be present and a positive integer, or a
|
|
21
|
+
`"<major>"` or `"<major>.<minor>"` string such as `"3"` or `"3.1"`. It must
|
|
22
|
+
live inside `n8n`, not at the root.
|
|
22
23
|
- `n8n.nodes` must be a non-empty array of strings, each starting with `dist/`.
|
|
23
24
|
- `n8n.credentials`, if present, must be an array of strings, each starting
|
|
24
25
|
with `dist/`.
|
|
@@ -53,7 +54,7 @@ the templates published by `@n8n/node-cli` and `@n8n/create-node`.
|
|
|
53
54
|
{
|
|
54
55
|
"name": "n8n-nodes-example",
|
|
55
56
|
"n8n": {
|
|
56
|
-
"n8nNodesApiVersion": "
|
|
57
|
+
"n8nNodesApiVersion": "v1",
|
|
57
58
|
"nodes": ["nodes/Foo/Foo.node.js"]
|
|
58
59
|
}
|
|
59
60
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# Enforce proper resource/operation pattern for better UX in n8n nodes (`@n8n/community-nodes/resource-operation-pattern`)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
💼 This rule is enabled in the following configs: ✅ `recommended`, ☑️ `recommendedWithoutN8nCloudSupport`.
|
|
4
4
|
|
|
5
5
|
<!-- end auto-generated rule header -->
|
|
6
6
|
|
|
7
7
|
## Rule Details
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Reports an error when a node has more than 5 operations without resources. Use resources to group related operations. This pattern makes complex nodes easier to navigate.
|
|
10
10
|
|
|
11
11
|
When you have many operations, users benefit from having them organized into logical resource groups (e.g., "User", "Project", "File") rather than seeing a long flat list of operations.
|
|
12
12
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@n8n/eslint-plugin-community-nodes",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.36.0",
|
|
5
5
|
"main": "./dist/plugin.js",
|
|
6
6
|
"types": "./dist/plugin.d.ts",
|
|
7
7
|
"exports": {
|
|
@@ -11,15 +11,16 @@
|
|
|
11
11
|
}
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
+
"@n8n/utils": "1.51.0",
|
|
14
15
|
"@typescript-eslint/typescript-estree": "8.70.0",
|
|
15
16
|
"@typescript-eslint/utils": "8.70.0",
|
|
16
17
|
"fastest-levenshtein": "1.0.16"
|
|
17
18
|
},
|
|
18
19
|
"devDependencies": {
|
|
19
20
|
"@n8n/typescript-config": "1.12.0",
|
|
20
|
-
"@n8n/vitest-config": "1.
|
|
21
|
+
"@n8n/vitest-config": "1.24.0",
|
|
21
22
|
"@types/node": "24.10.1",
|
|
22
|
-
"n8n-workflow": "2.
|
|
23
|
+
"n8n-workflow": "2.44.0",
|
|
23
24
|
"@typescript-eslint/rule-tester": "8.70.0",
|
|
24
25
|
"eslint-doc-generator": "^2.2.2",
|
|
25
26
|
"eslint-plugin-eslint-plugin": "^7.0.0",
|
package/src/plugin.ts
CHANGED
|
@@ -44,7 +44,7 @@ const configs = {
|
|
|
44
44
|
'@n8n/community-nodes/no-unsafe-connection-type-cast': 'error',
|
|
45
45
|
'@n8n/community-nodes/icon-validation': 'error',
|
|
46
46
|
'@n8n/community-nodes/icon-prefer-themed-variants': 'warn',
|
|
47
|
-
'@n8n/community-nodes/resource-operation-pattern': '
|
|
47
|
+
'@n8n/community-nodes/resource-operation-pattern': 'error',
|
|
48
48
|
'@n8n/community-nodes/trigger-node-conventions': 'error',
|
|
49
49
|
'@n8n/community-nodes/credential-documentation-url': 'error',
|
|
50
50
|
'@n8n/community-nodes/cred-class-field-icon-missing': 'error',
|
|
@@ -95,7 +95,7 @@ const configs = {
|
|
|
95
95
|
'@n8n/community-nodes/icon-validation': 'error',
|
|
96
96
|
'@n8n/community-nodes/icon-prefer-themed-variants': 'warn',
|
|
97
97
|
'@n8n/community-nodes/credential-documentation-url': 'error',
|
|
98
|
-
'@n8n/community-nodes/resource-operation-pattern': '
|
|
98
|
+
'@n8n/community-nodes/resource-operation-pattern': 'error',
|
|
99
99
|
'@n8n/community-nodes/trigger-node-conventions': 'error',
|
|
100
100
|
'@n8n/community-nodes/cred-class-field-icon-missing': 'error',
|
|
101
101
|
'@n8n/community-nodes/cred-class-oauth2-naming': 'error',
|
|
@@ -26,6 +26,21 @@ ruleTester.run('n8n-object-validation', N8nObjectValidationRule, {
|
|
|
26
26
|
filename: 'package.json',
|
|
27
27
|
code: '{ "name": "n8n-nodes-example", "n8n": { "n8nNodesApiVersion": 2, "nodes": ["dist/x.js"] } }',
|
|
28
28
|
},
|
|
29
|
+
{
|
|
30
|
+
name: 'major-only string api version is allowed',
|
|
31
|
+
filename: 'package.json',
|
|
32
|
+
code: '{ "name": "n8n-nodes-example", "n8n": { "n8nNodesApiVersion": "3", "nodes": ["dist/x.js"] } }',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'major.minor string api version is allowed',
|
|
36
|
+
filename: 'package.json',
|
|
37
|
+
code: '{ "name": "n8n-nodes-example", "n8n": { "n8nNodesApiVersion": "3.1", "nodes": ["dist/x.js"] } }',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'two-digit minor string api version is allowed',
|
|
41
|
+
filename: 'package.json',
|
|
42
|
+
code: '{ "name": "n8n-nodes-example", "n8n": { "n8nNodesApiVersion": "3.10", "nodes": ["dist/x.js"] } }',
|
|
43
|
+
},
|
|
29
44
|
{
|
|
30
45
|
name: 'non-package.json file is ignored',
|
|
31
46
|
filename: 'some-config.json',
|
|
@@ -77,10 +92,22 @@ ruleTester.run('n8n-object-validation', N8nObjectValidationRule, {
|
|
|
77
92
|
errors: [{ messageId: 'missingNodesApiVersion' }],
|
|
78
93
|
},
|
|
79
94
|
{
|
|
80
|
-
name: 'n8nNodesApiVersion is a string',
|
|
95
|
+
name: 'n8nNodesApiVersion is a non-numeric string',
|
|
96
|
+
filename: 'package.json',
|
|
97
|
+
code: '{ "name": "n8n-nodes-example", "n8n": { "n8nNodesApiVersion": "v3", "nodes": ["dist/x.js"] } }',
|
|
98
|
+
errors: [{ messageId: 'invalidNodesApiVersion', data: { value: 'v3' } }],
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: 'n8nNodesApiVersion is a string with a patch part',
|
|
102
|
+
filename: 'package.json',
|
|
103
|
+
code: '{ "name": "n8n-nodes-example", "n8n": { "n8nNodesApiVersion": "3.1.0", "nodes": ["dist/x.js"] } }',
|
|
104
|
+
errors: [{ messageId: 'invalidNodesApiVersion', data: { value: '3.1.0' } }],
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: 'n8nNodesApiVersion is a zero-major string',
|
|
81
108
|
filename: 'package.json',
|
|
82
|
-
code: '{ "name": "n8n-nodes-example", "n8n": { "n8nNodesApiVersion": "1", "nodes": ["dist/x.js"] } }',
|
|
83
|
-
errors: [{ messageId: 'invalidNodesApiVersion', data: { value: '1' } }],
|
|
109
|
+
code: '{ "name": "n8n-nodes-example", "n8n": { "n8nNodesApiVersion": "0.1", "nodes": ["dist/x.js"] } }',
|
|
110
|
+
errors: [{ messageId: 'invalidNodesApiVersion', data: { value: '0.1' } }],
|
|
84
111
|
},
|
|
85
112
|
{
|
|
86
113
|
name: 'n8nNodesApiVersion is zero',
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { parseNodesApiLevel } from '@n8n/utils/nodes-api-level';
|
|
1
2
|
import type { TSESLint, TSESTree } from '@typescript-eslint/utils';
|
|
2
3
|
import { AST_NODE_TYPES } from '@typescript-eslint/utils';
|
|
3
4
|
|
|
@@ -34,9 +35,9 @@ export const N8nObjectValidationRule = createRule<[], MessageIds>({
|
|
|
34
35
|
wrongLocationApiVersion:
|
|
35
36
|
'"n8nNodesApiVersion" must be inside the "n8n" section, not at the root level of package.json.',
|
|
36
37
|
missingNodesApiVersion:
|
|
37
|
-
'The "n8n" object must declare "n8nNodesApiVersion" (a positive integer).',
|
|
38
|
+
'The "n8n" object must declare "n8nNodesApiVersion" (a positive integer, or a "<major>" or "<major>.<minor>" string).',
|
|
38
39
|
invalidNodesApiVersion:
|
|
39
|
-
'"n8n.n8nNodesApiVersion" must be a positive integer, got {{ value }}.',
|
|
40
|
+
'"n8n.n8nNodesApiVersion" must be a positive integer, or a "<major>" or "<major>.<minor>" string, got {{ value }}.',
|
|
40
41
|
missingN8nNodes: 'The "n8n" object must declare "nodes" as an array of "dist/" paths.',
|
|
41
42
|
n8nNodesNotArray: '"n8n.nodes" must be an array of "dist/" paths.',
|
|
42
43
|
emptyN8nNodes: '"n8n.nodes" must contain at least one path.',
|
|
@@ -108,7 +109,7 @@ function validateApiVersion(context: Context, n8nObject: TSESTree.ObjectExpressi
|
|
|
108
109
|
}
|
|
109
110
|
|
|
110
111
|
const valueNode = apiVersionProp.value;
|
|
111
|
-
if (valueNode.type !== AST_NODE_TYPES.Literal ||
|
|
112
|
+
if (valueNode.type !== AST_NODE_TYPES.Literal || parseNodesApiLevel(valueNode.value) === null) {
|
|
112
113
|
context.report({
|
|
113
114
|
node: apiVersionProp,
|
|
114
115
|
messageId: 'invalidNodesApiVersion',
|
|
@@ -194,7 +195,3 @@ function validatePathArray(
|
|
|
194
195
|
}
|
|
195
196
|
}
|
|
196
197
|
}
|
|
197
|
-
|
|
198
|
-
function isPositiveInteger(value: unknown): boolean {
|
|
199
|
-
return typeof value === 'number' && Number.isInteger(value) && value > 0;
|
|
200
|
-
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { RuleTester } from '@typescript-eslint/rule-tester';
|
|
2
|
+
import { Linter } from 'eslint';
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
2
4
|
|
|
5
|
+
import { configs } from '../plugin.js';
|
|
3
6
|
import { ResourceOperationPatternRule } from './resource-operation-pattern.js';
|
|
4
7
|
|
|
5
8
|
const ruleTester = new RuleTester();
|
|
@@ -215,3 +218,61 @@ ruleTester.run('resource-operation-pattern', ResourceOperationPatternRule, {
|
|
|
215
218
|
},
|
|
216
219
|
],
|
|
217
220
|
});
|
|
221
|
+
|
|
222
|
+
const resourceOperationRuleId = '@n8n/community-nodes/resource-operation-pattern';
|
|
223
|
+
|
|
224
|
+
function nodeWithSixOperations(hasResource: boolean): string {
|
|
225
|
+
return `
|
|
226
|
+
class TestNode extends Node {
|
|
227
|
+
description = {
|
|
228
|
+
properties: [
|
|
229
|
+
${hasResource ? "{ name: 'resource', type: 'options', options: [{ name: 'User', value: 'user' }] }," : ''}
|
|
230
|
+
{
|
|
231
|
+
name: 'operation',
|
|
232
|
+
type: 'options',
|
|
233
|
+
options: [
|
|
234
|
+
{ name: 'Get', value: 'get' },
|
|
235
|
+
{ name: 'Create', value: 'create' },
|
|
236
|
+
{ name: 'Update', value: 'update' },
|
|
237
|
+
{ name: 'Delete', value: 'delete' },
|
|
238
|
+
{ name: 'List', value: 'list' },
|
|
239
|
+
{ name: 'Search', value: 'search' },
|
|
240
|
+
],
|
|
241
|
+
},
|
|
242
|
+
],
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
`;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
describe.each([
|
|
249
|
+
['recommended', configs.recommended],
|
|
250
|
+
['recommendedWithoutN8nCloudSupport', configs.recommendedWithoutN8nCloudSupport],
|
|
251
|
+
] as const)('%s config', (_name, config) => {
|
|
252
|
+
const linter = new Linter();
|
|
253
|
+
const lint = (code: string) =>
|
|
254
|
+
linter.verify(
|
|
255
|
+
code,
|
|
256
|
+
{
|
|
257
|
+
files: ['**/*.node.ts'],
|
|
258
|
+
plugins: config.plugins,
|
|
259
|
+
rules: { [resourceOperationRuleId]: config.rules[resourceOperationRuleId] },
|
|
260
|
+
},
|
|
261
|
+
{ filename: 'TestNode.node.ts' },
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
// CE-3284: The resource finding must fail lint even when it is the only finding.
|
|
265
|
+
it('reports missing resources as the only error', () => {
|
|
266
|
+
expect(lint(nodeWithSixOperations(false))).toEqual([
|
|
267
|
+
expect.objectContaining({
|
|
268
|
+
ruleId: resourceOperationRuleId,
|
|
269
|
+
messageId: 'tooManyOperationsWithoutResources',
|
|
270
|
+
severity: 2,
|
|
271
|
+
}),
|
|
272
|
+
]);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('accepts operations organized by resource', () => {
|
|
276
|
+
expect(lint(nodeWithSixOperations(true))).toEqual([]);
|
|
277
|
+
});
|
|
278
|
+
});
|