@n8n/eslint-plugin-community-nodes 0.13.0 → 0.15.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 (60) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/README.md +32 -24
  3. package/dist/plugin.d.ts +36 -0
  4. package/dist/plugin.d.ts.map +1 -1
  5. package/dist/plugin.js +12 -0
  6. package/dist/plugin.js.map +1 -1
  7. package/dist/rules/index.d.ts +6 -0
  8. package/dist/rules/index.d.ts.map +1 -1
  9. package/dist/rules/index.js +12 -0
  10. package/dist/rules/index.js.map +1 -1
  11. package/dist/rules/no-overrides-field.d.ts +2 -0
  12. package/dist/rules/no-overrides-field.d.ts.map +1 -0
  13. package/dist/rules/no-overrides-field.js +37 -0
  14. package/dist/rules/no-overrides-field.js.map +1 -0
  15. package/dist/rules/no-runtime-dependencies.d.ts +2 -0
  16. package/dist/rules/no-runtime-dependencies.d.ts.map +1 -0
  17. package/dist/rules/no-runtime-dependencies.js +41 -0
  18. package/dist/rules/no-runtime-dependencies.js.map +1 -0
  19. package/dist/rules/require-node-api-error.d.ts +2 -0
  20. package/dist/rules/require-node-api-error.d.ts.map +1 -0
  21. package/dist/rules/require-node-api-error.js +77 -0
  22. package/dist/rules/require-node-api-error.js.map +1 -0
  23. package/dist/rules/valid-credential-references.d.ts +2 -0
  24. package/dist/rules/valid-credential-references.d.ts.map +1 -0
  25. package/dist/rules/valid-credential-references.js +77 -0
  26. package/dist/rules/valid-credential-references.js.map +1 -0
  27. package/dist/rules/valid-peer-dependencies.d.ts +2 -0
  28. package/dist/rules/valid-peer-dependencies.d.ts.map +1 -0
  29. package/dist/rules/valid-peer-dependencies.js +107 -0
  30. package/dist/rules/valid-peer-dependencies.js.map +1 -0
  31. package/dist/rules/webhook-lifecycle-complete.d.ts +2 -0
  32. package/dist/rules/webhook-lifecycle-complete.d.ts.map +1 -0
  33. package/dist/rules/webhook-lifecycle-complete.js +106 -0
  34. package/dist/rules/webhook-lifecycle-complete.js.map +1 -0
  35. package/docs/rules/no-overrides-field.md +50 -0
  36. package/docs/rules/no-runtime-dependencies.md +58 -0
  37. package/docs/rules/node-class-description-icon-missing.md +4 -2
  38. package/docs/rules/require-community-node-keyword.md +3 -3
  39. package/docs/rules/require-node-api-error.md +62 -0
  40. package/docs/rules/require-node-description-fields.md +1 -1
  41. package/docs/rules/valid-credential-references.md +78 -0
  42. package/docs/rules/valid-peer-dependencies.md +72 -0
  43. package/docs/rules/webhook-lifecycle-complete.md +88 -0
  44. package/package.json +5 -5
  45. package/src/plugin.ts +12 -0
  46. package/src/rules/index.ts +12 -0
  47. package/src/rules/no-overrides-field.test.ts +50 -0
  48. package/src/rules/no-overrides-field.ts +43 -0
  49. package/src/rules/no-runtime-dependencies.test.ts +50 -0
  50. package/src/rules/no-runtime-dependencies.ts +50 -0
  51. package/src/rules/require-node-api-error.test.ts +199 -0
  52. package/src/rules/require-node-api-error.ts +90 -0
  53. package/src/rules/valid-credential-references.test.ts +230 -0
  54. package/src/rules/valid-credential-references.ts +105 -0
  55. package/src/rules/valid-peer-dependencies.test.ts +130 -0
  56. package/src/rules/valid-peer-dependencies.ts +116 -0
  57. package/src/rules/webhook-lifecycle-complete.test.ts +217 -0
  58. package/src/rules/webhook-lifecycle-complete.ts +130 -0
  59. package/tsconfig.build.tsbuildinfo +1 -1
  60. package/tsconfig.json +0 -1
