@n8n/eslint-plugin-community-nodes 0.12.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 (63) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/README.md +30 -23
  3. package/dist/plugin.d.ts +36 -6
  4. package/dist/plugin.d.ts.map +1 -1
  5. package/dist/plugin.js +12 -2
  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/node-class-description-icon-missing.d.ts +1 -0
  16. package/dist/rules/node-class-description-icon-missing.d.ts.map +1 -1
  17. package/dist/rules/node-class-description-icon-missing.js +6 -1
  18. package/dist/rules/node-class-description-icon-missing.js.map +1 -1
  19. package/dist/rules/require-community-node-keyword.d.ts +2 -0
  20. package/dist/rules/require-community-node-keyword.d.ts.map +1 -0
  21. package/dist/rules/require-community-node-keyword.js +65 -0
  22. package/dist/rules/require-community-node-keyword.js.map +1 -0
  23. package/dist/rules/require-node-api-error.d.ts +2 -0
  24. package/dist/rules/require-node-api-error.d.ts.map +1 -0
  25. package/dist/rules/require-node-api-error.js +77 -0
  26. package/dist/rules/require-node-api-error.js.map +1 -0
  27. package/dist/rules/require-node-description-fields.d.ts +2 -0
  28. package/dist/rules/require-node-description-fields.d.ts.map +1 -0
  29. package/dist/rules/require-node-description-fields.js +58 -0
  30. package/dist/rules/require-node-description-fields.js.map +1 -0
  31. package/dist/rules/valid-peer-dependencies.d.ts +2 -0
  32. package/dist/rules/valid-peer-dependencies.d.ts.map +1 -0
  33. package/dist/rules/valid-peer-dependencies.js +107 -0
  34. package/dist/rules/valid-peer-dependencies.js.map +1 -0
  35. package/dist/rules/webhook-lifecycle-complete.d.ts +2 -0
  36. package/dist/rules/webhook-lifecycle-complete.d.ts.map +1 -0
  37. package/dist/rules/webhook-lifecycle-complete.js +98 -0
  38. package/dist/rules/webhook-lifecycle-complete.js.map +1 -0
  39. package/docs/rules/no-overrides-field.md +50 -0
  40. package/docs/rules/node-class-description-icon-missing.md +4 -2
  41. package/docs/rules/require-community-node-keyword.md +45 -0
  42. package/docs/rules/require-node-api-error.md +62 -0
  43. package/docs/rules/require-node-description-fields.md +46 -0
  44. package/docs/rules/valid-peer-dependencies.md +72 -0
  45. package/docs/rules/webhook-lifecycle-complete.md +88 -0
  46. package/package.json +5 -5
  47. package/src/plugin.ts +12 -2
  48. package/src/rules/index.ts +12 -0
  49. package/src/rules/no-overrides-field.test.ts +50 -0
  50. package/src/rules/no-overrides-field.ts +43 -0
  51. package/src/rules/node-class-description-icon-missing.ts +7 -1
  52. package/src/rules/require-community-node-keyword.test.ts +82 -0
  53. package/src/rules/require-community-node-keyword.ts +78 -0
  54. package/src/rules/require-node-api-error.test.ts +199 -0
  55. package/src/rules/require-node-api-error.ts +90 -0
  56. package/src/rules/require-node-description-fields.test.ts +171 -0
  57. package/src/rules/require-node-description-fields.ts +77 -0
  58. package/src/rules/valid-peer-dependencies.test.ts +130 -0
  59. package/src/rules/valid-peer-dependencies.ts +116 -0
  60. package/src/rules/webhook-lifecycle-complete.test.ts +212 -0
  61. package/src/rules/webhook-lifecycle-complete.ts +120 -0
  62. package/tsconfig.build.tsbuildinfo +1 -1
  63. package/tsconfig.json +0 -1
