@n8n/eslint-plugin-community-nodes 0.2.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/.turbo/turbo-build.log +4 -0
- package/LICENSE.md +88 -0
- package/LICENSE_EE.md +27 -0
- package/dist/plugin.d.ts +61 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +51 -0
- package/dist/plugin.js.map +1 -0
- package/dist/rules/credential-password-field.d.ts +3 -0
- package/dist/rules/credential-password-field.d.ts.map +1 -0
- package/dist/rules/credential-password-field.js +97 -0
- package/dist/rules/credential-password-field.js.map +1 -0
- package/dist/rules/credential-test-required.d.ts +3 -0
- package/dist/rules/credential-test-required.d.ts.map +1 -0
- package/dist/rules/credential-test-required.js +79 -0
- package/dist/rules/credential-test-required.js.map +1 -0
- package/dist/rules/icon-validation.d.ts +3 -0
- package/dist/rules/icon-validation.d.ts.map +1 -0
- package/dist/rules/icon-validation.js +131 -0
- package/dist/rules/icon-validation.js.map +1 -0
- package/dist/rules/index.d.ts +13 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +23 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/no-credential-reuse.d.ts +3 -0
- package/dist/rules/no-credential-reuse.d.ts.map +1 -0
- package/dist/rules/no-credential-reuse.js +62 -0
- package/dist/rules/no-credential-reuse.js.map +1 -0
- package/dist/rules/no-deprecated-workflow-functions.d.ts +3 -0
- package/dist/rules/no-deprecated-workflow-functions.d.ts.map +1 -0
- package/dist/rules/no-deprecated-workflow-functions.js +144 -0
- package/dist/rules/no-deprecated-workflow-functions.js.map +1 -0
- package/dist/rules/no-restricted-globals.d.ts +3 -0
- package/dist/rules/no-restricted-globals.d.ts.map +1 -0
- package/dist/rules/no-restricted-globals.js +58 -0
- package/dist/rules/no-restricted-globals.js.map +1 -0
- package/dist/rules/no-restricted-imports.d.ts +3 -0
- package/dist/rules/no-restricted-imports.d.ts.map +1 -0
- package/dist/rules/no-restricted-imports.js +80 -0
- package/dist/rules/no-restricted-imports.js.map +1 -0
- package/dist/rules/node-usable-as-tool.d.ts +3 -0
- package/dist/rules/node-usable-as-tool.d.ts.map +1 -0
- package/dist/rules/node-usable-as-tool.js +57 -0
- package/dist/rules/node-usable-as-tool.js.map +1 -0
- package/dist/rules/package-name-convention.d.ts +3 -0
- package/dist/rules/package-name-convention.d.ts.map +1 -0
- package/dist/rules/package-name-convention.js +52 -0
- package/dist/rules/package-name-convention.js.map +1 -0
- package/dist/rules/resource-operation-pattern.d.ts +3 -0
- package/dist/rules/resource-operation-pattern.d.ts.map +1 -0
- package/dist/rules/resource-operation-pattern.js +77 -0
- package/dist/rules/resource-operation-pattern.js.map +1 -0
- package/dist/utils/ast-utils.d.ts +25 -0
- package/dist/utils/ast-utils.d.ts.map +1 -0
- package/dist/utils/ast-utils.js +117 -0
- package/dist/utils/ast-utils.js.map +1 -0
- package/dist/utils/file-utils.d.ts +12 -0
- package/dist/utils/file-utils.d.ts.map +1 -0
- package/dist/utils/file-utils.js +154 -0
- package/dist/utils/file-utils.js.map +1 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +46 -0
- package/src/plugin.ts +55 -0
- package/src/rules/credential-password-field.test.ts +231 -0
- package/src/rules/credential-password-field.ts +123 -0
- package/src/rules/credential-test-required.test.ts +147 -0
- package/src/rules/credential-test-required.ts +99 -0
- package/src/rules/icon-validation.test.ts +196 -0
- package/src/rules/icon-validation.ts +158 -0
- package/src/rules/index.ts +24 -0
- package/src/rules/no-credential-reuse.test.ts +226 -0
- package/src/rules/no-credential-reuse.ts +81 -0
- package/src/rules/no-deprecated-workflow-functions.test.ts +117 -0
- package/src/rules/no-deprecated-workflow-functions.ts +166 -0
- package/src/rules/no-restricted-globals.test.ts +135 -0
- package/src/rules/no-restricted-globals.ts +71 -0
- package/src/rules/no-restricted-imports.test.ts +181 -0
- package/src/rules/no-restricted-imports.ts +86 -0
- package/src/rules/node-usable-as-tool.test.ts +80 -0
- package/src/rules/node-usable-as-tool.ts +70 -0
- package/src/rules/package-name-convention.test.ts +112 -0
- package/src/rules/package-name-convention.ts +63 -0
- package/src/rules/resource-operation-pattern.test.ts +216 -0
- package/src/rules/resource-operation-pattern.ts +97 -0
- package/src/utils/ast-utils.ts +179 -0
- package/src/utils/file-utils.ts +204 -0
- package/src/utils/index.ts +2 -0
- package/tsconfig.json +11 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/vite.config.ts +4 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { ESLintUtils, TSESTree } from '@typescript-eslint/utils';
|
|
2
|
+
import {
|
|
3
|
+
isNodeTypeClass,
|
|
4
|
+
findClassProperty,
|
|
5
|
+
findObjectProperty,
|
|
6
|
+
getStringLiteralValue,
|
|
7
|
+
isFileType,
|
|
8
|
+
} from '../utils/index.js';
|
|
9
|
+
|
|
10
|
+
export const ResourceOperationPatternRule = ESLintUtils.RuleCreator.withoutDocs({
|
|
11
|
+
meta: {
|
|
12
|
+
type: 'problem',
|
|
13
|
+
docs: {
|
|
14
|
+
description: 'Enforce proper resource/operation pattern for better UX in n8n nodes',
|
|
15
|
+
},
|
|
16
|
+
messages: {
|
|
17
|
+
tooManyOperationsWithoutResources:
|
|
18
|
+
'Node has {{ operationCount }} operations without resources. Use resources to organize operations when there are more than 5 operations.',
|
|
19
|
+
},
|
|
20
|
+
schema: [],
|
|
21
|
+
},
|
|
22
|
+
defaultOptions: [],
|
|
23
|
+
create(context) {
|
|
24
|
+
if (!isFileType(context.filename, '.node.ts')) {
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const analyzeNodeDescription = (descriptionValue: TSESTree.Expression | null): void => {
|
|
29
|
+
if (!descriptionValue || descriptionValue.type !== 'ObjectExpression') {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const propertiesProperty = findObjectProperty(descriptionValue, 'properties');
|
|
34
|
+
if (!propertiesProperty?.value || propertiesProperty.value.type !== 'ArrayExpression') {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const propertiesArray = propertiesProperty.value;
|
|
39
|
+
let hasResources = false;
|
|
40
|
+
let operationCount = 0;
|
|
41
|
+
let operationNode: TSESTree.Node | null = null;
|
|
42
|
+
|
|
43
|
+
for (const property of propertiesArray.elements) {
|
|
44
|
+
if (!property || property.type !== 'ObjectExpression') {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const nameProperty = findObjectProperty(property, 'name');
|
|
49
|
+
const typeProperty = findObjectProperty(property, 'type');
|
|
50
|
+
|
|
51
|
+
const name = nameProperty ? getStringLiteralValue(nameProperty.value) : null;
|
|
52
|
+
const type = typeProperty ? getStringLiteralValue(typeProperty.value) : null;
|
|
53
|
+
|
|
54
|
+
if (!name || !type) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (name === 'resource' && type === 'options') {
|
|
59
|
+
hasResources = true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (name === 'operation' && type === 'options') {
|
|
63
|
+
operationNode = property;
|
|
64
|
+
const optionsProperty = findObjectProperty(property, 'options');
|
|
65
|
+
if (optionsProperty?.value?.type === 'ArrayExpression') {
|
|
66
|
+
operationCount = optionsProperty.value.elements.length;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (operationCount > 5 && !hasResources && operationNode) {
|
|
72
|
+
context.report({
|
|
73
|
+
node: operationNode,
|
|
74
|
+
messageId: 'tooManyOperationsWithoutResources',
|
|
75
|
+
data: {
|
|
76
|
+
operationCount: operationCount.toString(),
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
ClassDeclaration(node) {
|
|
84
|
+
if (!isNodeTypeClass(node)) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const descriptionProperty = findClassProperty(node, 'description');
|
|
89
|
+
if (!descriptionProperty) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
analyzeNodeDescription(descriptionProperty.value);
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
},
|
|
97
|
+
});
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/utils';
|
|
2
|
+
|
|
3
|
+
function implementsInterface(node: TSESTree.ClassDeclaration, interfaceName: string): boolean {
|
|
4
|
+
return (
|
|
5
|
+
node.implements?.some(
|
|
6
|
+
(impl) =>
|
|
7
|
+
impl.type === 'TSClassImplements' &&
|
|
8
|
+
impl.expression.type === 'Identifier' &&
|
|
9
|
+
impl.expression.name === interfaceName,
|
|
10
|
+
) ?? false
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function isNodeTypeClass(node: TSESTree.ClassDeclaration): boolean {
|
|
15
|
+
if (implementsInterface(node, 'INodeType')) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (node.superClass?.type === 'Identifier' && node.superClass.name === 'Node') {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function isCredentialTypeClass(node: TSESTree.ClassDeclaration): boolean {
|
|
27
|
+
return implementsInterface(node, 'ICredentialType');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function findClassProperty(
|
|
31
|
+
node: TSESTree.ClassDeclaration,
|
|
32
|
+
propertyName: string,
|
|
33
|
+
): TSESTree.PropertyDefinition | null {
|
|
34
|
+
const property = node.body.body.find(
|
|
35
|
+
(member) =>
|
|
36
|
+
member.type === 'PropertyDefinition' &&
|
|
37
|
+
member.key?.type === 'Identifier' &&
|
|
38
|
+
member.key.name === propertyName,
|
|
39
|
+
);
|
|
40
|
+
return property?.type === 'PropertyDefinition' ? property : null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function findObjectProperty(
|
|
44
|
+
obj: TSESTree.ObjectExpression,
|
|
45
|
+
propertyName: string,
|
|
46
|
+
): TSESTree.Property | null {
|
|
47
|
+
const property = obj.properties.find(
|
|
48
|
+
(prop) =>
|
|
49
|
+
prop.type === 'Property' && prop.key.type === 'Identifier' && prop.key.name === propertyName,
|
|
50
|
+
);
|
|
51
|
+
return property?.type === 'Property' ? property : null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function getLiteralValue(node: TSESTree.Node | null): string | boolean | number | null {
|
|
55
|
+
if (node?.type === 'Literal') {
|
|
56
|
+
return node.value as string | boolean | number | null;
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function getStringLiteralValue(node: TSESTree.Node | null): string | null {
|
|
62
|
+
const value = getLiteralValue(node);
|
|
63
|
+
return typeof value === 'string' ? value : null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function getModulePath(node: TSESTree.Node | null): string | null {
|
|
67
|
+
const stringValue = getStringLiteralValue(node);
|
|
68
|
+
if (stringValue) {
|
|
69
|
+
return stringValue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (
|
|
73
|
+
node?.type === 'TemplateLiteral' &&
|
|
74
|
+
node.expressions.length === 0 &&
|
|
75
|
+
node.quasis.length === 1
|
|
76
|
+
) {
|
|
77
|
+
return node.quasis[0]?.value.cooked ?? null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function getBooleanLiteralValue(node: TSESTree.Node | null): boolean | null {
|
|
84
|
+
const value = getLiteralValue(node);
|
|
85
|
+
return typeof value === 'boolean' ? value : null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function findArrayLiteralProperty(
|
|
89
|
+
obj: TSESTree.ObjectExpression,
|
|
90
|
+
propertyName: string,
|
|
91
|
+
): TSESTree.ArrayExpression | null {
|
|
92
|
+
const property = findObjectProperty(obj, propertyName);
|
|
93
|
+
if (property?.value.type === 'ArrayExpression') {
|
|
94
|
+
return property.value;
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function hasArrayLiteralValue(
|
|
100
|
+
node: TSESTree.PropertyDefinition,
|
|
101
|
+
searchValue: string,
|
|
102
|
+
): boolean {
|
|
103
|
+
if (node.value?.type !== 'ArrayExpression') return false;
|
|
104
|
+
|
|
105
|
+
return node.value.elements.some(
|
|
106
|
+
(element) =>
|
|
107
|
+
element?.type === 'Literal' &&
|
|
108
|
+
typeof element.value === 'string' &&
|
|
109
|
+
element.value === searchValue,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function getTopLevelObjectInJson(
|
|
114
|
+
node: TSESTree.ObjectExpression,
|
|
115
|
+
): TSESTree.ObjectExpression | null {
|
|
116
|
+
if (node.parent?.type === AST_NODE_TYPES.Property) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
return node;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function isFileType(filename: string, extension: string): boolean {
|
|
123
|
+
return filename.endsWith(extension);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function isDirectRequireCall(node: TSESTree.CallExpression): boolean {
|
|
127
|
+
return (
|
|
128
|
+
node.callee.type === 'Identifier' && node.callee.name === 'require' && node.arguments.length > 0
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function isRequireMemberCall(node: TSESTree.CallExpression): boolean {
|
|
133
|
+
return (
|
|
134
|
+
node.callee.type === 'MemberExpression' &&
|
|
135
|
+
node.callee.object.type === 'Identifier' &&
|
|
136
|
+
node.callee.object.name === 'require' &&
|
|
137
|
+
node.arguments.length > 0
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function extractCredentialInfoFromArray(
|
|
142
|
+
element: TSESTree.ArrayExpression['elements'][number],
|
|
143
|
+
): { name: string; testedBy?: string; node: TSESTree.Node } | null {
|
|
144
|
+
if (!element) return null;
|
|
145
|
+
|
|
146
|
+
const stringValue = getStringLiteralValue(element);
|
|
147
|
+
if (stringValue) {
|
|
148
|
+
return { name: stringValue, node: element };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (element.type === 'ObjectExpression') {
|
|
152
|
+
const nameProperty = findObjectProperty(element, 'name');
|
|
153
|
+
const testedByProperty = findObjectProperty(element, 'testedBy');
|
|
154
|
+
|
|
155
|
+
if (nameProperty) {
|
|
156
|
+
const nameValue = getStringLiteralValue(nameProperty.value);
|
|
157
|
+
const testedByValue = testedByProperty
|
|
158
|
+
? getStringLiteralValue(testedByProperty.value)
|
|
159
|
+
: undefined;
|
|
160
|
+
|
|
161
|
+
if (nameValue) {
|
|
162
|
+
return {
|
|
163
|
+
name: nameValue,
|
|
164
|
+
testedBy: testedByValue || undefined,
|
|
165
|
+
node: nameProperty.value,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function extractCredentialNameFromArray(
|
|
175
|
+
element: TSESTree.ArrayExpression['elements'][number],
|
|
176
|
+
): { name: string; node: TSESTree.Node } | null {
|
|
177
|
+
const info = extractCredentialInfoFromArray(element);
|
|
178
|
+
return info ? { name: info.name, node: info.node } : null;
|
|
179
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
2
|
+
import { join, dirname, parse as parsePath } from 'node:path';
|
|
3
|
+
import { parse, simpleTraverse, TSESTree } from '@typescript-eslint/typescript-estree';
|
|
4
|
+
import {
|
|
5
|
+
isCredentialTypeClass,
|
|
6
|
+
isNodeTypeClass,
|
|
7
|
+
findClassProperty,
|
|
8
|
+
getStringLiteralValue,
|
|
9
|
+
findArrayLiteralProperty,
|
|
10
|
+
extractCredentialInfoFromArray,
|
|
11
|
+
} from './ast-utils.js';
|
|
12
|
+
|
|
13
|
+
export function findPackageJson(startPath: string): string | null {
|
|
14
|
+
let currentDir = startPath;
|
|
15
|
+
|
|
16
|
+
while (parsePath(currentDir).dir !== parsePath(currentDir).root) {
|
|
17
|
+
const testPath = join(currentDir, 'package.json');
|
|
18
|
+
if (existsSync(testPath)) {
|
|
19
|
+
return testPath;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
currentDir = dirname(currentDir);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function readPackageJsonN8n(packageJsonPath: string): any {
|
|
29
|
+
try {
|
|
30
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
31
|
+
return packageJson.n8n || {};
|
|
32
|
+
} catch {
|
|
33
|
+
return {};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function resolveN8nFilePaths(packageJsonPath: string, filePaths: string[]): string[] {
|
|
38
|
+
const packageDir = dirname(packageJsonPath);
|
|
39
|
+
const resolvedFiles: string[] = [];
|
|
40
|
+
|
|
41
|
+
for (const filePath of filePaths) {
|
|
42
|
+
const sourcePath = filePath.replace(/^dist\//, '').replace(/\.js$/, '.ts');
|
|
43
|
+
const fullSourcePath = join(packageDir, sourcePath);
|
|
44
|
+
|
|
45
|
+
if (existsSync(fullSourcePath)) {
|
|
46
|
+
resolvedFiles.push(fullSourcePath);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return resolvedFiles;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function readPackageJsonCredentials(packageJsonPath: string): Set<string> {
|
|
54
|
+
const n8nConfig = readPackageJsonN8n(packageJsonPath);
|
|
55
|
+
const credentialPaths = n8nConfig.credentials || [];
|
|
56
|
+
const credentialFiles = resolveN8nFilePaths(packageJsonPath, credentialPaths);
|
|
57
|
+
const credentialNames: string[] = [];
|
|
58
|
+
|
|
59
|
+
for (const credentialFile of credentialFiles) {
|
|
60
|
+
try {
|
|
61
|
+
const credentialName = extractCredentialNameFromFile(credentialFile);
|
|
62
|
+
if (credentialName) {
|
|
63
|
+
credentialNames.push(credentialName);
|
|
64
|
+
}
|
|
65
|
+
} catch {
|
|
66
|
+
// Silently continue if file can't be parsed
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return new Set(credentialNames);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function extractCredentialNameFromFile(credentialFilePath: string): string | null {
|
|
74
|
+
try {
|
|
75
|
+
const sourceCode = readFileSync(credentialFilePath, 'utf8');
|
|
76
|
+
const ast = parse(sourceCode, {
|
|
77
|
+
jsx: false,
|
|
78
|
+
range: true,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
let credentialName: string | null = null;
|
|
82
|
+
|
|
83
|
+
simpleTraverse(ast, {
|
|
84
|
+
enter(node: TSESTree.Node) {
|
|
85
|
+
if (node.type === 'ClassDeclaration' && isCredentialTypeClass(node)) {
|
|
86
|
+
const nameProperty = findClassProperty(node, 'name');
|
|
87
|
+
if (nameProperty) {
|
|
88
|
+
const nameValue = getStringLiteralValue(nameProperty.value);
|
|
89
|
+
if (nameValue) {
|
|
90
|
+
credentialName = nameValue;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
return credentialName;
|
|
98
|
+
} catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function validateIconPath(
|
|
104
|
+
iconPath: string,
|
|
105
|
+
baseDir: string,
|
|
106
|
+
): {
|
|
107
|
+
isValid: boolean;
|
|
108
|
+
isFile: boolean;
|
|
109
|
+
isSvg: boolean;
|
|
110
|
+
exists: boolean;
|
|
111
|
+
} {
|
|
112
|
+
const isFile = iconPath.startsWith('file:');
|
|
113
|
+
const relativePath = iconPath.replace(/^file:/, '');
|
|
114
|
+
const isSvg = relativePath.endsWith('.svg');
|
|
115
|
+
const fullPath = join(baseDir, relativePath);
|
|
116
|
+
const exists = existsSync(fullPath);
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
isValid: isFile && isSvg && exists,
|
|
120
|
+
isFile,
|
|
121
|
+
isSvg,
|
|
122
|
+
exists,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function readPackageJsonNodes(packageJsonPath: string): string[] {
|
|
127
|
+
const n8nConfig = readPackageJsonN8n(packageJsonPath);
|
|
128
|
+
const nodePaths = n8nConfig.nodes || [];
|
|
129
|
+
return resolveN8nFilePaths(packageJsonPath, nodePaths);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function areAllCredentialUsagesTestedByNodes(
|
|
133
|
+
credentialName: string,
|
|
134
|
+
packageDir: string,
|
|
135
|
+
): boolean {
|
|
136
|
+
const packageJsonPath = join(packageDir, 'package.json');
|
|
137
|
+
if (!existsSync(packageJsonPath)) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const nodeFiles = readPackageJsonNodes(packageJsonPath);
|
|
142
|
+
let hasAnyCredentialUsage = false;
|
|
143
|
+
|
|
144
|
+
for (const nodeFile of nodeFiles) {
|
|
145
|
+
const result = checkCredentialUsageInFile(nodeFile, credentialName);
|
|
146
|
+
if (result.hasUsage) {
|
|
147
|
+
hasAnyCredentialUsage = true;
|
|
148
|
+
if (!result.allTestedBy) {
|
|
149
|
+
return false; // Found usage without testedBy
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return hasAnyCredentialUsage;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function checkCredentialUsageInFile(
|
|
158
|
+
nodeFile: string,
|
|
159
|
+
credentialName: string,
|
|
160
|
+
): { hasUsage: boolean; allTestedBy: boolean } {
|
|
161
|
+
try {
|
|
162
|
+
const sourceCode = readFileSync(nodeFile, 'utf8');
|
|
163
|
+
const ast = parse(sourceCode, { jsx: false, range: true });
|
|
164
|
+
|
|
165
|
+
let hasUsage = false;
|
|
166
|
+
let allTestedBy = true;
|
|
167
|
+
|
|
168
|
+
simpleTraverse(ast, {
|
|
169
|
+
enter(node: TSESTree.Node) {
|
|
170
|
+
if (node.type === 'ClassDeclaration' && isNodeTypeClass(node)) {
|
|
171
|
+
const descriptionProperty = findClassProperty(node, 'description');
|
|
172
|
+
if (
|
|
173
|
+
!descriptionProperty?.value ||
|
|
174
|
+
descriptionProperty.value.type !== 'ObjectExpression'
|
|
175
|
+
) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const credentialsArray = findArrayLiteralProperty(
|
|
180
|
+
descriptionProperty.value,
|
|
181
|
+
'credentials',
|
|
182
|
+
);
|
|
183
|
+
if (!credentialsArray) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
for (const element of credentialsArray.elements) {
|
|
188
|
+
const credentialInfo = extractCredentialInfoFromArray(element);
|
|
189
|
+
if (credentialInfo?.name === credentialName) {
|
|
190
|
+
hasUsage = true;
|
|
191
|
+
if (!credentialInfo.testedBy) {
|
|
192
|
+
allTestedBy = false;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
return { hasUsage, allTestedBy };
|
|
201
|
+
} catch {
|
|
202
|
+
return { hasUsage: false, allTestedBy: true };
|
|
203
|
+
}
|
|
204
|
+
}
|
package/tsconfig.json
ADDED