@n8n/eslint-plugin-community-nodes 0.19.0 → 0.21.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 (84) hide show
  1. package/.turbo/turbo-build$colon$unchecked.log +4 -0
  2. package/README.md +9 -1
  3. package/dist/plugin.d.ts +48 -0
  4. package/dist/plugin.d.ts.map +1 -1
  5. package/dist/plugin.js +16 -0
  6. package/dist/plugin.js.map +1 -1
  7. package/dist/rules/cred-filename-against-convention.d.ts +2 -0
  8. package/dist/rules/cred-filename-against-convention.d.ts.map +1 -0
  9. package/dist/rules/cred-filename-against-convention.js +42 -0
  10. package/dist/rules/cred-filename-against-convention.js.map +1 -0
  11. package/dist/rules/icon-prefer-themed-variants.d.ts +2 -0
  12. package/dist/rules/icon-prefer-themed-variants.d.ts.map +1 -0
  13. package/dist/rules/icon-prefer-themed-variants.js +53 -0
  14. package/dist/rules/icon-prefer-themed-variants.js.map +1 -0
  15. package/dist/rules/icon-validation.d.ts +1 -1
  16. package/dist/rules/icon-validation.d.ts.map +1 -1
  17. package/dist/rules/icon-validation.js +4 -25
  18. package/dist/rules/icon-validation.js.map +1 -1
  19. package/dist/rules/index.d.ts +9 -1
  20. package/dist/rules/index.d.ts.map +1 -1
  21. package/dist/rules/index.js +16 -0
  22. package/dist/rules/index.js.map +1 -1
  23. package/dist/rules/no-dangerous-functions.d.ts +2 -0
  24. package/dist/rules/no-dangerous-functions.d.ts.map +1 -0
  25. package/dist/rules/no-dangerous-functions.js +121 -0
  26. package/dist/rules/no-dangerous-functions.js.map +1 -0
  27. package/dist/rules/no-emoji-in-options.d.ts +2 -0
  28. package/dist/rules/no-emoji-in-options.d.ts.map +1 -0
  29. package/dist/rules/no-emoji-in-options.js +86 -0
  30. package/dist/rules/no-emoji-in-options.js.map +1 -0
  31. package/dist/rules/node-filename-against-convention.d.ts +2 -0
  32. package/dist/rules/node-filename-against-convention.d.ts.map +1 -0
  33. package/dist/rules/node-filename-against-convention.js +61 -0
  34. package/dist/rules/node-filename-against-convention.js.map +1 -0
  35. package/dist/rules/node-registration-complete.d.ts +2 -0
  36. package/dist/rules/node-registration-complete.d.ts.map +1 -0
  37. package/dist/rules/node-registration-complete.js +60 -0
  38. package/dist/rules/node-registration-complete.js.map +1 -0
  39. package/dist/rules/require-version.d.ts +2 -0
  40. package/dist/rules/require-version.d.ts.map +1 -0
  41. package/dist/rules/require-version.js +50 -0
  42. package/dist/rules/require-version.js.map +1 -0
  43. package/dist/rules/valid-author.d.ts +2 -0
  44. package/dist/rules/valid-author.d.ts.map +1 -0
  45. package/dist/rules/valid-author.js +89 -0
  46. package/dist/rules/valid-author.js.map +1 -0
  47. package/dist/typecheck.tsbuildinfo +1 -0
  48. package/dist/utils/file-utils.d.ts +7 -2
  49. package/dist/utils/file-utils.d.ts.map +1 -1
  50. package/dist/utils/file-utils.js +47 -10
  51. package/dist/utils/file-utils.js.map +1 -1
  52. package/docs/rules/cred-filename-against-convention.md +42 -0
  53. package/docs/rules/icon-prefer-themed-variants.md +71 -0
  54. package/docs/rules/icon-validation.md +5 -3
  55. package/docs/rules/no-dangerous-functions.md +41 -0
  56. package/docs/rules/no-emoji-in-options.md +60 -0
  57. package/docs/rules/node-filename-against-convention.md +50 -0
  58. package/docs/rules/node-registration-complete.md +46 -0
  59. package/docs/rules/require-version.md +51 -0
  60. package/docs/rules/valid-author.md +60 -0
  61. package/package.json +5 -4
  62. package/src/plugin.ts +16 -0
  63. package/src/rules/cred-filename-against-convention.test.ts +72 -0
  64. package/src/rules/cred-filename-against-convention.ts +48 -0
  65. package/src/rules/icon-prefer-themed-variants.test.ts +128 -0
  66. package/src/rules/icon-prefer-themed-variants.ts +70 -0
  67. package/src/rules/icon-validation.test.ts +10 -0
  68. package/src/rules/icon-validation.ts +4 -28
  69. package/src/rules/index.ts +16 -0
  70. package/src/rules/no-dangerous-functions.test.ts +83 -0
  71. package/src/rules/no-dangerous-functions.ts +155 -0
  72. package/src/rules/no-emoji-in-options.test.ts +157 -0
  73. package/src/rules/no-emoji-in-options.ts +105 -0
  74. package/src/rules/node-filename-against-convention.test.ts +115 -0
  75. package/src/rules/node-filename-against-convention.ts +76 -0
  76. package/src/rules/node-registration-complete.test.ts +87 -0
  77. package/src/rules/node-registration-complete.ts +79 -0
  78. package/src/rules/require-version.test.ts +90 -0
  79. package/src/rules/require-version.ts +62 -0
  80. package/src/rules/valid-author.test.ts +108 -0
  81. package/src/rules/valid-author.ts +100 -0
  82. package/src/utils/file-utils.ts +58 -11
  83. package/.turbo/turbo-build.log +0 -4
  84. package/tsconfig.build.tsbuildinfo +0 -1
