@n8n/eslint-plugin-community-nodes 0.13.0 → 0.14.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 (46) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/README.md +30 -24
  3. package/dist/plugin.d.ts +24 -0
  4. package/dist/plugin.d.ts.map +1 -1
  5. package/dist/plugin.js +8 -0
  6. package/dist/plugin.js.map +1 -1
  7. package/dist/rules/index.d.ts +4 -0
  8. package/dist/rules/index.d.ts.map +1 -1
  9. package/dist/rules/index.js +8 -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/require-node-api-error.d.ts +2 -0
  16. package/dist/rules/require-node-api-error.d.ts.map +1 -0
  17. package/dist/rules/require-node-api-error.js +77 -0
  18. package/dist/rules/require-node-api-error.js.map +1 -0
  19. package/dist/rules/valid-peer-dependencies.d.ts +2 -0
  20. package/dist/rules/valid-peer-dependencies.d.ts.map +1 -0
  21. package/dist/rules/valid-peer-dependencies.js +107 -0
  22. package/dist/rules/valid-peer-dependencies.js.map +1 -0
  23. package/dist/rules/webhook-lifecycle-complete.d.ts +2 -0
  24. package/dist/rules/webhook-lifecycle-complete.d.ts.map +1 -0
  25. package/dist/rules/webhook-lifecycle-complete.js +98 -0
  26. package/dist/rules/webhook-lifecycle-complete.js.map +1 -0
  27. package/docs/rules/no-overrides-field.md +50 -0
  28. package/docs/rules/node-class-description-icon-missing.md +4 -2
  29. package/docs/rules/require-community-node-keyword.md +3 -3
  30. package/docs/rules/require-node-api-error.md +62 -0
  31. package/docs/rules/require-node-description-fields.md +1 -1
  32. package/docs/rules/valid-peer-dependencies.md +72 -0
  33. package/docs/rules/webhook-lifecycle-complete.md +88 -0
  34. package/package.json +5 -5
  35. package/src/plugin.ts +8 -0
  36. package/src/rules/index.ts +8 -0
  37. package/src/rules/no-overrides-field.test.ts +50 -0
  38. package/src/rules/no-overrides-field.ts +43 -0
  39. package/src/rules/require-node-api-error.test.ts +199 -0
  40. package/src/rules/require-node-api-error.ts +90 -0
  41. package/src/rules/valid-peer-dependencies.test.ts +130 -0
  42. package/src/rules/valid-peer-dependencies.ts +116 -0
  43. package/src/rules/webhook-lifecycle-complete.test.ts +212 -0
  44. package/src/rules/webhook-lifecycle-complete.ts +120 -0
  45. package/tsconfig.build.tsbuildinfo +1 -1
  46. package/tsconfig.json +0 -1
