@n8n/eslint-plugin-community-nodes 0.29.0 → 0.30.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.
@@ -1 +1 @@
1
- {"version":3,"file":"node-registration-complete.d.ts","sourceRoot":"","sources":["../../src/rules/node-registration-complete.ts"],"names":[],"mappings":"AAWA,eAAO,MAAM,4BAA4B,sJAqDvC,CAAC"}
1
+ {"version":3,"file":"node-registration-complete.d.ts","sourceRoot":"","sources":["../../src/rules/node-registration-complete.ts"],"names":[],"mappings":"AAYA,eAAO,MAAM,4BAA4B,sJAkEvC,CAAC"}
@@ -1,6 +1,6 @@
1
1
  import { AST_NODE_TYPES } from '@typescript-eslint/utils';
2
2
  import * as path from 'node:path';
3
- import { createRule, findJsonProperty, findNodeSourceFilesOnDisk, readPackageJsonNodes, } from '../utils/index.js';
3
+ import { createRule, findJsonProperty, findNodeSourceFilesOnDisk, isContainedWithin, readPackageJsonNodes, } from '../utils/index.js';
4
4
  export const NodeRegistrationCompleteRule = createRule({
5
5
  name: 'node-registration-complete',
6
6
  meta: {
@@ -28,11 +28,23 @@ export const NodeRegistrationCompleteRule = createRule({
28
28
  if (nodeFilesOnDisk.length === 0) {
29
29
  return;
30
30
  }
31
- const registered = new Set(readPackageJsonNodes(context.filename).map((filePath) => path.resolve(filePath)));
31
+ const registeredFiles = readPackageJsonNodes(context.filename).map((filePath) => path.resolve(filePath));
32
+ const registered = new Set(registeredFiles);
33
+ // Directories of registered entry files, used to recognise versioned
34
+ // nodes: a `VersionedNodeType` entry file (e.g. `SoterGuard.node.ts`) is
35
+ // registered, and its per-version implementations live in subdirectories
36
+ // (e.g. `v1/SoterGuardV1.node.ts`) pulled in by that entry file.
37
+ const registeredDirs = registeredFiles.map((filePath) => path.dirname(filePath));
32
38
  const packageDir = path.dirname(context.filename);
33
39
  const reportTarget = resolveReportTarget(node);
34
40
  for (const nodeFile of nodeFilesOnDisk) {
35
- if (registered.has(path.resolve(nodeFile))) {
41
+ const resolvedNodeFile = path.resolve(nodeFile);
42
+ if (registered.has(resolvedNodeFile)) {
43
+ continue;
44
+ }
45
+ // Skip per-version implementation files nested under a registered
46
+ // versioned node's entry directory.
47
+ if (isRegisteredThroughVersionedEntry(resolvedNodeFile, registeredDirs)) {
36
48
  continue;
37
49
  }
38
50
  context.report({
@@ -45,6 +57,17 @@ export const NodeRegistrationCompleteRule = createRule({
45
57
  };
46
58
  },
47
59
  });
60
+ /**
61
+ * Determines whether a node file is a per-version implementation of a registered
62
+ * versioned node. Such files sit in a subdirectory of the registered
63
+ * `VersionedNodeType` entry file (e.g. `SoterGuard/v1/SoterGuardV1.node.ts` under
64
+ * the entry `SoterGuard/SoterGuard.node.ts`), so they are discovered through the
65
+ * entry file rather than being listed individually in `n8n.nodes`.
66
+ */
67
+ function isRegisteredThroughVersionedEntry(nodeFile, registeredDirs) {
68
+ const nodeDir = path.dirname(nodeFile);
69
+ return registeredDirs.some((registeredDir) => registeredDir !== nodeDir && isContainedWithin(registeredDir, nodeDir));
70
+ }
48
71
  /**
49
72
  * Reports against the most specific available node: the `n8n.nodes` array, the
50
73
  * `n8n` object, or the package.json root object as a fallback.
@@ -1 +1 @@
1
- {"version":3,"file":"node-registration-complete.js","sourceRoot":"","sources":["../../src/rules/node-registration-complete.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,EACN,UAAU,EACV,gBAAgB,EAChB,yBAAyB,EACzB,oBAAoB,GACpB,MAAM,mBAAmB,CAAC;AAE3B,MAAM,CAAC,MAAM,4BAA4B,GAAG,UAAU,CAAC;IACtD,IAAI,EAAE,4BAA4B;IAClC,IAAI,EAAE;QACL,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACL,WAAW,EACV,+GAA+G;SAChH;QACD,QAAQ,EAAE;YACT,iBAAiB,EAChB,iIAAiI;SAClI;QACD,MAAM,EAAE,EAAE;KACV;IACD,cAAc,EAAE,EAAE;IAClB,MAAM,CAAC,OAAO;QACb,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;YAChD,OAAO,EAAE,CAAC;QACX,CAAC;QAED,OAAO;YACN,gBAAgB,CAAC,IAA+B;gBAC/C,yDAAyD;gBACzD,IAAI,IAAI,CAAC,MAAM,EAAE,IAAI,KAAK,cAAc,CAAC,mBAAmB,EAAE,CAAC;oBAC9D,OAAO;gBACR,CAAC;gBAED,MAAM,eAAe,GAAG,yBAAyB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;gBACpE,IAAI,eAAe,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBAClC,OAAO;gBACR,CAAC;gBAED,MAAM,UAAU,GAAG,IAAI,GAAG,CACzB,oBAAoB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAChF,CAAC;gBAEF,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;gBAClD,MAAM,YAAY,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC;gBAE/C,KAAK,MAAM,QAAQ,IAAI,eAAe,EAAE,CAAC;oBACxC,IAAI,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;wBAC5C,SAAS;oBACV,CAAC;oBAED,OAAO,CAAC,MAAM,CAAC;wBACd,IAAI,EAAE,YAAY;wBAClB,SAAS,EAAE,mBAAmB;wBAC9B,IAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,EAAE;qBACvD,CAAC,CAAC;gBACJ,CAAC;YACF,CAAC;SACD,CAAC;IACH,CAAC;CACD,CAAC,CAAC;AAEH;;;GAGG;AACH,SAAS,mBAAmB,CAAC,IAA+B;IAC3D,MAAM,WAAW,GAAG,gBAAgB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAClD,IAAI,WAAW,EAAE,KAAK,CAAC,IAAI,KAAK,cAAc,CAAC,gBAAgB,EAAE,CAAC;QACjE,OAAO,WAAW,IAAI,IAAI,CAAC;IAC5B,CAAC;IAED,MAAM,aAAa,GAAG,gBAAgB,CAAC,WAAW,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACnE,OAAO,aAAa,IAAI,WAAW,CAAC;AACrC,CAAC"}
1
+ {"version":3,"file":"node-registration-complete.js","sourceRoot":"","sources":["../../src/rules/node-registration-complete.ts"],"names":[],"mappings":"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"}
@@ -44,3 +44,29 @@ Every node file is registered:
44
44
  }
45
45
  }
46
46
  ```
47
+
48
+ ### Versioned nodes
49
+
50
+ A versioned node registers only its `VersionedNodeType` entry file. Its per-version
51
+ implementations live in subdirectories of that entry file and are pulled in by it, so
52
+ they must **not** be listed individually in `n8n.nodes`. The rule recognises this and
53
+ does not flag the version files.
54
+
55
+ Given:
56
+
57
+ ```
58
+ nodes/SoterGuard/SoterGuard.node.ts
59
+ nodes/SoterGuard/v1/SoterGuardV1.node.ts
60
+ nodes/SoterGuard/v2/SoterGuardV2.node.ts
61
+ ```
62
+
63
+ registering just the entry file is correct:
64
+
65
+ ```json
66
+ {
67
+ "name": "n8n-nodes-my-service",
68
+ "n8n": {
69
+ "nodes": ["dist/nodes/SoterGuard/SoterGuard.node.js"]
70
+ }
71
+ }
72
+ ```
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.29.0",
4
+ "version": "0.30.0",
5
5
  "main": "./dist/plugin.js",
6
6
  "types": "./dist/plugin.d.ts",
7
7
  "exports": {
@@ -25,9 +25,9 @@
25
25
  "typescript": "npm:@typescript/typescript6@6.0.2",
26
26
  "vite": "^8.0.2",
27
27
  "vitest": "^4.1.9",
28
- "@n8n/typescript-config": "1.9.0",
29
- "@n8n/vitest-config": "1.20.0",
30
- "n8n-workflow": "2.35.0"
28
+ "@n8n/typescript-config": "1.10.0",
29
+ "@n8n/vitest-config": "1.21.0",
30
+ "n8n-workflow": "2.37.0"
31
31
  },
32
32
  "peerDependencies": {
33
33
  "eslint": "9.29.0",
@@ -20,6 +20,16 @@ const packageJsonPath = '/tmp/package.json';
20
20
  const fooNode = '/tmp/nodes/Foo/Foo.node.ts';
21
21
  const barNode = '/tmp/nodes/Bar/Bar.node.ts';
22
22
 
23
+ // Versioned node: the v1/v2 implementations sit behind a `VersionedNodeType`
24
+ // entry file, and only that entry file is listed in `n8n.nodes`.
25
+ const versionedEntryNode = '/tmp/nodes/SoterGuard/SoterGuard.node.ts';
26
+ const versionedV1Node = '/tmp/nodes/SoterGuard/v1/SoterGuardV1.node.ts';
27
+ const versionedV2Node = '/tmp/nodes/SoterGuard/v2/SoterGuardV2.node.ts';
28
+
29
+ // A sibling node file in the same directory as a registered node (not nested in a
30
+ // version subdirectory) is not a versioned implementation and must still be flagged.
31
+ const fooSiblingNode = '/tmp/nodes/Foo/FooExtra.node.ts';
32
+
23
33
  const ruleTester = new RuleTester();
24
34
 
25
35
  function setup(onDisk: string[], registered: string[]): void {
@@ -46,6 +56,17 @@ ruleTester.run('node-registration-complete', NodeRegistrationCompleteRule, {
46
56
  filename: 'some-config.json',
47
57
  code: '{ "name": "n8n-nodes-example" }',
48
58
  },
59
+ {
60
+ // A versioned node registers only its `VersionedNodeType` entry file in
61
+ // `n8n.nodes`; the per-version implementation files (v1/v2) are pulled in
62
+ // by that entry file and must not be flagged as unregistered.
63
+ name: 'versioned node registered via its entry file does not flag its version implementations',
64
+ filename: packageJsonPath,
65
+ code: '{ "name": "n8n-nodes-example", "n8n": { "nodes": ["dist/nodes/SoterGuard/SoterGuard.node.js"] } }',
66
+ before() {
67
+ setup([versionedEntryNode, versionedV1Node, versionedV2Node], [versionedEntryNode]);
68
+ },
69
+ },
49
70
  ],
50
71
  invalid: [
51
72
  {
@@ -74,6 +95,20 @@ ruleTester.run('node-registration-complete', NodeRegistrationCompleteRule, {
74
95
  { messageId: 'nodeNotRegistered', data: { nodeFile: 'nodes/Bar/Bar.node.ts' } },
75
96
  ],
76
97
  },
98
+ {
99
+ // The unregistered file sits directly beside a registered node, not in a
100
+ // version subdirectory, so it is not covered by the versioned-node entry
101
+ // and must still be reported.
102
+ name: 'unregistered sibling in a registered node directory is still flagged',
103
+ filename: packageJsonPath,
104
+ code: '{ "name": "n8n-nodes-example", "n8n": { "nodes": ["dist/nodes/Foo/Foo.node.js"] } }',
105
+ before() {
106
+ setup([fooNode, fooSiblingNode], [fooNode]);
107
+ },
108
+ errors: [
109
+ { messageId: 'nodeNotRegistered', data: { nodeFile: 'nodes/Foo/FooExtra.node.ts' } },
110
+ ],
111
+ },
77
112
  {
78
113
  name: 'node files exist on disk but there is no n8n object',
79
114
  filename: packageJsonPath,
@@ -6,6 +6,7 @@ import {
6
6
  createRule,
7
7
  findJsonProperty,
8
8
  findNodeSourceFilesOnDisk,
9
+ isContainedWithin,
9
10
  readPackageJsonNodes,
10
11
  } from '../utils/index.js';
11
12
 
@@ -41,15 +42,28 @@ export const NodeRegistrationCompleteRule = createRule({
41
42
  return;
42
43
  }
43
44
 
44
- const registered = new Set(
45
- readPackageJsonNodes(context.filename).map((filePath) => path.resolve(filePath)),
45
+ const registeredFiles = readPackageJsonNodes(context.filename).map((filePath) =>
46
+ path.resolve(filePath),
46
47
  );
48
+ const registered = new Set(registeredFiles);
49
+ // Directories of registered entry files, used to recognise versioned
50
+ // nodes: a `VersionedNodeType` entry file (e.g. `SoterGuard.node.ts`) is
51
+ // registered, and its per-version implementations live in subdirectories
52
+ // (e.g. `v1/SoterGuardV1.node.ts`) pulled in by that entry file.
53
+ const registeredDirs = registeredFiles.map((filePath) => path.dirname(filePath));
47
54
 
48
55
  const packageDir = path.dirname(context.filename);
49
56
  const reportTarget = resolveReportTarget(node);
50
57
 
51
58
  for (const nodeFile of nodeFilesOnDisk) {
52
- if (registered.has(path.resolve(nodeFile))) {
59
+ const resolvedNodeFile = path.resolve(nodeFile);
60
+ if (registered.has(resolvedNodeFile)) {
61
+ continue;
62
+ }
63
+
64
+ // Skip per-version implementation files nested under a registered
65
+ // versioned node's entry directory.
66
+ if (isRegisteredThroughVersionedEntry(resolvedNodeFile, registeredDirs)) {
53
67
  continue;
54
68
  }
55
69
 
@@ -64,6 +78,20 @@ export const NodeRegistrationCompleteRule = createRule({
64
78
  },
65
79
  });
66
80
 
81
+ /**
82
+ * Determines whether a node file is a per-version implementation of a registered
83
+ * versioned node. Such files sit in a subdirectory of the registered
84
+ * `VersionedNodeType` entry file (e.g. `SoterGuard/v1/SoterGuardV1.node.ts` under
85
+ * the entry `SoterGuard/SoterGuard.node.ts`), so they are discovered through the
86
+ * entry file rather than being listed individually in `n8n.nodes`.
87
+ */
88
+ function isRegisteredThroughVersionedEntry(nodeFile: string, registeredDirs: string[]): boolean {
89
+ const nodeDir = path.dirname(nodeFile);
90
+ return registeredDirs.some(
91
+ (registeredDir) => registeredDir !== nodeDir && isContainedWithin(registeredDir, nodeDir),
92
+ );
93
+ }
94
+
67
95
  /**
68
96
  * Reports against the most specific available node: the `n8n.nodes` array, the
69
97
  * `n8n` object, or the package.json root object as a fallback.
@@ -1,4 +0,0 @@
1
-
2
- > @n8n/eslint-plugin-community-nodes@0.29.0 build /home/runner/work/n8n/n8n/packages/@n8n/eslint-plugin-community-nodes
3
- > tsc --project tsconfig.build.json
4
-