@n8n/eslint-plugin-community-nodes 0.18.0 → 0.20.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 (50) hide show
  1. package/.turbo/turbo-build$colon$unchecked.log +4 -0
  2. package/README.md +38 -36
  3. package/dist/plugin.d.ts +6 -0
  4. package/dist/plugin.d.ts.map +1 -1
  5. package/dist/plugin.js +2 -0
  6. package/dist/plugin.js.map +1 -1
  7. package/dist/rules/cred-class-name-field-conventions.d.ts +2 -0
  8. package/dist/rules/cred-class-name-field-conventions.d.ts.map +1 -0
  9. package/dist/rules/cred-class-name-field-conventions.js +89 -0
  10. package/dist/rules/cred-class-name-field-conventions.js.map +1 -0
  11. package/dist/rules/credential-password-field.js +1 -1
  12. package/dist/rules/index.d.ts +2 -1
  13. package/dist/rules/index.d.ts.map +1 -1
  14. package/dist/rules/index.js +2 -0
  15. package/dist/rules/index.js.map +1 -1
  16. package/dist/rules/no-credential-reuse.js +1 -1
  17. package/dist/rules/no-restricted-globals.js +1 -1
  18. package/dist/rules/no-template-placeholders.d.ts.map +1 -1
  19. package/dist/rules/no-template-placeholders.js +3 -1
  20. package/dist/rules/no-template-placeholders.js.map +1 -1
  21. package/dist/rules/node-usable-as-tool.js +1 -1
  22. package/dist/rules/package-name-convention.d.ts +1 -1
  23. package/dist/rules/package-name-convention.d.ts.map +1 -1
  24. package/dist/rules/package-name-convention.js +19 -0
  25. package/dist/rules/package-name-convention.js.map +1 -1
  26. package/dist/rules/require-node-api-error.d.ts +2 -1
  27. package/dist/rules/require-node-api-error.d.ts.map +1 -1
  28. package/dist/rules/require-node-api-error.js +2 -3
  29. package/dist/rules/require-node-api-error.js.map +1 -1
  30. package/dist/rules/valid-credential-references.js +1 -1
  31. package/docs/rules/cred-class-name-field-conventions.md +38 -0
  32. package/docs/rules/no-builder-hint-leakage.md +38 -0
  33. package/docs/rules/package-name-convention.md +14 -0
  34. package/package.json +7 -5
  35. package/src/plugin.ts +2 -0
  36. package/src/rules/cred-class-name-field-conventions.test.ts +121 -0
  37. package/src/rules/cred-class-name-field-conventions.ts +102 -0
  38. package/src/rules/credential-password-field.ts +1 -1
  39. package/src/rules/index.ts +2 -0
  40. package/src/rules/no-credential-reuse.ts +1 -1
  41. package/src/rules/no-restricted-globals.ts +1 -1
  42. package/src/rules/no-template-placeholders.test.ts +18 -0
  43. package/src/rules/no-template-placeholders.ts +3 -1
  44. package/src/rules/node-usable-as-tool.ts +1 -1
  45. package/src/rules/package-name-convention.test.ts +32 -5
  46. package/src/rules/package-name-convention.ts +23 -0
  47. package/src/rules/require-node-api-error.ts +4 -3
  48. package/src/rules/valid-credential-references.ts +1 -1
  49. package/tsconfig.build.tsbuildinfo +1 -1
  50. package/.turbo/turbo-build.log +0 -4