@@ -0,0 +1,2 @@
1
+ export declare const WebhookLifecycleCompleteRule: import("@typescript-eslint/utils/ts-eslint").RuleModule<"missingWebhookMethods" | "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,qLAwEvC,CAAC"}
@@ -0,0 +1,98 @@
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
+ missingLifecycleMethod: 'Webhook trigger lifecycle is incomplete. `webhookMethods.{{group}}` is missing: {{missing}}. All of `checkExists`, `create`, and `delete` must be implemented.',
43
+ },
44
+ schema: [],
45
+ },
46
+ defaultOptions: [],
47
+ create(context) {
48
+ return {
49
+ ClassDeclaration(node) {
50
+ if (!isNodeTypeClass(node))
51
+ return;
52
+ const descriptionProperty = findClassProperty(node, 'description');
53
+ if (!descriptionProperty)
54
+ return;
55
+ const descriptionValue = descriptionProperty.value;
56
+ if (descriptionValue?.type !== AST_NODE_TYPES.ObjectExpression)
57
+ return;
58
+ const webhookMethodsProperty = findClassProperty(node, 'webhookMethods');
59
+ if (!hasWebhooksDeclared(descriptionValue) && !webhookMethodsProperty) {
60
+ return;
61
+ }
62
+ if (!webhookMethodsProperty?.value) {
63
+ context.report({
64
+ node: node.id ?? node,
65
+ messageId: 'missingWebhookMethods',
66
+ });
67
+ return;
68
+ }
69
+ if (webhookMethodsProperty.value.type !== AST_NODE_TYPES.ObjectExpression) {
70
+ return;
71
+ }
72
+ for (const groupProperty of webhookMethodsProperty.value.properties) {
73
+ if (groupProperty.type !== AST_NODE_TYPES.Property)
74
+ continue;
75
+ if (groupProperty.value.type !== AST_NODE_TYPES.ObjectExpression)
76
+ continue;
77
+ const groupName = groupProperty.key.type === AST_NODE_TYPES.Identifier
78
+ ? groupProperty.key.name
79
+ : groupProperty.key.type === AST_NODE_TYPES.Literal
80
+ ? String(groupProperty.key.value)
81
+ : 'default';
82
+ const missing = findMissingMethods(groupProperty.value);
83
+ if (missing.length === 0)
84
+ continue;
85
+ context.report({
86
+ node: groupProperty.key,
87
+ messageId: 'missingLifecycleMethod',
88
+ data: {
89
+ group: groupName,
90
+ missing: missing.map((m) => `\`${m}\``).join(', '),
91
+ },
92
+ });
93
+ }
94
+ },
95
+ };
96
+ },
97
+ });
98
+ //# 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":"AAAA,OAAO,EAAE,cAAc,EAAiB,MAAM,0BAA0B,CAAC;AAEzE,OAAO,EACN,UAAU,EACV,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,GACf,MAAM,mBAAmB,CAAC;AAE3B,MAAM,gBAAgB,GAAG,CAAC,aAAa,EAAE,QAAQ,EAAE,QAAQ,CAAU,CAAC;AAGtE;;;;;;GAMG;AACH,SAAS,mBAAmB,CAAC,gBAA2C;IACvE,MAAM,gBAAgB,GAAG,kBAAkB,CAAC,gBAAgB,EAAE,UAAU,CAAC,CAAC;IAC1E,IAAI,gBAAgB,EAAE,KAAK,CAAC,IAAI,KAAK,cAAc,CAAC,eAAe;QAAE,OAAO,KAAK,CAAC;IAClF,OAAO,gBAAgB,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;AACnD,CAAC;AAED,qFAAqF;AACrF,SAAS,gBAAgB,CAAC,QAAuC,EAAE,IAAY;IAC9E,IAAI,QAAQ,CAAC,IAAI,KAAK,cAAc,CAAC,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5D,IAAI,QAAQ,CAAC,QAAQ;QAAE,OAAO,KAAK,CAAC;IAEpC,MAAM,UAAU,GACf,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,KAAK,cAAc,CAAC,UAAU,IAAI,QAAQ,CAAC,GAAG,CAAC,IAAI,KAAK,IAAI,CAAC;QAC/E,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,KAAK,cAAc,CAAC,OAAO,IAAI,QAAQ,CAAC,GAAG,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC;IAC/E,IAAI,CAAC,UAAU;QAAE,OAAO,KAAK,CAAC;IAE9B,OAAO,CACN,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,cAAc,CAAC,kBAAkB;QACzD,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,cAAc,CAAC,uBAAuB,CAC9D,CAAC;AACH,CAAC;AAED,SAAS,kBAAkB,CAAC,KAAgC;IAC3D,OAAO,gBAAgB,CAAC,MAAM,CAC7B,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,gBAAgB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CACpF,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,4BAA4B,GAAG,UAAU,CAAC;IACtD,IAAI,EAAE,4BAA4B;IAClC,IAAI,EAAE;QACL,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACL,WAAW,EACV,gHAAgH;SACjH;QACD,QAAQ,EAAE;YACT,qBAAqB,EACpB,0LAA0L;YAC3L,sBAAsB,EACrB,gKAAgK;SACjK;QACD,MAAM,EAAE,EAAE;KACV;IACD,cAAc,EAAE,EAAE;IAClB,MAAM,CAAC,OAAO;QACb,OAAO;YACN,gBAAgB,CAAC,IAAI;gBACpB,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC;oBAAE,OAAO;gBAEnC,MAAM,mBAAmB,GAAG,iBAAiB,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;gBACnE,IAAI,CAAC,mBAAmB;oBAAE,OAAO;gBAEjC,MAAM,gBAAgB,GAAG,mBAAmB,CAAC,KAAK,CAAC;gBACnD,IAAI,gBAAgB,EAAE,IAAI,KAAK,cAAc,CAAC,gBAAgB;oBAAE,OAAO;gBAEvE,MAAM,sBAAsB,GAAG,iBAAiB,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;gBAEzE,IAAI,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,IAAI,CAAC,sBAAsB,EAAE,CAAC;oBACvE,OAAO;gBACR,CAAC;gBAED,IAAI,CAAC,sBAAsB,EAAE,KAAK,EAAE,CAAC;oBACpC,OAAO,CAAC,MAAM,CAAC;wBACd,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,IAAI;wBACrB,SAAS,EAAE,uBAAuB;qBAClC,CAAC,CAAC;oBACH,OAAO;gBACR,CAAC;gBAED,IAAI,sBAAsB,CAAC,KAAK,CAAC,IAAI,KAAK,cAAc,CAAC,gBAAgB,EAAE,CAAC;oBAC3E,OAAO;gBACR,CAAC;gBAED,KAAK,MAAM,aAAa,IAAI,sBAAsB,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC;oBACrE,IAAI,aAAa,CAAC,IAAI,KAAK,cAAc,CAAC,QAAQ;wBAAE,SAAS;oBAC7D,IAAI,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,cAAc,CAAC,gBAAgB;wBAAE,SAAS;oBAE3E,MAAM,SAAS,GACd,aAAa,CAAC,GAAG,CAAC,IAAI,KAAK,cAAc,CAAC,UAAU;wBACnD,CAAC,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI;wBACxB,CAAC,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,KAAK,cAAc,CAAC,OAAO;4BAClD,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC;4BACjC,CAAC,CAAC,SAAS,CAAC;oBAEf,MAAM,OAAO,GAAG,kBAAkB,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;oBACxD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;wBAAE,SAAS;oBAEnC,OAAO,CAAC,MAAM,CAAC;wBACd,IAAI,EAAE,aAAa,CAAC,GAAG;wBACvB,SAAS,EAAE,wBAAwB;wBACnC,IAAI,EAAE;4BACL,KAAK,EAAE,SAAS;4BAChB,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;yBAClD;qBACD,CAAC,CAAC;gBACJ,CAAC;YACF,CAAC;SACD,CAAC;IACH,CAAC;CACD,CAAC,CAAC"}
@@ -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
+ ```
@@ -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,72 @@
1
+ # Require community node package.json peerDependencies to contain only "n8n-workflow": "*" (and optionally "ai-node-sdk") (`@n8n/community-nodes/valid-peer-dependencies`)
2
+
3
+ 💼 This rule is enabled in the following configs: ✅ `recommended`, ☑️ `recommendedWithoutN8nCloudSupport`.
4
+
5
+ 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
6
+
7
+ <!-- end auto-generated rule header -->
8
+
9
+ ## Rule Details
10
+
11
+ Community node packages must declare their n8n integration via `peerDependencies` so that they resolve against the host n8n installation rather than bundling their own copy. The only permitted entries are:
12
+
13
+ - `n8n-workflow` — required, must be exactly `"*"` (no pinned or ranged versions)
14
+ - `ai-node-sdk` — optional, present only for AI nodes (its shape is validated by [`ai-node-package-json`](ai-node-package-json.md))
15
+
16
+ Any other entry (notably `n8n-core`) is flagged because it causes duplicate or incompatible copies of n8n internals to be loaded at runtime.
17
+
18
+ The rule checks:
19
+
20
+ - `peerDependencies` is present in `package.json`
21
+ - `n8n-workflow` is listed with value `"*"`
22
+ - No other packages (besides `ai-node-sdk`) appear in `peerDependencies`
23
+
24
+ ## Examples
25
+
26
+ ### ❌ Incorrect
27
+
28
+ ```json
29
+ {
30
+ "name": "n8n-nodes-example"
31
+ }
32
+ ```
33
+
34
+ ```json
35
+ {
36
+ "name": "n8n-nodes-example",
37
+ "peerDependencies": {
38
+ "n8n-workflow": "^1.0.0"
39
+ }
40
+ }
41
+ ```
42
+
43
+ ```json
44
+ {
45
+ "name": "n8n-nodes-example",
46
+ "peerDependencies": {
47
+ "n8n-workflow": "*",
48
+ "n8n-core": "*"
49
+ }
50
+ }
51
+ ```
52
+
53
+ ### ✅ Correct
54
+
55
+ ```json
56
+ {
57
+ "name": "n8n-nodes-example",
58
+ "peerDependencies": {
59
+ "n8n-workflow": "*"
60
+ }
61
+ }
62
+ ```
63
+
64
+ ```json
65
+ {
66
+ "name": "n8n-nodes-my-ai-node",
67
+ "peerDependencies": {
68
+ "n8n-workflow": "*",
69
+ "ai-node-sdk": "*"
70
+ }
71
+ }
72
+ ```
@@ -0,0 +1,88 @@
1
+ # Require webhook trigger nodes to implement the complete webhookMethods lifecycle (checkExists, create, delete) (`@n8n/community-nodes/webhook-lifecycle-complete`)
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
+ Webhook-based trigger nodes must implement a complete webhook lifecycle so that
10
+ n8n can register, verify, and clean up webhooks on the third-party service.
11
+ Missing any of the three methods results in leaked webhooks, duplicated
12
+ registrations, or workflows that silently stop firing.
13
+
14
+ This rule applies to node classes that:
15
+
16
+ - declare a non-empty `webhooks` array in their `description`, or
17
+ - define a `webhookMethods` class property.
18
+
19
+ For every webhook group inside `webhookMethods` (typically `default`), the
20
+ methods `checkExists`, `create`, and `delete` must all be implemented.
21
+
22
+ Polling triggers (trigger nodes without a `webhooks` array and without
23
+ `webhookMethods`) are intentionally out of scope.
24
+
25
+ ## Examples
26
+
27
+ ### ❌ Incorrect
28
+
29
+ Webhook trigger without any lifecycle methods:
30
+
31
+ ```typescript
32
+ export class MyTrigger implements INodeType {
33
+ description: INodeTypeDescription = {
34
+ displayName: 'My Trigger',
35
+ name: 'myTrigger',
36
+ group: ['trigger'],
37
+ version: 1,
38
+ description: 'Trigger on events',
39
+ defaults: { name: 'My Trigger' },
40
+ inputs: [],
41
+ outputs: ['main'],
42
+ webhooks: [
43
+ { name: 'default', httpMethod: 'POST', responseMode: 'onReceived', path: 'webhook' },
44
+ ],
45
+ properties: [],
46
+ };
47
+ }
48
+ ```
49
+
50
+ Webhook trigger with an incomplete `webhookMethods.default`:
51
+
52
+ ```typescript
53
+ export class MyTrigger implements INodeType {
54
+ description: INodeTypeDescription = { /* ... */ };
55
+
56
+ webhookMethods = {
57
+ default: {
58
+ async create(this: IHookFunctions): Promise<boolean> { /* ... */ return true; },
59
+ // Missing checkExists and delete
60
+ },
61
+ };
62
+ }
63
+ ```
64
+
65
+ ### ✅ Correct
66
+
67
+ ```typescript
68
+ export class MyTrigger implements INodeType {
69
+ description: INodeTypeDescription = { /* ... */ };
70
+
71
+ webhookMethods = {
72
+ default: {
73
+ async checkExists(this: IHookFunctions): Promise<boolean> {
74
+ // Return true if the webhook is already registered on the third-party service.
75
+ return true;
76
+ },
77
+ async create(this: IHookFunctions): Promise<boolean> {
78
+ // Register the webhook with the third-party service and persist any IDs.
79
+ return true;
80
+ },
81
+ async delete(this: IHookFunctions): Promise<boolean> {
82
+ // Remove the webhook from the third-party service.
83
+ return true;
84
+ },
85
+ },
86
+ };
87
+ }
88
+ ```
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.13.0",
4
+ "version": "0.14.0",
5
5
  "main": "./dist/plugin.js",
6
6
  "types": "./dist/plugin.d.ts",
7
7
  "exports": {
@@ -23,8 +23,8 @@
23
23
  "typescript": "6.0.2",
24
24
  "vitest": "^4.1.1",
25
25
  "@n8n/typescript-config": "1.4.0",
26
- "n8n-workflow": "2.18.0",
27
- "@n8n/vitest-config": "1.9.0"
26
+ "n8n-workflow": "2.19.0",
27
+ "@n8n/vitest-config": "1.10.0"
28
28
  },
29
29
  "peerDependencies": {
30
30
  "eslint": ">= 9",
@@ -54,14 +54,14 @@
54
54
  },
55
55
  "scripts": {
56
56
  "build": "tsc --project tsconfig.build.json",
57
- "build:docs": "eslint-doc-generator",
57
+ "build:docs": "pnpm build && eslint-doc-generator",
58
58
  "clean": "rimraf dist .turbo",
59
59
  "dev": "pnpm watch",
60
60
  "format": "biome format --write .",
61
61
  "format:check": "biome ci .",
62
62
  "lint": "eslint src",
63
63
  "lint:fix": "eslint src --fix",
64
- "lint:docs": "eslint-doc-generator --check",
64
+ "lint:docs": "pnpm build && eslint-doc-generator --check",
65
65
  "test": "vitest run",
66
66
  "test:unit": "vitest run",
67
67
  "test:dev": "vitest",
package/src/plugin.ts CHANGED
@@ -31,6 +31,7 @@ const configs = {
31
31
  '@n8n/community-nodes/no-credential-reuse': 'error',
32
32
  '@n8n/community-nodes/no-forbidden-lifecycle-scripts': 'error',
33
33
  '@n8n/community-nodes/no-http-request-with-manual-auth': 'error',
34
+ '@n8n/community-nodes/no-overrides-field': 'error',
34
35
  '@n8n/community-nodes/icon-validation': 'error',
35
36
  '@n8n/community-nodes/options-sorted-alphabetically': 'warn',
36
37
  '@n8n/community-nodes/resource-operation-pattern': 'warn',
@@ -40,7 +41,10 @@ const configs = {
40
41
  '@n8n/community-nodes/missing-paired-item': 'error',
41
42
  '@n8n/community-nodes/require-community-node-keyword': 'warn',
42
43
  '@n8n/community-nodes/require-continue-on-fail': 'error',
44
+ '@n8n/community-nodes/require-node-api-error': 'error',
43
45
  '@n8n/community-nodes/require-node-description-fields': 'error',
46
+ '@n8n/community-nodes/valid-peer-dependencies': 'error',
47
+ '@n8n/community-nodes/webhook-lifecycle-complete': 'error',
44
48
  },
45
49
  },
46
50
  recommendedWithoutN8nCloudSupport: {
@@ -58,6 +62,7 @@ const configs = {
58
62
  '@n8n/community-nodes/no-credential-reuse': 'error',
59
63
  '@n8n/community-nodes/no-forbidden-lifecycle-scripts': 'error',
60
64
  '@n8n/community-nodes/no-http-request-with-manual-auth': 'error',
65
+ '@n8n/community-nodes/no-overrides-field': 'error',
61
66
  '@n8n/community-nodes/icon-validation': 'error',
62
67
  '@n8n/community-nodes/options-sorted-alphabetically': 'warn',
63
68
  '@n8n/community-nodes/credential-documentation-url': 'error',
@@ -67,7 +72,10 @@ const configs = {
67
72
  '@n8n/community-nodes/missing-paired-item': 'error',
68
73
  '@n8n/community-nodes/require-community-node-keyword': 'warn',
69
74
  '@n8n/community-nodes/require-continue-on-fail': 'error',
75
+ '@n8n/community-nodes/require-node-api-error': 'error',
70
76
  '@n8n/community-nodes/require-node-description-fields': 'error',
77
+ '@n8n/community-nodes/valid-peer-dependencies': 'error',
78
+ '@n8n/community-nodes/webhook-lifecycle-complete': 'error',
71
79
  },
72
80
  },
73
81
  } satisfies Record<string, Linter.Config>;
@@ -11,6 +11,7 @@ import { NoCredentialReuseRule } from './no-credential-reuse.js';
11
11
  import { NoDeprecatedWorkflowFunctionsRule } from './no-deprecated-workflow-functions.js';
12
12
  import { NoForbiddenLifecycleScriptsRule } from './no-forbidden-lifecycle-scripts.js';
13
13
  import { NoHttpRequestWithManualAuthRule } from './no-http-request-with-manual-auth.js';
14
+ import { NoOverridesFieldRule } from './no-overrides-field.js';
14
15
  import { NoRestrictedGlobalsRule } from './no-restricted-globals.js';
15
16
  import { NoRestrictedImportsRule } from './no-restricted-imports.js';
16
17
  import { NodeClassDescriptionIconMissingRule } from './node-class-description-icon-missing.js';
@@ -20,8 +21,11 @@ import { OptionsSortedAlphabeticallyRule } from './options-sorted-alphabetically
20
21
  import { PackageNameConventionRule } from './package-name-convention.js';
21
22
  import { RequireCommunityNodeKeywordRule } from './require-community-node-keyword.js';
22
23
  import { RequireContinueOnFailRule } from './require-continue-on-fail.js';
24
+ import { RequireNodeApiErrorRule } from './require-node-api-error.js';
23
25
  import { RequireNodeDescriptionFieldsRule } from './require-node-description-fields.js';
24
26
  import { ResourceOperationPatternRule } from './resource-operation-pattern.js';
27
+ import { ValidPeerDependenciesRule } from './valid-peer-dependencies.js';
28
+ import { WebhookLifecycleCompleteRule } from './webhook-lifecycle-complete.js';
25
29
 
26
30
  export const rules = {
27
31
  'ai-node-package-json': AiNodePackageJsonRule,
@@ -36,6 +40,7 @@ export const rules = {
36
40
  'no-credential-reuse': NoCredentialReuseRule,
37
41
  'no-forbidden-lifecycle-scripts': NoForbiddenLifecycleScriptsRule,
38
42
  'no-http-request-with-manual-auth': NoHttpRequestWithManualAuthRule,
43
+ 'no-overrides-field': NoOverridesFieldRule,
39
44
  'icon-validation': IconValidationRule,
40
45
  'resource-operation-pattern': ResourceOperationPatternRule,
41
46
  'credential-documentation-url': CredentialDocumentationUrlRule,
@@ -45,5 +50,8 @@ export const rules = {
45
50
  'missing-paired-item': MissingPairedItemRule,
46
51
  'require-community-node-keyword': RequireCommunityNodeKeywordRule,
47
52
  'require-continue-on-fail': RequireContinueOnFailRule,
53
+ 'require-node-api-error': RequireNodeApiErrorRule,
48
54
  'require-node-description-fields': RequireNodeDescriptionFieldsRule,
55
+ 'valid-peer-dependencies': ValidPeerDependenciesRule,
56
+ 'webhook-lifecycle-complete': WebhookLifecycleCompleteRule,
49
57
  } satisfies Record<string, AnyRuleModule>;
@@ -0,0 +1,50 @@
1
+ import { RuleTester } from '@typescript-eslint/rule-tester';
2
+
3
+ import { NoOverridesFieldRule } from './no-overrides-field.js';
4
+
5
+ const ruleTester = new RuleTester();
6
+
7
+ ruleTester.run('no-overrides-field', NoOverridesFieldRule, {
8
+ valid: [
9
+ {
10
+ name: 'no overrides field',
11
+ filename: 'package.json',
12
+ code: '{ "name": "n8n-nodes-example", "version": "1.0.0" }',
13
+ },
14
+ {
15
+ name: 'package.json with dependencies but no overrides',
16
+ filename: 'package.json',
17
+ code: '{ "name": "n8n-nodes-example", "peerDependencies": { "n8n-workflow": "*" } }',
18
+ },
19
+ {
20
+ name: 'non-package.json file is ignored',
21
+ filename: 'some-config.json',
22
+ code: '{ "overrides": { "axios": "1.0.0" } }',
23
+ },
24
+ {
25
+ name: 'nested "overrides" key inside another field is allowed',
26
+ filename: 'package.json',
27
+ code: '{ "name": "n8n-nodes-example", "config": { "overrides": { "axios": "1.0.0" } } }',
28
+ },
29
+ ],
30
+ invalid: [
31
+ {
32
+ name: 'overrides as object is forbidden',
33
+ filename: 'package.json',
34
+ code: '{ "name": "n8n-nodes-example", "overrides": { "axios": "1.0.0" } }',
35
+ errors: [{ messageId: 'overridesForbidden' }],
36
+ },
37
+ {
38
+ name: 'empty overrides object is forbidden',
39
+ filename: 'package.json',
40
+ code: '{ "name": "n8n-nodes-example", "overrides": {} }',
41
+ errors: [{ messageId: 'overridesForbidden' }],
42
+ },
43
+ {
44
+ name: 'real-world overrides (CNOC-404 Sinch) is forbidden',
45
+ filename: 'package.json',
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
+ errors: [{ messageId: 'overridesForbidden' }],
48
+ },
49
+ ],
50
+ });
@@ -0,0 +1,43 @@
1
+ import type { TSESTree } from '@typescript-eslint/utils';
2
+ import { AST_NODE_TYPES } from '@typescript-eslint/utils';
3
+
4
+ import { createRule, findJsonProperty } from '../utils/index.js';
5
+
6
+ export const NoOverridesFieldRule = createRule({
7
+ name: 'no-overrides-field',
8
+ meta: {
9
+ type: 'problem',
10
+ docs: {
11
+ description: 'Ban the "overrides" field in community node package.json',
12
+ },
13
+ messages: {
14
+ overridesForbidden:
15
+ 'The "overrides" field is not allowed in community node packages. Dependency overrides can introduce incompatible versions of shared libraries into the n8n runtime and cause conflicts with other nodes.',
16
+ },
17
+ schema: [],
18
+ },
19
+ defaultOptions: [],
20
+ create(context) {
21
+ if (!context.filename.endsWith('package.json')) {
22
+ return {};
23
+ }
24
+
25
+ return {
26
+ ObjectExpression(node: TSESTree.ObjectExpression) {
27
+ if (node.parent?.type !== AST_NODE_TYPES.ExpressionStatement) {
28
+ return;
29
+ }
30
+
31
+ const overridesProp = findJsonProperty(node, 'overrides');
32
+ if (!overridesProp) {
33
+ return;
34
+ }
35
+
36
+ context.report({
37
+ node: overridesProp,
38
+ messageId: 'overridesForbidden',
39
+ });
40
+ },
41
+ };
42
+ },
43
+ });