@@ -0,0 +1,77 @@
1
+ import { TSESTree } from '@typescript-eslint/types';
2
+ import { isNodeTypeClass, findClassProperty, findArrayLiteralProperty, extractCredentialNameFromArray, findPackageJson, readPackageJsonCredentials, isFileType, findSimilarStrings, createRule, } from '../utils/index.js';
3
+ export const ValidCredentialReferencesRule = createRule({
4
+ name: 'valid-credential-references',
5
+ meta: {
6
+ type: 'problem',
7
+ docs: {
8
+ description: 'Ensure credentials referenced in node descriptions exist as credential classes in the package',
9
+ },
10
+ messages: {
11
+ credentialNotFound: 'Credential "{{ credentialName }}" does not exist in this package. Check for typos or ensure the credential class is declared and listed in package.json.',
12
+ didYouMean: "Did you mean '{{ suggestedName }}'?",
13
+ },
14
+ schema: [],
15
+ hasSuggestions: true,
16
+ },
17
+ defaultOptions: [],
18
+ create(context) {
19
+ if (!isFileType(context.filename, '.node.ts')) {
20
+ return {};
21
+ }
22
+ let packageCredentials = null;
23
+ const loadPackageCredentials = () => {
24
+ if (packageCredentials !== null) {
25
+ return packageCredentials;
26
+ }
27
+ const packageJsonPath = findPackageJson(context.filename);
28
+ if (!packageJsonPath) {
29
+ packageCredentials = new Set();
30
+ return packageCredentials;
31
+ }
32
+ packageCredentials = readPackageJsonCredentials(packageJsonPath);
33
+ return packageCredentials;
34
+ };
35
+ return {
36
+ ClassDeclaration(node) {
37
+ if (!isNodeTypeClass(node)) {
38
+ return;
39
+ }
40
+ const descriptionProperty = findClassProperty(node, 'description');
41
+ if (!descriptionProperty?.value ||
42
+ descriptionProperty.value.type !== TSESTree.AST_NODE_TYPES.ObjectExpression) {
43
+ return;
44
+ }
45
+ const credentialsArray = findArrayLiteralProperty(descriptionProperty.value, 'credentials');
46
+ if (!credentialsArray) {
47
+ return;
48
+ }
49
+ const knownCredentials = loadPackageCredentials();
50
+ if (knownCredentials.size === 0) {
51
+ return;
52
+ }
53
+ credentialsArray.elements.forEach((element) => {
54
+ const credentialInfo = extractCredentialNameFromArray(element);
55
+ if (!credentialInfo || knownCredentials.has(credentialInfo.name)) {
56
+ return;
57
+ }
58
+ const similar = findSimilarStrings(credentialInfo.name, knownCredentials);
59
+ const suggestions = similar.map((suggestedName) => ({
60
+ messageId: 'didYouMean',
61
+ data: { suggestedName },
62
+ fix(fixer) {
63
+ return fixer.replaceText(credentialInfo.node, `"${suggestedName}"`);
64
+ },
65
+ }));
66
+ context.report({
67
+ node: credentialInfo.node,
68
+ messageId: 'credentialNotFound',
69
+ data: { credentialName: credentialInfo.name },
70
+ suggest: suggestions,
71
+ });
72
+ });
73
+ },
74
+ };
75
+ },
76
+ });
77
+ //# sourceMappingURL=valid-credential-references.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"valid-credential-references.js","sourceRoot":"","sources":["../../src/rules/valid-credential-references.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare const ValidPeerDependenciesRule: import("@typescript-eslint/utils/ts-eslint").RuleModule<"missingPeerDependencies" | "invalidPeerDependenciesType" | "missingN8nWorkflow" | "pinnedN8nWorkflow" | "forbiddenPeerDependency", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
2
+ //# sourceMappingURL=valid-peer-dependencies.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"valid-peer-dependencies.d.ts","sourceRoot":"","sources":["../../src/rules/valid-peer-dependencies.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,yBAAyB,qQA0GpC,CAAC"}
@@ -0,0 +1,107 @@
1
+ import { AST_NODE_TYPES } from '@typescript-eslint/utils';
2
+ import { createRule, findJsonProperty } from '../utils/index.js';
3
+ const REQUIRED_DEP = 'n8n-workflow';
4
+ const REQUIRED_VERSION = '*';
5
+ const ALLOWED_DEPS = new Set([REQUIRED_DEP, 'ai-node-sdk']);
6
+ export const ValidPeerDependenciesRule = createRule({
7
+ name: 'valid-peer-dependencies',
8
+ meta: {
9
+ type: 'problem',
10
+ docs: {
11
+ description: 'Require community node package.json peerDependencies to contain only "n8n-workflow": "*" (and optionally "ai-node-sdk")',
12
+ },
13
+ fixable: 'code',
14
+ messages: {
15
+ missingPeerDependencies: `The package.json must have a "peerDependencies" section containing "${REQUIRED_DEP}": "${REQUIRED_VERSION}".`,
16
+ invalidPeerDependenciesType: `"peerDependencies" must be an object mapping package names to version ranges (containing "${REQUIRED_DEP}": "${REQUIRED_VERSION}").`,
17
+ missingN8nWorkflow: `"peerDependencies" must include "${REQUIRED_DEP}": "${REQUIRED_VERSION}".`,
18
+ pinnedN8nWorkflow: `"peerDependencies.${REQUIRED_DEP}" must be "${REQUIRED_VERSION}", got {{ value }}.`,
19
+ forbiddenPeerDependency: '"{{ name }}" is not allowed in "peerDependencies". Only "n8n-workflow" and "ai-node-sdk" are permitted.',
20
+ },
21
+ schema: [],
22
+ },
23
+ defaultOptions: [],
24
+ create(context) {
25
+ if (!context.filename.endsWith('package.json')) {
26
+ return {};
27
+ }
28
+ return {
29
+ ObjectExpression(node) {
30
+ if (node.parent?.type !== AST_NODE_TYPES.ExpressionStatement) {
31
+ return;
32
+ }
33
+ const peerDepsProp = findJsonProperty(node, 'peerDependencies');
34
+ if (!peerDepsProp) {
35
+ context.report({
36
+ node,
37
+ messageId: 'missingPeerDependencies',
38
+ fix(fixer) {
39
+ const insertion = `"peerDependencies": { "${REQUIRED_DEP}": "${REQUIRED_VERSION}" }`;
40
+ const lastProp = node.properties[node.properties.length - 1];
41
+ if (!lastProp) {
42
+ return fixer.replaceText(node, `{ ${insertion} }`);
43
+ }
44
+ return fixer.insertTextAfter(lastProp, `, ${insertion}`);
45
+ },
46
+ });
47
+ return;
48
+ }
49
+ if (peerDepsProp.value.type !== AST_NODE_TYPES.ObjectExpression) {
50
+ context.report({
51
+ node: peerDepsProp,
52
+ messageId: 'invalidPeerDependenciesType',
53
+ });
54
+ return;
55
+ }
56
+ const peerDepsObject = peerDepsProp.value;
57
+ const workflowEntry = findJsonProperty(peerDepsObject, REQUIRED_DEP);
58
+ if (!workflowEntry) {
59
+ context.report({
60
+ node: peerDepsProp,
61
+ messageId: 'missingN8nWorkflow',
62
+ fix(fixer) {
63
+ const insertion = `"${REQUIRED_DEP}": "${REQUIRED_VERSION}"`;
64
+ const lastProp = peerDepsObject.properties[peerDepsObject.properties.length - 1];
65
+ if (!lastProp) {
66
+ return fixer.replaceText(peerDepsObject, `{ ${insertion} }`);
67
+ }
68
+ return fixer.insertTextAfter(lastProp, `, ${insertion}`);
69
+ },
70
+ });
71
+ }
72
+ else if (workflowEntry.value.type !== AST_NODE_TYPES.Literal ||
73
+ workflowEntry.value.value !== REQUIRED_VERSION) {
74
+ const valueNode = workflowEntry.value;
75
+ const rawValue = valueNode.type === AST_NODE_TYPES.Literal ? String(valueNode.raw) : 'non-literal';
76
+ context.report({
77
+ node: workflowEntry,
78
+ messageId: 'pinnedN8nWorkflow',
79
+ data: { value: rawValue },
80
+ fix(fixer) {
81
+ if (valueNode.type !== AST_NODE_TYPES.Literal)
82
+ return null;
83
+ return fixer.replaceText(valueNode, `"${REQUIRED_VERSION}"`);
84
+ },
85
+ });
86
+ }
87
+ for (const prop of peerDepsObject.properties) {
88
+ if (prop.type !== AST_NODE_TYPES.Property)
89
+ continue;
90
+ if (prop.key.type !== AST_NODE_TYPES.Literal)
91
+ continue;
92
+ const name = prop.key.value;
93
+ if (typeof name !== 'string')
94
+ continue;
95
+ if (ALLOWED_DEPS.has(name))
96
+ continue;
97
+ context.report({
98
+ node: prop,
99
+ messageId: 'forbiddenPeerDependency',
100
+ data: { name },
101
+ });
102
+ }
103
+ },
104
+ };
105
+ },
106
+ });
107
+ //# sourceMappingURL=valid-peer-dependencies.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"valid-peer-dependencies.js","sourceRoot":"","sources":["../../src/rules/valid-peer-dependencies.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare const WebhookLifecycleCompleteRule: import("@typescript-eslint/utils/ts-eslint").RuleModule<"missingWebhookMethods" | "emptyWebhookMethods" | "missingLifecycleMethod", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
2
+ //# sourceMappingURL=webhook-lifecycle-complete.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook-lifecycle-complete.d.ts","sourceRoot":"","sources":["../../src/rules/webhook-lifecycle-complete.ts"],"names":[],"mappings":"AA+CA,eAAO,MAAM,4BAA4B,6MAkFvC,CAAC"}
@@ -0,0 +1,106 @@
1
+ import { AST_NODE_TYPES } from '@typescript-eslint/utils';
2
+ import { createRule, findClassProperty, findObjectProperty, isNodeTypeClass, } from '../utils/index.js';
3
+ const REQUIRED_METHODS = ['checkExists', 'create', 'delete'];
4
+ /**
5
+ * Returns true if the description declares webhook endpoints, indicating the
6
+ * node is a webhook-based trigger that needs a complete lifecycle.
7
+ *
8
+ * Polling triggers (group `['trigger']` without a `webhooks` array) do not
9
+ * register remote webhooks and are intentionally out of scope.
10
+ */
11
+ function hasWebhooksDeclared(descriptionValue) {
12
+ const webhooksProperty = findObjectProperty(descriptionValue, 'webhooks');
13
+ if (webhooksProperty?.value.type !== AST_NODE_TYPES.ArrayExpression)
14
+ return false;
15
+ return webhooksProperty.value.elements.length > 0;
16
+ }
17
+ /** Returns true when the property defines a (possibly async) method named `name`. */
18
+ function isMethodProperty(property, name) {
19
+ if (property.type !== AST_NODE_TYPES.Property)
20
+ return false;
21
+ if (property.computed)
22
+ return false;
23
+ const keyMatches = (property.key.type === AST_NODE_TYPES.Identifier && property.key.name === name) ||
24
+ (property.key.type === AST_NODE_TYPES.Literal && property.key.value === name);
25
+ if (!keyMatches)
26
+ return false;
27
+ return (property.value.type === AST_NODE_TYPES.FunctionExpression ||
28
+ property.value.type === AST_NODE_TYPES.ArrowFunctionExpression);
29
+ }
30
+ function findMissingMethods(group) {
31
+ return REQUIRED_METHODS.filter((method) => !group.properties.some((property) => isMethodProperty(property, method)));
32
+ }
33
+ export const WebhookLifecycleCompleteRule = createRule({
34
+ name: 'webhook-lifecycle-complete',
35
+ meta: {
36
+ type: 'problem',
37
+ docs: {
38
+ description: 'Require webhook trigger nodes to implement the complete webhookMethods lifecycle (checkExists, create, delete)',
39
+ },
40
+ messages: {
41
+ missingWebhookMethods: 'Webhook trigger node is missing the `webhookMethods` property. Implement `checkExists`, `create`, and `delete` to register, verify, and clean up the webhook on the third-party service.',
42
+ emptyWebhookMethods: 'Webhook trigger node has an empty `webhookMethods` object. Define at least one lifecycle group with `checkExists`, `create`, and `delete` methods.',
43
+ missingLifecycleMethod: 'Webhook trigger lifecycle is incomplete. `webhookMethods.{{group}}` is missing: {{missing}}. All of `checkExists`, `create`, and `delete` must be implemented.',
44
+ },
45
+ schema: [],
46
+ },
47
+ defaultOptions: [],
48
+ create(context) {
49
+ return {
50
+ ClassDeclaration(node) {
51
+ if (!isNodeTypeClass(node))
52
+ return;
53
+ const descriptionProperty = findClassProperty(node, 'description');
54
+ if (!descriptionProperty)
55
+ return;
56
+ const descriptionValue = descriptionProperty.value;
57
+ if (descriptionValue?.type !== AST_NODE_TYPES.ObjectExpression)
58
+ return;
59
+ const webhookMethodsProperty = findClassProperty(node, 'webhookMethods');
60
+ if (!hasWebhooksDeclared(descriptionValue) && !webhookMethodsProperty) {
61
+ return;
62
+ }
63
+ if (!webhookMethodsProperty?.value) {
64
+ context.report({
65
+ node: node.id ?? node,
66
+ messageId: 'missingWebhookMethods',
67
+ });
68
+ return;
69
+ }
70
+ if (webhookMethodsProperty.value.type !== AST_NODE_TYPES.ObjectExpression) {
71
+ return;
72
+ }
73
+ if (webhookMethodsProperty.value.properties.length === 0) {
74
+ context.report({
75
+ node: webhookMethodsProperty.key,
76
+ messageId: 'emptyWebhookMethods',
77
+ });
78
+ return;
79
+ }
80
+ for (const groupProperty of webhookMethodsProperty.value.properties) {
81
+ if (groupProperty.type !== AST_NODE_TYPES.Property)
82
+ continue;
83
+ if (groupProperty.value.type !== AST_NODE_TYPES.ObjectExpression)
84
+ continue;
85
+ const groupName = groupProperty.key.type === AST_NODE_TYPES.Identifier
86
+ ? groupProperty.key.name
87
+ : groupProperty.key.type === AST_NODE_TYPES.Literal
88
+ ? String(groupProperty.key.value)
89
+ : 'default';
90
+ const missing = findMissingMethods(groupProperty.value);
91
+ if (missing.length === 0)
92
+ continue;
93
+ context.report({
94
+ node: groupProperty.key,
95
+ messageId: 'missingLifecycleMethod',
96
+ data: {
97
+ group: groupName,
98
+ missing: missing.map((m) => `\`${m}\``).join(', '),
99
+ },
100
+ });
101
+ }
102
+ },
103
+ };
104
+ },
105
+ });
106
+ //# sourceMappingURL=webhook-lifecycle-complete.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook-lifecycle-complete.js","sourceRoot":"","sources":["../../src/rules/webhook-lifecycle-complete.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ # Ban the "overrides" field in community node package.json (`@n8n/community-nodes/no-overrides-field`)
2
+
3
+ 💼 This rule is enabled in the following configs: ✅ `recommended`, ☑️ `recommendedWithoutN8nCloudSupport`.
4
+
5
+ <!-- end auto-generated rule header -->
6
+
7
+ ## Rule Details
8
+
9
+ The `overrides` field in `package.json` lets a package force specific versions of its (transitive) dependencies. In the context of n8n community nodes this is dangerous:
10
+
11
+ - Community nodes are installed into a shared n8n runtime alongside other nodes. Overriding a shared library (e.g. `axios`, `@langchain/core`, `minimatch`) can silently substitute an incompatible version for every other node that depends on it, causing hard-to-diagnose runtime failures.
12
+ - Community nodes are distributed as pre-built packages with their dependencies already bundled or declared as `peerDependencies`. Any version pinning that the node actually needs should happen during development, not at install time on the user's n8n instance.
13
+ - `overrides` is frequently copy-pasted from an unrelated internal project and is almost never intentional in a community node.
14
+
15
+ If you have a genuine compatibility need, bundle the dependency into the published artifact or declare it via `peerDependencies` instead.
16
+
17
+ ## Examples
18
+
19
+ ### Incorrect
20
+
21
+ ```json
22
+ {
23
+ "name": "n8n-nodes-example",
24
+ "overrides": {
25
+ "axios": "1.7.0"
26
+ }
27
+ }
28
+ ```
29
+
30
+ ```json
31
+ {
32
+ "name": "n8n-nodes-example",
33
+ "overrides": {
34
+ "axios": "1.7.0",
35
+ "@langchain/core": "0.3.0",
36
+ "minimatch": "9.0.5"
37
+ }
38
+ }
39
+ ```
40
+
41
+ ### Correct
42
+
43
+ ```json
44
+ {
45
+ "name": "n8n-nodes-example",
46
+ "peerDependencies": {
47
+ "n8n-workflow": "*"
48
+ }
49
+ }
50
+ ```
@@ -0,0 +1,58 @@
1
+ # Disallow non-empty "dependencies" in community node package.json (`@n8n/community-nodes/no-runtime-dependencies`)
2
+
3
+ 💼 This rule is enabled in the following configs: ✅ `recommended`, ☑️ `recommendedWithoutN8nCloudSupport`.
4
+
5
+ <!-- end auto-generated rule header -->
6
+
7
+ ## Rule Details
8
+
9
+ The `dependencies` field in `package.json` declares packages that are installed alongside the node at runtime. In the context of n8n community nodes this is dangerous:
10
+
11
+ - Community nodes run inside the shared n8n runtime alongside all other installed nodes. Any package listed in `dependencies` gets installed into that shared environment and can shadow or conflict with versions already used by n8n or other nodes.
12
+ - Unlike application packages, community nodes should not own their runtime environment. Shared libraries must be declared in `peerDependencies` (so the host runtime supplies them) or bundled at build time into the published artifact.
13
+ - A non-empty `dependencies` section is a strong signal that the package was scaffolded from a generic Node.js template without adapting it to the n8n community node model.
14
+
15
+ ## Examples
16
+
17
+ ### Incorrect
18
+
19
+ ```json
20
+ {
21
+ "name": "n8n-nodes-example",
22
+ "dependencies": {
23
+ "axios": "1.0.0"
24
+ }
25
+ }
26
+ ```
27
+
28
+ ```json
29
+ {
30
+ "name": "n8n-nodes-example",
31
+ "dependencies": {
32
+ "axios": "1.7.0",
33
+ "fast-xml-parser": "4.4.0",
34
+ "minimatch": "9.0.5"
35
+ }
36
+ }
37
+ ```
38
+
39
+ ### Correct
40
+
41
+ ```json
42
+ {
43
+ "name": "n8n-nodes-example",
44
+ "peerDependencies": {
45
+ "n8n-workflow": "*"
46
+ }
47
+ }
48
+ ```
49
+
50
+ ```json
51
+ {
52
+ "name": "n8n-nodes-example",
53
+ "dependencies": {},
54
+ "peerDependencies": {
55
+ "n8n-workflow": "*"
56
+ }
57
+ }
58
+ ```
@@ -1,11 +1,13 @@
1
- # Node class description must have an `icon` property defined (`@n8n/community-nodes/node-class-description-icon-missing`)
1
+ # Node class description must have an `icon` property defined. Deprecated: use `require-node-description-fields` instead (`@n8n/community-nodes/node-class-description-icon-missing`)
2
2
 