@@ -0,0 +1,38 @@
1
+ # Credential `name` field must end with `Api` and start with a lowercase letter (`@n8n/community-nodes/cred-class-name-field-conventions`)
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 of a credential class (those implementing `ICredentialType` in `*.credentials.ts` files) is the internal identifier referenced by nodes and stored in the credential registry. n8n convention requires this identifier to:
12
+
13
+ - End with `Api` (e.g. `githubApi`), so credentials are easily recognisable across the codebase.
14
+ - Start with a lowercase letter (camelCase), since it is an identifier value rather than a class name.
15
+
16
+ Both checks are automatically fixable.
17
+
18
+ ## Examples
19
+
20
+ ### ❌ Incorrect
21
+
22
+ ```typescript
23
+ export class GithubApi implements ICredentialType {
24
+ name = 'Github';
25
+ displayName = 'GitHub API';
26
+ properties: INodeProperties[] = [];
27
+ }
28
+ ```
29
+
30
+ ### ✅ Correct
31
+
32
+ ```typescript
33
+ export class GithubApi implements ICredentialType {
34
+ name = 'githubApi';
35
+ displayName = 'GitHub API';
36
+ properties: INodeProperties[] = [];
37
+ }
38
+ ```
@@ -0,0 +1,38 @@
1
+ # Disallow wire-format expression syntax (={{...}}) and NodeConnectionType string literals in builderHint texts and AI-builder prompts. Use expr() and SDK-canonical references instead (`@n8n/community-nodes/no-builder-hint-leakage`)
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
+ `builderHint` texts and AI-builder prompts are authored as human-facing guidance, but they sometimes leak n8n's internal wire format:
10
+
11
+ - Raw expression syntax such as `={{ $json.foo }}`, which only makes sense inside the execution engine.
12
+ - `NodeConnectionType` string literals such as `ai_languageModel` or `ai_tool`, which are structured connection identifiers rather than prose.
13
+
14
+ When these leak into hints and prompts, they confuse the builder experience and the AI assistant. Use the `expr()` SDK helper for expressions and the SDK-canonical reference helpers (e.g. `languageModel()`, `tool()`, `memory()`) instead.
15
+
16
+ ## Options
17
+
18
+ This rule accepts an options object:
19
+
20
+ - `scope` (`'builderHint' | 'all'`, default `'builderHint'`) — `builderHint` only scans string values inside `builderHint` property values. `all` scans every string in the file, intended for AI-builder prompt files.
21
+
22
+ ## Examples
23
+
24
+ ### ❌ Incorrect
25
+
26
+ ```typescript
27
+ const node = {
28
+ builderHint: 'Set the model with ={{ $json.model }} and connect an ai_languageModel.',
29
+ };
30
+ ```
31
+
32
+ ### ✅ Correct
33
+
34
+ ```typescript
35
+ const node = {
36
+ builderHint: 'Set the model with expr($json.model) and connect a languageModel().',
37
+ };
38
+ ```
@@ -10,6 +10,8 @@
10
10
 
11
11
  Validates that your package name follows the correct n8n community node naming convention. Package names must start with `n8n-nodes-` and can optionally be scoped.
12
12
 
13
+ The rule also requires a `name` field to be present and rejects the default placeholder (`n8n-nodes-<...>`) that ships with the node starter template, so packages are not published with a missing or unfilled name.
14
+
13
15
  ## Examples
14
16
 
15
17
  ### ❌ Incorrect
@@ -32,6 +34,18 @@ Validates that your package name follows the correct n8n community node naming c
32
34
  }
33
35
  ```
34
36
 
37
+ ```json
38
+ {
39
+ "name": "n8n-nodes-<...>"
40
+ }
41
+ ```
42
+
43
+ ```json
44
+ {
45
+ "version": "1.0.0"
46
+ }
47
+ ```
48
+
35
49
  ### ✅ Correct
36
50
 
37
51
  ```json
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.18.0",
4
+ "version": "0.20.0",
5
5
  "main": "./dist/plugin.js",
6
6
  "types": "./dist/plugin.d.ts",
7
7
  "exports": {
@@ -11,6 +11,7 @@
11
11
  }
12
12
  },
13
13
  "dependencies": {
14
+ "@typescript-eslint/typescript-estree": "^8.35.0",
14
15
  "@typescript-eslint/utils": "^8.35.0",
15
16
  "fastest-levenshtein": "1.0.16"
16
17
  },
@@ -22,9 +23,9 @@
22
23
  "rimraf": "6.0.1",
23
24
  "typescript": "6.0.2",
24
25
  "vitest": "^4.1.1",
25
- "@n8n/typescript-config": "1.4.0",
26
- "@n8n/vitest-config": "1.12.0",
27
- "n8n-workflow": "2.23.0"
26
+ "@n8n/typescript-config": "1.5.0",
27
+ "@n8n/vitest-config": "1.14.0",
28
+ "n8n-workflow": "2.26.0"
28
29
  },
29
30
  "peerDependencies": {
30
31
  "eslint": "9.29.0",
@@ -54,12 +55,13 @@
54
55
  },
