@n8n/eslint-plugin-community-nodes 0.20.0 → 0.22.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 (101) hide show
  1. package/.turbo/turbo-build$colon$unchecked.log +1 -1
  2. package/README.md +47 -38
  3. package/dist/plugin.d.ts +54 -0
  4. package/dist/plugin.d.ts.map +1 -1
  5. package/dist/plugin.js +18 -0
  6. package/dist/plugin.js.map +1 -1
  7. package/dist/rules/ai-node-package-json.d.ts.map +1 -1
  8. package/dist/rules/ai-node-package-json.js +4 -3
  9. package/dist/rules/ai-node-package-json.js.map +1 -1
  10. package/dist/rules/cred-filename-against-convention.d.ts +2 -0
  11. package/dist/rules/cred-filename-against-convention.d.ts.map +1 -0
  12. package/dist/rules/cred-filename-against-convention.js +42 -0
  13. package/dist/rules/cred-filename-against-convention.js.map +1 -0
  14. package/dist/rules/icon-prefer-themed-variants.d.ts +2 -0
  15. package/dist/rules/icon-prefer-themed-variants.d.ts.map +1 -0
  16. package/dist/rules/icon-prefer-themed-variants.js +53 -0
  17. package/dist/rules/icon-prefer-themed-variants.js.map +1 -0
  18. package/dist/rules/icon-validation.d.ts +1 -1
  19. package/dist/rules/icon-validation.d.ts.map +1 -1
  20. package/dist/rules/icon-validation.js +4 -25
  21. package/dist/rules/icon-validation.js.map +1 -1
  22. package/dist/rules/index.d.ts +10 -1
  23. package/dist/rules/index.d.ts.map +1 -1
  24. package/dist/rules/index.js +18 -0
  25. package/dist/rules/index.js.map +1 -1
  26. package/dist/rules/no-dangerous-functions.d.ts +2 -0
  27. package/dist/rules/no-dangerous-functions.d.ts.map +1 -0
  28. package/dist/rules/no-dangerous-functions.js +121 -0
  29. package/dist/rules/no-dangerous-functions.js.map +1 -0
  30. package/dist/rules/no-emoji-in-options.d.ts +2 -0
  31. package/dist/rules/no-emoji-in-options.d.ts.map +1 -0
  32. package/dist/rules/no-emoji-in-options.js +86 -0
  33. package/dist/rules/no-emoji-in-options.js.map +1 -0
  34. package/dist/rules/node-class-description-name-camelcase.d.ts +2 -0
  35. package/dist/rules/node-class-description-name-camelcase.d.ts.map +1 -0
  36. package/dist/rules/node-class-description-name-camelcase.js +92 -0
  37. package/dist/rules/node-class-description-name-camelcase.js.map +1 -0
  38. package/dist/rules/node-filename-against-convention.d.ts +2 -0
  39. package/dist/rules/node-filename-against-convention.d.ts.map +1 -0
  40. package/dist/rules/node-filename-against-convention.js +61 -0
  41. package/dist/rules/node-filename-against-convention.js.map +1 -0
  42. package/dist/rules/node-registration-complete.d.ts +2 -0
  43. package/dist/rules/node-registration-complete.d.ts.map +1 -0
  44. package/dist/rules/node-registration-complete.js +60 -0
  45. package/dist/rules/node-registration-complete.js.map +1 -0
  46. package/dist/rules/require-version.d.ts +2 -0
  47. package/dist/rules/require-version.d.ts.map +1 -0
  48. package/dist/rules/require-version.js +50 -0
  49. package/dist/rules/require-version.js.map +1 -0
  50. package/dist/rules/valid-author.d.ts +2 -0
  51. package/dist/rules/valid-author.d.ts.map +1 -0
  52. package/dist/rules/valid-author.js +89 -0
  53. package/dist/rules/valid-author.js.map +1 -0
  54. package/dist/rules/valid-peer-dependencies.d.ts.map +1 -1
  55. package/dist/rules/valid-peer-dependencies.js +5 -3
  56. package/dist/rules/valid-peer-dependencies.js.map +1 -1
  57. package/dist/typecheck.tsbuildinfo +1 -0
  58. package/dist/utils/file-utils.d.ts +7 -2
  59. package/dist/utils/file-utils.d.ts.map +1 -1
  60. package/dist/utils/file-utils.js +47 -10
  61. package/dist/utils/file-utils.js.map +1 -1
  62. package/docs/rules/cred-filename-against-convention.md +42 -0
  63. package/docs/rules/icon-prefer-themed-variants.md +71 -0
  64. package/docs/rules/icon-validation.md +5 -3
  65. package/docs/rules/no-dangerous-functions.md +41 -0
  66. package/docs/rules/no-emoji-in-options.md +60 -0
  67. package/docs/rules/node-class-description-name-camelcase.md +41 -0
  68. package/docs/rules/node-filename-against-convention.md +50 -0
  69. package/docs/rules/node-registration-complete.md +46 -0
  70. package/docs/rules/require-version.md +51 -0
  71. package/docs/rules/valid-author.md +60 -0
  72. package/docs/rules/valid-peer-dependencies.md +1 -1
  73. package/package.json +4 -4
  74. package/src/plugin.ts +18 -0
  75. package/src/rules/ai-node-package-json.test.ts +5 -0
  76. package/src/rules/ai-node-package-json.ts +4 -3
  77. package/src/rules/cred-filename-against-convention.test.ts +72 -0
  78. package/src/rules/cred-filename-against-convention.ts +48 -0
  79. package/src/rules/icon-prefer-themed-variants.test.ts +128 -0
  80. package/src/rules/icon-prefer-themed-variants.ts +70 -0
  81. package/src/rules/icon-validation.test.ts +10 -0
  82. package/src/rules/icon-validation.ts +4 -28
  83. package/src/rules/index.ts +18 -0
  84. package/src/rules/no-dangerous-functions.test.ts +83 -0
  85. package/src/rules/no-dangerous-functions.ts +155 -0
  86. package/src/rules/no-emoji-in-options.test.ts +157 -0
  87. package/src/rules/no-emoji-in-options.ts +105 -0
  88. package/src/rules/node-class-description-name-camelcase.test.ts +121 -0
  89. package/src/rules/node-class-description-name-camelcase.ts +114 -0
  90. package/src/rules/node-filename-against-convention.test.ts +115 -0
  91. package/src/rules/node-filename-against-convention.ts +76 -0
  92. package/src/rules/node-registration-complete.test.ts +87 -0
  93. package/src/rules/node-registration-complete.ts +79 -0
  94. package/src/rules/require-version.test.ts +90 -0
  95. package/src/rules/require-version.ts +62 -0
  96. package/src/rules/valid-author.test.ts +108 -0
  97. package/src/rules/valid-author.ts +100 -0
  98. package/src/rules/valid-peer-dependencies.test.ts +5 -0
  99. package/src/rules/valid-peer-dependencies.ts +5 -3
  100. package/src/utils/file-utils.ts +58 -11
  101. package/tsconfig.build.tsbuildinfo +0 -1