3
- ❌ This rule is **deprecated**. Use [`require-node-description-fields`](require-node-description-fields.md) instead.
3
+ ❌ This rule is deprecated.
4
4
 
5
5
  💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).
6
6
 
7
7
  <!-- end auto-generated rule header -->
8
8
 
9
+ > **Deprecated:** Use [`require-node-description-fields`](require-node-description-fields.md) instead.
10
+
9
11
  ## Rule Details
10
12
 
11
13
  Validates that node classes define an `icon` property in their `description` object. Icons are required for nodes to display correctly in the n8n editor.
@@ -1,8 +1,8 @@
1
- # Require the "n8n-community-node-package" keyword in package.json (`@n8n/community-nodes/require-community-node-keyword`)
1
+ # Require the "n8n-community-node-package" keyword in community node package.json (`@n8n/community-nodes/require-community-node-keyword`)
2
2
 
3
- ⚠️ This rule is set to `warn` in the following configs: ✅ `recommended`, ☑️ `recommendedWithoutN8nCloudSupport`.
3
+ ⚠️ This rule _warns_ in the following configs: ✅ `recommended`, ☑️ `recommendedWithoutN8nCloudSupport`.
4
4
 
5
- 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/use/command-line-interface#--fix).
5
+ 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
6
6
 