@@ -0,0 +1,77 @@
1
+ import { DefinitionType } from '@typescript-eslint/scope-manager';
2
+ import { AST_NODE_TYPES } from '@typescript-eslint/utils';
3
+ import { isFileType } from '../utils/index.js';
4
+ import { createRule } from '../utils/rule-creator.js';
5
+ const ALLOWED_ERROR_CLASSES = new Set(['NodeApiError', 'NodeOperationError']);
6
+ function getThrowCalleeName(argument) {
7
+ if (argument.type === AST_NODE_TYPES.NewExpression) {
8
+ if (argument.callee.type === AST_NODE_TYPES.Identifier) {
9
+ return argument.callee.name;
10
+ }
11
+ }
12
+ return null;
13
+ }
14
+ function isInsideCatchClause(node) {
15
+ let current = node.parent;
16
+ while (current) {
17
+ if (current.type === AST_NODE_TYPES.CatchClause) {
18
+ return true;
19
+ }
20
+ current = current.parent;
21
+ }
22
+ return false;
23
+ }
24
+ export const RequireNodeApiErrorRule = createRule({
25
+ name: 'require-node-api-error',
26
+ meta: {
27
+ type: 'problem',
28
+ docs: {
29
+ description: 'Require NodeApiError or NodeOperationError for error wrapping in catch blocks. ' +
30
+ 'Raw errors lose HTTP context in the n8n UI.',
31
+ },
32
+ messages: {
33
+ useNodeApiError: 'Use `NodeApiError` or `NodeOperationError` instead of re-throwing raw errors. ' +
34
+ 'Example: `throw new NodeApiError(this.getNode(), error as JsonObject)`',
35
+ useNodeApiErrorInsteadOfGeneric: 'Use `NodeApiError` or `NodeOperationError` instead of `{{ errorClass }}`. ' +
36
+ 'Example: `throw new NodeApiError(this.getNode(), error as JsonObject)`',
37
+ },
38
+ schema: [],
39
+ },
40
+ defaultOptions: [],
41
+ create(context) {
42
+ const isNodeFile = isFileType(context.filename, '.node.ts');
43
+ const isHelperFile = context.filename.endsWith('.ts') &&
44
+ !isNodeFile &&
45
+ !isFileType(context.filename, '.credentials.ts');
46
+ if (!isNodeFile && !isHelperFile) {
47
+ return {};
48
+ }
49
+ return {
50
+ ThrowStatement(node) {
51
+ if (!isInsideCatchClause(node))
52
+ return;
53
+ if (!node.argument)
54
+ return;
55
+ const { argument } = node;
56
+ if (argument.type === AST_NODE_TYPES.Identifier) {
57
+ const scope = context.sourceCode.getScope(node);
58
+ const ref = scope.references.find((r) => r.identifier === argument);
59
+ const isCatchParam = ref?.resolved?.defs.some((def) => def.type === DefinitionType.CatchClause) ?? false;
60
+ if (isCatchParam) {
61
+ context.report({ node, messageId: 'useNodeApiError' });
62
+ }
63
+ return;
64
+ }
65
+ const calleeName = getThrowCalleeName(argument);
66
+ if (calleeName !== null && !ALLOWED_ERROR_CLASSES.has(calleeName)) {
67
+ context.report({
68
+ node,
69
+ messageId: 'useNodeApiErrorInsteadOfGeneric',
70
+ data: { errorClass: calleeName },
71
+ });
72
+ }
73
+ },
74
+ };
75
+ },
76
+ });
77
+ //# sourceMappingURL=require-node-api-error.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"require-node-api-error.js","sourceRoot":"","sources":["../../src/rules/require-node-api-error.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare const RequireNodeDescriptionFieldsRule: import("@typescript-eslint/utils/ts-eslint").RuleModule<"missingField" | "missingFields", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
2
+ //# sourceMappingURL=require-node-description-fields.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"require-node-description-fields.d.ts","sourceRoot":"","sources":["../../src/rules/require-node-description-fields.ts"],"names":[],"mappings":"AAiBA,eAAO,MAAM,gCAAgC,mKA2D3C,CAAC"}
@@ -0,0 +1,58 @@
1
+ import { TSESTree } from '@typescript-eslint/utils';
2
+ import { isNodeTypeClass, findClassProperty, findObjectProperty, isFileType, createRule, } from '../utils/index.js';
3
+ // Fields that are optional in the TypeScript interface but required by
4
+ // community node review standards. TypeScript-required fields (displayName,
5
+ // name, group, version, description, etc.) are already caught by tsc.
6
+ const REQUIRED_FIELDS = ['icon', 'subtitle'];
7
+ export const RequireNodeDescriptionFieldsRule = createRule({
8
+ name: 'require-node-description-fields',
9
+ meta: {
10
+ type: 'problem',
11
+ docs: {
12
+ description: `Node class description must define all required fields: ${REQUIRED_FIELDS.join(', ')}`,
13
+ },
14
+ messages: {
15
+ missingField: 'Node class description is missing required `{{field}}` property',
16
+ missingFields: 'Node class description is missing required properties: {{fields}}',
17
+ },
18
+ schema: [],
19
+ },
20
+ defaultOptions: [],
21
+ create(context) {
22
+ if (!isFileType(context.filename, '.node.ts')) {
23
+ return {};
24
+ }
25
+ return {
26
+ ClassDeclaration(node) {
27
+ if (!isNodeTypeClass(node)) {
28
+ return;
29
+ }
30
+ const descriptionProperty = findClassProperty(node, 'description');
31
+ if (!descriptionProperty?.value ||
32
+ descriptionProperty.value.type !== TSESTree.AST_NODE_TYPES.ObjectExpression) {
33
+ return;
34
+ }
35
+ const descriptionValue = descriptionProperty.value;
36
+ const missingFields = REQUIRED_FIELDS.filter((field) => !findObjectProperty(descriptionValue, field));
37
+ if (missingFields.length === 0) {
38
+ return;
39
+ }
40
+ if (missingFields.length === 1) {
41
+ context.report({
42
+ node: descriptionProperty,
43
+ messageId: 'missingField',
44
+ data: { field: missingFields[0] },
45
+ });
46
+ }
47
+ else {
48
+ context.report({
49
+ node: descriptionProperty,
50
+ messageId: 'missingFields',
51
+ data: { fields: missingFields.map((f) => `\`${f}\``).join(', ') },
52
+ });
53
+ }
54
+ },
55
+ };
56
+ },
57
+ });
58
+ //# sourceMappingURL=require-node-description-fields.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"require-node-description-fields.js","sourceRoot":"","sources":["../../src/rules/require-node-description-fields.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEpD,OAAO,EACN,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,UAAU,EACV,UAAU,GACV,MAAM,mBAAmB,CAAC;AAE3B,uEAAuE;AACvE,4EAA4E;AAC5E,sEAAsE;AACtE,MAAM,eAAe,GAAG,CAAC,MAAM,EAAE,UAAU,CAAU,CAAC;AAItD,MAAM,CAAC,MAAM,gCAAgC,GAAG,UAAU,CAAC;IAC1D,IAAI,EAAE,iCAAiC;IACvC,IAAI,EAAE;QACL,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACL,WAAW,EAAE,2DAA2D,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;SACpG;QACD,QAAQ,EAAE;YACT,YAAY,EAAE,iEAAiE;YAC/E,aAAa,EAAE,mEAAmE;SAClF;QACD,MAAM,EAAE,EAAE;KACV;IACD,cAAc,EAAE,EAAE;IAClB,MAAM,CAAC,OAAO;QACb,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,QAAQ,EAAE,UAAU,CAAC,EAAE,CAAC;YAC/C,OAAO,EAAE,CAAC;QACX,CAAC;QAED,OAAO;YACN,gBAAgB,CAAC,IAAI;gBACpB,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC;oBAC5B,OAAO;gBACR,CAAC;gBAED,MAAM,mBAAmB,GAAG,iBAAiB,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;gBACnE,IACC,CAAC,mBAAmB,EAAE,KAAK;oBAC3B,mBAAmB,CAAC,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,cAAc,CAAC,gBAAgB,EAC1E,CAAC;oBACF,OAAO;gBACR,CAAC;gBAED,MAAM,gBAAgB,GAAG,mBAAmB,CAAC,KAAK,CAAC;gBAEnD,MAAM,aAAa,GAAoB,eAAe,CAAC,MAAM,CAC5D,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,kBAAkB,CAAC,gBAAgB,EAAE,KAAK,CAAC,CACvD,CAAC;gBAEF,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBAChC,OAAO;gBACR,CAAC;gBAED,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBAChC,OAAO,CAAC,MAAM,CAAC;wBACd,IAAI,EAAE,mBAAmB;wBACzB,SAAS,EAAE,cAAc;wBACzB,IAAI,EAAE,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE;qBACjC,CAAC,CAAC;gBACJ,CAAC;qBAAM,CAAC;oBACP,OAAO,CAAC,MAAM,CAAC;wBACd,IAAI,EAAE,mBAAmB;wBACzB,SAAS,EAAE,eAAe;wBAC1B,IAAI,EAAE,EAAE,MAAM,EAAE,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;qBACjE,CAAC,CAAC;gBACJ,CAAC;YACF,CAAC;SACD,CAAC;IACH,CAAC;CACD,CAAC,CAAC"}
@@ -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" | "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":"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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
+ ```
@@ -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 enabled in the following configs: ✅ `recommended`, ☑️ `recommendedWithoutN8nCloudSupport`.
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.
@@ -0,0 +1,45 @@
1
+ # Require the "n8n-community-node-package" keyword in community node package.json (`@n8n/community-nodes/require-community-node-keyword`)
2
+
3
+ ⚠️ This rule _warns_ 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
+ Validates that the `package.json` of a community node package includes `"n8n-community-node-package"` in its `keywords` array. This keyword is required for n8n to discover and list community node packages.
12
+
13
+ ## Examples
14
+
15
+ ### ❌ Incorrect
16
+
17
+ ```json
18
+ {
19
+ "name": "n8n-nodes-my-service",
20
+ "version": "1.0.0"
21
+ }
22
+ ```
23
+
24
+ ```json
25
+ {
26
+ "name": "n8n-nodes-my-service",
27
+ "keywords": ["n8n", "automation"]
28
+ }
29
+ ```
30
+
31
+ ### ✅ Correct
32
+
33
+ ```json
34
+ {
35
+ "name": "n8n-nodes-my-service",
36
+ "keywords": ["n8n-community-node-package"]
37
+ }
38
+ ```
39
+
40
+ ```json
41
+ {
42
+ "name": "n8n-nodes-my-service",
43
+ "keywords": ["n8n", "automation", "n8n-community-node-package"]
44
+ }
45
+ ```
@@ -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
+ ```
@@ -0,0 +1,46 @@
1
+ # Node class description must define all required fields: icon, subtitle (`@n8n/community-nodes/require-node-description-fields`)
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
+ Enforces that community node class descriptions include fields that are optional in the TypeScript interface but required by community node review standards.
10
+
11
+ This rule supersedes `node-class-description-icon-missing`, which only checked for the `icon` field.
12
+
13
+ ## Examples
14
+
15
+ ### ❌ Incorrect
16
+
17
+ ```typescript
18
+ export class MyNode implements INodeType {
19
+ description: INodeTypeDescription = {
20
+ displayName: 'My Node',
21
+ name: 'myNode',
22
+ icon: 'file:myNode.svg',
23
+ group: ['transform'],
24
+ version: 1,
25
+ description: 'My node description',
26
+ // Missing subtitle
27
+ };
28
+ }
29
+ ```
30
+
31
+ ### ✅ Correct
32
+
33
+ ```typescript
34
+ export class MyNode implements INodeType {
35
+ description: INodeTypeDescription = {
36
+ displayName: 'My Node',
37
+ name: 'myNode',
38
+ icon: 'file:myNode.svg',
39
+ group: ['transform'],
40
+ version: 1,
41
+ description: 'My node description',
42
+ subtitle: '={{$parameter["operation"]}}',
43
+ // ...
44
+ };
45
+ }
46
+ ```