55
56
  "scripts": {
56
57
  "build": "tsc --project tsconfig.build.json",
58
+ "build:unchecked": "tsc --project tsconfig.build.json --noCheck",
57
59
  "build:docs": "pnpm build && eslint-doc-generator",
58
60
  "clean": "rimraf dist .turbo",
59
61
  "dev": "pnpm watch",
60
62
  "format": "biome format --write .",
61
63
  "format:check": "biome ci .",
62
- "lint": "eslint src",
64
+ "lint": "eslint src && pnpm lint:docs",
63
65
  "lint:fix": "eslint src --fix",
64
66
  "lint:docs": "pnpm build && eslint-doc-generator --check",
65
67
  "test": "vitest run",
package/src/plugin.ts CHANGED
@@ -40,6 +40,7 @@ const configs = {
40
40
  '@n8n/community-nodes/resource-operation-pattern': 'warn',
41
41
  '@n8n/community-nodes/credential-documentation-url': 'error',
42
42
  '@n8n/community-nodes/cred-class-field-icon-missing': 'error',
43
+ '@n8n/community-nodes/cred-class-name-field-conventions': 'error',
43
44
  '@n8n/community-nodes/cred-class-name-suffix': 'error',
44
45
  '@n8n/community-nodes/cred-class-oauth2-naming': 'error',
45
46
  '@n8n/community-nodes/node-connection-type-literal': 'error',
@@ -80,6 +81,7 @@ const configs = {
80
81
  '@n8n/community-nodes/credential-documentation-url': 'error',
81
82
  '@n8n/community-nodes/resource-operation-pattern': 'warn',
82
83
  '@n8n/community-nodes/cred-class-field-icon-missing': 'error',
84
+ '@n8n/community-nodes/cred-class-name-field-conventions': 'error',
83
85
  '@n8n/community-nodes/cred-class-name-suffix': 'error',
84
86
  '@n8n/community-nodes/cred-class-oauth2-naming': 'error',
85
87
  '@n8n/community-nodes/node-connection-type-literal': 'error',
@@ -0,0 +1,121 @@
1
+ import { RuleTester } from '@typescript-eslint/rule-tester';
2
+
3
+ import { CredClassNameFieldConventionsRule } from './cred-class-name-field-conventions.js';
4
+
5
+ const ruleTester = new RuleTester();
6
+
7
+ const credFilePath = '/tmp/TestCredential.credentials.ts';
8
+ const nonCredFilePath = '/tmp/SomeHelper.ts';
9
+
10
+ function createCredentialCode(name: string): string {
11
+ return `
12
+ import type { ICredentialType, INodeProperties } from 'n8n-workflow';
13
+
14
+ export class TestApi implements ICredentialType {
15
+ name = '${name}';
16
+ displayName = 'Test API';
17
+ properties: INodeProperties[] = [];
18
+ }`;
19
+ }
20
+
21
+ function createRegularClass(name: string): string {
22
+ return `
23
+ export class SomeHelper {
24
+ name = '${name}';
25
+ }`;
26
+ }
27
+
28
+ // Embeds the raw literal text (including its quotes) verbatim, so tests can
29
+ // exercise names containing quote characters that need escaping.
30
+ function createCredentialCodeWithLiteral(literal: string): string {
31
+ return `
32
+ import type { ICredentialType, INodeProperties } from 'n8n-workflow';
33
+
34
+ export class TestApi implements ICredentialType {
35
+ name = ${literal};
36
+ displayName = 'Test API';
37
+ properties: INodeProperties[] = [];
38
+ }`;
39
+ }
40
+
41
+ ruleTester.run('cred-class-name-field-conventions', CredClassNameFieldConventionsRule, {
42
+ valid: [
43
+ {
44
+ name: 'name field with Api suffix and lowercase first char',
45
+ filename: credFilePath,
46
+ code: createCredentialCode('githubApi'),
47
+ },
48
+ {
49
+ name: 'OAuth2 name field is also valid',
50
+ filename: credFilePath,
51
+ code: createCredentialCode('githubOAuth2Api'),
52
+ },
53
+ {
54
+ name: 'class not implementing ICredentialType is ignored',
55
+ filename: credFilePath,
56
+ code: createRegularClass('Github'),
57
+ },
58
+ {
59
+ name: 'non-.credentials.ts file is ignored',
60
+ filename: nonCredFilePath,
61
+ code: createCredentialCode('Github'),
62
+ },
63
+ ],
64
+ invalid: [
65
+ {
66
+ name: 'name field missing Api suffix',
67
+ filename: credFilePath,
68
+ code: createCredentialCode('github'),
69
+ errors: [{ messageId: 'missingSuffix', data: { value: 'github' } }],
70
+ output: createCredentialCode('githubApi'),
71
+ },
72
+ {
73
+ name: 'name field with uppercase first char',
74
+ filename: credFilePath,
75
+ code: createCredentialCode('GithubApi'),
76
+ errors: [{ messageId: 'uppercaseFirstChar', data: { value: 'GithubApi' } }],
77
+ output: createCredentialCode('githubApi'),
78
+ },
79
+ {
80
+ name: 'name field with both uppercase first char and missing suffix',
81
+ filename: credFilePath,
82
+ code: createCredentialCode('Github'),
83
+ errors: [
84
+ { messageId: 'uppercaseFirstChar', data: { value: 'Github' } },
85
+ { messageId: 'missingSuffix', data: { value: 'Github' } },
86
+ ],
87
+ output: createCredentialCode('githubApi'),
88
+ },
89
+ {
90
+ name: 'name field ending in Ap',
91
+ filename: credFilePath,
92
+ code: createCredentialCode('githubAp'),
93
+ errors: [{ messageId: 'missingSuffix', data: { value: 'githubAp' } }],
94
+ output: createCredentialCode('githubApi'),
95
+ },
96
+ {
97
+ name: 'name field ending in A',
98
+ filename: credFilePath,
99
+ code: createCredentialCode('githubA'),
100
+ errors: [{ messageId: 'missingSuffix', data: { value: 'githubA' } }],
101
+ output: createCredentialCode('githubApi'),
102
+ },
103
+ {
104
+ name: 'autofix escapes single quotes in the name value',
105
+ filename: credFilePath,
106
+ code: createCredentialCodeWithLiteral("'git\\'hub'"),
107
+ errors: [{ messageId: 'missingSuffix', data: { value: "git'hub" } }],
108
+ output: createCredentialCodeWithLiteral("'git\\'hubApi'"),
109
+ },
110
+ {
111
+ name: 'autofix preserves double quotes and escapes them in the name value',
112
+ filename: credFilePath,
113
+ code: createCredentialCodeWithLiteral('"Git\\"hub"'),
114
+ errors: [
115
+ { messageId: 'uppercaseFirstChar', data: { value: 'Git"hub' } },
116
+ { messageId: 'missingSuffix', data: { value: 'Git"hub' } },
117
+ ],
118
+ output: createCredentialCodeWithLiteral('"git\\"hubApi"'),
119
+ },
120
+ ],
121
+ });
@@ -0,0 +1,102 @@
1
+ import {
2
+ createRule,
3
+ findClassProperty,
4
+ getStringLiteralValue,
5
+ isCredentialTypeClass,
6
+ isFileType,
7
+ } from '../utils/index.js';
8
+
9
+ function lowercaseFirstChar(name: string): string {
10
+ return name.charAt(0).toLowerCase() + name.slice(1);
11
+ }
12
+
13
+ function addApiSuffix(name: string): string {
14
+ if (name.endsWith('Api')) return name;
15
+ if (name.endsWith('Ap')) return `${name}i`;
16
+ if (name.endsWith('A')) return `${name}pi`;
17
+ return `${name}Api`;
18
+ }
19
+
20
+ // Serialize a value as a string literal using the original quote character,
21
+ // escaping any characters that would otherwise break the literal so the
22
+ // autofix never emits invalid code (e.g. names containing quotes).
23
+ function toStringLiteral(value: string, quote: string): string {
24
+ const escaped = value
25
+ .replace(/\\/g, '\\\\')
26
+ .replace(/\n/g, '\\n')
27
+ .replace(/\r/g, '\\r')
28
+ .split(quote)
29
+ .join(`\\${quote}`);
30
+ return `${quote}${escaped}${quote}`;
31
+ }
32
+
33
+ export const CredClassNameFieldConventionsRule = createRule({
34
+ name: 'cred-class-name-field-conventions',
35
+ meta: {
36
+ type: 'problem',
37
+ docs: {
38
+ description: 'Credential `name` field must end with `Api` and start with a lowercase letter',
39
+ },
40
+ messages: {
41
+ missingSuffix: "Credential `name` field '{{value}}' must end with 'Api'",
42
+ uppercaseFirstChar: "Credential `name` field '{{value}}' must start with a lowercase letter",
43
+ },
44
+ fixable: 'code',
45
+ schema: [],
46
+ },
47
+ defaultOptions: [],
48
+ create(context) {
49
+ if (!isFileType(context.filename, '.credentials.ts')) {
50
+ return {};
51
+ }
52
+
53
+ return {
54
+ ClassDeclaration(node) {
55
+ if (!isCredentialTypeClass(node)) {
56
+ return;
57
+ }
58
+
59
+ const nameProperty = findClassProperty(node, 'name');
60
+ if (!nameProperty?.value) {
61
+ return;
62
+ }
63
+
64
+ const nameValue = getStringLiteralValue(nameProperty.value);
65
+ if (nameValue === null) {
66
+ return;
67
+ }
68
+
69
+ const startsLowercase = !/^[A-Z]/.test(nameValue);
70
+ const endsWithApi = nameValue.endsWith('Api');
71
+ if (startsLowercase && endsWithApi) {
72
+ return;
73
+ }
74
+
75
+ // Compute the fully-corrected value once so each fix yields the
76
+ // same result in a single pass, regardless of which one applies.
77
+ const fixedValue = addApiSuffix(lowercaseFirstChar(nameValue));
78
+ const valueNode = nameProperty.value;
79
+ const quote = context.sourceCode.getText(valueNode).charAt(0);
80
+ const replacement = toStringLiteral(fixedValue, quote);
81
+
82
+ if (!startsLowercase) {
83
+ context.report({
84
+ node: valueNode,
85
+ messageId: 'uppercaseFirstChar',
86
+ data: { value: nameValue },
87
+ fix: (fixer) => fixer.replaceText(valueNode, replacement),
88
+ });
89
+ }
90
+
91
+ if (!endsWithApi) {
92
+ context.report({
93
+ node: valueNode,
94
+ messageId: 'missingSuffix',
95
+ data: { value: nameValue },
96
+ fix: (fixer) => fixer.replaceText(valueNode, replacement),
97
+ });
98
+ }
99
+ },
100
+ };
101
+ },
102
+ });
@@ -1,4 +1,4 @@
1
- import { TSESTree } from '@typescript-eslint/types';
1
+ import { TSESTree } from '@typescript-eslint/utils';
2
2
  import type { ReportFixFunction } from '@typescript-eslint/utils/ts-eslint';
3
3
 
4
4
  import {
@@ -2,6 +2,7 @@ import type { AnyRuleModule } from '@typescript-eslint/utils/ts-eslint';
2
2
 
3
3
  import { AiNodePackageJsonRule } from './ai-node-package-json.js';
4
4
  import { CredClassFieldIconMissingRule } from './cred-class-field-icon-missing.js';
5
+ import { CredClassNameFieldConventionsRule } from './cred-class-name-field-conventions.js';
5
6
  import { CredClassNameSuffixRule } from './cred-class-name-suffix.js';
6
7
  import { CredClassOAuth2NamingRule } from './cred-class-oauth2-naming.js';
7
8
  import { CredentialDocumentationUrlRule } from './credential-documentation-url.js';
@@ -57,6 +58,7 @@ export const rules = {
57
58
  'credential-documentation-url': CredentialDocumentationUrlRule,
58
59
  'node-class-description-icon-missing': NodeClassDescriptionIconMissingRule,
59
60
  'cred-class-field-icon-missing': CredClassFieldIconMissingRule,
61
+ 'cred-class-name-field-conventions': CredClassNameFieldConventionsRule,
60
62
  'cred-class-name-suffix': CredClassNameSuffixRule,
61
63
  'cred-class-oauth2-naming': CredClassOAuth2NamingRule,
62
64
  'node-connection-type-literal': NodeConnectionTypeLiteralRule,
@@ -1,4 +1,4 @@
1
- import { TSESTree } from '@typescript-eslint/types';
1
+ import { TSESTree } from '@typescript-eslint/utils';
2
2
  import type { ReportSuggestionArray } from '@typescript-eslint/utils/ts-eslint';
3
3
 
4
4
  import {
@@ -1,4 +1,4 @@
1
- import { TSESTree } from '@typescript-eslint/types';
1
+ import { TSESTree } from '@typescript-eslint/utils';
2
2
  import type { TSESLint } from '@typescript-eslint/utils';
3
3
 
4
4
  import { createRule } from '../utils/index.js';
@@ -37,6 +37,24 @@ ruleTester.run('no-template-placeholders', NoTemplatePlaceholdersRule, {
37
37
  filename: 'package.json',
38
38
  code: '{ "name": "n8n-nodes-example", "private": false, "engines": { "node": ">=18" } }',
39
39
  },
40
+ {
41
+ name: 'npm person string with email in author is not flagged',
42
+ filename: 'package.json',
43
+ code: '{ "author": "Jane Doe <jane@example.com>" }',
44
+ },
45
+ {
46
+ name: 'npm person string with email and url in author is not flagged',
47
+ filename: 'package.json',
48
+ code: '{ "author": "Jane Doe <jane@example.com> (https://example.com)" }',
49
+ },
50
+ {
51
+ name: 'npm person strings in contributors and maintainers are not flagged',
52
+ filename: 'package.json',
53
+ code: `{
54
+ "contributors": ["Jane Doe <jane@example.com> (https://example.com)"],
55
+ "maintainers": ["John Roe <john.roe+n8n@example.org>"]
56
+ }`,
57
+ },
40
58
  ],
41
59
  invalid: [
42
60
  {
@@ -3,7 +3,9 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils';
3
3
 
4
4
  import { createRule } from '../utils/index.js';
5
5
 
6
- const ANGLE_PLACEHOLDER = /<[^<>\n]+?>/;
6
+ // Excludes `@` so that emails inside npm "person" strings
7
+ // (e.g. `"author": "Jane Doe <jane@example.com>"`) are not flagged.
8
+ const ANGLE_PLACEHOLDER = /<[^<>\n@]+?>/;
7
9
  const MUSTACHE_PLACEHOLDER = /\{\{[^{}\n]+?\}\}/;
8
10
 
9
11
  function findPlaceholder(value: string): { pattern: string; type: 'angle' | 'mustache' } | null {
@@ -1,4 +1,4 @@
1
- import { TSESTree } from '@typescript-eslint/types';
1
+ import { TSESTree } from '@typescript-eslint/utils';
2
2
 
3
3
  import {
4
4
  isNodeTypeClass,
@@ -26,11 +26,6 @@ ruleTester.run('package-name-convention', PackageNameConventionRule, {
26
26
  filename: 'package.json',
27
27
  code: '{ "name": "@author/n8n-nodes-service", "version": "1.0.0" }',
28
28
  },
29
- {
30
- name: 'object without name property',
31
- filename: 'package.json',
32
- code: '{ "version": "1.0.0", "description": "test" }',
33
- },
34
29
  {
35
30
  name: 'non-package.json file ignored',
36
31
  filename: 'some-config.json',
@@ -185,5 +180,37 @@ ruleTester.run('package-name-convention', PackageNameConventionRule, {
185
180
  },
186
181
  ],
187
182
  },
183
+ {
184
+ name: 'missing name property',
185
+ filename: 'package.json',
186
+ code: '{ "version": "1.0.0", "description": "test" }',
187
+ errors: [
188
+ {
189
+ messageId: 'missingName',
190
+ },
191
+ ],
192
+ },
193
+ {
194
+ name: 'default placeholder name',
195
+ filename: 'package.json',
196
+ code: '{ "name": "n8n-nodes-<...>", "version": "1.0.0" }',
197
+ errors: [
198
+ {
199
+ messageId: 'defaultPlaceholderName',
200
+ data: { packageName: 'n8n-nodes-<...>' },
201
+ },
202
+ ],
203
+ },
204
+ {
205
+ name: 'name containing default placeholder',
206
+ filename: 'package.json',
207
+ code: '{ "name": "@company/n8n-nodes-<...>", "version": "1.0.0" }',
208
+ errors: [
209
+ {
210
+ messageId: 'defaultPlaceholderName',
211
+ data: { packageName: '@company/n8n-nodes-<...>' },
212
+ },
213
+ ],
214
+ },
188
215
  ],
189
216
  });
@@ -15,6 +15,10 @@ export const PackageNameConventionRule = createRule({
15
15
  renameTo: "Rename to '{{suggestedName}}'",
16
16
  invalidPackageName:
17
17
  'Package name "{{ packageName }}" must follow the convention "n8n-nodes-[PACKAGE-NAME]" or "@[AUTHOR]/n8n-nodes-[PACKAGE-NAME]"',
18
+ missingName:
19
+ 'Package name is missing. Add a "name" field following the convention "n8n-nodes-[PACKAGE-NAME]" or "@[AUTHOR]/n8n-nodes-[PACKAGE-NAME]"',
20
+ defaultPlaceholderName:
21
+ 'Package name "{{ packageName }}" still contains the default placeholder. Replace "<...>" with your package name',
18
22
  },
19
23
  schema: [],
20
24
  hasSuggestions: true,
@@ -34,6 +38,10 @@ export const PackageNameConventionRule = createRule({
34
38
  const nameProperty = findJsonProperty(node, 'name');
35
39
 
36
40
  if (!nameProperty) {
41
+ context.report({
42
+ node,
43
+ messageId: 'missingName',
44
+ });
37
45
  return;
38
46
  }
39
47
 
@@ -44,6 +52,17 @@ export const PackageNameConventionRule = createRule({
44
52
  const packageName = nameProperty.value.value;
45
53
  const packageNameStr = typeof packageName === 'string' ? packageName : null;
46
54
 
55
+ if (packageNameStr && isDefaultPlaceholderName(packageNameStr)) {
56
+ context.report({
57
+ node: nameProperty,
58
+ messageId: 'defaultPlaceholderName',
59
+ data: {
60
+ packageName: packageNameStr,
61
+ },
62
+ });
63
+ return;
64
+ }
65
+
47
66
  if (!packageNameStr || !isValidPackageName(packageNameStr)) {
48
67
  const suggestions: ReportSuggestionArray<'invalidPackageName' | 'renameTo'> = [];
49
68
 
@@ -75,6 +94,10 @@ export const PackageNameConventionRule = createRule({
75
94
  },
76
95
  });
77
96
 
97
+ function isDefaultPlaceholderName(name: string): boolean {
98
+ return name.includes('<...>');
99
+ }
100
+
78
101
  function isValidPackageName(name: string): boolean {
79
102
  const unscoped = /^n8n-nodes-.+$/;
80
103
  const scoped = /^@.+\/n8n-nodes-.+$/;
@@ -1,5 +1,4 @@
1
- import { DefinitionType } from '@typescript-eslint/scope-manager';
2
- import { AST_NODE_TYPES, type TSESTree } from '@typescript-eslint/utils';
1
+ import { AST_NODE_TYPES, TSESLint, type TSESTree } from '@typescript-eslint/utils';
3
2
 
4
3
  import { isFileType } from '../utils/index.js';
5
4
  import { createRule } from '../utils/rule-creator.js';
@@ -68,7 +67,9 @@ export const RequireNodeApiErrorRule = createRule({
68
67
  const scope = context.sourceCode.getScope(node);
69
68
  const ref = scope.references.find((r) => r.identifier === argument);
70
69
  const isCatchParam =
71
- ref?.resolved?.defs.some((def) => def.type === DefinitionType.CatchClause) ?? false;
70
+ ref?.resolved?.defs.some(
71
+ (def) => def.type === TSESLint.Scope.DefinitionType.CatchClause,
72
+ ) ?? false;
72
73
 
73
74
  if (isCatchParam) {
74
75
  context.report({ node, messageId: 'useNodeApiError' });
@@ -1,4 +1,4 @@
1
- import { TSESTree } from '@typescript-eslint/types';
1
+ import { TSESTree } from '@typescript-eslint/utils';
2
2
  import type { ReportSuggestionArray } from '@typescript-eslint/utils/ts-eslint';
3
3
 
4
4
  import {