7
7
  <!-- end auto-generated rule header -->
8
8
 
@@ -0,0 +1,62 @@
1
+ # Require NodeApiError or NodeOperationError for error wrapping in catch blocks. Raw errors lose HTTP context in the n8n UI (`@n8n/community-nodes/require-node-api-error`)
2
+
3
+ 💼 This rule is enabled in the following configs: ✅ `recommended`, ☑️ `recommendedWithoutN8nCloudSupport`.
4
+
5
+ <!-- end auto-generated rule header -->
6
+
7
+ ## Rule Details
8
+
9
+ When errors are caught and re-thrown in n8n nodes, they must be wrapped in
10
+ `NodeApiError` or `NodeOperationError`. Raw re-throws and generic `Error`
11
+ constructors lose HTTP context (status code, response body, etc.) that the n8n
12
+ UI relies on to display meaningful error information to users.
13
+
14
+ ## Examples
15
+
16
+ ### Incorrect
17
+
18
+ ```js
19
+ try {
20
+ await apiRequest();
21
+ } catch (error) {
22
+ throw error;
23
+ }
24
+ ```
25
+
26
+ ```js
27
+ try {
28
+ await apiRequest();
29
+ } catch (error) {
30
+ throw new Error('Request failed');
31
+ }
32
+ ```
33
+
34
+ ### Correct
35
+
36
+ ```js
37
+ try {
38
+ await apiRequest();
39
+ } catch (error) {
40
+ throw new NodeApiError(this.getNode(), error as JsonObject);
41
+ }
42
+ ```
43
+
44
+ ```js
45
+ try {
46
+ await apiRequest();
47
+ } catch (error) {
48
+ throw new NodeOperationError(this.getNode(), 'Operation failed', { itemIndex: i });
49
+ }
50
+ ```
51
+
52
+ ```js
53
+ try {
54
+ await apiRequest();
55
+ } catch (error) {
56
+ if (this.continueOnFail()) {
57
+ returnData.push({ json: { error: error.message } });
58
+ continue;
59
+ }
60
+ throw new NodeApiError(this.getNode(), error as JsonObject);
61
+ }
62
+ ```
@@ -1,4 +1,4 @@
1
- # Node class description must define all required fields (`@n8n/community-nodes/require-node-description-fields`)
1
+ # Node class description must define all required fields: icon, subtitle (`@n8n/community-nodes/require-node-description-fields`)
2
2
 