@@ -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
+ });
@@ -0,0 +1,128 @@
1
+ import { RuleTester } from '@typescript-eslint/rule-tester';
2
+
3
+ import { IconPreferThemedVariantsRule } from './icon-prefer-themed-variants.js';
4
+
5
+ const ruleTester = new RuleTester();
6
+
7
+ const nodeFilePath = '/tmp/TestNode.node.ts';
8
+ const credentialFilePath = '/tmp/TestCredential.credentials.ts';
9
+
10
+ function createNodeCode(icon?: string | { light: string; dark: string }): string {
11
+ let iconProperty = '';
12
+ if (icon) {
13
+ if (typeof icon === 'string') {
14
+ iconProperty = `icon: '${icon}',`;
15
+ } else {
16
+ iconProperty = `icon: {
17
+ light: '${icon.light}',
18
+ dark: '${icon.dark}'
19
+ },`;
20
+ }
21
+ }
22
+
23
+ return `
24
+ import type { INodeType, INodeTypeDescription } from 'n8n-workflow';
25
+
26
+ export class TestNode implements INodeType {
27
+ description: INodeTypeDescription = {
28
+ displayName: 'Test Node',
29
+ name: 'testNode',
30
+ ${iconProperty}
31
+ group: ['input'],
32
+ version: 1,
33
+ description: 'A test node',
34
+ defaults: {
35
+ name: 'Test Node',
36
+ },
37
+ inputs: ['main'],
38
+ outputs: ['main'],
39
+ properties: [],
40
+ };
41
+ }`;
42
+ }
43
+
44
+ function createCredentialCode(icon?: string | { light: string; dark: string }): string {
45
+ let iconProperty = '';
46
+ if (icon) {
47
+ if (typeof icon === 'string') {
48
+ iconProperty = `icon = '${icon}';`;
49
+ } else {
50
+ iconProperty = `icon = {
51
+ light: '${icon.light}',
52
+ dark: '${icon.dark}'
53
+ };`;
54
+ }
55
+ }
56
+
57
+ return `
58
+ import type { ICredentialType, INodeProperties } from 'n8n-workflow';
59
+
60
+ export class TestCredential implements ICredentialType {
61
+ name = 'testApi';
62
+ displayName = 'Test API';
63
+ ${iconProperty}
64
+ properties: INodeProperties[] = [];
65
+ }`;
66
+ }
67
+
68
+ function createNonNodeClass(icon: string): string {
69
+ return `
70
+ export class NotANode {
71
+ icon = '${icon}';
72
+ }`;
73
+ }
74
+
75
+ ruleTester.run('icon-prefer-themed-variants', IconPreferThemedVariantsRule, {
76
+ valid: [
77
+ {
78
+ name: 'non-node class ignored',
79
+ filename: nodeFilePath,
80
+ code: createNonNodeClass('file:icon.svg'),
81
+ },
82
+ {
83
+ name: 'non-node file ignored',
84
+ filename: '/tmp/regular-file.ts',
85
+ code: createNodeCode('file:icon.svg'),
86
+ },
87
+ {
88
+ name: 'node with no icon property ignored',
89
+ filename: nodeFilePath,
90
+ code: createNodeCode(),
91
+ },
92
+ {
93
+ name: 'node with themed light/dark icons',
94
+ filename: nodeFilePath,
95
+ code: createNodeCode({
96
+ light: 'file:icons/icon.light.svg',
97
+ dark: 'file:icons/icon.dark.svg',
98
+ }),
99
+ },
100
+ {
101
+ name: 'credential with no icon property ignored',
102
+ filename: credentialFilePath,
103
+ code: createCredentialCode(),
104
+ },
105
+ {
106
+ name: 'credential with themed light/dark icons',
107
+ filename: credentialFilePath,
108
+ code: createCredentialCode({
109
+ light: 'file:icons/icon.light.svg',
110
+ dark: 'file:icons/icon.dark.svg',
111
+ }),
112
+ },
113
+ ],
114
+ invalid: [
115
+ {
116
+ name: 'node with single string icon',
117
+ filename: nodeFilePath,
118
+ code: createNodeCode('file:icons/icon.svg'),
119
+ errors: [{ messageId: 'missingThemedVariants' }],
120
+ },
121
+ {
122
+ name: 'credential with single string icon',
123
+ filename: credentialFilePath,
124
+ code: createCredentialCode('file:icons/icon.svg'),
125
+ errors: [{ messageId: 'missingThemedVariants' }],
126
+ },
127
+ ],
128
+ });
@@ -0,0 +1,70 @@
1
+ import { TSESTree } from '@typescript-eslint/utils';
2
+
3
+ import {
4
+ isNodeTypeClass,
5
+ isCredentialTypeClass,
6
+ findClassProperty,
7
+ findObjectProperty,
8
+ isFileType,
9
+ createRule,
10
+ } from '../utils/index.js';
11
+
12
+ const messages = {
13
+ missingThemedVariants:
14
+ 'Icon is defined as a single file. Provide both light and dark variants using the `{ light, dark }` form so the icon renders well on both themes.',
15
+ } as const;
16
+
17
+ export const IconPreferThemedVariantsRule = createRule({
18
+ name: 'icon-prefer-themed-variants',
19
+ meta: {
20
+ type: 'suggestion',
21
+ docs: {
22
+ description:
23
+ 'Encourage node and credential icons to provide light/dark variants instead of a single icon file',
24
+ },
25
+ messages,
26
+ schema: [],
27
+ },
28
+ defaultOptions: [],
29
+ create(context) {
30
+ if (
31
+ !isFileType(context.filename, '.node.ts') &&
32
+ !isFileType(context.filename, '.credentials.ts')
33
+ ) {
34
+ return {};
35
+ }
36
+
37
+ const checkIconValue = (iconValue: TSESTree.Node) => {
38
+ if (
39
+ iconValue.type === TSESTree.AST_NODE_TYPES.Literal &&
40
+ typeof iconValue.value === 'string'
41
+ ) {
42
+ context.report({
43
+ node: iconValue,
44
+ messageId: 'missingThemedVariants',
45
+ });
46
+ }
47
+ };
48
+
49
+ return {
50
+ ClassDeclaration(node) {
51
+ if (isNodeTypeClass(node)) {
52
+ const descriptionProperty = findClassProperty(node, 'description');
53
+ if (descriptionProperty?.value?.type !== TSESTree.AST_NODE_TYPES.ObjectExpression) {
54
+ return;
55
+ }
56
+
57
+ const iconProperty = findObjectProperty(descriptionProperty.value, 'icon');
58
+ if (iconProperty) {
59
+ checkIconValue(iconProperty.value);
60
+ }
61
+ } else if (isCredentialTypeClass(node)) {
62
+ const iconProperty = findClassProperty(node, 'icon');
63
+ if (iconProperty?.value) {
64
+ checkIconValue(iconProperty.value);
65
+ }
66
+ }
67
+ },
68
+ };
69
+ },
70
+ });
@@ -146,6 +146,11 @@ ruleTester.run('icon-validation', IconValidationRule, {
146
146
  filename: nodeFilePath,
147
147
  code: createNodeCode('file:icons/TestNode.svg', true),
148
148
  },
149
+ {
150
+ name: 'node with valid PNG string icon in description',
151
+ filename: nodeFilePath,
152
+ code: createNodeCode('file:icons/NotSvg.png', true),
153
+ },
149
154
  {
150
155
  name: 'node with valid light/dark icons in description',
151
156
  filename: nodeFilePath,
@@ -162,6 +167,11 @@ ruleTester.run('icon-validation', IconValidationRule, {
162
167
  filename: credentialFilePath,
163
168
  code: createCredentialCode('file:icons/TestNode.svg'),
164
169
  },
170
+ {
171
+ name: 'credential with valid PNG string icon',
172
+ filename: credentialFilePath,
173
+ code: createCredentialCode('file:icons/NotSvg.png'),
174
+ },
165
175
  {
166
176
  name: 'credential with valid light/dark icons',
167
177
  filename: credentialFilePath,
@@ -9,20 +9,18 @@ import {
9
9
  findObjectProperty,
10
10
  getStringLiteralValue,
11
11
  validateIconPath,
12
- findSimilarSvgFiles,
12
+ findSimilarIconFiles,
13
13
  isFileType,
14
14
  createRule,
15
15
  } from '../utils/index.js';
16
16
 
17
17
  const messages = {
18
18
  iconFileNotFound: 'Icon file "{{ iconPath }}" does not exist',
19
- iconNotSvg: 'Icon file "{{ iconPath }}" must be an SVG file (end with .svg)',
20
19
  lightDarkSame: 'Light and dark icons cannot be the same file. Both point to "{{ iconPath }}"',
21
20
  invalidIconPath: 'Icon path "{{ iconPath }}" must use file: protocol and be a string',
22
21
  missingIcon: 'Node/Credential class must have an icon property defined',
23
22
  addPlaceholder: 'Add icon property with placeholder',
24
23
  addFileProtocol: "Add 'file:' protocol to icon path",
25
- changeExtension: "Change icon extension to '.svg'",
26
24
  similarIcon: "Use existing icon '{{ suggestedName }}'",
27
25
  } as const;
28
26
 
@@ -32,7 +30,7 @@ export const IconValidationRule = createRule({
32
30
  type: 'problem',
33
31
  docs: {
34
32
  description:
35
- 'Validate node and credential icon files exist, are SVG format, and light/dark icons are different',
33
+ 'Validate node and credential icon files exist, use the file: protocol, and that light/dark icons are different',
36
34
  },
37
35
  messages,
38
36
  schema: [],
@@ -80,34 +78,12 @@ export const IconValidationRule = createRule({
80
78
  return false;
81
79
  }
82
80
 
83
- if (!validation.isSvg) {
84
- const relativePath = iconPath.replace(/^file:/, '');
85
- const suggestions: ReportSuggestionArray<keyof typeof messages> = [];
86
-
87
- const pathWithoutExt = relativePath.replace(/\.[^/.]+$/, '');
88
- const svgPath = `${pathWithoutExt}.svg`;
89
- suggestions.push({
90
- messageId: 'changeExtension',
91
- fix(fixer) {
92
- return fixer.replaceText(node, `"file:${svgPath}"`);
93
- },
94
- });
95
-
96
- context.report({
97
- node,
98
- messageId: 'iconNotSvg',
99
- data: { iconPath: relativePath },
100
- suggest: suggestions,
101
- });
102
- return false;
103
- }
104
-
105
81
  if (!validation.exists) {
106
82
  const relativePath = iconPath.replace(/^file:/, '');
107
83
  const suggestions: ReportSuggestionArray<keyof typeof messages> = [];
108
84
 
109
- // Find similar SVG files in the same directory
110
- const similarFiles = findSimilarSvgFiles(relativePath, currentDir);
85
+ // Find similar icon files in the same directory
86
+ const similarFiles = findSimilarIconFiles(relativePath, currentDir);
111
87
  for (const similarFile of similarFiles) {
112
88
  suggestions.push({
113
89
  messageId: 'similarIcon',
@@ -5,15 +5,19 @@ import { CredClassFieldIconMissingRule } from './cred-class-field-icon-missing.j
5
5
  import { CredClassNameFieldConventionsRule } from './cred-class-name-field-conventions.js';
6
6
  import { CredClassNameSuffixRule } from './cred-class-name-suffix.js';
7
7
  import { CredClassOAuth2NamingRule } from './cred-class-oauth2-naming.js';
8
+ import { CredFilenameAgainstConventionRule } from './cred-filename-against-convention.js';
8
9
  import { CredentialDocumentationUrlRule } from './credential-documentation-url.js';
9
10
  import { CredentialPasswordFieldRule } from './credential-password-field.js';
10
11
  import { CredentialTestRequiredRule } from './credential-test-required.js';
12
+ import { IconPreferThemedVariantsRule } from './icon-prefer-themed-variants.js';
11
13
  import { IconValidationRule } from './icon-validation.js';
12
14
  import { MissingPairedItemRule } from './missing-paired-item.js';
13
15
  import { N8nObjectValidationRule } from './n8n-object-validation.js';
14
16
  import { NoBuilderHintLeakageRule } from './no-builder-hint-leakage.js';
15
17
  import { NoCredentialReuseRule } from './no-credential-reuse.js';
18
+ import { NoDangerousFunctionsRule } from './no-dangerous-functions.js';
16
19
  import { NoDeprecatedWorkflowFunctionsRule } from './no-deprecated-workflow-functions.js';
20
+ import { NoEmojiInOptionsRule } from './no-emoji-in-options.js';
17
21
  import { NoForbiddenLifecycleScriptsRule } from './no-forbidden-lifecycle-scripts.js';
18
22
  import { NoHttpRequestWithManualAuthRule } from './no-http-request-with-manual-auth.js';
19
23
  import { NoOverridesFieldRule } from './no-overrides-field.js';
@@ -23,7 +27,9 @@ import { NoRuntimeDependenciesRule } from './no-runtime-dependencies.js';
23
27
  import { NoTemplatePlaceholdersRule } from './no-template-placeholders.js';
24
28
  import { NodeClassDescriptionIconMissingRule } from './node-class-description-icon-missing.js';
25
29
  import { NodeConnectionTypeLiteralRule } from './node-connection-type-literal.js';
30
+ import { NodeFilenameAgainstConventionRule } from './node-filename-against-convention.js';
26
31
  import { NodeOperationErrorItemIndexRule } from './node-operation-error-itemindex.js';
32
+ import { NodeRegistrationCompleteRule } from './node-registration-complete.js';
27
33
  import { NodeUsableAsToolRule } from './node-usable-as-tool.js';
28
34
  import { OptionsSortedAlphabeticallyRule } from './options-sorted-alphabetically.js';
29
35
  import { PackageNameConventionRule } from './package-name-convention.js';
@@ -31,7 +37,9 @@ import { RequireCommunityNodeKeywordRule } from './require-community-node-keywor
31
37
  import { RequireContinueOnFailRule } from './require-continue-on-fail.js';
32
38
  import { RequireNodeApiErrorRule } from './require-node-api-error.js';
33
39
  import { RequireNodeDescriptionFieldsRule } from './require-node-description-fields.js';
40
+ import { RequireVersionRule } from './require-version.js';
34
41
  import { ResourceOperationPatternRule } from './resource-operation-pattern.js';
42
+ import { ValidAuthorRule } from './valid-author.js';
35
43
  import { ValidCredentialReferencesRule } from './valid-credential-references.js';
36
44
  import { ValidDescriptionRule } from './valid-description.js';
37
45
  import { ValidPeerDependenciesRule } from './valid-peer-dependencies.js';
@@ -43,17 +51,20 @@ export const rules = {
43
51
  'no-restricted-imports': NoRestrictedImportsRule,
44
52
  'credential-password-field': CredentialPasswordFieldRule,
45
53
  'no-deprecated-workflow-functions': NoDeprecatedWorkflowFunctionsRule,
54
+ 'no-emoji-in-options': NoEmojiInOptionsRule,
46
55
  'node-usable-as-tool': NodeUsableAsToolRule,
47
56
  'options-sorted-alphabetically': OptionsSortedAlphabeticallyRule,
48
57
  'package-name-convention': PackageNameConventionRule,
49
58
  'credential-test-required': CredentialTestRequiredRule,
50
59
  'no-credential-reuse': NoCredentialReuseRule,
60
+ 'no-dangerous-functions': NoDangerousFunctionsRule,
51
61
  'no-forbidden-lifecycle-scripts': NoForbiddenLifecycleScriptsRule,
52
62
  'no-http-request-with-manual-auth': NoHttpRequestWithManualAuthRule,
53
63
  'no-overrides-field': NoOverridesFieldRule,
54
64
  'no-runtime-dependencies': NoRuntimeDependenciesRule,
55
65
  'no-template-placeholders': NoTemplatePlaceholdersRule,
56
66
  'icon-validation': IconValidationRule,
67
+ 'icon-prefer-themed-variants': IconPreferThemedVariantsRule,
57
68
  'resource-operation-pattern': ResourceOperationPatternRule,
58
69
  'credential-documentation-url': CredentialDocumentationUrlRule,
59
70
  'node-class-description-icon-missing': NodeClassDescriptionIconMissingRule,
@@ -61,8 +72,11 @@ export const rules = {
61
72
  'cred-class-name-field-conventions': CredClassNameFieldConventionsRule,
62
73
  'cred-class-name-suffix': CredClassNameSuffixRule,
63
74
  'cred-class-oauth2-naming': CredClassOAuth2NamingRule,
75
+ 'cred-filename-against-convention': CredFilenameAgainstConventionRule,
64
76
  'node-connection-type-literal': NodeConnectionTypeLiteralRule,
77
+ 'node-filename-against-convention': NodeFilenameAgainstConventionRule,
65
78
  'node-operation-error-itemindex': NodeOperationErrorItemIndexRule,
79
+ 'node-registration-complete': NodeRegistrationCompleteRule,
66
80
  'missing-paired-item': MissingPairedItemRule,
67
81
  'no-builder-hint-leakage': NoBuilderHintLeakageRule,
68
82
  'n8n-object-validation': N8nObjectValidationRule,
@@ -70,6 +84,8 @@ export const rules = {
70
84
  'require-continue-on-fail': RequireContinueOnFailRule,
71
85
  'require-node-api-error': RequireNodeApiErrorRule,
72
86
  'require-node-description-fields': RequireNodeDescriptionFieldsRule,
87
+ 'require-version': RequireVersionRule,
88
+ 'valid-author': ValidAuthorRule,
73
89
  'valid-credential-references': ValidCredentialReferencesRule,
74
90
  'valid-description': ValidDescriptionRule,
75
91
  'valid-peer-dependencies': ValidPeerDependenciesRule,
@@ -0,0 +1,83 @@
1
+ import { RuleTester } from '@typescript-eslint/rule-tester';
2
+
3
+ import { NoDangerousFunctionsRule } from './no-dangerous-functions.js';
4
+
5
+ const ruleTester = new RuleTester();
6
+
7
+ ruleTester.run('no-dangerous-functions', NoDangerousFunctionsRule, {
8
+ valid: [
9
+ // `exec`/`spawn` not originating from `child_process` must not be flagged.
10
+ { name: 'regex exec', code: 'const match = /foo/.exec(input);' },
11
+ { name: 'regex exec via variable', code: 'regex.exec(input);' },
12
+ { name: 'unrelated exec member', code: 'db.exec("SELECT 1");' },
13
+ { name: 'unrelated spawn member', code: 'queue.spawn(job);' },
14
+ { name: 'locally declared exec', code: 'function exec() {} exec();' },
15
+ // Importing without calling is fine.
16
+ { name: 'import without call', code: "import { exec } from 'child_process';" },
17
+ // Non-dangerous members of the namespace import are fine.
18
+ {
19
+ name: 'non-dangerous namespace member',
20
+ code: "import * as cp from 'child_process'; const p = cp.execPath;",
21
+ },
22
+ // `eval`/`Function` as identifier references (not calls) are fine.
23
+ { name: 'eval reference only', code: 'const f = eval;' },
24
+ { name: 'Function reference only', code: 'const F = Function;' },
25
+ // Non-child_process module is irrelevant.
26
+ {
27
+ name: 'spawn from unrelated module',
28
+ code: "import { spawn } from 'some-lib'; spawn('x');",
29
+ },
30
+ ],
31
+ invalid: [
32
+ {
33
+ name: 'SECURITY: eval call',
34
+ code: "eval('1 + 1');",
35
+ errors: [{ messageId: 'noEval' }],
36
+ },
37
+ {
38
+ name: 'SECURITY: Function constructor with new',
39
+ code: "const fn = new Function('return process');",
40
+ errors: [{ messageId: 'noFunctionConstructor' }],
41
+ },
42
+ {
43
+ name: 'SECURITY: Function constructor without new',
44
+ code: "const fn = Function('return 1');",
45
+ errors: [{ messageId: 'noFunctionConstructor' }],
46
+ },
47
+ {
48
+ name: 'SECURITY: exec from child_process',
49
+ code: "import { exec } from 'child_process'; exec('ls');",
50
+ errors: [{ messageId: 'noChildProcess', data: { name: 'exec' } }],
51
+ },
52
+ {
53
+ name: 'SECURITY: aliased exec from node:child_process',
54
+ code: "import { exec as run } from 'node:child_process'; run('ls');",
55
+ errors: [{ messageId: 'noChildProcess', data: { name: 'exec' } }],
56
+ },
57
+ {
58
+ name: 'SECURITY: spawn from child_process',
59
+ code: "import { spawn } from 'child_process'; spawn('ls', ['-la']);",
60
+ errors: [{ messageId: 'noChildProcess', data: { name: 'spawn' } }],
61
+ },
62
+ {
63
+ name: 'SECURITY: namespace execSync',
64
+ code: "import * as cp from 'child_process'; cp.execSync('ls');",
65
+ errors: [{ messageId: 'noChildProcess', data: { name: 'execSync' } }],
66
+ },
67
+ {
68
+ name: 'SECURITY: default import spawnSync',
69
+ code: "import childProcess from 'node:child_process'; childProcess.spawnSync('ls');",
70
+ errors: [{ messageId: 'noChildProcess', data: { name: 'spawnSync' } }],
71
+ },
72
+ {
73
+ name: 'SECURITY: destructured require execFile',
74
+ code: "const { execFile } = require('child_process'); execFile('ls');",
75
+ errors: [{ messageId: 'noChildProcess', data: { name: 'execFile' } }],
76
+ },
77
+ {
78
+ name: 'SECURITY: namespace require fork',
79
+ code: "const cp = require('node:child_process'); cp.fork('./worker.js');",
80
+ errors: [{ messageId: 'noChildProcess', data: { name: 'fork' } }],
81
+ },
82
+ ],
83
+ });