@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.
- package/dist/rules/node-registration-complete.d.ts.map +1 -1
- package/dist/rules/node-registration-complete.js +26 -3
- package/dist/rules/node-registration-complete.js.map +1 -1
- package/docs/rules/node-registration-complete.md +26 -0
- package/package.json +4 -4
- package/src/rules/node-registration-complete.test.ts +35 -0
- package/src/rules/node-registration-complete.ts +31 -3
- package/.turbo/turbo-build.log +0 -4
- package/dist/typecheck.tsbuildinfo +0 -1
- package/tsconfig.json +0 -9
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-registration-complete.d.ts","sourceRoot":"","sources":["../../src/rules/node-registration-complete.ts"],"names":[],"mappings":"
|
|
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
|
|
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
|
-
|
|
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,
|
|
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.
|
|
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.
|
|
29
|
-
"@n8n/vitest-config": "1.
|
|
30
|
-
"n8n-workflow": "2.
|
|
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
|
|
45
|
-
|
|
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
|
-
|
|
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.
|
package/.turbo/turbo-build.log
DELETED