@n8n/eslint-plugin-community-nodes 0.31.0 → 0.33.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/README.md +2 -1
- package/dist/plugin.d.ts +6 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +2 -0
- package/dist/rules/ai-node-package-json.d.ts +3 -1
- package/dist/rules/ai-node-package-json.d.ts.map +1 -1
- package/dist/rules/cred-class-field-icon-missing.d.ts +3 -1
- package/dist/rules/cred-class-field-icon-missing.d.ts.map +1 -1
- package/dist/rules/cred-class-oauth2-naming.d.ts +3 -1
- package/dist/rules/cred-class-oauth2-naming.d.ts.map +1 -1
- package/dist/rules/credential-documentation-url.d.ts +3 -1
- package/dist/rules/credential-documentation-url.d.ts.map +1 -1
- package/dist/rules/credential-password-field.d.ts +3 -1
- package/dist/rules/credential-password-field.d.ts.map +1 -1
- package/dist/rules/credential-password-field.js +1 -2
- package/dist/rules/credential-test-required.d.ts +3 -1
- package/dist/rules/credential-test-required.d.ts.map +1 -1
- package/dist/rules/credential-unnecessary-password.d.ts +3 -1
- package/dist/rules/credential-unnecessary-password.d.ts.map +1 -1
- package/dist/rules/credential-unnecessary-password.js +1 -2
- package/dist/rules/icon-prefer-themed-variants.d.ts +3 -1
- package/dist/rules/icon-prefer-themed-variants.d.ts.map +1 -1
- package/dist/rules/icon-validation.d.ts +3 -1
- package/dist/rules/icon-validation.d.ts.map +1 -1
- package/dist/rules/icon-validation.js +5 -15
- package/dist/rules/index.d.ts +132 -43
- package/dist/rules/index.d.ts.map +1 -1
- package/dist/rules/index.js +2 -0
- package/dist/rules/missing-paired-item.d.ts +3 -1
- package/dist/rules/missing-paired-item.d.ts.map +1 -1
- package/dist/rules/n8n-object-validation.d.ts +3 -1
- package/dist/rules/n8n-object-validation.d.ts.map +1 -1
- package/dist/rules/no-builder-hint-leakage.d.ts +3 -1
- package/dist/rules/no-builder-hint-leakage.d.ts.map +1 -1
- package/dist/rules/no-credential-reuse.d.ts +3 -1
- package/dist/rules/no-credential-reuse.d.ts.map +1 -1
- package/dist/rules/no-credential-reuse.js +4 -6
- package/dist/rules/no-dangerous-functions.d.ts +3 -1
- package/dist/rules/no-dangerous-functions.d.ts.map +1 -1
- package/dist/rules/no-dead-files.d.ts +3 -1
- package/dist/rules/no-dead-files.d.ts.map +1 -1
- package/dist/rules/no-deprecated-workflow-functions.d.ts +3 -1
- package/dist/rules/no-deprecated-workflow-functions.d.ts.map +1 -1
- package/dist/rules/no-emoji-in-options.d.ts +3 -1
- package/dist/rules/no-emoji-in-options.d.ts.map +1 -1
- package/dist/rules/no-emoji-in-options.js +4 -4
- package/dist/rules/no-forbidden-lifecycle-scripts.d.ts +3 -1
- package/dist/rules/no-forbidden-lifecycle-scripts.d.ts.map +1 -1
- package/dist/rules/no-forbidden-lifecycle-scripts.js +1 -1
- package/dist/rules/no-hardcoded-secrets.d.ts +3 -1
- package/dist/rules/no-hardcoded-secrets.d.ts.map +1 -1
- package/dist/rules/no-http-request-with-manual-auth.d.ts +3 -1
- package/dist/rules/no-http-request-with-manual-auth.d.ts.map +1 -1
- package/dist/rules/no-overrides-field.d.ts +3 -1
- package/dist/rules/no-overrides-field.d.ts.map +1 -1
- package/dist/rules/no-restricted-globals.d.ts +3 -1
- package/dist/rules/no-restricted-globals.d.ts.map +1 -1
- package/dist/rules/no-restricted-imports.d.ts +3 -1
- package/dist/rules/no-restricted-imports.d.ts.map +1 -1
- package/dist/rules/no-runtime-dependencies.d.ts +3 -1
- package/dist/rules/no-runtime-dependencies.d.ts.map +1 -1
- package/dist/rules/no-silent-error-swallowing.d.ts +3 -1
- package/dist/rules/no-silent-error-swallowing.d.ts.map +1 -1
- package/dist/rules/no-template-placeholders.d.ts +3 -1
- package/dist/rules/no-template-placeholders.d.ts.map +1 -1
- package/dist/rules/no-unsafe-connection-type-cast.d.ts +5 -0
- package/dist/rules/no-unsafe-connection-type-cast.d.ts.map +1 -0
- package/dist/rules/no-unsafe-connection-type-cast.js +104 -0
- package/dist/rules/node-class-description-icon-missing.d.ts +3 -1
- package/dist/rules/node-class-description-icon-missing.d.ts.map +1 -1
- package/dist/rules/node-class-description-icon-missing.js +1 -2
- package/dist/rules/node-connection-type-literal.d.ts +3 -1
- package/dist/rules/node-connection-type-literal.d.ts.map +1 -1
- package/dist/rules/node-registration-complete.d.ts +3 -1
- package/dist/rules/node-registration-complete.d.ts.map +1 -1
- package/dist/rules/node-usable-as-tool.d.ts +3 -1
- package/dist/rules/node-usable-as-tool.d.ts.map +1 -1
- package/dist/rules/package-name-convention.d.ts +3 -1
- package/dist/rules/package-name-convention.d.ts.map +1 -1
- package/dist/rules/require-files-array.d.ts +3 -1
- package/dist/rules/require-files-array.d.ts.map +1 -1
- package/dist/rules/require-homepage.d.ts +3 -1
- package/dist/rules/require-homepage.d.ts.map +1 -1
- package/dist/rules/require-node-api-error.d.ts +3 -1
- package/dist/rules/require-node-api-error.d.ts.map +1 -1
- package/dist/rules/require-node-description-fields.d.ts +3 -1
- package/dist/rules/require-node-description-fields.d.ts.map +1 -1
- package/dist/rules/require-node-description-fields.js +1 -2
- package/dist/rules/require-version.d.ts +3 -1
- package/dist/rules/require-version.d.ts.map +1 -1
- package/dist/rules/resource-operation-pattern.d.ts +3 -1
- package/dist/rules/resource-operation-pattern.d.ts.map +1 -1
- package/dist/rules/resource-operation-pattern.js +3 -4
- package/dist/rules/trigger-node-conventions.d.ts +3 -1
- package/dist/rules/trigger-node-conventions.d.ts.map +1 -1
- package/dist/rules/valid-author.d.ts +3 -1
- package/dist/rules/valid-author.d.ts.map +1 -1
- package/dist/rules/valid-author.js +1 -1
- package/dist/rules/valid-credential-references.d.ts +3 -1
- package/dist/rules/valid-credential-references.d.ts.map +1 -1
- package/dist/rules/valid-credential-references.js +4 -6
- package/dist/rules/valid-description.d.ts +3 -1
- package/dist/rules/valid-description.d.ts.map +1 -1
- package/dist/rules/valid-peer-dependencies.d.ts +3 -1
- package/dist/rules/valid-peer-dependencies.d.ts.map +1 -1
- package/dist/rules/webhook-lifecycle-complete.d.ts +3 -1
- package/dist/rules/webhook-lifecycle-complete.d.ts.map +1 -1
- package/dist/utils/ast-utils.d.ts +6 -0
- package/dist/utils/ast-utils.d.ts.map +1 -1
- package/dist/utils/ast-utils.js +50 -0
- package/dist/utils/file-utils.d.ts.map +1 -1
- package/dist/utils/file-utils.js +1 -2
- package/dist/utils/rule-creator.d.ts +3 -1
- package/dist/utils/rule-creator.d.ts.map +1 -1
- package/docs/rules/icon-prefer-themed-variants.md +2 -2
- package/docs/rules/icon-validation.md +11 -11
- package/docs/rules/no-unsafe-connection-type-cast.md +58 -0
- package/eslint.config.mjs +2 -3
- package/package.json +8 -8
- package/src/plugin.ts +2 -0
- package/src/rules/credential-password-field.ts +1 -4
- package/src/rules/credential-unnecessary-password.ts +1 -4
- package/src/rules/icon-validation.test.ts +19 -21
- package/src/rules/icon-validation.ts +5 -20
- package/src/rules/index.ts +2 -0
- package/src/rules/no-credential-reuse.test.ts +54 -0
- package/src/rules/no-credential-reuse.ts +4 -8
- package/src/rules/no-emoji-in-options.test.ts +77 -0
- package/src/rules/no-emoji-in-options.ts +4 -4
- package/src/rules/no-forbidden-lifecycle-scripts.ts +1 -1
- package/src/rules/no-unsafe-connection-type-cast.test.ts +306 -0
- package/src/rules/no-unsafe-connection-type-cast.ts +125 -0
- package/src/rules/node-class-description-icon-missing.ts +1 -4
- package/src/rules/require-node-description-fields.ts +1 -4
- package/src/rules/resource-operation-pattern.ts +3 -6
- package/src/rules/valid-author.ts +1 -1
- package/src/rules/valid-credential-references.test.ts +75 -0
- package/src/rules/valid-credential-references.ts +4 -8
- package/src/utils/ast-utils.ts +67 -0
- package/src/utils/file-utils.ts +3 -6
- package/dist/plugin.js.map +0 -1
- package/dist/rules/ai-node-package-json.js.map +0 -1
- package/dist/rules/cred-class-field-icon-missing.js.map +0 -1
- package/dist/rules/cred-class-oauth2-naming.js.map +0 -1
- package/dist/rules/credential-documentation-url.js.map +0 -1
- package/dist/rules/credential-password-field.js.map +0 -1
- package/dist/rules/credential-test-required.js.map +0 -1
- package/dist/rules/credential-unnecessary-password.js.map +0 -1
- package/dist/rules/icon-prefer-themed-variants.js.map +0 -1
- package/dist/rules/icon-validation.js.map +0 -1
- package/dist/rules/index.js.map +0 -1
- package/dist/rules/missing-paired-item.js.map +0 -1
- package/dist/rules/n8n-object-validation.js.map +0 -1
- package/dist/rules/no-builder-hint-leakage.js.map +0 -1
- package/dist/rules/no-credential-reuse.js.map +0 -1
- package/dist/rules/no-dangerous-functions.js.map +0 -1
- package/dist/rules/no-dead-files.js.map +0 -1
- package/dist/rules/no-deprecated-workflow-functions.js.map +0 -1
- package/dist/rules/no-emoji-in-options.js.map +0 -1
- package/dist/rules/no-forbidden-lifecycle-scripts.js.map +0 -1
- package/dist/rules/no-hardcoded-secrets.js.map +0 -1
- package/dist/rules/no-http-request-with-manual-auth.js.map +0 -1
- package/dist/rules/no-overrides-field.js.map +0 -1
- package/dist/rules/no-restricted-globals.js.map +0 -1
- package/dist/rules/no-restricted-imports.js.map +0 -1
- package/dist/rules/no-runtime-dependencies.js.map +0 -1
- package/dist/rules/no-silent-error-swallowing.js.map +0 -1
- package/dist/rules/no-template-placeholders.js.map +0 -1
- package/dist/rules/node-class-description-icon-missing.js.map +0 -1
- package/dist/rules/node-connection-type-literal.js.map +0 -1
- package/dist/rules/node-registration-complete.js.map +0 -1
- package/dist/rules/node-usable-as-tool.js.map +0 -1
- package/dist/rules/package-name-convention.js.map +0 -1
- package/dist/rules/require-files-array.js.map +0 -1
- package/dist/rules/require-homepage.js.map +0 -1
- package/dist/rules/require-node-api-error.js.map +0 -1
- package/dist/rules/require-node-description-fields.js.map +0 -1
- package/dist/rules/require-version.js.map +0 -1
- package/dist/rules/resource-operation-pattern.js.map +0 -1
- package/dist/rules/trigger-node-conventions.js.map +0 -1
- package/dist/rules/valid-author.js.map +0 -1
- package/dist/rules/valid-credential-references.js.map +0 -1
- package/dist/rules/valid-description.js.map +0 -1
- package/dist/rules/valid-peer-dependencies.js.map +0 -1
- package/dist/rules/webhook-lifecycle-complete.js.map +0 -1
- package/dist/utils/ast-utils.js.map +0 -1
- package/dist/utils/constants.js.map +0 -1
- package/dist/utils/credential-fields.js.map +0 -1
- package/dist/utils/file-utils.js.map +0 -1
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/rule-creator.js.map +0 -1
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { isNodeTypeClass, findClassProperty, findArrayLiteralProperty, extractCredentialNameFromArray, findPackageJson, readPackageJsonCredentials, isFileType, findSimilarStrings, createRule, } from '../utils/index.js';
|
|
1
|
+
import { isNodeTypeClass, findNodeDescriptionObject, findArrayLiteralProperty, extractCredentialNameFromArray, findPackageJson, readPackageJsonCredentials, isFileType, findSimilarStrings, createRule, } from '../utils/index.js';
|
|
3
2
|
export const ValidCredentialReferencesRule = createRule({
|
|
4
3
|
name: 'valid-credential-references',
|
|
5
4
|
meta: {
|
|
@@ -37,12 +36,11 @@ export const ValidCredentialReferencesRule = createRule({
|
|
|
37
36
|
if (!isNodeTypeClass(node)) {
|
|
38
37
|
return;
|
|
39
38
|
}
|
|
40
|
-
const
|
|
41
|
-
if (!
|
|
42
|
-
descriptionProperty.value.type !== TSESTree.AST_NODE_TYPES.ObjectExpression) {
|
|
39
|
+
const description = findNodeDescriptionObject(node);
|
|
40
|
+
if (!description) {
|
|
43
41
|
return;
|
|
44
42
|
}
|
|
45
|
-
const credentialsArray = findArrayLiteralProperty(
|
|
43
|
+
const credentialsArray = findArrayLiteralProperty(description, 'credentials');
|
|
46
44
|
if (!credentialsArray) {
|
|
47
45
|
return;
|
|
48
46
|
}
|
|
@@ -1,2 +1,4 @@
|
|
|
1
|
-
export declare const ValidDescriptionRule: import("@typescript-eslint/utils/ts-eslint").RuleModule<"emptyDescription" | "missingDescription", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener
|
|
1
|
+
export declare const ValidDescriptionRule: import("@typescript-eslint/utils/ts-eslint").RuleModule<"emptyDescription" | "missingDescription", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
2
|
+
name: string;
|
|
3
|
+
};
|
|
2
4
|
//# sourceMappingURL=valid-description.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"valid-description.d.ts","sourceRoot":"","sources":["../../src/rules/valid-description.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,oBAAoB
|
|
1
|
+
{"version":3,"file":"valid-description.d.ts","sourceRoot":"","sources":["../../src/rules/valid-description.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,oBAAoB;;CAmD/B,CAAC"}
|
|
@@ -1,2 +1,4 @@
|
|
|
1
|
-
export declare const ValidPeerDependenciesRule: import("@typescript-eslint/utils/ts-eslint").RuleModule<"forbiddenPeerDependency" | "invalidPeerDependenciesType" | "missingN8nWorkflow" | "missingPeerDependencies" | "pinnedN8nWorkflow", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener
|
|
1
|
+
export declare const ValidPeerDependenciesRule: import("@typescript-eslint/utils/ts-eslint").RuleModule<"forbiddenPeerDependency" | "invalidPeerDependenciesType" | "missingN8nWorkflow" | "missingPeerDependencies" | "pinnedN8nWorkflow", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
2
|
+
name: string;
|
|
3
|
+
};
|
|
2
4
|
//# sourceMappingURL=valid-peer-dependencies.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"valid-peer-dependencies.d.ts","sourceRoot":"","sources":["../../src/rules/valid-peer-dependencies.ts"],"names":[],"mappings":"AAWA,eAAO,MAAM,yBAAyB,
|
|
1
|
+
{"version":3,"file":"valid-peer-dependencies.d.ts","sourceRoot":"","sources":["../../src/rules/valid-peer-dependencies.ts"],"names":[],"mappings":"AAWA,eAAO,MAAM,yBAAyB;;CA0GpC,CAAC"}
|
|
@@ -1,2 +1,4 @@
|
|
|
1
|
-
export declare const WebhookLifecycleCompleteRule: import("@typescript-eslint/utils/ts-eslint").RuleModule<"emptyWebhookMethods" | "missingLifecycleMethod" | "missingWebhookMethods", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener
|
|
1
|
+
export declare const WebhookLifecycleCompleteRule: import("@typescript-eslint/utils/ts-eslint").RuleModule<"emptyWebhookMethods" | "missingLifecycleMethod" | "missingWebhookMethods", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
2
|
+
name: string;
|
|
3
|
+
};
|
|
2
4
|
//# sourceMappingURL=webhook-lifecycle-complete.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhook-lifecycle-complete.d.ts","sourceRoot":"","sources":["../../src/rules/webhook-lifecycle-complete.ts"],"names":[],"mappings":"AA8CA,eAAO,MAAM,4BAA4B,
|
|
1
|
+
{"version":3,"file":"webhook-lifecycle-complete.d.ts","sourceRoot":"","sources":["../../src/rules/webhook-lifecycle-complete.ts"],"names":[],"mappings":"AA8CA,eAAO,MAAM,4BAA4B;;CAkFvC,CAAC"}
|
|
@@ -10,6 +10,12 @@ export declare function isTriggerNodeClass(node: TSESTree.ClassDeclaration): boo
|
|
|
10
10
|
*/
|
|
11
11
|
export declare function isTriggerNode(node: TSESTree.ClassDeclaration, descriptionValue: TSESTree.ObjectExpression): boolean;
|
|
12
12
|
export declare function findClassProperty(node: TSESTree.ClassDeclaration, propertyName: string): TSESTree.PropertyDefinition | null;
|
|
13
|
+
/**
|
|
14
|
+
* Returns the `description` object literal of a node class. Versioned nodes
|
|
15
|
+
* follow the layout in the node-building docs and assign `this.description` in
|
|
16
|
+
* their constructor, so look there when the class property has no initializer.
|
|
17
|
+
*/
|
|
18
|
+
export declare function findNodeDescriptionObject(node: TSESTree.ClassDeclaration): TSESTree.ObjectExpression | null;
|
|
13
19
|
export declare function findObjectProperty(obj: TSESTree.ObjectExpression, propertyName: string): TSESTree.Property | null;
|
|
14
20
|
export declare function getLiteralValue(node: TSESTree.Node | null): string | boolean | number | null;
|
|
15
21
|
export declare function getStringLiteralValue(node: TSESTree.Node | null): string | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ast-utils.d.ts","sourceRoot":"","sources":["../../src/utils/ast-utils.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ast-utils.d.ts","sourceRoot":"","sources":["../../src/utils/ast-utils.ts"],"names":[],"mappings":"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"}
|
package/dist/utils/ast-utils.js
CHANGED
|
@@ -39,6 +39,56 @@ export function findClassProperty(node, propertyName) {
|
|
|
39
39
|
member.key.name === propertyName);
|
|
40
40
|
return property?.type === AST_NODE_TYPES.PropertyDefinition ? property : null;
|
|
41
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Returns the object literal behind an expression, and looks through the type
|
|
44
|
+
* assertions node authors write, e.g. `{ … } as INodeTypeDescription`.
|
|
45
|
+
*/
|
|
46
|
+
function asObjectExpression(node) {
|
|
47
|
+
let current = node;
|
|
48
|
+
while (current?.type === AST_NODE_TYPES.TSAsExpression ||
|
|
49
|
+
current?.type === AST_NODE_TYPES.TSSatisfiesExpression ||
|
|
50
|
+
current?.type === AST_NODE_TYPES.TSTypeAssertion ||
|
|
51
|
+
current?.type === AST_NODE_TYPES.TSNonNullExpression) {
|
|
52
|
+
current = current.expression;
|
|
53
|
+
}
|
|
54
|
+
return current?.type === AST_NODE_TYPES.ObjectExpression ? current : null;
|
|
55
|
+
}
|
|
56
|
+
function findConstructorAssignedDescription(node) {
|
|
57
|
+
const constructor = node.body.body.find((member) => member.type === AST_NODE_TYPES.MethodDefinition && member.kind === 'constructor');
|
|
58
|
+
if (constructor?.type !== AST_NODE_TYPES.MethodDefinition) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
for (const statement of constructor.value.body?.body ?? []) {
|
|
62
|
+
if (statement.type !== AST_NODE_TYPES.ExpressionStatement)
|
|
63
|
+
continue;
|
|
64
|
+
const { expression } = statement;
|
|
65
|
+
if (expression.type === AST_NODE_TYPES.AssignmentExpression &&
|
|
66
|
+
expression.operator === '=' &&
|
|
67
|
+
expression.left.type === AST_NODE_TYPES.MemberExpression &&
|
|
68
|
+
expression.left.object.type === AST_NODE_TYPES.ThisExpression &&
|
|
69
|
+
expression.left.property.type === AST_NODE_TYPES.Identifier &&
|
|
70
|
+
expression.left.property.name === 'description') {
|
|
71
|
+
const description = asObjectExpression(expression.right);
|
|
72
|
+
if (description) {
|
|
73
|
+
return description;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Returns the `description` object literal of a node class. Versioned nodes
|
|
81
|
+
* follow the layout in the node-building docs and assign `this.description` in
|
|
82
|
+
* their constructor, so look there when the class property has no initializer.
|
|
83
|
+
*/
|
|
84
|
+
export function findNodeDescriptionObject(node) {
|
|
85
|
+
const property = findClassProperty(node, 'description');
|
|
86
|
+
const propertyValue = asObjectExpression(property?.value ?? null);
|
|
87
|
+
if (propertyValue) {
|
|
88
|
+
return propertyValue;
|
|
89
|
+
}
|
|
90
|
+
return findConstructorAssignedDescription(node);
|
|
91
|
+
}
|
|
42
92
|
export function findObjectProperty(obj, propertyName) {
|
|
43
93
|
const property = obj.properties.find((prop) => prop.type === AST_NODE_TYPES.Property &&
|
|
44
94
|
prop.key.type === AST_NODE_TYPES.Identifier &&
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-utils.d.ts","sourceRoot":"","sources":["../../src/utils/file-utils.ts"],"names":[],"mappings":"AAgBA;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAShF;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAU3E;AAED,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAahE;AA+BD;;;;;GAKG;AACH,wBAAgB,8BAA8B,CAAC,eAAe,EAAE,MAAM,GAAG,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,CAO1F;AAkBD,wBAAgB,0BAA0B,CAAC,eAAe,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAkB/E;AAED,wBAAgB,6BAA6B,CAAC,kBAAkB,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA4BvF;AAED,wBAAgB,gBAAgB,CAC/B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,GACb;IACF,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;CAChB,CAYA;AAED,wBAAgB,oBAAoB,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,EAAE,CAItE;AAwBD;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,EAAE,CAS3E;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CACrC,eAAe,EAAE,MAAM,EACvB,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,GACpC,MAAM,EAAE,CAYV;AAED,wBAAgB,mCAAmC,CAClD,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,GAChB,OAAO,CAoBT;
|
|
1
|
+
{"version":3,"file":"file-utils.d.ts","sourceRoot":"","sources":["../../src/utils/file-utils.ts"],"names":[],"mappings":"AAgBA;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAShF;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAU3E;AAED,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAahE;AA+BD;;;;;GAKG;AACH,wBAAgB,8BAA8B,CAAC,eAAe,EAAE,MAAM,GAAG,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,CAO1F;AAkBD,wBAAgB,0BAA0B,CAAC,eAAe,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAkB/E;AAED,wBAAgB,6BAA6B,CAAC,kBAAkB,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA4BvF;AAED,wBAAgB,gBAAgB,CAC/B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,GACb;IACF,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;CAChB,CAYA;AAED,wBAAgB,oBAAoB,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,EAAE,CAItE;AAwBD;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,EAAE,CAS3E;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CACrC,eAAe,EAAE,MAAM,EACvB,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,GACpC,MAAM,EAAE,CAYV;AAED,wBAAgB,mCAAmC,CAClD,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,GAChB,OAAO,CAoBT;AA8DD,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAiClF"}
|
package/dist/utils/file-utils.js
CHANGED
|
@@ -229,8 +229,7 @@ function checkCredentialUsageInFile(nodeFile, credentialName) {
|
|
|
229
229
|
enter(node) {
|
|
230
230
|
if (node.type === AST_NODE_TYPES.ClassDeclaration && isNodeTypeClass(node)) {
|
|
231
231
|
const descriptionProperty = findClassProperty(node, 'description');
|
|
232
|
-
if (
|
|
233
|
-
descriptionProperty.value.type !== AST_NODE_TYPES.ObjectExpression) {
|
|
232
|
+
if (descriptionProperty?.value?.type !== AST_NODE_TYPES.ObjectExpression) {
|
|
234
233
|
return;
|
|
235
234
|
}
|
|
236
235
|
const credentialsArray = findArrayLiteralProperty(descriptionProperty.value, 'credentials');
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
|
-
export declare const createRule: <Options extends readonly unknown[], MessageIds extends string>({ meta, name, ...rule }: Readonly<ESLintUtils.RuleWithMetaAndName<Options, MessageIds, unknown>>) => ESLintUtils.RuleModule<MessageIds, Options, unknown, ESLintUtils.RuleListener
|
|
2
|
+
export declare const createRule: <Options extends readonly unknown[], MessageIds extends string>({ meta, name, ...rule }: Readonly<ESLintUtils.RuleWithMetaAndName<Options, MessageIds, unknown>>) => ESLintUtils.RuleModule<MessageIds, Options, unknown, ESLintUtils.RuleListener> & {
|
|
3
|
+
name: string;
|
|
4
|
+
};
|
|
3
5
|
//# sourceMappingURL=rule-creator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rule-creator.d.ts","sourceRoot":"","sources":["../../src/utils/rule-creator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAKvD,eAAO,MAAM,UAAU,
|
|
1
|
+
{"version":3,"file":"rule-creator.d.ts","sourceRoot":"","sources":["../../src/utils/rule-creator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAKvD,eAAO,MAAM,UAAU;;CAA2E,CAAC"}
|
|
@@ -17,8 +17,8 @@ an error: most existing nodes ship a single icon, so this nudges authors toward
|
|
|
17
17
|
themed variants without failing verification.
|
|
18
18
|
|
|
19
19
|
The companion `icon-validation` rule still validates that the referenced files
|
|
20
|
-
exist
|
|
21
|
-
|
|
20
|
+
exist and use the `file:` protocol. Both slots can point to the same file if one
|
|
21
|
+
icon design works on both themes.
|
|
22
22
|
|
|
23
23
|
## Examples
|
|
24
24
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Validate node and credential icon files exist
|
|
1
|
+
# Validate node and credential icon files exist and use the file: protocol (`@n8n/community-nodes/icon-validation`)
|
|
2
2
|
|
|
3
3
|
💼 This rule is enabled in the following configs: ✅ `recommended`, ☑️ `recommendedWithoutN8nCloudSupport`.
|
|
4
4
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
## Rule Details
|
|
10
10
|
|
|
11
|
-
Validates that your node and credential icon files exist and use the correct `file:` protocol.
|
|
11
|
+
Validates that your node and credential icon files exist and use the correct `file:` protocol. The `light` and `dark` slots can point to the same file: one theme-agnostic icon is a valid choice, and you do not have to copy it under a second filename.
|
|
12
12
|
|
|
13
13
|
Both SVG and PNG icons are accepted. SVG is recommended (it scales cleanly and supports theming), but PNG is allowed — see the `icon-prefer-themed-variants` rule for the related light/dark nudge.
|
|
14
14
|
|
|
@@ -27,28 +27,28 @@ export class MyNode implements INodeType {
|
|
|
27
27
|
}
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
+
### ✅ Correct
|
|
31
|
+
|
|
30
32
|
```typescript
|
|
31
33
|
export class MyNode implements INodeType {
|
|
32
34
|
description: INodeTypeDescription = {
|
|
33
35
|
displayName: 'My Node',
|
|
34
36
|
name: 'myNode',
|
|
35
|
-
icon:
|
|
36
|
-
light: 'file:icons/my-icon.svg',
|
|
37
|
-
dark: 'file:icons/my-icon.svg', // Same file for both themes
|
|
38
|
-
},
|
|
37
|
+
icon: 'file:icons/my-service.svg', // Correct format
|
|
39
38
|
// ...
|
|
40
39
|
};
|
|
41
40
|
}
|
|
42
41
|
```
|
|
43
42
|
|
|
44
|
-
### ✅ Correct
|
|
45
|
-
|
|
46
43
|
```typescript
|
|
47
44
|
export class MyNode implements INodeType {
|
|
48
45
|
description: INodeTypeDescription = {
|
|
49
46
|
displayName: 'My Node',
|
|
50
47
|
name: 'myNode',
|
|
51
|
-
icon:
|
|
48
|
+
icon: {
|
|
49
|
+
light: 'file:icons/my-service-light.svg',
|
|
50
|
+
dark: 'file:icons/my-service-dark.svg', // Different files
|
|
51
|
+
},
|
|
52
52
|
// ...
|
|
53
53
|
};
|
|
54
54
|
}
|
|
@@ -60,8 +60,8 @@ export class MyNode implements INodeType {
|
|
|
60
60
|
displayName: 'My Node',
|
|
61
61
|
name: 'myNode',
|
|
62
62
|
icon: {
|
|
63
|
-
light: 'file:icons/my-service
|
|
64
|
-
dark: 'file:icons/my-service
|
|
63
|
+
light: 'file:icons/my-service.svg',
|
|
64
|
+
dark: 'file:icons/my-service.svg', // One file that works on both themes
|
|
65
65
|
},
|
|
66
66
|
// ...
|
|
67
67
|
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Disallow type-erasing casts (`as never`, `as any`, `as unknown`) on node description `inputs`/`outputs` (`@n8n/community-nodes/no-unsafe-connection-type-cast`)
|
|
2
|
+
|
|
3
|
+
💼 This rule is enabled in the following configs: ✅ `recommended`, ☑️ `recommendedWithoutN8nCloudSupport`.
|
|
4
|
+
|
|
5
|
+
💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).
|
|
6
|
+
|
|
7
|
+
<!-- end auto-generated rule header -->
|
|
8
|
+
|
|
9
|
+
## Rule Details
|
|
10
|
+
|
|
11
|
+
A cast such as `inputs: ['main'] as never` compiles, so nothing in CI objects to it. It also switches off the check that makes `inputs` and `outputs` trustworthy. TypeScript stops verifying the value against `INodeTypeDescription`, and the [`node-connection-type-literal`](node-connection-type-literal.md) rule stops seeing the array, because the cast wraps it. A malformed connection declaration then reaches review and build with no automated signal at all. Removing the cast restores both checks.
|
|
12
|
+
|
|
13
|
+
There is no legitimate reason to erase the type here. Dynamic connection lists are already allowed as expression strings, and correct static lists type-check on their own:
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
inputs: `={{ (${configureInputs})($parameter) }}`,
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The rule flags `as never`, `as any` and `as unknown`, including inside a chain like `as unknown as NodeConnectionType[]`, where the intermediate `unknown` is what defeats the checker. Expressions that keep the value structurally checked are left alone: `as const`, `satisfies T` and a plain `as NodeConnectionType[]`.
|
|
20
|
+
|
|
21
|
+
A `satisfies` around a cast does not restore the check, so `['main'] as never satisfies NodeConnectionType[]` is still reported. The suggestion removes the cast and keeps the `satisfies`.
|
|
22
|
+
|
|
23
|
+
Removing the cast is offered as a suggestion rather than an autofix, because it may surface a real type error that was being suppressed. That error is the point: it is the bug the cast was hiding.
|
|
24
|
+
|
|
25
|
+
## Examples
|
|
26
|
+
|
|
27
|
+
### ❌ Incorrect
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import type { INodeType, INodeTypeDescription } from 'n8n-workflow';
|
|
31
|
+
|
|
32
|
+
export class MyNode implements INodeType {
|
|
33
|
+
description: INodeTypeDescription = {
|
|
34
|
+
displayName: 'My Node',
|
|
35
|
+
name: 'myNode',
|
|
36
|
+
inputs: ['main'] as never,
|
|
37
|
+
outputs: ['main'] as never,
|
|
38
|
+
properties: [],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### ✅ Correct
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
import type { INodeType, INodeTypeDescription } from 'n8n-workflow';
|
|
47
|
+
import { NodeConnectionTypes } from 'n8n-workflow';
|
|
48
|
+
|
|
49
|
+
export class MyNode implements INodeType {
|
|
50
|
+
description: INodeTypeDescription = {
|
|
51
|
+
displayName: 'My Node',
|
|
52
|
+
name: 'myNode',
|
|
53
|
+
inputs: [NodeConnectionTypes.Main],
|
|
54
|
+
outputs: [NodeConnectionTypes.Main],
|
|
55
|
+
properties: [],
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
```
|
package/eslint.config.mjs
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { defineConfig } from 'eslint/config';
|
|
2
|
-
import {
|
|
2
|
+
import { backendConfig } from '@n8n/eslint-config/backend';
|
|
3
3
|
import eslintPlugin from 'eslint-plugin-eslint-plugin';
|
|
4
4
|
|
|
5
5
|
export default defineConfig([
|
|
6
|
-
|
|
6
|
+
backendConfig,
|
|
7
7
|
eslintPlugin.configs.recommended,
|
|
8
8
|
{
|
|
9
9
|
files: ['src/**/*.ts'],
|
|
10
10
|
languageOptions: {
|
|
11
11
|
parserOptions: {
|
|
12
|
-
project: './tsconfig.json',
|
|
13
12
|
allowDefaultProject: true,
|
|
14
13
|
},
|
|
15
14
|
},
|
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.33.0",
|
|
5
5
|
"main": "./dist/plugin.js",
|
|
6
6
|
"types": "./dist/plugin.d.ts",
|
|
7
7
|
"exports": {
|
|
@@ -11,23 +11,23 @@
|
|
|
11
11
|
}
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@typescript-eslint/typescript-estree": "
|
|
15
|
-
"@typescript-eslint/utils": "
|
|
14
|
+
"@typescript-eslint/typescript-estree": "8.70.0",
|
|
15
|
+
"@typescript-eslint/utils": "8.70.0",
|
|
16
16
|
"fastest-levenshtein": "1.0.16"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
|
+
"@n8n/typescript-config": "1.12.0",
|
|
20
|
+
"@n8n/vitest-config": "1.22.0",
|
|
19
21
|
"@types/node": "24.10.1",
|
|
20
|
-
"
|
|
22
|
+
"n8n-workflow": "2.40.0",
|
|
23
|
+
"@typescript-eslint/rule-tester": "8.70.0",
|
|
21
24
|
"eslint-doc-generator": "^2.2.2",
|
|
22
25
|
"eslint-plugin-eslint-plugin": "^7.0.0",
|
|
23
26
|
"rimraf": "6.0.1",
|
|
24
27
|
"@typescript/native": "npm:typescript@7.0.2",
|
|
25
28
|
"typescript": "npm:@typescript/typescript6@6.0.2",
|
|
26
29
|
"vite": "^8.0.2",
|
|
27
|
-
"vitest": "^4.1.9"
|
|
28
|
-
"@n8n/typescript-config": "1.11.0",
|
|
29
|
-
"n8n-workflow": "2.38.0",
|
|
30
|
-
"@n8n/vitest-config": "1.21.0"
|
|
30
|
+
"vitest": "^4.1.9"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"eslint": "9.29.0",
|
package/src/plugin.ts
CHANGED
|
@@ -41,6 +41,7 @@ const configs = {
|
|
|
41
41
|
'@n8n/community-nodes/no-runtime-dependencies': 'error',
|
|
42
42
|
'@n8n/community-nodes/no-silent-error-swallowing': 'error',
|
|
43
43
|
'@n8n/community-nodes/no-template-placeholders': 'error',
|
|
44
|
+
'@n8n/community-nodes/no-unsafe-connection-type-cast': 'error',
|
|
44
45
|
'@n8n/community-nodes/icon-validation': 'error',
|
|
45
46
|
'@n8n/community-nodes/icon-prefer-themed-variants': 'warn',
|
|
46
47
|
'@n8n/community-nodes/resource-operation-pattern': 'warn',
|
|
@@ -89,6 +90,7 @@ const configs = {
|
|
|
89
90
|
'@n8n/community-nodes/no-runtime-dependencies': 'error',
|
|
90
91
|
'@n8n/community-nodes/no-silent-error-swallowing': 'error',
|
|
91
92
|
'@n8n/community-nodes/no-template-placeholders': 'error',
|
|
93
|
+
'@n8n/community-nodes/no-unsafe-connection-type-cast': 'error',
|
|
92
94
|
'@n8n/community-nodes/icon-validation': 'error',
|
|
93
95
|
'@n8n/community-nodes/icon-prefer-themed-variants': 'warn',
|
|
94
96
|
'@n8n/community-nodes/credential-documentation-url': 'error',
|
|
@@ -69,10 +69,7 @@ export const CredentialPasswordFieldRule = createRule({
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
const propertiesProperty = findClassProperty(node, 'properties');
|
|
72
|
-
if (
|
|
73
|
-
!propertiesProperty?.value ||
|
|
74
|
-
propertiesProperty.value.type !== TSESTree.AST_NODE_TYPES.ArrayExpression
|
|
75
|
-
) {
|
|
72
|
+
if (propertiesProperty?.value?.type !== TSESTree.AST_NODE_TYPES.ArrayExpression) {
|
|
76
73
|
return;
|
|
77
74
|
}
|
|
78
75
|
|
|
@@ -33,10 +33,7 @@ export const CredentialUnnecessaryPasswordRule = createRule({
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
const propertiesProperty = findClassProperty(node, 'properties');
|
|
36
|
-
if (
|
|
37
|
-
!propertiesProperty?.value ||
|
|
38
|
-
propertiesProperty.value.type !== TSESTree.AST_NODE_TYPES.ArrayExpression
|
|
39
|
-
) {
|
|
36
|
+
if (propertiesProperty?.value?.type !== TSESTree.AST_NODE_TYPES.ArrayExpression) {
|
|
40
37
|
return;
|
|
41
38
|
}
|
|
42
39
|
|
|
@@ -162,6 +162,17 @@ ruleTester.run('icon-validation', IconValidationRule, {
|
|
|
162
162
|
true,
|
|
163
163
|
),
|
|
164
164
|
},
|
|
165
|
+
{
|
|
166
|
+
name: 'node with the same file for light and dark icons',
|
|
167
|
+
filename: nodeFilePath,
|
|
168
|
+
code: createNodeCode(
|
|
169
|
+
{
|
|
170
|
+
light: 'file:icons/SameIcon.svg',
|
|
171
|
+
dark: 'file:icons/SameIcon.svg',
|
|
172
|
+
},
|
|
173
|
+
true,
|
|
174
|
+
),
|
|
175
|
+
},
|
|
165
176
|
{
|
|
166
177
|
name: 'credential with valid string icon',
|
|
167
178
|
filename: credentialFilePath,
|
|
@@ -180,6 +191,14 @@ ruleTester.run('icon-validation', IconValidationRule, {
|
|
|
180
191
|
dark: 'file:icons/ValidIcon.dark.svg',
|
|
181
192
|
}),
|
|
182
193
|
},
|
|
194
|
+
{
|
|
195
|
+
name: 'credential with the same file for light and dark icons',
|
|
196
|
+
filename: credentialFilePath,
|
|
197
|
+
code: createCredentialCode({
|
|
198
|
+
light: 'file:icons/SameIcon.svg',
|
|
199
|
+
dark: 'file:icons/SameIcon.svg',
|
|
200
|
+
}),
|
|
201
|
+
},
|
|
183
202
|
],
|
|
184
203
|
invalid: [
|
|
185
204
|
{
|
|
@@ -205,18 +224,6 @@ ruleTester.run('icon-validation', IconValidationRule, {
|
|
|
205
224
|
code: createNodeCode('file:icons/NonExistent.svg', true),
|
|
206
225
|
errors: [{ messageId: 'iconFileNotFound', data: { iconPath: 'icons/NonExistent.svg' } }],
|
|
207
226
|
},
|
|
208
|
-
{
|
|
209
|
-
name: 'light and dark icons are the same file in description',
|
|
210
|
-
filename: nodeFilePath,
|
|
211
|
-
code: createNodeCode(
|
|
212
|
-
{
|
|
213
|
-
light: 'file:icons/SameIcon.svg',
|
|
214
|
-
dark: 'file:icons/SameIcon.svg',
|
|
215
|
-
},
|
|
216
|
-
true,
|
|
217
|
-
),
|
|
218
|
-
errors: [{ messageId: 'lightDarkSame', data: { iconPath: 'icons/SameIcon.svg' } }],
|
|
219
|
-
},
|
|
220
227
|
{
|
|
221
228
|
name: 'credential missing icon property',
|
|
222
229
|
filename: credentialFilePath,
|
|
@@ -240,15 +247,6 @@ ruleTester.run('icon-validation', IconValidationRule, {
|
|
|
240
247
|
code: createCredentialCode('file:icons/NonExistent.svg'),
|
|
241
248
|
errors: [{ messageId: 'iconFileNotFound', data: { iconPath: 'icons/NonExistent.svg' } }],
|
|
242
249
|
},
|
|
243
|
-
{
|
|
244
|
-
name: 'credential light and dark icons are the same file',
|
|
245
|
-
filename: credentialFilePath,
|
|
246
|
-
code: createCredentialCode({
|
|
247
|
-
light: 'file:icons/SameIcon.svg',
|
|
248
|
-
dark: 'file:icons/SameIcon.svg',
|
|
249
|
-
}),
|
|
250
|
-
errors: [{ messageId: 'lightDarkSame', data: { iconPath: 'icons/SameIcon.svg' } }],
|
|
251
|
-
},
|
|
252
250
|
{
|
|
253
251
|
name: 'node icon file does not exist but similar file exists - should suggest similar file',
|
|
254
252
|
filename: nodeFilePath,
|
|
@@ -16,7 +16,6 @@ import {
|
|
|
16
16
|
|
|
17
17
|
const messages = {
|
|
18
18
|
iconFileNotFound: 'Icon file "{{ iconPath }}" does not exist',
|
|
19
|
-
lightDarkSame: 'Light and dark icons cannot be the same file. Both point to "{{ iconPath }}"',
|
|
20
19
|
invalidIconPath: 'Icon path "{{ iconPath }}" must use file: protocol and be a string',
|
|
21
20
|
missingIcon: 'Node/Credential class must have an icon property defined',
|
|
22
21
|
addPlaceholder: 'Add icon property with placeholder',
|
|
@@ -29,8 +28,7 @@ export const IconValidationRule = createRule({
|
|
|
29
28
|
meta: {
|
|
30
29
|
type: 'problem',
|
|
31
30
|
docs: {
|
|
32
|
-
description:
|
|
33
|
-
'Validate node and credential icon files exist, use the file: protocol, and that light/dark icons are different',
|
|
31
|
+
description: 'Validate node and credential icon files exist and use the file: protocol',
|
|
34
32
|
},
|
|
35
33
|
messages,
|
|
36
34
|
schema: [],
|
|
@@ -114,22 +112,12 @@ export const IconValidationRule = createRule({
|
|
|
114
112
|
const lightProperty = findObjectProperty(iconValue, 'light');
|
|
115
113
|
const darkProperty = findObjectProperty(iconValue, 'dark');
|
|
116
114
|
|
|
117
|
-
|
|
118
|
-
const darkPath = darkProperty ? getStringLiteralValue(darkProperty.value) : null;
|
|
119
|
-
|
|
115
|
+
// A theme-agnostic file can fill both slots, so only each path is checked
|
|
120
116
|
if (lightProperty) {
|
|
121
|
-
validateIcon(
|
|
117
|
+
validateIcon(getStringLiteralValue(lightProperty.value), lightProperty.value);
|
|
122
118
|
}
|
|
123
119
|
if (darkProperty) {
|
|
124
|
-
validateIcon(
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (lightPath && darkPath && lightPath === darkPath && lightProperty) {
|
|
128
|
-
context.report({
|
|
129
|
-
node: lightProperty.value,
|
|
130
|
-
messageId: 'lightDarkSame',
|
|
131
|
-
data: { iconPath: lightPath.replace(/^file:/, '') },
|
|
132
|
-
});
|
|
120
|
+
validateIcon(getStringLiteralValue(darkProperty.value), darkProperty.value);
|
|
133
121
|
}
|
|
134
122
|
}
|
|
135
123
|
};
|
|
@@ -145,10 +133,7 @@ export const IconValidationRule = createRule({
|
|
|
145
133
|
|
|
146
134
|
if (isNodeClass) {
|
|
147
135
|
const descriptionProperty = findClassProperty(node, 'description');
|
|
148
|
-
if (
|
|
149
|
-
!descriptionProperty?.value ||
|
|
150
|
-
descriptionProperty.value.type !== TSESTree.AST_NODE_TYPES.ObjectExpression
|
|
151
|
-
) {
|
|
136
|
+
if (descriptionProperty?.value?.type !== TSESTree.AST_NODE_TYPES.ObjectExpression) {
|
|
152
137
|
context.report({
|
|
153
138
|
node,
|
|
154
139
|
messageId: 'missingIcon',
|
package/src/rules/index.ts
CHANGED
|
@@ -26,6 +26,7 @@ import { NoRestrictedImportsRule } from './no-restricted-imports.js';
|
|
|
26
26
|
import { NoRuntimeDependenciesRule } from './no-runtime-dependencies.js';
|
|
27
27
|
import { NoSilentErrorSwallowingRule } from './no-silent-error-swallowing.js';
|
|
28
28
|
import { NoTemplatePlaceholdersRule } from './no-template-placeholders.js';
|
|
29
|
+
import { NoUnsafeConnectionTypeCastRule } from './no-unsafe-connection-type-cast.js';
|
|
29
30
|
import { NodeClassDescriptionIconMissingRule } from './node-class-description-icon-missing.js';
|
|
30
31
|
import { NodeConnectionTypeLiteralRule } from './node-connection-type-literal.js';
|
|
31
32
|
import { NodeRegistrationCompleteRule } from './node-registration-complete.js';
|
|
@@ -65,6 +66,7 @@ export const rules = {
|
|
|
65
66
|
'no-runtime-dependencies': NoRuntimeDependenciesRule,
|
|
66
67
|
'no-silent-error-swallowing': NoSilentErrorSwallowingRule,
|
|
67
68
|
'no-template-placeholders': NoTemplatePlaceholdersRule,
|
|
69
|
+
'no-unsafe-connection-type-cast': NoUnsafeConnectionTypeCastRule,
|
|
68
70
|
'icon-validation': IconValidationRule,
|
|
69
71
|
'icon-prefer-themed-variants': IconPreferThemedVariantsRule,
|
|
70
72
|
'resource-operation-pattern': ResourceOperationPatternRule,
|
|
@@ -19,6 +19,7 @@ const mockFindPackageJson = vi.mocked(fileUtils.findPackageJson);
|
|
|
19
19
|
const ruleTester = new RuleTester();
|
|
20
20
|
|
|
21
21
|
const nodeFilePath = '/tmp/TestNode.node.ts';
|
|
22
|
+
const versionedNodeFilePath = '/tmp/v1/TestNodeV1.node.ts';
|
|
22
23
|
|
|
23
24
|
function createNodeCode(
|
|
24
25
|
credentials: Array<string | { name: string; required?: boolean }> = [],
|
|
@@ -97,6 +98,36 @@ export class TestNode implements INodeType {
|
|
|
97
98
|
}`;
|
|
98
99
|
}
|
|
99
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Versioned node layout from the node-building docs: each version class
|
|
103
|
+
* implements `INodeType` and assigns `description` in its constructor, merging
|
|
104
|
+
* the `baseDescription` that the `VersionedNodeType` entry file passes in.
|
|
105
|
+
*/
|
|
106
|
+
function createVersionedNodeCode(credentialNameLiteral: string): string {
|
|
107
|
+
return `
|
|
108
|
+
import type { INodeType, INodeTypeBaseDescription, INodeTypeDescription } from 'n8n-workflow';
|
|
109
|
+
|
|
110
|
+
export class TestNodeV1 implements INodeType {
|
|
111
|
+
description: INodeTypeDescription;
|
|
112
|
+
|
|
113
|
+
constructor(baseDescription: INodeTypeBaseDescription) {
|
|
114
|
+
this.description = {
|
|
115
|
+
...baseDescription,
|
|
116
|
+
version: 1,
|
|
117
|
+
inputs: ['main'],
|
|
118
|
+
outputs: ['main'],
|
|
119
|
+
credentials: [
|
|
120
|
+
{
|
|
121
|
+
name: ${credentialNameLiteral},
|
|
122
|
+
required: true,
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
properties: [],
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}`;
|
|
129
|
+
}
|
|
130
|
+
|
|
100
131
|
// Helper function to create non-node class
|
|
101
132
|
function createNonNodeClass(): string {
|
|
102
133
|
return `
|
|
@@ -163,6 +194,29 @@ ruleTester.run('no-credential-reuse', NoCredentialReuseRule, {
|
|
|
163
194
|
},
|
|
164
195
|
],
|
|
165
196
|
invalid: [
|
|
197
|
+
{
|
|
198
|
+
name: 'SECURITY: versioned node assigning description in its constructor uses credential not in package',
|
|
199
|
+
filename: versionedNodeFilePath,
|
|
200
|
+
code: createVersionedNodeCode("'ExternalApi'"),
|
|
201
|
+
errors: [
|
|
202
|
+
{
|
|
203
|
+
messageId: 'credentialNotInPackage',
|
|
204
|
+
data: { credentialName: 'ExternalApi' },
|
|
205
|
+
suggestions: [
|
|
206
|
+
{
|
|
207
|
+
messageId: 'useAvailable',
|
|
208
|
+
data: { suggestedName: 'myApiCredential' },
|
|
209
|
+
output: createVersionedNodeCode('"myApiCredential"'),
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
messageId: 'useAvailable',
|
|
213
|
+
data: { suggestedName: 'anotherApiCredential' },
|
|
214
|
+
output: createVersionedNodeCode('"anotherApiCredential"'),
|
|
215
|
+
},
|
|
216
|
+
],
|
|
217
|
+
},
|
|
218
|
+
],
|
|
219
|
+
},
|
|
166
220
|
{
|
|
167
221
|
name: 'SECURITY: node using credential not in package (object form)',
|
|
168
222
|
filename: nodeFilePath,
|