@n8n/eslint-plugin-community-nodes 0.30.0 → 0.32.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 -0
- package/dist/plugin.d.ts +6 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +2 -0
- package/dist/plugin.js.map +1 -1
- package/dist/rules/index.d.ts +1 -0
- package/dist/rules/index.d.ts.map +1 -1
- package/dist/rules/index.js +2 -0
- package/dist/rules/index.js.map +1 -1
- package/dist/rules/no-credential-reuse.d.ts.map +1 -1
- package/dist/rules/no-credential-reuse.js +4 -6
- package/dist/rules/no-credential-reuse.js.map +1 -1
- package/dist/rules/no-emoji-in-options.js +4 -4
- package/dist/rules/no-emoji-in-options.js.map +1 -1
- package/dist/rules/no-overrides-field.d.ts.map +1 -1
- package/dist/rules/no-overrides-field.js +12 -8
- package/dist/rules/no-overrides-field.js.map +1 -1
- package/dist/rules/no-unsafe-connection-type-cast.d.ts +3 -0
- package/dist/rules/no-unsafe-connection-type-cast.d.ts.map +1 -0
- package/dist/rules/no-unsafe-connection-type-cast.js +104 -0
- package/dist/rules/no-unsafe-connection-type-cast.js.map +1 -0
- package/dist/rules/valid-credential-references.d.ts.map +1 -1
- package/dist/rules/valid-credential-references.js +4 -6
- package/dist/rules/valid-credential-references.js.map +1 -1
- package/dist/utils/ast-utils.d.ts +6 -0
- package/dist/utils/ast-utils.d.ts.map +1 -1
- package/dist/utils/ast-utils.js +50 -0
- package/dist/utils/ast-utils.js.map +1 -1
- package/docs/rules/no-unsafe-connection-type-cast.md +58 -0
- package/package.json +5 -5
- package/src/plugin.ts +2 -0
- package/src/rules/index.ts +2 -0
- package/src/rules/no-credential-reuse.test.ts +54 -0
- package/src/rules/no-credential-reuse.ts +4 -8
- package/src/rules/no-emoji-in-options.test.ts +77 -0
- package/src/rules/no-emoji-in-options.ts +4 -4
- package/src/rules/no-overrides-field.test.ts +17 -0
- package/src/rules/no-overrides-field.ts +12 -9
- package/src/rules/no-unsafe-connection-type-cast.test.ts +306 -0
- package/src/rules/no-unsafe-connection-type-cast.ts +125 -0
- package/src/rules/valid-credential-references.test.ts +75 -0
- package/src/rules/valid-credential-references.ts +4 -8
- package/src/utils/ast-utils.ts +67 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Disallow type-erasing casts (`as never`, `as any`, `as unknown`) on node description `inputs`/`outputs` (`@n8n/community-nodes/no-unsafe-connection-type-cast`)
|
|
2
|
+
|
|
3
|
+
💼 This rule is enabled in the following configs: ✅ `recommended`, ☑️ `recommendedWithoutN8nCloudSupport`.
|
|
4
|
+
|
|
5
|
+
💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).
|
|
6
|
+
|
|
7
|
+
<!-- end auto-generated rule header -->
|
|
8
|
+
|
|
9
|
+
## Rule Details
|
|
10
|
+
|
|
11
|
+
A cast such as `inputs: ['main'] as never` compiles, so nothing in CI objects to it. It also switches off the check that makes `inputs` and `outputs` trustworthy. TypeScript stops verifying the value against `INodeTypeDescription`, and the [`node-connection-type-literal`](node-connection-type-literal.md) rule stops seeing the array, because the cast wraps it. A malformed connection declaration then reaches review and build with no automated signal at all. Removing the cast restores both checks.
|
|
12
|
+
|
|
13
|
+
There is no legitimate reason to erase the type here. Dynamic connection lists are already allowed as expression strings, and correct static lists type-check on their own:
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
inputs: `={{ (${configureInputs})($parameter) }}`,
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The rule flags `as never`, `as any` and `as unknown`, including inside a chain like `as unknown as NodeConnectionType[]`, where the intermediate `unknown` is what defeats the checker. Expressions that keep the value structurally checked are left alone: `as const`, `satisfies T` and a plain `as NodeConnectionType[]`.
|
|
20
|
+
|
|
21
|
+
A `satisfies` around a cast does not restore the check, so `['main'] as never satisfies NodeConnectionType[]` is still reported. The suggestion removes the cast and keeps the `satisfies`.
|
|
22
|
+
|
|
23
|
+
Removing the cast is offered as a suggestion rather than an autofix, because it may surface a real type error that was being suppressed. That error is the point: it is the bug the cast was hiding.
|
|
24
|
+
|
|
25
|
+
## Examples
|
|
26
|
+
|
|
27
|
+
### ❌ Incorrect
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import type { INodeType, INodeTypeDescription } from 'n8n-workflow';
|
|
31
|
+
|
|
32
|
+
export class MyNode implements INodeType {
|
|
33
|
+
description: INodeTypeDescription = {
|
|
34
|
+
displayName: 'My Node',
|
|
35
|
+
name: 'myNode',
|
|
36
|
+
inputs: ['main'] as never,
|
|
37
|
+
outputs: ['main'] as never,
|
|
38
|
+
properties: [],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### ✅ Correct
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
import type { INodeType, INodeTypeDescription } from 'n8n-workflow';
|
|
47
|
+
import { NodeConnectionTypes } from 'n8n-workflow';
|
|
48
|
+
|
|
49
|
+
export class MyNode implements INodeType {
|
|
50
|
+
description: INodeTypeDescription = {
|
|
51
|
+
displayName: 'My Node',
|
|
52
|
+
name: 'myNode',
|
|
53
|
+
inputs: [NodeConnectionTypes.Main],
|
|
54
|
+
outputs: [NodeConnectionTypes.Main],
|
|
55
|
+
properties: [],
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
```
|
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.32.0",
|
|
5
5
|
"main": "./dist/plugin.js",
|
|
6
6
|
"types": "./dist/plugin.d.ts",
|
|
7
7
|
"exports": {
|
|
@@ -16,7 +16,10 @@
|
|
|
16
16
|
"fastest-levenshtein": "1.0.16"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
|
+
"@n8n/typescript-config": "1.11.0",
|
|
20
|
+
"@n8n/vitest-config": "1.21.0",
|
|
19
21
|
"@types/node": "24.10.1",
|
|
22
|
+
"n8n-workflow": "2.39.0",
|
|
20
23
|
"@typescript-eslint/rule-tester": "^8.35.0",
|
|
21
24
|
"eslint-doc-generator": "^2.2.2",
|
|
22
25
|
"eslint-plugin-eslint-plugin": "^7.0.0",
|
|
@@ -24,10 +27,7 @@
|
|
|
24
27
|
"@typescript/native": "npm:typescript@7.0.2",
|
|
25
28
|
"typescript": "npm:@typescript/typescript6@6.0.2",
|
|
26
29
|
"vite": "^8.0.2",
|
|
27
|
-
"vitest": "^4.1.9"
|
|
28
|
-
"@n8n/typescript-config": "1.10.0",
|
|
29
|
-
"@n8n/vitest-config": "1.21.0",
|
|
30
|
-
"n8n-workflow": "2.37.0"
|
|
30
|
+
"vitest": "^4.1.9"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"eslint": "9.29.0",
|
package/src/plugin.ts
CHANGED
|
@@ -41,6 +41,7 @@ const configs = {
|
|
|
41
41
|
'@n8n/community-nodes/no-runtime-dependencies': 'error',
|
|
42
42
|
'@n8n/community-nodes/no-silent-error-swallowing': 'error',
|
|
43
43
|
'@n8n/community-nodes/no-template-placeholders': 'error',
|
|
44
|
+
'@n8n/community-nodes/no-unsafe-connection-type-cast': 'error',
|
|
44
45
|
'@n8n/community-nodes/icon-validation': 'error',
|
|
45
46
|
'@n8n/community-nodes/icon-prefer-themed-variants': 'warn',
|
|
46
47
|
'@n8n/community-nodes/resource-operation-pattern': 'warn',
|
|
@@ -89,6 +90,7 @@ const configs = {
|
|
|
89
90
|
'@n8n/community-nodes/no-runtime-dependencies': 'error',
|
|
90
91
|
'@n8n/community-nodes/no-silent-error-swallowing': 'error',
|
|
91
92
|
'@n8n/community-nodes/no-template-placeholders': 'error',
|
|
93
|
+
'@n8n/community-nodes/no-unsafe-connection-type-cast': 'error',
|
|
92
94
|
'@n8n/community-nodes/icon-validation': 'error',
|
|
93
95
|
'@n8n/community-nodes/icon-prefer-themed-variants': 'warn',
|
|
94
96
|
'@n8n/community-nodes/credential-documentation-url': 'error',
|
package/src/rules/index.ts
CHANGED
|
@@ -26,6 +26,7 @@ import { NoRestrictedImportsRule } from './no-restricted-imports.js';
|
|
|
26
26
|
import { NoRuntimeDependenciesRule } from './no-runtime-dependencies.js';
|
|
27
27
|
import { NoSilentErrorSwallowingRule } from './no-silent-error-swallowing.js';
|
|
28
28
|
import { NoTemplatePlaceholdersRule } from './no-template-placeholders.js';
|
|
29
|
+
import { NoUnsafeConnectionTypeCastRule } from './no-unsafe-connection-type-cast.js';
|
|
29
30
|
import { NodeClassDescriptionIconMissingRule } from './node-class-description-icon-missing.js';
|
|
30
31
|
import { NodeConnectionTypeLiteralRule } from './node-connection-type-literal.js';
|
|
31
32
|
import { NodeRegistrationCompleteRule } from './node-registration-complete.js';
|
|
@@ -65,6 +66,7 @@ export const rules = {
|
|
|
65
66
|
'no-runtime-dependencies': NoRuntimeDependenciesRule,
|
|
66
67
|
'no-silent-error-swallowing': NoSilentErrorSwallowingRule,
|
|
67
68
|
'no-template-placeholders': NoTemplatePlaceholdersRule,
|
|
69
|
+
'no-unsafe-connection-type-cast': NoUnsafeConnectionTypeCastRule,
|
|
68
70
|
'icon-validation': IconValidationRule,
|
|
69
71
|
'icon-prefer-themed-variants': IconPreferThemedVariantsRule,
|
|
70
72
|
'resource-operation-pattern': ResourceOperationPatternRule,
|
|
@@ -19,6 +19,7 @@ const mockFindPackageJson = vi.mocked(fileUtils.findPackageJson);
|
|
|
19
19
|
const ruleTester = new RuleTester();
|
|
20
20
|
|
|
21
21
|
const nodeFilePath = '/tmp/TestNode.node.ts';
|
|
22
|
+
const versionedNodeFilePath = '/tmp/v1/TestNodeV1.node.ts';
|
|
22
23
|
|
|
23
24
|
function createNodeCode(
|
|
24
25
|
credentials: Array<string | { name: string; required?: boolean }> = [],
|
|
@@ -97,6 +98,36 @@ export class TestNode implements INodeType {
|
|
|
97
98
|
}`;
|
|
98
99
|
}
|
|
99
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Versioned node layout from the node-building docs: each version class
|
|
103
|
+
* implements `INodeType` and assigns `description` in its constructor, merging
|
|
104
|
+
* the `baseDescription` that the `VersionedNodeType` entry file passes in.
|
|
105
|
+
*/
|
|
106
|
+
function createVersionedNodeCode(credentialNameLiteral: string): string {
|
|
107
|
+
return `
|
|
108
|
+
import type { INodeType, INodeTypeBaseDescription, INodeTypeDescription } from 'n8n-workflow';
|
|
109
|
+
|
|
110
|
+
export class TestNodeV1 implements INodeType {
|
|
111
|
+
description: INodeTypeDescription;
|
|
112
|
+
|
|
113
|
+
constructor(baseDescription: INodeTypeBaseDescription) {
|
|
114
|
+
this.description = {
|
|
115
|
+
...baseDescription,
|
|
116
|
+
version: 1,
|
|
117
|
+
inputs: ['main'],
|
|
118
|
+
outputs: ['main'],
|
|
119
|
+
credentials: [
|
|
120
|
+
{
|
|
121
|
+
name: ${credentialNameLiteral},
|
|
122
|
+
required: true,
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
properties: [],
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}`;
|
|
129
|
+
}
|
|
130
|
+
|
|
100
131
|
// Helper function to create non-node class
|
|
101
132
|
function createNonNodeClass(): string {
|
|
102
133
|
return `
|
|
@@ -163,6 +194,29 @@ ruleTester.run('no-credential-reuse', NoCredentialReuseRule, {
|
|
|
163
194
|
},
|
|
164
195
|
],
|
|
165
196
|
invalid: [
|
|
197
|
+
{
|
|
198
|
+
name: 'SECURITY: versioned node assigning description in its constructor uses credential not in package',
|
|
199
|
+
filename: versionedNodeFilePath,
|
|
200
|
+
code: createVersionedNodeCode("'ExternalApi'"),
|
|
201
|
+
errors: [
|
|
202
|
+
{
|
|
203
|
+
messageId: 'credentialNotInPackage',
|
|
204
|
+
data: { credentialName: 'ExternalApi' },
|
|
205
|
+
suggestions: [
|
|
206
|
+
{
|
|
207
|
+
messageId: 'useAvailable',
|
|
208
|
+
data: { suggestedName: 'myApiCredential' },
|
|
209
|
+
output: createVersionedNodeCode('"myApiCredential"'),
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
messageId: 'useAvailable',
|
|
213
|
+
data: { suggestedName: 'anotherApiCredential' },
|
|
214
|
+
output: createVersionedNodeCode('"anotherApiCredential"'),
|
|
215
|
+
},
|
|
216
|
+
],
|
|
217
|
+
},
|
|
218
|
+
],
|
|
219
|
+
},
|
|
166
220
|
{
|
|
167
221
|
name: 'SECURITY: node using credential not in package (object form)',
|
|
168
222
|
filename: nodeFilePath,
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { TSESTree } from '@typescript-eslint/utils';
|
|
2
1
|
import type { ReportSuggestionArray } from '@typescript-eslint/utils/ts-eslint';
|
|
3
2
|
|
|
4
3
|
import {
|
|
5
4
|
isNodeTypeClass,
|
|
6
|
-
|
|
5
|
+
findNodeDescriptionObject,
|
|
7
6
|
findArrayLiteralProperty,
|
|
8
7
|
extractCredentialNameFromArray,
|
|
9
8
|
findPackageJson,
|
|
@@ -59,15 +58,12 @@ export const NoCredentialReuseRule = createRule({
|
|
|
59
58
|
return;
|
|
60
59
|
}
|
|
61
60
|
|
|
62
|
-
const
|
|
63
|
-
if (
|
|
64
|
-
!descriptionProperty?.value ||
|
|
65
|
-
descriptionProperty.value.type !== TSESTree.AST_NODE_TYPES.ObjectExpression
|
|
66
|
-
) {
|
|
61
|
+
const description = findNodeDescriptionObject(node);
|
|
62
|
+
if (!description) {
|
|
67
63
|
return;
|
|
68
64
|
}
|
|
69
65
|
|
|
70
|
-
const credentialsArray = findArrayLiteralProperty(
|
|
66
|
+
const credentialsArray = findArrayLiteralProperty(description, 'credentials');
|
|
71
67
|
if (!credentialsArray) {
|
|
72
68
|
return;
|
|
73
69
|
}
|
|
@@ -16,6 +16,27 @@ function createNodeCode(body: string): string {
|
|
|
16
16
|
`;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Versioned node layout from the node-building docs: the version class assigns
|
|
21
|
+
* `description` in its constructor instead of as a property initializer.
|
|
22
|
+
*/
|
|
23
|
+
function createVersionedNodeCode(body: string): string {
|
|
24
|
+
return `
|
|
25
|
+
import type { INodeType, INodeTypeBaseDescription, INodeTypeDescription } from 'n8n-workflow';
|
|
26
|
+
|
|
27
|
+
export class TestNodeV1 implements INodeType {
|
|
28
|
+
description: INodeTypeDescription;
|
|
29
|
+
|
|
30
|
+
constructor(baseDescription: INodeTypeBaseDescription) {
|
|
31
|
+
this.description = {
|
|
32
|
+
...baseDescription,
|
|
33
|
+
${body}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
`;
|
|
38
|
+
}
|
|
39
|
+
|
|
19
40
|
ruleTester.run('no-emoji-in-options', NoEmojiInOptionsRule, {
|
|
20
41
|
valid: [
|
|
21
42
|
{
|
|
@@ -75,6 +96,62 @@ ruleTester.run('no-emoji-in-options', NoEmojiInOptionsRule, {
|
|
|
75
96
|
},
|
|
76
97
|
],
|
|
77
98
|
invalid: [
|
|
99
|
+
{
|
|
100
|
+
name: 'emoji in option name of a versioned node assigning description in its constructor',
|
|
101
|
+
filename: '/tmp/v1/TestNodeV1.node.ts',
|
|
102
|
+
code: createVersionedNodeCode(`
|
|
103
|
+
version: 1,
|
|
104
|
+
properties: [
|
|
105
|
+
{
|
|
106
|
+
displayName: 'Operation',
|
|
107
|
+
name: 'operation',
|
|
108
|
+
type: 'options',
|
|
109
|
+
options: [
|
|
110
|
+
{ name: '✅ Create', value: 'create' },
|
|
111
|
+
{ name: 'Delete', value: 'delete' },
|
|
112
|
+
],
|
|
113
|
+
default: 'create',
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
`),
|
|
117
|
+
errors: [{ messageId: 'emojiInOption', data: { key: 'name', emoji: '✅' } }],
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: 'emoji in a description that is cast with `as`',
|
|
121
|
+
filename: '/tmp/TestNode.node.ts',
|
|
122
|
+
code: `
|
|
123
|
+
import type { INodeType, INodeTypeDescription } from 'n8n-workflow';
|
|
124
|
+
|
|
125
|
+
export class TestNode implements INodeType {
|
|
126
|
+
description = {
|
|
127
|
+
displayName: '🚀 Rocket Node',
|
|
128
|
+
name: 'testNode',
|
|
129
|
+
properties: [],
|
|
130
|
+
} as INodeTypeDescription;
|
|
131
|
+
}
|
|
132
|
+
`,
|
|
133
|
+
errors: [{ messageId: 'emojiInOption', data: { key: 'displayName', emoji: '🚀' } }],
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
name: 'emoji in a constructor-assigned description that is cast with `as`',
|
|
137
|
+
filename: '/tmp/v1/TestNodeV1.node.ts',
|
|
138
|
+
code: `
|
|
139
|
+
import type { INodeType, INodeTypeBaseDescription, INodeTypeDescription } from 'n8n-workflow';
|
|
140
|
+
|
|
141
|
+
export class TestNodeV1 implements INodeType {
|
|
142
|
+
description: INodeTypeDescription;
|
|
143
|
+
|
|
144
|
+
constructor(baseDescription: INodeTypeBaseDescription) {
|
|
145
|
+
this.description = {
|
|
146
|
+
...baseDescription,
|
|
147
|
+
displayName: '🚀 Rocket Node',
|
|
148
|
+
properties: [],
|
|
149
|
+
} as INodeTypeDescription;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
`,
|
|
153
|
+
errors: [{ messageId: 'emojiInOption', data: { key: 'displayName', emoji: '🚀' } }],
|
|
154
|
+
},
|
|
78
155
|
{
|
|
79
156
|
name: 'emoji in node displayName',
|
|
80
157
|
filename: '/tmp/TestNode.node.ts',
|
|
@@ -3,7 +3,7 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils';
|
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
isNodeTypeClass,
|
|
6
|
-
|
|
6
|
+
findNodeDescriptionObject,
|
|
7
7
|
getStringLiteralValue,
|
|
8
8
|
isFileType,
|
|
9
9
|
createRule,
|
|
@@ -93,12 +93,12 @@ export const NoEmojiInOptionsRule = createRule({
|
|
|
93
93
|
return;
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
const
|
|
97
|
-
if (!
|
|
96
|
+
const description = findNodeDescriptionObject(node);
|
|
97
|
+
if (!description) {
|
|
98
98
|
return;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
traverse(
|
|
101
|
+
traverse(description);
|
|
102
102
|
},
|
|
103
103
|
};
|
|
104
104
|
},
|
|
@@ -46,5 +46,22 @@ ruleTester.run('no-overrides-field', NoOverridesFieldRule, {
|
|
|
46
46
|
code: '{ "name": "n8n-nodes-sinch", "overrides": { "axios": "1.7.0", "fast-xml-parser": "4.4.0", "minimatch": "9.0.5", "@langchain/core": "0.3.0", "qs": "6.13.0" } }',
|
|
47
47
|
errors: [{ messageId: 'overridesForbidden' }],
|
|
48
48
|
},
|
|
49
|
+
// CE-2377: `resolutions` is the Yarn equivalent of npm's `overrides` — it
|
|
50
|
+
// forces/replaces a transitive dependency to a maintainer-controlled
|
|
51
|
+
// version or an arbitrary tarball/git URL that never appears in the node's
|
|
52
|
+
// readable source. Community node vetting must flag it for the same
|
|
53
|
+
// supply-chain reason it flags `overrides`, but the rule ignores it today.
|
|
54
|
+
{
|
|
55
|
+
name: 'resolutions as object is forbidden',
|
|
56
|
+
filename: 'package.json',
|
|
57
|
+
code: '{ "name": "n8n-nodes-example", "resolutions": { "axios": "1.0.0" } }',
|
|
58
|
+
errors: [{ messageId: 'overridesForbidden' }],
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: 'resolutions pinning a transitive dep to an arbitrary tarball is forbidden',
|
|
62
|
+
filename: 'package.json',
|
|
63
|
+
code: '{ "name": "n8n-nodes-example", "resolutions": { "minimatch": "https://example.com/evil.tgz" } }',
|
|
64
|
+
errors: [{ messageId: 'overridesForbidden' }],
|
|
65
|
+
},
|
|
49
66
|
],
|
|
50
67
|
});
|
|
@@ -12,7 +12,7 @@ export const NoOverridesFieldRule = createRule({
|
|
|
12
12
|
},
|
|
13
13
|
messages: {
|
|
14
14
|
overridesForbidden:
|
|
15
|
-
'The "overrides"
|
|
15
|
+
'The "overrides"/"resolutions" fields are not allowed in community node packages. Each community package installs into an isolated dependency tree, so overrides do not affect other nodes or n8n core — in practice they are copy-pasted boilerplate with no useful effect, and can silently force a transitive dependency to an arbitrary version or tarball. Use the helpers on the execute context (this.helpers.httpRequest, etc.) instead; most community nodes do not need third-party runtime libraries.',
|
|
16
16
|
},
|
|
17
17
|
schema: [],
|
|
18
18
|
},
|
|
@@ -28,15 +28,18 @@ export const NoOverridesFieldRule = createRule({
|
|
|
28
28
|
return;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
// `resolutions` is the Yarn equivalent of npm's `overrides`; both force
|
|
32
|
+
// a transitive dependency to an arbitrary version/tarball, so treat them
|
|
33
|
+
// the same way.
|
|
34
|
+
for (const field of ['overrides', 'resolutions']) {
|
|
35
|
+
const prop = findJsonProperty(node, field);
|
|
36
|
+
if (prop) {
|
|
37
|
+
context.report({
|
|
38
|
+
node: prop,
|
|
39
|
+
messageId: 'overridesForbidden',
|
|
40
|
+
});
|
|
41
|
+
}
|
|
34
42
|
}
|
|
35
|
-
|
|
36
|
-
context.report({
|
|
37
|
-
node: overridesProp,
|
|
38
|
-
messageId: 'overridesForbidden',
|
|
39
|
-
});
|
|
40
43
|
},
|
|
41
44
|
};
|
|
42
45
|
},
|