@n8n/eslint-plugin-community-nodes 0.3.0 → 0.5.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 +2 -2
- package/README.md +60 -0
- package/dist/plugin.d.ts +144 -28
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +28 -25
- package/dist/plugin.js.map +1 -1
- package/dist/rules/credential-documentation-url.d.ts +7 -0
- package/dist/rules/credential-documentation-url.d.ts.map +1 -0
- package/dist/rules/credential-documentation-url.js +100 -0
- package/dist/rules/credential-documentation-url.js.map +1 -0
- package/dist/rules/credential-password-field.d.ts +1 -2
- package/dist/rules/credential-password-field.d.ts.map +1 -1
- package/dist/rules/credential-password-field.js +25 -14
- package/dist/rules/credential-password-field.js.map +1 -1
- package/dist/rules/credential-test-required.d.ts +1 -2
- package/dist/rules/credential-test-required.d.ts.map +1 -1
- package/dist/rules/credential-test-required.js +43 -5
- package/dist/rules/credential-test-required.js.map +1 -1
- package/dist/rules/icon-validation.d.ts +1 -2
- package/dist/rules/icon-validation.d.ts.map +1 -1
- package/dist/rules/icon-validation.js +81 -15
- package/dist/rules/icon-validation.js.map +1 -1
- package/dist/rules/index.d.ts +9 -5
- package/dist/rules/index.d.ts.map +1 -1
- package/dist/rules/index.js +7 -5
- package/dist/rules/index.js.map +1 -1
- package/dist/rules/no-credential-reuse.d.ts +1 -2
- package/dist/rules/no-credential-reuse.d.ts.map +1 -1
- package/dist/rules/no-credential-reuse.js +33 -4
- package/dist/rules/no-credential-reuse.js.map +1 -1
- package/dist/rules/no-deprecated-workflow-functions.d.ts +1 -2
- package/dist/rules/no-deprecated-workflow-functions.d.ts.map +1 -1
- package/dist/rules/no-deprecated-workflow-functions.js +38 -10
- package/dist/rules/no-deprecated-workflow-functions.js.map +1 -1
- package/dist/rules/no-restricted-globals.d.ts +2 -2
- package/dist/rules/no-restricted-globals.d.ts.map +1 -1
- package/dist/rules/no-restricted-globals.js +5 -3
- package/dist/rules/no-restricted-globals.js.map +1 -1
- package/dist/rules/no-restricted-imports.d.ts +1 -2
- package/dist/rules/no-restricted-imports.d.ts.map +1 -1
- package/dist/rules/no-restricted-imports.js +3 -3
- package/dist/rules/no-restricted-imports.js.map +1 -1
- package/dist/rules/node-usable-as-tool.d.ts +1 -2
- package/dist/rules/node-usable-as-tool.d.ts.map +1 -1
- package/dist/rules/node-usable-as-tool.js +6 -5
- package/dist/rules/node-usable-as-tool.js.map +1 -1
- package/dist/rules/package-name-convention.d.ts +1 -2
- package/dist/rules/package-name-convention.d.ts.map +1 -1
- package/dist/rules/package-name-convention.js +38 -2
- package/dist/rules/package-name-convention.js.map +1 -1
- package/dist/rules/resource-operation-pattern.d.ts +1 -2
- package/dist/rules/resource-operation-pattern.d.ts.map +1 -1
- package/dist/rules/resource-operation-pattern.js +9 -7
- package/dist/rules/resource-operation-pattern.js.map +1 -1
- package/dist/utils/ast-utils.d.ts +2 -1
- package/dist/utils/ast-utils.d.ts.map +1 -1
- package/dist/utils/ast-utils.js +37 -19
- package/dist/utils/ast-utils.js.map +1 -1
- package/dist/utils/file-utils.d.ts +14 -0
- package/dist/utils/file-utils.d.ts.map +1 -1
- package/dist/utils/file-utils.js +85 -18
- package/dist/utils/file-utils.js.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/rule-creator.d.ts +3 -0
- package/dist/utils/rule-creator.d.ts.map +1 -0
- package/dist/utils/rule-creator.js +5 -0
- package/dist/utils/rule-creator.js.map +1 -0
- package/docs/rules/credential-documentation-url.md +94 -0
- package/docs/rules/credential-password-field.md +45 -0
- package/docs/rules/credential-test-required.md +58 -0
- package/docs/rules/icon-validation.md +67 -0
- package/docs/rules/no-credential-reuse.md +82 -0
- package/docs/rules/no-deprecated-workflow-functions.md +61 -0
- package/docs/rules/no-restricted-globals.md +44 -0
- package/docs/rules/no-restricted-imports.md +47 -0
- package/docs/rules/node-usable-as-tool.md +43 -0
- package/docs/rules/package-name-convention.md +52 -0
- package/docs/rules/resource-operation-pattern.md +84 -0
- package/eslint.config.mjs +27 -0
- package/package.json +25 -4
- package/src/plugin.ts +30 -26
- package/src/rules/credential-documentation-url.test.ts +306 -0
- package/src/rules/credential-documentation-url.ts +129 -0
- package/src/rules/credential-password-field.test.ts +1 -0
- package/src/rules/credential-password-field.ts +34 -16
- package/src/rules/credential-test-required.test.ts +84 -57
- package/src/rules/credential-test-required.ts +51 -5
- package/src/rules/icon-validation.test.ts +97 -14
- package/src/rules/icon-validation.ts +95 -14
- package/src/rules/index.ts +8 -5
- package/src/rules/no-credential-reuse.test.ts +306 -58
- package/src/rules/no-credential-reuse.ts +43 -3
- package/src/rules/no-deprecated-workflow-functions.test.ts +70 -0
- package/src/rules/no-deprecated-workflow-functions.ts +44 -10
- package/src/rules/no-restricted-globals.test.ts +1 -0
- package/src/rules/no-restricted-globals.ts +6 -3
- package/src/rules/no-restricted-imports.test.ts +1 -0
- package/src/rules/no-restricted-imports.ts +8 -3
- package/src/rules/node-usable-as-tool.test.ts +1 -0
- package/src/rules/node-usable-as-tool.ts +8 -6
- package/src/rules/package-name-convention.test.ts +82 -5
- package/src/rules/package-name-convention.ts +46 -2
- package/src/rules/resource-operation-pattern.test.ts +1 -0
- package/src/rules/resource-operation-pattern.ts +13 -6
- package/src/utils/ast-utils.ts +47 -19
- package/src/utils/file-utils.ts +108 -18
- package/src/utils/index.ts +1 -0
- package/src/utils/rule-creator.ts +6 -0
- package/tsconfig.build.json +4 -0
- package/tsconfig.eslint.json +5 -0
- package/tsconfig.json +1 -2
package/src/utils/file-utils.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import type { TSESTree } from '@typescript-eslint/typescript-estree';
|
|
2
|
+
import { parse, simpleTraverse, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree';
|
|
3
|
+
import { readFileSync, existsSync, readdirSync } from 'node:fs';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import { dirname, parse as parsePath } from 'node:path';
|
|
6
|
+
|
|
4
7
|
import {
|
|
5
8
|
isCredentialTypeClass,
|
|
6
9
|
isNodeTypeClass,
|
|
@@ -8,14 +11,49 @@ import {
|
|
|
8
11
|
getStringLiteralValue,
|
|
9
12
|
findArrayLiteralProperty,
|
|
10
13
|
extractCredentialInfoFromArray,
|
|
14
|
+
findSimilarStrings,
|
|
11
15
|
} from './ast-utils.js';
|
|
12
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Checks if the given childPath is contained within the parentPath. Resolves
|
|
19
|
+
* the paths before comparing them, so that relative paths are also supported.
|
|
20
|
+
*/
|
|
21
|
+
export function isContainedWithin(parentPath: string, childPath: string): boolean {
|
|
22
|
+
parentPath = path.resolve(parentPath);
|
|
23
|
+
childPath = path.resolve(childPath);
|
|
24
|
+
|
|
25
|
+
if (parentPath === childPath) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return childPath.startsWith(parentPath + path.sep);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Joins the given paths to the parentPath, ensuring that the resulting path
|
|
34
|
+
* is still contained within the parentPath. If not, it throws an error to
|
|
35
|
+
* prevent path traversal vulnerabilities.
|
|
36
|
+
*
|
|
37
|
+
* @throws {UnexpectedError} If the resulting path is not contained within the parentPath.
|
|
38
|
+
*/
|
|
39
|
+
export function safeJoinPath(parentPath: string, ...paths: string[]): string {
|
|
40
|
+
const candidate = path.join(parentPath, ...paths);
|
|
41
|
+
|
|
42
|
+
if (!isContainedWithin(parentPath, candidate)) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
`Path traversal detected, refusing to join paths: ${parentPath} and ${JSON.stringify(paths)}`,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return candidate;
|
|
49
|
+
}
|
|
50
|
+
|
|
13
51
|
export function findPackageJson(startPath: string): string | null {
|
|
14
|
-
let currentDir = startPath;
|
|
52
|
+
let currentDir = path.dirname(startPath);
|
|
15
53
|
|
|
16
54
|
while (parsePath(currentDir).dir !== parsePath(currentDir).root) {
|
|
17
|
-
const testPath =
|
|
18
|
-
if (
|
|
55
|
+
const testPath = safeJoinPath(currentDir, 'package.json');
|
|
56
|
+
if (fileExistsWithCaseSync(testPath)) {
|
|
19
57
|
return testPath;
|
|
20
58
|
}
|
|
21
59
|
|
|
@@ -25,10 +63,24 @@ export function findPackageJson(startPath: string): string | null {
|
|
|
25
63
|
return null;
|
|
26
64
|
}
|
|
27
65
|
|
|
28
|
-
|
|
66
|
+
interface PackageJsonN8n {
|
|
67
|
+
credentials?: string[];
|
|
68
|
+
nodes?: string[];
|
|
69
|
+
[key: string]: unknown;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function isValidPackageJson(obj: unknown): obj is { n8n?: PackageJsonN8n } {
|
|
73
|
+
return typeof obj === 'object' && obj !== null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function readPackageJsonN8n(packageJsonPath: string): PackageJsonN8n {
|
|
29
77
|
try {
|
|
30
|
-
const
|
|
31
|
-
|
|
78
|
+
const content = readFileSync(packageJsonPath, 'utf8');
|
|
79
|
+
const parsed: unknown = JSON.parse(content);
|
|
80
|
+
if (isValidPackageJson(parsed)) {
|
|
81
|
+
return parsed.n8n ?? {};
|
|
82
|
+
}
|
|
83
|
+
return {};
|
|
32
84
|
} catch {
|
|
33
85
|
return {};
|
|
34
86
|
}
|
|
@@ -40,7 +92,7 @@ function resolveN8nFilePaths(packageJsonPath: string, filePaths: string[]): stri
|
|
|
40
92
|
|
|
41
93
|
for (const filePath of filePaths) {
|
|
42
94
|
const sourcePath = filePath.replace(/^dist\//, '').replace(/\.js$/, '.ts');
|
|
43
|
-
const fullSourcePath =
|
|
95
|
+
const fullSourcePath = safeJoinPath(packageDir, sourcePath);
|
|
44
96
|
|
|
45
97
|
if (existsSync(fullSourcePath)) {
|
|
46
98
|
resolvedFiles.push(fullSourcePath);
|
|
@@ -52,7 +104,7 @@ function resolveN8nFilePaths(packageJsonPath: string, filePaths: string[]): stri
|
|
|
52
104
|
|
|
53
105
|
export function readPackageJsonCredentials(packageJsonPath: string): Set<string> {
|
|
54
106
|
const n8nConfig = readPackageJsonN8n(packageJsonPath);
|
|
55
|
-
const credentialPaths = n8nConfig.credentials
|
|
107
|
+
const credentialPaths = n8nConfig.credentials ?? [];
|
|
56
108
|
const credentialFiles = resolveN8nFilePaths(packageJsonPath, credentialPaths);
|
|
57
109
|
const credentialNames: string[] = [];
|
|
58
110
|
|
|
@@ -82,7 +134,7 @@ export function extractCredentialNameFromFile(credentialFilePath: string): strin
|
|
|
82
134
|
|
|
83
135
|
simpleTraverse(ast, {
|
|
84
136
|
enter(node: TSESTree.Node) {
|
|
85
|
-
if (node.type ===
|
|
137
|
+
if (node.type === AST_NODE_TYPES.ClassDeclaration && isCredentialTypeClass(node)) {
|
|
86
138
|
const nameProperty = findClassProperty(node, 'name');
|
|
87
139
|
if (nameProperty) {
|
|
88
140
|
const nameValue = getStringLiteralValue(nameProperty.value);
|
|
@@ -112,8 +164,9 @@ export function validateIconPath(
|
|
|
112
164
|
const isFile = iconPath.startsWith('file:');
|
|
113
165
|
const relativePath = iconPath.replace(/^file:/, '');
|
|
114
166
|
const isSvg = relativePath.endsWith('.svg');
|
|
115
|
-
|
|
116
|
-
const
|
|
167
|
+
// Should not use safeJoinPath here because iconPath can be outside of the node class folder
|
|
168
|
+
const fullPath = path.join(baseDir, relativePath);
|
|
169
|
+
const exists = fileExistsWithCaseSync(fullPath);
|
|
117
170
|
|
|
118
171
|
return {
|
|
119
172
|
isValid: isFile && isSvg && exists,
|
|
@@ -125,7 +178,7 @@ export function validateIconPath(
|
|
|
125
178
|
|
|
126
179
|
export function readPackageJsonNodes(packageJsonPath: string): string[] {
|
|
127
180
|
const n8nConfig = readPackageJsonN8n(packageJsonPath);
|
|
128
|
-
const nodePaths = n8nConfig.nodes
|
|
181
|
+
const nodePaths = n8nConfig.nodes ?? [];
|
|
129
182
|
return resolveN8nFilePaths(packageJsonPath, nodePaths);
|
|
130
183
|
}
|
|
131
184
|
|
|
@@ -133,7 +186,7 @@ export function areAllCredentialUsagesTestedByNodes(
|
|
|
133
186
|
credentialName: string,
|
|
134
187
|
packageDir: string,
|
|
135
188
|
): boolean {
|
|
136
|
-
const packageJsonPath =
|
|
189
|
+
const packageJsonPath = safeJoinPath(packageDir, 'package.json');
|
|
137
190
|
if (!existsSync(packageJsonPath)) {
|
|
138
191
|
return false;
|
|
139
192
|
}
|
|
@@ -167,11 +220,11 @@ function checkCredentialUsageInFile(
|
|
|
167
220
|
|
|
168
221
|
simpleTraverse(ast, {
|
|
169
222
|
enter(node: TSESTree.Node) {
|
|
170
|
-
if (node.type ===
|
|
223
|
+
if (node.type === AST_NODE_TYPES.ClassDeclaration && isNodeTypeClass(node)) {
|
|
171
224
|
const descriptionProperty = findClassProperty(node, 'description');
|
|
172
225
|
if (
|
|
173
226
|
!descriptionProperty?.value ||
|
|
174
|
-
descriptionProperty.value.type !==
|
|
227
|
+
descriptionProperty.value.type !== AST_NODE_TYPES.ObjectExpression
|
|
175
228
|
) {
|
|
176
229
|
return;
|
|
177
230
|
}
|
|
@@ -202,3 +255,40 @@ function checkCredentialUsageInFile(
|
|
|
202
255
|
return { hasUsage: false, allTestedBy: true };
|
|
203
256
|
}
|
|
204
257
|
}
|
|
258
|
+
|
|
259
|
+
function fileExistsWithCaseSync(filePath: string): boolean {
|
|
260
|
+
try {
|
|
261
|
+
const dir = path.dirname(filePath);
|
|
262
|
+
const file = path.basename(filePath);
|
|
263
|
+
const files = new Set(readdirSync(dir));
|
|
264
|
+
|
|
265
|
+
return files.has(file);
|
|
266
|
+
} catch {
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export function findSimilarSvgFiles(targetPath: string, baseDir: string): string[] {
|
|
272
|
+
try {
|
|
273
|
+
const targetFileName = path.basename(targetPath, path.extname(targetPath));
|
|
274
|
+
const targetDir = path.dirname(targetPath);
|
|
275
|
+
// Should not use safeJoinPath here because iconPath can be outside of the node class folder
|
|
276
|
+
const searchDir = path.join(baseDir, targetDir);
|
|
277
|
+
|
|
278
|
+
if (!existsSync(searchDir)) {
|
|
279
|
+
return [];
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const files = readdirSync(searchDir);
|
|
283
|
+
const svgFileNames = files
|
|
284
|
+
.filter((file) => file.endsWith('.svg'))
|
|
285
|
+
.map((file) => path.basename(file, '.svg'));
|
|
286
|
+
|
|
287
|
+
const candidateNames = new Set(svgFileNames);
|
|
288
|
+
const similarNames = findSimilarStrings(targetFileName, candidateNames);
|
|
289
|
+
|
|
290
|
+
return similarNames.map((name) => path.join(targetDir, `${name}.svg`));
|
|
291
|
+
} catch {
|
|
292
|
+
return [];
|
|
293
|
+
}
|
|
294
|
+
}
|
package/src/utils/index.ts
CHANGED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
|
+
|
|
3
|
+
const REPO_URL = 'https://github.com/n8n-io/n8n';
|
|
4
|
+
const DOCS_PATH = 'blob/master/packages/@n8n/eslint-plugin-community-nodes/docs/rules';
|
|
5
|
+
|
|
6
|
+
export const createRule = ESLintUtils.RuleCreator((name) => `${REPO_URL}/${DOCS_PATH}/${name}.md`);
|