@@ -0,0 +1,41 @@
1
+ # Disallow `eval`, the `Function` constructor, and `child_process` process-spawning functions (`exec`, `spawn`, etc.) in community nodes (`@n8n/community-nodes/no-dangerous-functions`)
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
+ Community nodes run inside the n8n runtime, often on shared infrastructure. Functions that execute arbitrary code from strings or spawn operating-system processes are a primary vector for remote code execution and command injection, and have no legitimate use in a community node. This rule bans them outright:
10
+
11
+ - **`eval(...)`** — executes arbitrary code from a string.
12
+ - **`Function(...)` / `new Function(...)`** — the `Function` constructor is an `eval` equivalent that builds a callable from a string body.
13
+ - **`child_process` process spawners** — `exec`, `execSync`, `execFile`, `execFileSync`, `spawn`, `spawnSync`, and `fork`.
14
+
15
+ The `child_process` functions are detected only when they originate from the `child_process` / `node:child_process` module (via `import` or `require`), so unrelated methods such as `RegExp.prototype.exec` are not affected.
16
+
17
+ This complements [`no-restricted-imports`](no-restricted-imports.md) (which blocks the `child_process` module entirely on n8n Cloud) and [`no-restricted-globals`](no-restricted-globals.md), providing a clear, specific error and defense-in-depth that also applies when the import restrictions are relaxed.
18
+
19
+ ## Examples
20
+
21
+ ### ❌ Incorrect
22
+
23
+ ```typescript
24
+ import { exec } from 'child_process';
25
+
26
+ eval(userProvidedCode);
27
+
28
+ const compiled = new Function('return ' + expression);
29
+
30
+ exec(`rm -rf ${userInput}`);
31
+ ```
32
+
33
+ ### ✅ Correct
34
+
35
+ ```typescript
36
+ // Parse data instead of evaluating it.
37
+ const value = JSON.parse(rawJson);
38
+
39
+ // Use n8n helpers and well-scoped library APIs instead of spawning processes.
40
+ const response = await this.helpers.httpRequest({ url });
41
+ ```
@@ -0,0 +1,60 @@
1
+ # Disallow emoji characters in node option name and displayName values (`@n8n/community-nodes/no-emoji-in-options`)
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
+ User-facing labels in a node — the node's `displayName`, each property's
10
+ `displayName`, and the `name` of entries in `options` arrays — are rendered
11
+ directly in the n8n editor. Emoji in these labels render inconsistently across
12
+ platforms, break alphabetical sorting and search, and clash with the editor's
13
+ visual language. This rule flags any emoji character (pictographs such as 🚀 or
14
+ ✅, and regional-indicator flag emoji such as 🇺🇸) found in a `name` or
15
+ `displayName` string within a node's `description`.
16
+
17
+ Accented and non-Latin characters (e.g. `Café`, `日本語`) are allowed — only
18
+ emoji are reported.
19
+
20
+ ## Examples
21
+
22
+ ### Incorrect
23
+
24
+ ```typescript
25
+ export class MyNode implements INodeType {
26
+ description: INodeTypeDescription = {
27
+ displayName: '🚀 My Node',
28
+ name: 'myNode',
29
+ properties: [
30
+ {
31
+ displayName: 'Operation',
32
+ name: 'operation',
33
+ type: 'options',
34
+ options: [{ name: '✅ Create', value: 'create' }],
35
+ default: 'create',
36
+ },
37
+ ],
38
+ };
39
+ }
40
+ ```
41
+
42
+ ### Correct
43
+
44
+ ```typescript
45
+ export class MyNode implements INodeType {
46
+ description: INodeTypeDescription = {
47
+ displayName: 'My Node',
48
+ name: 'myNode',
49
+ properties: [
50
+ {
51
+ displayName: 'Operation',
52
+ name: 'operation',
53
+ type: 'options',
54
+ options: [{ name: 'Create', value: 'create' }],
55
+ default: 'create',
56
+ },
57
+ ],
58
+ };
59
+ }
60
+ ```
@@ -0,0 +1,41 @@
1
+ # Node class `description.name` must be camelCase (`@n8n/community-nodes/node-class-description-name-camelcase`)
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
+ The `name` field inside a node class `description` (those implementing `INodeType` or extending `Node` in `*.node.ts` files) is the internal identifier used to register and reference the node. n8n convention requires this identifier to be camelCase: it must start with a lowercase letter and contain only letters and digits, with no spaces, hyphens, underscores, or other separators.
12
+
13
+ The value must match `^[a-z][a-zA-Z0-9]*$`.
14
+
15
+ The rule is automatically fixable: separators are removed, each subsequent word is upper-cased, and the first character is lower-cased (e.g. `My Node` → `myNode`). Names that cannot be repaired into a valid identifier (for example, those starting with a digit) are reported without an autofix.
16
+
17
+ ## Examples
18
+
19
+ ### ❌ Incorrect
20
+
21
+ ```typescript
22
+ export class MyNode implements INodeType {
23
+ description: INodeTypeDescription = {
24
+ displayName: 'My Node',
25
+ name: 'My Node',
26
+ // ...
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
+ // ...
39
+ };
40
+ }
41
+ ```
@@ -0,0 +1,50 @@
1
+ # Node filename must match the node `description.name` (`@n8n/community-nodes/node-filename-against-convention`)
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
+ Every `*.node.ts` file must be named after its node's `description.name`, with the
10
+ first character upper-cased. The source of truth is `description.name` — **not**
11
+ the class name. Keeping the filename in sync with the registered node name makes
12
+ nodes predictable to locate and matches the convention n8n tooling expects.
13
+
14
+ A trailing version suffix (`V2`, `V3`, …) is allowed, so versioned implementations
15
+ such as `GithubV2.node.ts` are accepted for a node whose `description.name` is
16
+ `github`.
17
+
18
+ The comparison is case-sensitive: a node named `github` must live in
19
+ `Github.node.ts`, not `github.node.ts` or `GitHub.node.ts`.
20
+
21
+ This rule is not auto-fixable because ESLint cannot rename files. Rename the file
22
+ to the reported name.
23
+
24
+ ## Examples
25
+
26
+ ### ❌ Incorrect
27
+
28
+ ```typescript
29
+ // File: Gitlab.node.ts
30
+ export class Github implements INodeType {
31
+ description: INodeTypeDescription = {
32
+ displayName: 'GitHub',
33
+ name: 'github', // expected file to be named "Github.node.ts"
34
+ // ...
35
+ };
36
+ }
37
+ ```
38
+
39
+ ### ✅ Correct
40
+
41
+ ```typescript
42
+ // File: Github.node.ts (or GithubV2.node.ts)
43
+ export class Github implements INodeType {
44
+ description: INodeTypeDescription = {
45
+ displayName: 'GitHub',
46
+ name: 'github',
47
+ // ...
48
+ };
49
+ }
50
+ ```
@@ -0,0 +1,46 @@
1
+ # Ensure every `.node.ts` file in the `nodes/` directory is registered in the "n8n.nodes" array of package.json (`@n8n/community-nodes/node-registration-complete`)
2
+
3
+ ⚠️ This rule _warns_ in the following configs: ✅ `recommended`, ☑️ `recommendedWithoutN8nCloudSupport`.
4
+
5
+ <!-- end auto-generated rule header -->
6
+
7
+ ## Rule Details
8
+
9
+ n8n discovers the nodes in a community package from the `n8n.nodes` array in `package.json`. Any `.node.ts` file that exists in the package's `nodes/` directory but is missing from that array will be silently excluded from the published package — the node simply won't show up in n8n.
10
+
11
+ This rule cross-references the `.node.ts` files found in the `nodes/` directory against the entries in `n8n.nodes` and flags every node file that is not registered, so missing registrations are caught at lint time rather than after publishing.
12
+
13
+ ## Examples
14
+
15
+ Given a package with these files on disk:
16
+
17
+ ```
18
+ nodes/Foo/Foo.node.ts
19
+ nodes/Bar/Bar.node.ts
20
+ ```
21
+
22
+ ### ❌ Incorrect
23
+
24
+ `Bar.node.ts` exists but is not registered:
25
+
26
+ ```json
27
+ {
28
+ "name": "n8n-nodes-my-service",
29
+ "n8n": {
30
+ "nodes": ["dist/nodes/Foo/Foo.node.js"]
31
+ }
32
+ }
33
+ ```
34
+
35
+ ### ✅ Correct
36
+
37
+ Every node file is registered:
38
+
39
+ ```json
40
+ {
41
+ "name": "n8n-nodes-my-service",
42
+ "n8n": {
43
+ "nodes": ["dist/nodes/Foo/Foo.node.js", "dist/nodes/Bar/Bar.node.js"]
44
+ }
45
+ }
46
+ ```
@@ -0,0 +1,51 @@
1
+ # Require a valid "version" field in community node package.json (`@n8n/community-nodes/require-version`)
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
+ Every community node package must declare a `version` field in its `package.json`. npm refuses to publish a package without a valid [semantic version](https://semver.org/), and n8n relies on the version to track and update installed community packages.
10
+
11
+ This rule reports a missing `version` key as well as a value that is not a valid semantic version string (for example `"1.0"`, `"v1.0.0"`, a range like `"^1.0.0"`, an empty string, or a non-string value).
12
+
13
+ ## Examples
14
+
15
+ ### ❌ Incorrect
16
+
17
+ ```json
18
+ {
19
+ "name": "n8n-nodes-acme"
20
+ }
21
+ ```
22
+
23
+ ```json
24
+ {
25
+ "name": "n8n-nodes-acme",
26
+ "version": "1.0"
27
+ }
28
+ ```
29
+
30
+ ```json
31
+ {
32
+ "name": "n8n-nodes-acme",
33
+ "version": "v1.0.0"
34
+ }
35
+ ```
36
+
37
+ ### ✅ Correct
38
+
39
+ ```json
40
+ {
41
+ "name": "n8n-nodes-acme",
42
+ "version": "1.0.0"
43
+ }
44
+ ```
45
+
46
+ ```json
47
+ {
48
+ "name": "n8n-nodes-acme",
49
+ "version": "0.1.0-beta.1"
50
+ }
51
+ ```
@@ -0,0 +1,60 @@
1
+ # Require a non-empty author name and email in package.json (`@n8n/community-nodes/valid-author`)
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
+ A published community node package should identify its author. This rule
10
+ requires the `package.json` to declare an `author` with at minimum a non-empty
11
+ `name` and `email`, so users and n8n can reach the maintainer.
12
+
13
+ Both npm `author` forms are supported:
14
+
15
+ - the object form `{ "name": "...", "email": "..." }`
16
+ - the shorthand string form `"Name <email> (url)"`
17
+
18
+ Whitespace-only values count as empty. The literal `<...>` placeholder pattern
19
+ left over from the node starter template is handled separately by the
20
+ [`no-template-placeholders`](./no-template-placeholders.md) rule.
21
+
22
+ ## Examples
23
+
24
+ ### ❌ Incorrect
25
+
26
+ ```json
27
+ {
28
+ "name": "n8n-nodes-my-service"
29
+ }
30
+ ```
31
+
32
+ ```json
33
+ {
34
+ "name": "n8n-nodes-my-service",
35
+ "author": { "name": "Jane Doe" }
36
+ }
37
+ ```
38
+
39
+ ```json
40
+ {
41
+ "name": "n8n-nodes-my-service",
42
+ "author": "Jane Doe"
43
+ }
44
+ ```
45
+
46
+ ### ✅ Correct
47
+
48
+ ```json
49
+ {
50
+ "name": "n8n-nodes-my-service",
51
+ "author": { "name": "Jane Doe", "email": "jane@example.com" }
52
+ }
53
+ ```
54
+
55
+ ```json
56
+ {
57
+ "name": "n8n-nodes-my-service",
58
+ "author": "Jane Doe <jane@example.com> (https://example.com)"
59
+ }
60
+ ```
@@ -1,4 +1,4 @@
1
- # Require community node package.json peerDependencies to contain only "n8n-workflow": "*" (and optionally "ai-node-sdk") (`@n8n/community-nodes/valid-peer-dependencies`)
1
+ # Require community node package.json peerDependencies to contain only "n8n-workflow": "*" (and optionally "@n8n/ai-node-sdk") (`@n8n/community-nodes/valid-peer-dependencies`)
2
2
 
3
3
  💼 This rule is enabled in the following configs: ✅ `recommended`, ☑️ `recommendedWithoutN8nCloudSupport`.
4
4
 
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.20.0",
4
+ "version": "0.22.0",
5
5
  "main": "./dist/plugin.js",
6
6
  "types": "./dist/plugin.d.ts",
7
7
  "exports": {
@@ -23,9 +23,9 @@
23
23
  "rimraf": "6.0.1",
24
24
  "typescript": "6.0.2",
25
25
  "vitest": "^4.1.1",
26
- "@n8n/typescript-config": "1.5.0",
27
- "@n8n/vitest-config": "1.14.0",
28
- "n8n-workflow": "2.26.0"
26
+ "@n8n/typescript-config": "1.6.0",
27
+ "@n8n/vitest-config": "1.15.0",
28
+ "n8n-workflow": "2.28.0"
29
29
  },
30
30
  "peerDependencies": {
31
31
  "eslint": "9.29.0",
package/src/plugin.ts CHANGED
@@ -26,16 +26,19 @@ const configs = {
26
26
  '@n8n/community-nodes/credential-password-field': 'error',
27
27
  '@n8n/community-nodes/n8n-object-validation': 'error',
28
28
  '@n8n/community-nodes/no-deprecated-workflow-functions': 'error',
29
+ '@n8n/community-nodes/no-emoji-in-options': 'error',
29
30
  '@n8n/community-nodes/node-usable-as-tool': 'error',
30
31
  '@n8n/community-nodes/package-name-convention': 'error',
31
32
  '@n8n/community-nodes/credential-test-required': 'error',
32
33
  '@n8n/community-nodes/no-credential-reuse': 'error',
34
+ '@n8n/community-nodes/no-dangerous-functions': 'error',
33
35
  '@n8n/community-nodes/no-forbidden-lifecycle-scripts': 'error',
34
36
  '@n8n/community-nodes/no-http-request-with-manual-auth': 'error',
35
37
  '@n8n/community-nodes/no-overrides-field': 'error',
36
38
  '@n8n/community-nodes/no-runtime-dependencies': 'error',
37
39
  '@n8n/community-nodes/no-template-placeholders': 'error',
38
40
  '@n8n/community-nodes/icon-validation': 'error',
41
+ '@n8n/community-nodes/icon-prefer-themed-variants': 'warn',
39
42
  '@n8n/community-nodes/options-sorted-alphabetically': 'warn',
40
43
  '@n8n/community-nodes/resource-operation-pattern': 'warn',
41
44
  '@n8n/community-nodes/credential-documentation-url': 'error',
@@ -43,14 +46,20 @@ const configs = {
43
46
  '@n8n/community-nodes/cred-class-name-field-conventions': 'error',
44
47
  '@n8n/community-nodes/cred-class-name-suffix': 'error',
45
48
  '@n8n/community-nodes/cred-class-oauth2-naming': 'error',
49
+ '@n8n/community-nodes/cred-filename-against-convention': 'error',
46
50
  '@n8n/community-nodes/node-connection-type-literal': 'error',
51
+ '@n8n/community-nodes/node-class-description-name-camelcase': 'error',
52
+ '@n8n/community-nodes/node-filename-against-convention': 'error',
47
53
  '@n8n/community-nodes/missing-paired-item': 'error',
48
54
  '@n8n/community-nodes/no-builder-hint-leakage': 'error',
49
55
  '@n8n/community-nodes/node-operation-error-itemindex': 'error',
56
+ '@n8n/community-nodes/node-registration-complete': 'warn',
50
57
  '@n8n/community-nodes/require-community-node-keyword': 'warn',
51
58
  '@n8n/community-nodes/require-continue-on-fail': 'error',
52
59
  '@n8n/community-nodes/require-node-api-error': 'error',
53
60
  '@n8n/community-nodes/require-node-description-fields': 'error',
61
+ '@n8n/community-nodes/require-version': 'error',
62
+ '@n8n/community-nodes/valid-author': 'error',
54
63
  '@n8n/community-nodes/valid-credential-references': 'error',
55
64
  '@n8n/community-nodes/valid-description': 'error',
56
65
  '@n8n/community-nodes/valid-peer-dependencies': 'error',
@@ -67,16 +76,19 @@ const configs = {
67
76
  '@n8n/community-nodes/credential-password-field': 'error',
68
77
  '@n8n/community-nodes/n8n-object-validation': 'error',
69
78
  '@n8n/community-nodes/no-deprecated-workflow-functions': 'error',
79
+ '@n8n/community-nodes/no-emoji-in-options': 'error',
70
80
  '@n8n/community-nodes/node-usable-as-tool': 'error',
71
81
  '@n8n/community-nodes/package-name-convention': 'error',
72
82
  '@n8n/community-nodes/credential-test-required': 'error',
73
83
  '@n8n/community-nodes/no-credential-reuse': 'error',
84
+ '@n8n/community-nodes/no-dangerous-functions': 'error',
74
85
  '@n8n/community-nodes/no-forbidden-lifecycle-scripts': 'error',
75
86
  '@n8n/community-nodes/no-http-request-with-manual-auth': 'error',
76
87
  '@n8n/community-nodes/no-overrides-field': 'error',
77
88
  '@n8n/community-nodes/no-runtime-dependencies': 'error',
78
89
  '@n8n/community-nodes/no-template-placeholders': 'error',
79
90
  '@n8n/community-nodes/icon-validation': 'error',
91
+ '@n8n/community-nodes/icon-prefer-themed-variants': 'warn',
80
92
  '@n8n/community-nodes/options-sorted-alphabetically': 'warn',
81
93
  '@n8n/community-nodes/credential-documentation-url': 'error',
82
94
  '@n8n/community-nodes/resource-operation-pattern': 'warn',
@@ -84,14 +96,20 @@ const configs = {
84
96
  '@n8n/community-nodes/cred-class-name-field-conventions': 'error',
85
97
  '@n8n/community-nodes/cred-class-name-suffix': 'error',
86
98
  '@n8n/community-nodes/cred-class-oauth2-naming': 'error',
99
+ '@n8n/community-nodes/cred-filename-against-convention': 'error',
87
100
  '@n8n/community-nodes/node-connection-type-literal': 'error',
101
+ '@n8n/community-nodes/node-class-description-name-camelcase': 'error',
102
+ '@n8n/community-nodes/node-filename-against-convention': 'error',
88
103
  '@n8n/community-nodes/missing-paired-item': 'error',
89
104
  '@n8n/community-nodes/no-builder-hint-leakage': 'error',
90
105
  '@n8n/community-nodes/node-operation-error-itemindex': 'error',
106
+ '@n8n/community-nodes/node-registration-complete': 'warn',
91
107
  '@n8n/community-nodes/require-community-node-keyword': 'warn',
92
108
  '@n8n/community-nodes/require-continue-on-fail': 'error',
93
109
  '@n8n/community-nodes/require-node-api-error': 'error',
94
110
  '@n8n/community-nodes/require-node-description-fields': 'error',
111
+ '@n8n/community-nodes/require-version': 'error',
112
+ '@n8n/community-nodes/valid-author': 'error',
95
113
  '@n8n/community-nodes/valid-credential-references': 'error',
96
114
  '@n8n/community-nodes/valid-description': 'error',
97
115
  '@n8n/community-nodes/valid-peer-dependencies': 'error',
@@ -11,6 +11,11 @@ ruleTester.run('ai-node-package-json', AiNodePackageJsonRule, {
11
11
  filename: 'package.json',
12
12
  code: '{ "name": "n8n-nodes-example", "n8n": { "aiNodeSdkVersion": 1 }, "peerDependencies": { "n8n-workflow": "*", "ai-node-sdk": "*" } }',
13
13
  },
14
+ {
15
+ name: 'both n8n.aiNodeSdkVersion and scoped @n8n/ai-node-sdk peer dependency present',
16
+ filename: 'package.json',
17
+ code: '{ "name": "n8n-nodes-example", "n8n": { "aiNodeSdkVersion": 1 }, "peerDependencies": { "n8n-workflow": "*", "@n8n/ai-node-sdk": "*" } }',
18
+ },
14
19
  {
15
20
  name: 'neither n8n.aiNodeSdkVersion nor ai-node-sdk present (non-AI package)',
16
21
  filename: 'package.json',
@@ -13,9 +13,9 @@ export const AiNodePackageJsonRule = createRule({
13
13
  },
14
14
  messages: {
15
15
  missingPeerDep:
16
- 'Package declares "n8n.aiNodeSdkVersion" but is missing "ai-node-sdk" in peerDependencies. Add "ai-node-sdk": "*" to peerDependencies.',
16
+ 'Package declares "n8n.aiNodeSdkVersion" but is missing "@n8n/ai-node-sdk" in peerDependencies. Add "@n8n/ai-node-sdk": "*" to peerDependencies.',
17
17
  missingSdkVersion:
18
- 'Package has "ai-node-sdk" in peerDependencies but is missing "aiNodeSdkVersion" in the "n8n" section of package.json.',
18
+ 'Package has "@n8n/ai-node-sdk" in peerDependencies but is missing "aiNodeSdkVersion" in the "n8n" section of package.json.',
19
19
  invalidSdkVersion: '"n8n.aiNodeSdkVersion" must be a positive integer, got {{ value }}.',
20
20
  wrongLocation:
21
21
  '"aiNodeSdkVersion" must be inside the "n8n" section, not at the root level of package.json.',
@@ -49,7 +49,8 @@ export const AiNodePackageJsonRule = createRule({
49
49
  const hasAiNodeSdkVersion = aiNodeSdkVersionProp !== null;
50
50
  const hasAiNodeSdkPeerDep =
51
51
  peerDependenciesProp?.value.type === AST_NODE_TYPES.ObjectExpression &&
52
- findJsonProperty(peerDependenciesProp.value, 'ai-node-sdk') !== null;
52
+ (findJsonProperty(peerDependenciesProp.value, '@n8n/ai-node-sdk') !== null ||
53
+ findJsonProperty(peerDependenciesProp.value, 'ai-node-sdk') !== null);
53
54
 
54
55
  // Catch aiNodeSdkVersion placed at root level instead of inside n8n
55
56
  if (rootAiNodeSdkVersionProp) {
@@ -0,0 +1,72 @@
1
+ import { RuleTester } from '@typescript-eslint/rule-tester';
2
+
3
+ import { CredFilenameAgainstConventionRule } from './cred-filename-against-convention.js';
4
+
5
+ const ruleTester = new RuleTester();
6
+
7
+ function createCredentialCode(className: string): string {
8
+ return `
9
+ import type { ICredentialType, INodeProperties } from 'n8n-workflow';
10
+
11
+ export class ${className} implements ICredentialType {
12
+ name = '${className.charAt(0).toLowerCase() + className.slice(1)}';
13
+ displayName = '${className}';
14
+ properties: INodeProperties[] = [];
15
+ }`;
16
+ }
17
+
18
+ function createRegularClass(): string {
19
+ return `
20
+ export class SomeHelper {
21
+ name = 'githubApi';
22
+ }`;
23
+ }
24
+
25
+ ruleTester.run('cred-filename-against-convention', CredFilenameAgainstConventionRule, {
26
+ valid: [
27
+ {
28
+ name: 'filename matches credential class name',
29
+ filename: '/tmp/GithubApi.credentials.ts',
30
+ code: createCredentialCode('GithubApi'),
31
+ },
32
+ {
33
+ name: 'multi-word class name matches filename',
34
+ filename: '/tmp/GoogleSheetsOAuth2Api.credentials.ts',
35
+ code: createCredentialCode('GoogleSheetsOAuth2Api'),
36
+ },
37
+ {
38
+ name: 'class not implementing ICredentialType is ignored',
39
+ filename: '/tmp/Github.credentials.ts',
40
+ code: createRegularClass(),
41
+ },
42
+ {
43
+ name: 'non-.credentials.ts file is ignored',
44
+ filename: '/tmp/GithubApi.ts',
45
+ code: createCredentialCode('Mismatch'),
46
+ },
47
+ ],
48
+ invalid: [
49
+ {
50
+ name: 'filename does not match class name',
51
+ filename: '/tmp/GithubApi.credentials.ts',
52
+ code: createCredentialCode('GitlabApi'),
53
+ errors: [
54
+ {
55
+ messageId: 'renameFile',
56
+ data: { className: 'GitlabApi', expected: 'GitlabApi.credentials.ts' },
57
+ },
58
+ ],
59
+ },
60
+ {
61
+ name: 'filename has wrong casing',
62
+ filename: '/tmp/githubApi.credentials.ts',
63
+ code: createCredentialCode('GithubApi'),
64
+ errors: [
65
+ {
66
+ messageId: 'renameFile',
67
+ data: { className: 'GithubApi', expected: 'GithubApi.credentials.ts' },
68
+ },
69
+ ],
70
+ },
71
+ ],
72
+ });
@@ -0,0 +1,48 @@
1
+ import * as path from 'node:path';
2
+
3
+ import { isCredentialTypeClass, isFileType, createRule } from '../utils/index.js';
4
+
5
+ export const CredFilenameAgainstConventionRule = createRule({
6
+ name: 'cred-filename-against-convention',
7
+ meta: {
8
+ type: 'problem',
9
+ docs: {
10
+ description: 'Credential filename must match the credential class name',
11
+ },
12
+ messages: {
13
+ renameFile:
14
+ 'Credential filename must match the class name "{{className}}". Rename file to "{{expected}}".',
15
+ },
16
+ schema: [],
17
+ },
18
+ defaultOptions: [],
19
+ create(context) {
20
+ if (!isFileType(context.filename, '.credentials.ts')) {
21
+ return {};
22
+ }
23
+
24
+ return {
25
+ ClassDeclaration(node) {
26
+ if (!isCredentialTypeClass(node)) {
27
+ return;
28
+ }
29
+
30
+ const classNameNode = node.id;
31
+ if (!classNameNode) {
32
+ return;
33
+ }
34
+
35
+ const className = classNameNode.name;
36
+ const actualBaseName = path.basename(context.filename, '.credentials.ts');
37
+
38
+ if (actualBaseName !== className) {
39
+ context.report({
40
+ node: classNameNode,
41
+ messageId: 'renameFile',
42
+ data: { className, expected: `${className}.credentials.ts` },
43
+ });
44
+ }
45
+ },
46
+ };
47
+ },
48
+ });