3
3
  💼 This rule is enabled in the following configs: ✅ `recommended`, ☑️ `recommendedWithoutN8nCloudSupport`.
4
4
 
@@ -0,0 +1,78 @@
1
+ # Ensure credentials referenced in node descriptions exist as credential classes in the package (`@n8n/community-nodes/valid-credential-references`)
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
+ For each entry in `description.credentials[]`, this rule verifies that the referenced `name` matches the `name` class field of a credential class declared in the same package (as listed in `package.json` under `n8n.credentials`).
12
+
13
+ This catches typos and broken references. When `cred-class-name-suffix` is also enabled, this rule naturally enforces the naming convention in the common case while still allowing legitimately named credentials such as `httpHeaderAuth` or `webhookAuth`.
14
+
15
+ ## Examples
16
+
17
+ ### ❌ Incorrect
18
+
19
+ ```typescript
20
+ // MyApiCredential.credentials.ts
21
+ export class MyApiCredential implements ICredentialType {
22
+ name = 'myApiCredential';
23
+ // ...
24
+ }
25
+
26
+ // package.json: "n8n": { "credentials": ["dist/credentials/MyApiCredential.credentials.js"] }
27
+
28
+ export class MyNode implements INodeType {
29
+ description: INodeTypeDescription = {
30
+ credentials: [
31
+ {
32
+ name: 'myApiCredentail', // Typo — no credential with this name exists
33
+ required: true,
34
+ },
35
+ ],
36
+ // ...
37
+ };
38
+ }
39
+ ```
40
+
41
+ ### ✅ Correct
42
+
43
+ ```typescript
44
+ // MyApiCredential.credentials.ts
45
+ export class MyApiCredential implements ICredentialType {
46
+ name = 'myApiCredential';
47
+ // ...
48
+ }
49
+
50
+ // package.json: "n8n": { "credentials": ["dist/credentials/MyApiCredential.credentials.js"] }
51
+
52
+ export class MyNode implements INodeType {
53
+ description: INodeTypeDescription = {
54
+ credentials: [
55
+ {
56
+ name: 'myApiCredential', // Matches the credential class name property
57
+ required: true,
58
+ },
59
+ ],
60
+ // ...
61
+ };
62
+ }
63
+ ```
64
+
65
+ ## Setup
66
+
67
+ Declare your credential files in `package.json` so the rule can resolve credential class names:
68
+
69
+ ```json
70
+ {
71
+ "name": "n8n-nodes-my-service",
72
+ "n8n": {
73
+ "credentials": [
74
+ "dist/credentials/MyApiCredential.credentials.js"
75
+ ]
76
+ }
77
+ }
78
+ ```