first commit

This commit is contained in:
2025-10-26 23:10:15 +08:00
commit 8f0345b7be
14961 changed files with 2356381 additions and 0 deletions

View File

@@ -0,0 +1,177 @@
import type { ESLint } from 'eslint';
import { rules } from './rules/index.js';
declare const configs: {
recommended: {
ignores: string[];
plugins: {
'@n8n/community-nodes': {
meta: {
name: string;
version: string;
namespace: string;
};
rules: ESLint.Plugin["rules"];
};
};
rules: {
'@n8n/community-nodes/no-restricted-globals': "error";
'@n8n/community-nodes/no-restricted-imports': "error";
'@n8n/community-nodes/credential-password-field': "error";
'@n8n/community-nodes/no-deprecated-workflow-functions': "error";
'@n8n/community-nodes/node-usable-as-tool': "error";
'@n8n/community-nodes/package-name-convention': "error";
'@n8n/community-nodes/credential-test-required': "error";
'@n8n/community-nodes/no-credential-reuse': "error";
'@n8n/community-nodes/icon-validation': "error";
'@n8n/community-nodes/resource-operation-pattern': "warn";
'@n8n/community-nodes/credential-documentation-url': "error";
};
};
recommendedWithoutN8nCloudSupport: {
ignores: string[];
plugins: {
'@n8n/community-nodes': {
meta: {
name: string;
version: string;
namespace: string;
};
rules: ESLint.Plugin["rules"];
};
};
rules: {
'@n8n/community-nodes/credential-password-field': "error";
'@n8n/community-nodes/no-deprecated-workflow-functions': "error";
'@n8n/community-nodes/node-usable-as-tool': "error";
'@n8n/community-nodes/package-name-convention': "error";
'@n8n/community-nodes/credential-test-required': "error";
'@n8n/community-nodes/no-credential-reuse': "error";
'@n8n/community-nodes/icon-validation': "error";
'@n8n/community-nodes/credential-documentation-url': "error";
'@n8n/community-nodes/resource-operation-pattern': "warn";
};
};
};
declare const pluginWithConfigs: {
configs: {
recommended: {
ignores: string[];
plugins: {
'@n8n/community-nodes': {
meta: {
name: string;
version: string;
namespace: string;
};
rules: ESLint.Plugin["rules"];
};
};
rules: {
'@n8n/community-nodes/no-restricted-globals': "error";
'@n8n/community-nodes/no-restricted-imports': "error";
'@n8n/community-nodes/credential-password-field': "error";
'@n8n/community-nodes/no-deprecated-workflow-functions': "error";
'@n8n/community-nodes/node-usable-as-tool': "error";
'@n8n/community-nodes/package-name-convention': "error";
'@n8n/community-nodes/credential-test-required': "error";
'@n8n/community-nodes/no-credential-reuse': "error";
'@n8n/community-nodes/icon-validation': "error";
'@n8n/community-nodes/resource-operation-pattern': "warn";
'@n8n/community-nodes/credential-documentation-url': "error";
};
};
recommendedWithoutN8nCloudSupport: {
ignores: string[];
plugins: {
'@n8n/community-nodes': {
meta: {
name: string;
version: string;
namespace: string;
};
rules: ESLint.Plugin["rules"];
};
};
rules: {
'@n8n/community-nodes/credential-password-field': "error";
'@n8n/community-nodes/no-deprecated-workflow-functions': "error";
'@n8n/community-nodes/node-usable-as-tool': "error";
'@n8n/community-nodes/package-name-convention': "error";
'@n8n/community-nodes/credential-test-required': "error";
'@n8n/community-nodes/no-credential-reuse': "error";
'@n8n/community-nodes/icon-validation': "error";
'@n8n/community-nodes/credential-documentation-url': "error";
'@n8n/community-nodes/resource-operation-pattern': "warn";
};
};
};
meta: {
name: string;
version: string;
namespace: string;
};
rules: ESLint.Plugin["rules"];
};
declare const n8nCommunityNodesPlugin: {
configs: {
recommended: {
ignores: string[];
plugins: {
'@n8n/community-nodes': {
meta: {
name: string;
version: string;
namespace: string;
};
rules: ESLint.Plugin["rules"];
};
};
rules: {
'@n8n/community-nodes/no-restricted-globals': "error";
'@n8n/community-nodes/no-restricted-imports': "error";
'@n8n/community-nodes/credential-password-field': "error";
'@n8n/community-nodes/no-deprecated-workflow-functions': "error";
'@n8n/community-nodes/node-usable-as-tool': "error";
'@n8n/community-nodes/package-name-convention': "error";
'@n8n/community-nodes/credential-test-required': "error";
'@n8n/community-nodes/no-credential-reuse': "error";
'@n8n/community-nodes/icon-validation': "error";
'@n8n/community-nodes/resource-operation-pattern': "warn";
'@n8n/community-nodes/credential-documentation-url': "error";
};
};
recommendedWithoutN8nCloudSupport: {
ignores: string[];
plugins: {
'@n8n/community-nodes': {
meta: {
name: string;
version: string;
namespace: string;
};
rules: ESLint.Plugin["rules"];
};
};
rules: {
'@n8n/community-nodes/credential-password-field': "error";
'@n8n/community-nodes/no-deprecated-workflow-functions': "error";
'@n8n/community-nodes/node-usable-as-tool': "error";
'@n8n/community-nodes/package-name-convention': "error";
'@n8n/community-nodes/credential-test-required': "error";
'@n8n/community-nodes/no-credential-reuse': "error";
'@n8n/community-nodes/icon-validation': "error";
'@n8n/community-nodes/credential-documentation-url': "error";
'@n8n/community-nodes/resource-operation-pattern': "warn";
};
};
};
meta: {
name: string;
version: string;
namespace: string;
};
rules: ESLint.Plugin["rules"];
};
export default pluginWithConfigs;
export { rules, configs, n8nCommunityNodesPlugin };
//# sourceMappingURL=plugin.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAU,MAAM,QAAQ,CAAC;AAG7C,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAYzC,QAAA,MAAM,OAAO;;;;;;;;;;uBAHI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;uBAAtB,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC;;;;;;;;;;;;;;;CAwCE,CAAC;AAE1C,QAAA,MAAM,iBAAiB;;;;;;;;;;;2BA1CN,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;2BAAtB,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC;;;;;;;;;;;;;;;;;;;;;WAAtB,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC;CA0CiC,CAAC;AAEzE,QAAA,MAAM,uBAAuB;;;;;;;;;;;2BA5CZ,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;2BAAtB,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC;;;;;;;;;;;;;;;;;;;;;WAAtB,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC;CA4CU,CAAC;AAClD,eAAe,iBAAiB,CAAC;AACjC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,uBAAuB,EAAE,CAAC"}

View File

@@ -0,0 +1,54 @@
import pkg from '../package.json' with { type: 'json' };
import { rules } from './rules/index.js';
const plugin = {
meta: {
name: pkg.name,
version: pkg.version,
namespace: '@n8n/community-nodes',
},
// @ts-expect-error Rules type does not match for typescript-eslint and eslint
rules: rules,
};
const configs = {
recommended: {
ignores: ['eslint.config.{js,mjs,ts,mts}'],
plugins: {
'@n8n/community-nodes': plugin,
},
rules: {
'@n8n/community-nodes/no-restricted-globals': 'error',
'@n8n/community-nodes/no-restricted-imports': 'error',
'@n8n/community-nodes/credential-password-field': 'error',
'@n8n/community-nodes/no-deprecated-workflow-functions': 'error',
'@n8n/community-nodes/node-usable-as-tool': 'error',
'@n8n/community-nodes/package-name-convention': 'error',
'@n8n/community-nodes/credential-test-required': 'error',
'@n8n/community-nodes/no-credential-reuse': 'error',
'@n8n/community-nodes/icon-validation': 'error',
'@n8n/community-nodes/resource-operation-pattern': 'warn',
'@n8n/community-nodes/credential-documentation-url': 'error',
},
},
recommendedWithoutN8nCloudSupport: {
ignores: ['eslint.config.{js,mjs,ts,mts}'],
plugins: {
'@n8n/community-nodes': plugin,
},
rules: {
'@n8n/community-nodes/credential-password-field': 'error',
'@n8n/community-nodes/no-deprecated-workflow-functions': 'error',
'@n8n/community-nodes/node-usable-as-tool': 'error',
'@n8n/community-nodes/package-name-convention': 'error',
'@n8n/community-nodes/credential-test-required': 'error',
'@n8n/community-nodes/no-credential-reuse': 'error',
'@n8n/community-nodes/icon-validation': 'error',
'@n8n/community-nodes/credential-documentation-url': 'error',
'@n8n/community-nodes/resource-operation-pattern': 'warn',
},
},
};
const pluginWithConfigs = { ...plugin, configs };
const n8nCommunityNodesPlugin = pluginWithConfigs;
export default pluginWithConfigs;
export { rules, configs, n8nCommunityNodesPlugin };
//# sourceMappingURL=plugin.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAEA,OAAO,GAAG,MAAM,iBAAiB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAC;AACxD,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAEzC,MAAM,MAAM,GAAG;IACd,IAAI,EAAE;QACL,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,SAAS,EAAE,sBAAsB;KACjC;IACD,8EAA8E;IAC9E,KAAK,EAAE,KAA+B;CACd,CAAC;AAE1B,MAAM,OAAO,GAAG;IACf,WAAW,EAAE;QACZ,OAAO,EAAE,CAAC,+BAA+B,CAAC;QAC1C,OAAO,EAAE;YACR,sBAAsB,EAAE,MAAM;SAC9B;QACD,KAAK,EAAE;YACN,4CAA4C,EAAE,OAAO;YACrD,4CAA4C,EAAE,OAAO;YACrD,gDAAgD,EAAE,OAAO;YACzD,uDAAuD,EAAE,OAAO;YAChE,0CAA0C,EAAE,OAAO;YACnD,8CAA8C,EAAE,OAAO;YACvD,+CAA+C,EAAE,OAAO;YACxD,0CAA0C,EAAE,OAAO;YACnD,sCAAsC,EAAE,OAAO;YAC/C,iDAAiD,EAAE,MAAM;YACzD,mDAAmD,EAAE,OAAO;SAC5D;KACD;IACD,iCAAiC,EAAE;QAClC,OAAO,EAAE,CAAC,+BAA+B,CAAC;QAC1C,OAAO,EAAE;YACR,sBAAsB,EAAE,MAAM;SAC9B;QACD,KAAK,EAAE;YACN,gDAAgD,EAAE,OAAO;YACzD,uDAAuD,EAAE,OAAO;YAChE,0CAA0C,EAAE,OAAO;YACnD,8CAA8C,EAAE,OAAO;YACvD,+CAA+C,EAAE,OAAO;YACxD,0CAA0C,EAAE,OAAO;YACnD,sCAAsC,EAAE,OAAO;YAC/C,mDAAmD,EAAE,OAAO;YAC5D,iDAAiD,EAAE,MAAM;SACzD;KACD;CACuC,CAAC;AAE1C,MAAM,iBAAiB,GAAG,EAAE,GAAG,MAAM,EAAE,OAAO,EAA0B,CAAC;AAEzE,MAAM,uBAAuB,GAAG,iBAAiB,CAAC;AAClD,eAAe,iBAAiB,CAAC;AACjC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,uBAAuB,EAAE,CAAC"}

View File

@@ -0,0 +1,7 @@
type RuleOptions = {
allowUrls?: boolean;
allowSlugs?: boolean;
};
export declare const CredentialDocumentationUrlRule: import("@typescript-eslint/utils/ts-eslint").RuleModule<"invalidDocumentationUrl", [RuleOptions], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
export {};
//# sourceMappingURL=credential-documentation-url.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"credential-documentation-url.d.ts","sourceRoot":"","sources":["../../src/rules/credential-documentation-url.ts"],"names":[],"mappings":"AAOA,KAAK,WAAW,GAAG;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AA+CF,eAAO,MAAM,8BAA8B,uKAuEzC,CAAC"}

View File

@@ -0,0 +1,100 @@
import { isCredentialTypeClass, findClassProperty, getStringLiteralValue, createRule, } from '../utils/index.js';
const DEFAULT_OPTIONS = {
allowUrls: true,
allowSlugs: false,
};
function isValidUrl(value) {
try {
new URL(value);
return true;
}
catch {
return false;
}
}
function isValidSlug(value) {
// TODO: Remove this special case once these slugs are updated
if (['google/service-account', 'google/oauth-single-service', 'google/oauth-generic'].includes(value))
return true;
return value.split('/').every((segment) => /^[a-z][a-z0-9]*$/.test(segment));
}
function hasOnlyCaseIssues(value) {
return value.split('/').every((segment) => /^[a-zA-Z][a-zA-Z0-9]*$/.test(segment));
}
function validateDocumentationUrl(value, options) {
return (!!options.allowUrls && isValidUrl(value)) || (!!options.allowSlugs && isValidSlug(value));
}
function getExpectedFormatsMessage(options) {
const formats = [
...(options.allowUrls ? ['a valid URL'] : []),
...(options.allowSlugs ? ['a lowercase alphanumeric slug (can contain slashes)'] : []),
];
if (formats.length === 0)
return 'a valid format (none configured)';
if (formats.length === 1)
return formats[0];
return formats.slice(0, -1).join(', ') + ' or ' + formats[formats.length - 1];
}
export const CredentialDocumentationUrlRule = createRule({
name: 'credential-documentation-url',
meta: {
type: 'problem',
docs: {
description: 'Enforce valid credential documentationUrl format (URL or lowercase alphanumeric slug)',
},
messages: {
invalidDocumentationUrl: "documentationUrl '{{ value }}' must be {{ expectedFormats }}",
},
fixable: 'code',
schema: [
{
type: 'object',
properties: {
allowUrls: {
type: 'boolean',
description: 'Whether to allow valid URLs',
},
allowSlugs: {
type: 'boolean',
description: 'Whether to allow lowercase alphanumeric slugs with slashes',
},
},
additionalProperties: false,
},
],
},
defaultOptions: [DEFAULT_OPTIONS],
create(context, [options = {}]) {
const mergedOptions = { ...DEFAULT_OPTIONS, ...options };
return {
ClassDeclaration(node) {
if (!isCredentialTypeClass(node)) {
return;
}
const documentationUrlProperty = findClassProperty(node, 'documentationUrl');
if (!documentationUrlProperty?.value) {
return;
}
const documentationUrl = getStringLiteralValue(documentationUrlProperty.value);
if (documentationUrl === null) {
return;
}
if (!validateDocumentationUrl(documentationUrl, mergedOptions)) {
const canAutofix = !!mergedOptions.allowSlugs && hasOnlyCaseIssues(documentationUrl);
context.report({
node: documentationUrlProperty.value,
messageId: 'invalidDocumentationUrl',
data: {
value: documentationUrl,
expectedFormats: getExpectedFormatsMessage(mergedOptions),
},
fix: canAutofix
? (fixer) => fixer.replaceText(documentationUrlProperty.value, `'${documentationUrl.toLowerCase()}'`)
: undefined,
});
}
},
};
},
});
//# sourceMappingURL=credential-documentation-url.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"credential-documentation-url.js","sourceRoot":"","sources":["../../src/rules/credential-documentation-url.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,2 @@
export declare const CredentialPasswordFieldRule: import("@typescript-eslint/utils/ts-eslint").RuleModule<"missingPasswordOption", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
//# sourceMappingURL=credential-password-field.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"credential-password-field.d.ts","sourceRoot":"","sources":["../../src/rules/credential-password-field.ts"],"names":[],"mappings":"AAoFA,eAAO,MAAM,2BAA2B,0JAwDtC,CAAC"}

View File

@@ -0,0 +1,108 @@
import { TSESTree } from '@typescript-eslint/types';
import { isCredentialTypeClass, findClassProperty, findObjectProperty, getStringLiteralValue, getBooleanLiteralValue, createRule, } from '../utils/index.js';
const SENSITIVE_PATTERNS = [
'password',
'secret',
'token',
'cert',
'passphrase',
'apikey',
'secretkey',
'privatekey',
'authkey',
];
const NON_SENSITIVE_PATTERNS = ['url', 'pub', 'id'];
function isSensitiveFieldName(name) {
const lowerName = name.toLowerCase();
if (NON_SENSITIVE_PATTERNS.some((pattern) => lowerName.includes(pattern))) {
return false;
}
return SENSITIVE_PATTERNS.some((pattern) => lowerName.includes(pattern));
}
function hasPasswordTypeOption(element) {
const typeOptionsProperty = findObjectProperty(element, 'typeOptions');
if (typeOptionsProperty?.value.type !== TSESTree.AST_NODE_TYPES.ObjectExpression) {
return false;
}
const passwordProperty = findObjectProperty(typeOptionsProperty.value, 'password');
const passwordValue = passwordProperty ? getBooleanLiteralValue(passwordProperty.value) : null;
return passwordValue === true;
}
function createPasswordFix(element, typeOptionsProperty) {
return (fixer) => {
if (typeOptionsProperty?.value.type === TSESTree.AST_NODE_TYPES.ObjectExpression) {
const passwordProperty = findObjectProperty(typeOptionsProperty.value, 'password');
if (passwordProperty) {
return fixer.replaceText(passwordProperty.value, 'true');
}
const objectValue = typeOptionsProperty.value;
if (objectValue.properties.length > 0) {
const lastProperty = objectValue.properties[objectValue.properties.length - 1];
if (lastProperty) {
return fixer.insertTextAfter(lastProperty, ', password: true');
}
}
else {
const range = objectValue.range;
if (range) {
const openBrace = range[0] + 1;
return fixer.insertTextAfterRange([openBrace, openBrace], ' password: true ');
}
}
}
const lastProperty = element.properties[element.properties.length - 1];
if (lastProperty) {
return fixer.insertTextAfter(lastProperty, ',\n\t\t\ttypeOptions: { password: true }');
}
return null;
};
}
export const CredentialPasswordFieldRule = createRule({
name: 'credential-password-field',
meta: {
type: 'problem',
docs: {
description: 'Ensure credential fields with sensitive names have typeOptions.password = true',
},
messages: {
missingPasswordOption: "Field '{{ fieldName }}' appears to be a sensitive field but is missing 'typeOptions: { password: true }'",
},
fixable: 'code',
schema: [],
},
defaultOptions: [],
create(context) {
return {
ClassDeclaration(node) {
if (!isCredentialTypeClass(node)) {
return;
}
const propertiesProperty = findClassProperty(node, 'properties');
if (!propertiesProperty?.value ||
propertiesProperty.value.type !== TSESTree.AST_NODE_TYPES.ArrayExpression) {
return;
}
for (const element of propertiesProperty.value.elements) {
if (element?.type !== TSESTree.AST_NODE_TYPES.ObjectExpression) {
continue;
}
const nameProperty = findObjectProperty(element, 'name');
const fieldName = nameProperty ? getStringLiteralValue(nameProperty.value) : null;
if (!fieldName || !isSensitiveFieldName(fieldName)) {
continue;
}
if (!hasPasswordTypeOption(element)) {
const typeOptionsProperty = findObjectProperty(element, 'typeOptions');
context.report({
node: element,
messageId: 'missingPasswordOption',
data: { fieldName },
fix: createPasswordFix(element, typeOptionsProperty),
});
}
}
},
};
},
});
//# sourceMappingURL=credential-password-field.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"credential-password-field.js","sourceRoot":"","sources":["../../src/rules/credential-password-field.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,2 @@
export declare const CredentialTestRequiredRule: import("@typescript-eslint/utils/ts-eslint").RuleModule<"addTemplate" | "missingCredentialTest", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
//# sourceMappingURL=credential-test-required.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"credential-test-required.d.ts","sourceRoot":"","sources":["../../src/rules/credential-test-required.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,0BAA0B,0KAyHrC,CAAC"}

View File

@@ -0,0 +1,117 @@
import { dirname } from 'node:path';
import { isCredentialTypeClass, findClassProperty, hasArrayLiteralValue, isFileType, getStringLiteralValue, findPackageJson, areAllCredentialUsagesTestedByNodes, createRule, } from '../utils/index.js';
export const CredentialTestRequiredRule = createRule({
name: 'credential-test-required',
meta: {
type: 'problem',
docs: {
description: 'Ensure credentials have a credential test',
},
messages: {
addTemplate: 'Add basic credential test template',
missingCredentialTest: 'Credential class "{{ className }}" must have a test property or be tested by a node via testedBy',
},
schema: [],
hasSuggestions: true,
},
defaultOptions: [],
create(context) {
if (!isFileType(context.filename, '.credentials.ts')) {
return {};
}
let packageDir = null;
const getPackageDir = () => {
if (packageDir !== null) {
return packageDir;
}
const packageJsonPath = findPackageJson(context.filename);
if (!packageJsonPath) {
packageDir = '';
return packageDir;
}
packageDir = dirname(packageJsonPath);
return packageDir;
};
return {
ClassDeclaration(node) {
if (!isCredentialTypeClass(node)) {
return;
}
const extendsProperty = findClassProperty(node, 'extends');
if (extendsProperty && hasArrayLiteralValue(extendsProperty, 'oAuth2Api')) {
return;
}
const testProperty = findClassProperty(node, 'test');
if (testProperty) {
return;
}
const nameProperty = findClassProperty(node, 'name');
if (!nameProperty) {
return;
}
const credentialName = getStringLiteralValue(nameProperty.value);
if (!credentialName) {
return;
}
const pkgDir = getPackageDir();
if (!pkgDir) {
const suggestions = [];
const testProperty = createCredentialTestTemplate();
suggestions.push({
messageId: 'addTemplate',
fix(fixer) {
const classBody = node.body.body;
const lastProperty = classBody[classBody.length - 1];
if (lastProperty) {
return fixer.insertTextAfter(lastProperty, `\n\n${testProperty}`);
}
return null;
},
});
context.report({
node,
messageId: 'missingCredentialTest',
data: {
className: node.id?.name ?? 'Unknown',
},
suggest: suggestions,
});
return;
}
const allUsagesTestedByNodes = areAllCredentialUsagesTestedByNodes(credentialName, pkgDir);
if (!allUsagesTestedByNodes) {
const suggestions = [];
const testProperty = createCredentialTestTemplate();
suggestions.push({
messageId: 'addTemplate',
fix(fixer) {
const classBody = node.body.body;
const lastProperty = classBody[classBody.length - 1];
if (lastProperty) {
return fixer.insertTextAfter(lastProperty, `\n\n${testProperty}`);
}
return null;
},
});
context.report({
node,
messageId: 'missingCredentialTest',
data: {
className: node.id?.name ?? 'Unknown',
},
suggest: suggestions,
});
}
},
};
},
});
function createCredentialTestTemplate() {
return `\ttest: ICredentialTestRequest = {
\t\trequest: {
\t\t\tmethod: 'GET',
\t\t\turl: '={{$credentials.server}}/test', // Replace with actual endpoint
\t\t},
\t};`;
}
//# sourceMappingURL=credential-test-required.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"credential-test-required.js","sourceRoot":"","sources":["../../src/rules/credential-test-required.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,2 @@
export declare const IconValidationRule: import("@typescript-eslint/utils/ts-eslint").RuleModule<"iconFileNotFound" | "iconNotSvg" | "lightDarkSame" | "invalidIconPath" | "missingIcon" | "addPlaceholder" | "addFileProtocol" | "changeExtension" | "similarIcon", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
//# sourceMappingURL=icon-validation.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"icon-validation.d.ts","sourceRoot":"","sources":["../../src/rules/icon-validation.ts"],"names":[],"mappings":"AA4BA,eAAO,MAAM,kBAAkB,qSAkN7B,CAAC"}

View File

@@ -0,0 +1,197 @@
import { TSESTree } from '@typescript-eslint/utils';
import { dirname } from 'node:path';
import { isNodeTypeClass, isCredentialTypeClass, findClassProperty, findObjectProperty, getStringLiteralValue, validateIconPath, findSimilarSvgFiles, isFileType, createRule, } from '../utils/index.js';
const messages = {
iconFileNotFound: 'Icon file "{{ iconPath }}" does not exist',
iconNotSvg: 'Icon file "{{ iconPath }}" must be an SVG file (end with .svg)',
lightDarkSame: 'Light and dark icons cannot be the same file. Both point to "{{ iconPath }}"',
invalidIconPath: 'Icon path "{{ iconPath }}" must use file: protocol and be a string',
missingIcon: 'Node/Credential class must have an icon property defined',
addPlaceholder: 'Add icon property with placeholder',
addFileProtocol: "Add 'file:' protocol to icon path",
changeExtension: "Change icon extension to '.svg'",
similarIcon: "Use existing icon '{{ suggestedName }}'",
};
export const IconValidationRule = createRule({
name: 'icon-validation',
meta: {
type: 'problem',
docs: {
description: 'Validate node and credential icon files exist, are SVG format, and light/dark icons are different',
},
messages,
schema: [],
hasSuggestions: true,
},
defaultOptions: [],
create(context) {
if (!isFileType(context.filename, '.node.ts') &&
!isFileType(context.filename, '.credentials.ts')) {
return {};
}
const validateIcon = (iconPath, node) => {
if (!iconPath) {
context.report({
node,
messageId: 'invalidIconPath',
data: { iconPath: iconPath ?? '' },
});
return false;
}
const currentDir = dirname(context.filename);
const validation = validateIconPath(iconPath, currentDir);
if (!validation.isFile) {
const suggestions = [];
if (!iconPath.startsWith('file:')) {
suggestions.push({
messageId: 'addFileProtocol',
fix(fixer) {
return fixer.replaceText(node, `"file:${iconPath}"`);
},
});
}
context.report({
node,
messageId: 'invalidIconPath',
data: { iconPath },
suggest: suggestions,
});
return false;
}
if (!validation.isSvg) {
const relativePath = iconPath.replace(/^file:/, '');
const suggestions = [];
const pathWithoutExt = relativePath.replace(/\.[^/.]+$/, '');
const svgPath = `${pathWithoutExt}.svg`;
suggestions.push({
messageId: 'changeExtension',
fix(fixer) {
return fixer.replaceText(node, `"file:${svgPath}"`);
},
});
context.report({
node,
messageId: 'iconNotSvg',
data: { iconPath: relativePath },
suggest: suggestions,
});
return false;
}
if (!validation.exists) {
const relativePath = iconPath.replace(/^file:/, '');
const suggestions = [];
// Find similar SVG files in the same directory
const similarFiles = findSimilarSvgFiles(relativePath, currentDir);
for (const similarFile of similarFiles) {
suggestions.push({
messageId: 'similarIcon',
data: { suggestedName: similarFile },
fix(fixer) {
return fixer.replaceText(node, `"file:${similarFile}"`);
},
});
}
context.report({
node,
messageId: 'iconFileNotFound',
data: { iconPath: relativePath },
suggest: suggestions,
});
return false;
}
return true;
};
const validateIconValue = (iconValue) => {
if (iconValue.type === TSESTree.AST_NODE_TYPES.Literal) {
const iconPath = getStringLiteralValue(iconValue);
validateIcon(iconPath, iconValue);
}
else if (iconValue.type === TSESTree.AST_NODE_TYPES.ObjectExpression) {
const lightProperty = findObjectProperty(iconValue, 'light');
const darkProperty = findObjectProperty(iconValue, 'dark');
const lightPath = lightProperty ? getStringLiteralValue(lightProperty.value) : null;
const darkPath = darkProperty ? getStringLiteralValue(darkProperty.value) : null;
if (lightProperty) {
validateIcon(lightPath, lightProperty.value);
}
if (darkProperty) {
validateIcon(darkPath, darkProperty.value);
}
if (lightPath && darkPath && lightPath === darkPath && lightProperty) {
context.report({
node: lightProperty.value,
messageId: 'lightDarkSame',
data: { iconPath: lightPath.replace(/^file:/, '') },
});
}
}
};
return {
ClassDeclaration(node) {
const isNodeClass = isNodeTypeClass(node);
const isCredentialClass = isCredentialTypeClass(node);
if (!isNodeClass && !isCredentialClass) {
return;
}
if (isNodeClass) {
const descriptionProperty = findClassProperty(node, 'description');
if (!descriptionProperty?.value ||
descriptionProperty.value.type !== TSESTree.AST_NODE_TYPES.ObjectExpression) {
context.report({
node,
messageId: 'missingIcon',
});
return;
}
const descriptionValue = descriptionProperty.value;
const iconProperty = findObjectProperty(descriptionValue, 'icon');
if (!iconProperty) {
const suggestions = [];
suggestions.push({
messageId: 'addPlaceholder',
fix(fixer) {
const lastProperty = descriptionValue.properties[descriptionValue.properties.length - 1];
if (lastProperty) {
return fixer.insertTextAfter(lastProperty, ',\n\t\ticon: "file:./icon.svg"');
}
return null;
},
});
context.report({
node,
messageId: 'missingIcon',
suggest: suggestions,
});
return;
}
validateIconValue(iconProperty.value);
}
else if (isCredentialClass) {
const iconProperty = findClassProperty(node, 'icon');
if (!iconProperty?.value) {
const suggestions = [];
suggestions.push({
messageId: 'addPlaceholder',
fix(fixer) {
const classBody = node.body.body;
const lastProperty = classBody[classBody.length - 1];
if (lastProperty) {
return fixer.insertTextAfter(lastProperty, '\n\n\ticon = "file:./icon.svg";');
}
return null;
},
});
context.report({
node,
messageId: 'missingIcon',
suggest: suggestions,
});
return;
}
validateIconValue(iconProperty.value);
}
},
};
},
});
//# sourceMappingURL=icon-validation.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,17 @@
export declare const rules: {
'no-restricted-globals': import("@typescript-eslint/utils/ts-eslint").RuleModule<"restrictedGlobal", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
'no-restricted-imports': import("@typescript-eslint/utils/ts-eslint").RuleModule<"restrictedImport" | "restrictedRequire" | "restrictedDynamicImport", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
'credential-password-field': import("@typescript-eslint/utils/ts-eslint").RuleModule<"missingPasswordOption", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
'no-deprecated-workflow-functions': import("@typescript-eslint/utils/ts-eslint").RuleModule<"deprecatedRequestFunction" | "deprecatedFunction" | "deprecatedType" | "deprecatedWithoutReplacement" | "suggestReplaceFunction" | "suggestReplaceType", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
'node-usable-as-tool': import("@typescript-eslint/utils/ts-eslint").RuleModule<"missingUsableAsTool", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
'package-name-convention': import("@typescript-eslint/utils/ts-eslint").RuleModule<"renameTo" | "invalidPackageName", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
'credential-test-required': import("@typescript-eslint/utils/ts-eslint").RuleModule<"addTemplate" | "missingCredentialTest", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
'no-credential-reuse': import("@typescript-eslint/utils/ts-eslint").RuleModule<"didYouMean" | "useAvailable" | "credentialNotInPackage", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
'icon-validation': import("@typescript-eslint/utils/ts-eslint").RuleModule<"iconFileNotFound" | "iconNotSvg" | "lightDarkSame" | "invalidIconPath" | "missingIcon" | "addPlaceholder" | "addFileProtocol" | "changeExtension" | "similarIcon", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
'resource-operation-pattern': import("@typescript-eslint/utils/ts-eslint").RuleModule<"tooManyOperationsWithoutResources", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
'credential-documentation-url': import("@typescript-eslint/utils/ts-eslint").RuleModule<"invalidDocumentationUrl", [{
allowUrls?: boolean;
allowSlugs?: boolean;
}], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
};
//# sourceMappingURL=index.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;CAYuB,CAAC"}

View File

@@ -0,0 +1,25 @@
import { CredentialDocumentationUrlRule } from './credential-documentation-url.js';
import { CredentialPasswordFieldRule } from './credential-password-field.js';
import { CredentialTestRequiredRule } from './credential-test-required.js';
import { IconValidationRule } from './icon-validation.js';
import { NoCredentialReuseRule } from './no-credential-reuse.js';
import { NoDeprecatedWorkflowFunctionsRule } from './no-deprecated-workflow-functions.js';
import { NoRestrictedGlobalsRule } from './no-restricted-globals.js';
import { NoRestrictedImportsRule } from './no-restricted-imports.js';
import { NodeUsableAsToolRule } from './node-usable-as-tool.js';
import { PackageNameConventionRule } from './package-name-convention.js';
import { ResourceOperationPatternRule } from './resource-operation-pattern.js';
export const rules = {
'no-restricted-globals': NoRestrictedGlobalsRule,
'no-restricted-imports': NoRestrictedImportsRule,
'credential-password-field': CredentialPasswordFieldRule,
'no-deprecated-workflow-functions': NoDeprecatedWorkflowFunctionsRule,
'node-usable-as-tool': NodeUsableAsToolRule,
'package-name-convention': PackageNameConventionRule,
'credential-test-required': CredentialTestRequiredRule,
'no-credential-reuse': NoCredentialReuseRule,
'icon-validation': IconValidationRule,
'resource-operation-pattern': ResourceOperationPatternRule,
'credential-documentation-url': CredentialDocumentationUrlRule,
};
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,8BAA8B,EAAE,MAAM,mCAAmC,CAAC;AACnF,OAAO,EAAE,2BAA2B,EAAE,MAAM,gCAAgC,CAAC;AAC7E,OAAO,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAC3E,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,iCAAiC,EAAE,MAAM,uCAAuC,CAAC;AAC1F,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,yBAAyB,EAAE,MAAM,8BAA8B,CAAC;AACzE,OAAO,EAAE,4BAA4B,EAAE,MAAM,iCAAiC,CAAC;AAE/E,MAAM,CAAC,MAAM,KAAK,GAAG;IACpB,uBAAuB,EAAE,uBAAuB;IAChD,uBAAuB,EAAE,uBAAuB;IAChD,2BAA2B,EAAE,2BAA2B;IACxD,kCAAkC,EAAE,iCAAiC;IACrE,qBAAqB,EAAE,oBAAoB;IAC3C,yBAAyB,EAAE,yBAAyB;IACpD,0BAA0B,EAAE,0BAA0B;IACtD,qBAAqB,EAAE,qBAAqB;IAC5C,iBAAiB,EAAE,kBAAkB;IACrC,4BAA4B,EAAE,4BAA4B;IAC1D,8BAA8B,EAAE,8BAA8B;CACtB,CAAC"}

View File

@@ -0,0 +1,2 @@
export declare const NoCredentialReuseRule: import("@typescript-eslint/utils/ts-eslint").RuleModule<"didYouMean" | "useAvailable" | "credentialNotInPackage", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
//# sourceMappingURL=no-credential-reuse.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"no-credential-reuse.d.ts","sourceRoot":"","sources":["../../src/rules/no-credential-reuse.ts"],"names":[],"mappings":"AAeA,eAAO,MAAM,qBAAqB,2LAyGhC,CAAC"}

View File

@@ -0,0 +1,91 @@
import { TSESTree } from '@typescript-eslint/types';
import { isNodeTypeClass, findClassProperty, findArrayLiteralProperty, extractCredentialNameFromArray, findPackageJson, readPackageJsonCredentials, isFileType, findSimilarStrings, createRule, } from '../utils/index.js';
export const NoCredentialReuseRule = createRule({
name: 'no-credential-reuse',
meta: {
type: 'problem',
docs: {
description: 'Prevent credential re-use security issues by ensuring nodes only reference credentials from the same package',
},
messages: {
didYouMean: "Did you mean '{{ suggestedName }}'?",
useAvailable: "Use available credential '{{ suggestedName }}'",
credentialNotInPackage: 'SECURITY: Node references credential "{{ credentialName }}" which is not defined in this package. This creates a security risk as it attempts to reuse credentials from other packages. Nodes can only use credentials from the same package as listed in package.json n8n.credentials field.',
},
schema: [],
hasSuggestions: true,
},
defaultOptions: [],
create(context) {
if (!isFileType(context.filename, '.node.ts')) {
return {};
}
let packageCredentials = null;
const loadPackageCredentials = () => {
if (packageCredentials !== null) {
return packageCredentials;
}
const packageJsonPath = findPackageJson(context.filename);
if (!packageJsonPath) {
packageCredentials = new Set();
return packageCredentials;
}
packageCredentials = readPackageJsonCredentials(packageJsonPath);
return packageCredentials;
};
return {
ClassDeclaration(node) {
if (!isNodeTypeClass(node)) {
return;
}
const descriptionProperty = findClassProperty(node, 'description');
if (!descriptionProperty?.value ||
descriptionProperty.value.type !== TSESTree.AST_NODE_TYPES.ObjectExpression) {
return;
}
const credentialsArray = findArrayLiteralProperty(descriptionProperty.value, 'credentials');
if (!credentialsArray) {
return;
}
const allowedCredentials = loadPackageCredentials();
credentialsArray.elements.forEach((element) => {
const credentialInfo = extractCredentialNameFromArray(element);
if (credentialInfo && !allowedCredentials.has(credentialInfo.name)) {
const similarCredentials = findSimilarStrings(credentialInfo.name, allowedCredentials);
const suggestions = [];
for (const similarName of similarCredentials) {
suggestions.push({
messageId: 'didYouMean',
data: { suggestedName: similarName },
fix(fixer) {
return fixer.replaceText(credentialInfo.node, `"${similarName}"`);
},
});
}
if (suggestions.length === 0 && allowedCredentials.size > 0) {
const availableCredentials = Array.from(allowedCredentials).slice(0, 3);
for (const availableName of availableCredentials) {
suggestions.push({
messageId: 'useAvailable',
data: { suggestedName: availableName },
fix(fixer) {
return fixer.replaceText(credentialInfo.node, `"${availableName}"`);
},
});
}
}
context.report({
node: credentialInfo.node,
messageId: 'credentialNotInPackage',
data: {
credentialName: credentialInfo.name,
},
suggest: suggestions,
});
}
});
},
};
},
});
//# sourceMappingURL=no-credential-reuse.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"no-credential-reuse.js","sourceRoot":"","sources":["../../src/rules/no-credential-reuse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAGpD,OAAO,EACN,eAAe,EACf,iBAAiB,EACjB,wBAAwB,EACxB,8BAA8B,EAC9B,eAAe,EACf,0BAA0B,EAC1B,UAAU,EACV,kBAAkB,EAClB,UAAU,GACV,MAAM,mBAAmB,CAAC;AAE3B,MAAM,CAAC,MAAM,qBAAqB,GAAG,UAAU,CAAC;IAC/C,IAAI,EAAE,qBAAqB;IAC3B,IAAI,EAAE;QACL,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACL,WAAW,EACV,8GAA8G;SAC/G;QACD,QAAQ,EAAE;YACT,UAAU,EAAE,qCAAqC;YACjD,YAAY,EAAE,gDAAgD;YAC9D,sBAAsB,EACrB,+RAA+R;SAChS;QACD,MAAM,EAAE,EAAE;QACV,cAAc,EAAE,IAAI;KACpB;IACD,cAAc,EAAE,EAAE;IAClB,MAAM,CAAC,OAAO;QACb,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,QAAQ,EAAE,UAAU,CAAC,EAAE,CAAC;YAC/C,OAAO,EAAE,CAAC;QACX,CAAC;QAED,IAAI,kBAAkB,GAAuB,IAAI,CAAC;QAElD,MAAM,sBAAsB,GAAG,GAAgB,EAAE;YAChD,IAAI,kBAAkB,KAAK,IAAI,EAAE,CAAC;gBACjC,OAAO,kBAAkB,CAAC;YAC3B,CAAC;YAED,MAAM,eAAe,GAAG,eAAe,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;YAC1D,IAAI,CAAC,eAAe,EAAE,CAAC;gBACtB,kBAAkB,GAAG,IAAI,GAAG,EAAE,CAAC;gBAC/B,OAAO,kBAAkB,CAAC;YAC3B,CAAC;YAED,kBAAkB,GAAG,0BAA0B,CAAC,eAAe,CAAC,CAAC;YACjE,OAAO,kBAAkB,CAAC;QAC3B,CAAC,CAAC;QAEF,OAAO;YACN,gBAAgB,CAAC,IAAI;gBACpB,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC;oBAC5B,OAAO;gBACR,CAAC;gBAED,MAAM,mBAAmB,GAAG,iBAAiB,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;gBACnE,IACC,CAAC,mBAAmB,EAAE,KAAK;oBAC3B,mBAAmB,CAAC,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,cAAc,CAAC,gBAAgB,EAC1E,CAAC;oBACF,OAAO;gBACR,CAAC;gBAED,MAAM,gBAAgB,GAAG,wBAAwB,CAAC,mBAAmB,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;gBAC5F,IAAI,CAAC,gBAAgB,EAAE,CAAC;oBACvB,OAAO;gBACR,CAAC;gBAED,MAAM,kBAAkB,GAAG,sBAAsB,EAAE,CAAC;gBAEpD,gBAAgB,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;oBAC7C,MAAM,cAAc,GAAG,8BAA8B,CAAC,OAAO,CAAC,CAAC;oBAC/D,IAAI,cAAc,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC;wBACpE,MAAM,kBAAkB,GAAG,kBAAkB,CAAC,cAAc,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAC;wBACvF,MAAM,WAAW,GAEb,EAAE,CAAC;wBAEP,KAAK,MAAM,WAAW,IAAI,kBAAkB,EAAE,CAAC;4BAC9C,WAAW,CAAC,IAAI,CAAC;gCAChB,SAAS,EAAE,YAAY;gCACvB,IAAI,EAAE,EAAE,aAAa,EAAE,WAAW,EAAE;gCACpC,GAAG,CAAC,KAAK;oCACR,OAAO,KAAK,CAAC,WAAW,CAAC,cAAc,CAAC,IAAI,EAAE,IAAI,WAAW,GAAG,CAAC,CAAC;gCACnE,CAAC;6BACD,CAAC,CAAC;wBACJ,CAAC;wBAED,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,IAAI,kBAAkB,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;4BAC7D,MAAM,oBAAoB,GAAG,KAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;4BACxE,KAAK,MAAM,aAAa,IAAI,oBAAoB,EAAE,CAAC;gCAClD,WAAW,CAAC,IAAI,CAAC;oCAChB,SAAS,EAAE,cAAc;oCACzB,IAAI,EAAE,EAAE,aAAa,EAAE,aAAa,EAAE;oCACtC,GAAG,CAAC,KAAK;wCACR,OAAO,KAAK,CAAC,WAAW,CAAC,cAAc,CAAC,IAAI,EAAE,IAAI,aAAa,GAAG,CAAC,CAAC;oCACrE,CAAC;iCACD,CAAC,CAAC;4BACJ,CAAC;wBACF,CAAC;wBAED,OAAO,CAAC,MAAM,CAAC;4BACd,IAAI,EAAE,cAAc,CAAC,IAAI;4BACzB,SAAS,EAAE,wBAAwB;4BACnC,IAAI,EAAE;gCACL,cAAc,EAAE,cAAc,CAAC,IAAI;6BACnC;4BACD,OAAO,EAAE,WAAW;yBACpB,CAAC,CAAC;oBACJ,CAAC;gBACF,CAAC,CAAC,CAAC;YACJ,CAAC;SACD,CAAC;IACH,CAAC;CACD,CAAC,CAAC"}

View File

@@ -0,0 +1,2 @@
export declare const NoDeprecatedWorkflowFunctionsRule: import("@typescript-eslint/utils/ts-eslint").RuleModule<"deprecatedRequestFunction" | "deprecatedFunction" | "deprecatedType" | "deprecatedWithoutReplacement" | "suggestReplaceFunction" | "suggestReplaceType", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
//# sourceMappingURL=no-deprecated-workflow-functions.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"no-deprecated-workflow-functions.d.ts","sourceRoot":"","sources":["../../src/rules/no-deprecated-workflow-functions.ts"],"names":[],"mappings":"AA0BA,eAAO,MAAM,iCAAiC,2RA6I5C,CAAC"}

View File

@@ -0,0 +1,172 @@
import { AST_NODE_TYPES } from '@typescript-eslint/utils';
import { createRule } from '../utils/index.js';
const DEPRECATED_FUNCTIONS = {
request: 'httpRequest',
requestWithAuthentication: 'httpRequestWithAuthentication',
requestOAuth1: 'httpRequestWithAuthentication',
requestOAuth2: 'httpRequestWithAuthentication',
copyBinaryFile: null,
prepareOutputData: null,
};
const DEPRECATED_TYPES = {
IRequestOptions: 'IHttpRequestOptions',
};
function isDeprecatedFunctionName(name) {
return name in DEPRECATED_FUNCTIONS;
}
function isDeprecatedTypeName(name) {
return name in DEPRECATED_TYPES;
}
export const NoDeprecatedWorkflowFunctionsRule = createRule({
name: 'no-deprecated-workflow-functions',
meta: {
type: 'problem',
docs: {
description: 'Disallow usage of deprecated functions and types from n8n-workflow package',
},
messages: {
deprecatedRequestFunction: "'{{ functionName }}' is deprecated. Use '{{ replacement }}' instead for better authentication support and consistency.",
deprecatedFunction: "'{{ functionName }}' is deprecated and should be avoided. {{ message }}",
deprecatedType: "'{{ typeName }}' is deprecated. Use '{{ replacement }}' instead.",
deprecatedWithoutReplacement: "'{{ functionName }}' is deprecated and should be removed or replaced with alternative implementation.",
suggestReplaceFunction: "Replace '{{ functionName }}' with '{{ replacement }}'",
suggestReplaceType: "Replace '{{ typeName }}' with '{{ replacement }}'",
},
schema: [],
hasSuggestions: true,
},
defaultOptions: [],
create(context) {
const n8nWorkflowTypes = new Set();
return {
ImportDeclaration(node) {
if (node.source.value === 'n8n-workflow') {
node.specifiers.forEach((specifier) => {
if (specifier.type === AST_NODE_TYPES.ImportSpecifier &&
specifier.imported.type === AST_NODE_TYPES.Identifier) {
n8nWorkflowTypes.add(specifier.local.name);
}
});
}
},
MemberExpression(node) {
if (node.property.type === AST_NODE_TYPES.Identifier &&
isDeprecatedFunctionName(node.property.name)) {
if (!isThisHelpersAccess(node)) {
return;
}
const functionName = node.property.name;
const replacement = DEPRECATED_FUNCTIONS[functionName];
if (replacement) {
const messageId = functionName.includes('request')
? 'deprecatedRequestFunction'
: 'deprecatedFunction';
context.report({
node: node.property,
messageId,
data: {
functionName,
replacement,
message: getDeprecationMessage(functionName),
},
suggest: [
{
messageId: 'suggestReplaceFunction',
data: { functionName, replacement },
fix: (fixer) => fixer.replaceText(node.property, replacement),
},
],
});
}
else {
context.report({
node: node.property,
messageId: 'deprecatedWithoutReplacement',
data: {
functionName,
},
});
}
}
},
TSTypeReference(node) {
if (node.typeName.type === AST_NODE_TYPES.Identifier &&
isDeprecatedTypeName(node.typeName.name) &&
n8nWorkflowTypes.has(node.typeName.name)) {
const typeName = node.typeName.name;
const replacement = DEPRECATED_TYPES[typeName];
context.report({
node: node.typeName,
messageId: 'deprecatedType',
data: {
typeName,
replacement,
},
suggest: [
{
messageId: 'suggestReplaceType',
data: { typeName, replacement },
fix: (fixer) => fixer.replaceText(node.typeName, replacement),
},
],
});
}
},
ImportSpecifier(node) {
// Check if this import is from n8n-workflow by looking at the parent ImportDeclaration
const importDeclaration = node.parent;
if (importDeclaration?.type === AST_NODE_TYPES.ImportDeclaration &&
importDeclaration.source.value === 'n8n-workflow' &&
node.imported.type === AST_NODE_TYPES.Identifier &&
isDeprecatedTypeName(node.imported.name)) {
const typeName = node.imported.name;
const replacement = DEPRECATED_TYPES[typeName];
context.report({
node: node.imported,
messageId: 'deprecatedType',
data: {
typeName,
replacement,
},
suggest: [
{
messageId: 'suggestReplaceType',
data: { typeName, replacement },
fix: (fixer) => fixer.replaceText(node.imported, replacement),
},
],
});
}
},
};
},
});
/**
* Check if the MemberExpression follows the this.helpers.* pattern
*/
function isThisHelpersAccess(node) {
if (node.object?.type === AST_NODE_TYPES.MemberExpression) {
const outerObject = node.object;
return (outerObject.object?.type === AST_NODE_TYPES.ThisExpression &&
outerObject.property?.type === AST_NODE_TYPES.Identifier &&
outerObject.property.name === 'helpers');
}
return false;
}
function getDeprecationMessage(functionName) {
switch (functionName) {
case 'request':
return 'Use httpRequest for better type safety and consistency.';
case 'requestWithAuthentication':
case 'requestOAuth1':
case 'requestOAuth2':
return 'Use httpRequestWithAuthentication which provides unified authentication handling.';
case 'copyBinaryFile':
return 'This function has been removed. Handle binary data directly.';
case 'prepareOutputData':
return 'This function is deprecated. Return data directly from execute method.';
default:
return 'This function is deprecated and should be avoided.';
}
}
//# sourceMappingURL=no-deprecated-workflow-functions.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"no-deprecated-workflow-functions.js","sourceRoot":"","sources":["../../src/rules/no-deprecated-workflow-functions.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,3 @@
import type { TSESLint } from '@typescript-eslint/utils';
export declare const NoRestrictedGlobalsRule: TSESLint.RuleModule<"restrictedGlobal", [], unknown, TSESLint.RuleListener>;
//# sourceMappingURL=no-restricted-globals.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"no-restricted-globals.d.ts","sourceRoot":"","sources":["../../src/rules/no-restricted-globals.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAkBzD,eAAO,MAAM,uBAAuB,6EAsDlC,CAAC"}

View File

@@ -0,0 +1,60 @@
import { TSESTree } from '@typescript-eslint/types';
import { createRule } from '../utils/index.js';
const restrictedGlobals = [
'clearInterval',
'clearTimeout',
'global',
'globalThis',
'process',
'setInterval',
'setTimeout',
'setImmediate',
'clearImmediate',
'__dirname',
'__filename',
];
export const NoRestrictedGlobalsRule = createRule({
name: 'no-restricted-globals',
meta: {
type: 'problem',
docs: {
description: 'Disallow usage of restricted global variables in community nodes.',
},
messages: {
restrictedGlobal: "Use of restricted global '{{ name }}' is not allowed",
},
schema: [],
},
defaultOptions: [],
create(context) {
function checkReference(ref, name) {
const { parent } = ref.identifier;
// Skip property access (like console.process - we want process.exit but not obj.process)
if (parent?.type === TSESTree.AST_NODE_TYPES.MemberExpression &&
parent.property === ref.identifier &&
!parent.computed) {
return;
}
context.report({
node: ref.identifier,
messageId: 'restrictedGlobal',
data: { name },
});
}
return {
Program() {
const globalScope = context.sourceCode.getScope(context.sourceCode.ast);
const allReferences = [
...globalScope.variables
.filter((variable) => restrictedGlobals.includes(variable.name) && variable.defs.length === 0)
.flatMap((variable) => variable.references.map((ref) => ({ ref, name: variable.name }))),
...globalScope.through
.filter((ref) => restrictedGlobals.includes(ref.identifier.name))
.map((ref) => ({ ref, name: ref.identifier.name })),
];
allReferences.forEach(({ ref, name }) => checkReference(ref, name));
},
};
},
});
//# sourceMappingURL=no-restricted-globals.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"no-restricted-globals.js","sourceRoot":"","sources":["../../src/rules/no-restricted-globals.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAGpD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE/C,MAAM,iBAAiB,GAAG;IACzB,eAAe;IACf,cAAc;IACd,QAAQ;IACR,YAAY;IACZ,SAAS;IACT,aAAa;IACb,YAAY;IACZ,cAAc;IACd,gBAAgB;IAChB,WAAW;IACX,YAAY;CACZ,CAAC;AAEF,MAAM,CAAC,MAAM,uBAAuB,GAAG,UAAU,CAAC;IACjD,IAAI,EAAE,uBAAuB;IAC7B,IAAI,EAAE;QACL,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACL,WAAW,EAAE,mEAAmE;SAChF;QACD,QAAQ,EAAE;YACT,gBAAgB,EAAE,sDAAsD;SACxE;QACD,MAAM,EAAE,EAAE;KACV;IACD,cAAc,EAAE,EAAE;IAClB,MAAM,CAAC,OAAO;QACb,SAAS,cAAc,CAAC,GAA6B,EAAE,IAAY;YAClE,MAAM,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,UAAU,CAAC;YAElC,yFAAyF;YACzF,IACC,MAAM,EAAE,IAAI,KAAK,QAAQ,CAAC,cAAc,CAAC,gBAAgB;gBACzD,MAAM,CAAC,QAAQ,KAAK,GAAG,CAAC,UAAU;gBAClC,CAAC,MAAM,CAAC,QAAQ,EACf,CAAC;gBACF,OAAO;YACR,CAAC;YAED,OAAO,CAAC,MAAM,CAAC;gBACd,IAAI,EAAE,GAAG,CAAC,UAAU;gBACpB,SAAS,EAAE,kBAAkB;gBAC7B,IAAI,EAAE,EAAE,IAAI,EAAE;aACd,CAAC,CAAC;QACJ,CAAC;QAED,OAAO;YACN,OAAO;gBACN,MAAM,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;gBAExE,MAAM,aAAa,GAAG;oBACrB,GAAG,WAAW,CAAC,SAAS;yBACtB,MAAM,CACN,CAAC,QAAQ,EAAE,EAAE,CAAC,iBAAiB,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,CACrF;yBACA,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE,CACrB,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,CAChE;oBACF,GAAG,WAAW,CAAC,OAAO;yBACpB,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,iBAAiB,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;yBAChE,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC;iBACpD,CAAC;gBAEF,aAAa,CAAC,OAAO,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,cAAc,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;YACrE,CAAC;SACD,CAAC;IACH,CAAC;CACD,CAAC,CAAC"}

View File

@@ -0,0 +1,2 @@
export declare const NoRestrictedImportsRule: import("@typescript-eslint/utils/ts-eslint").RuleModule<"restrictedImport" | "restrictedRequire" | "restrictedDynamicImport", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
//# sourceMappingURL=no-restricted-imports.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"no-restricted-imports.d.ts","sourceRoot":"","sources":["../../src/rules/no-restricted-imports.ts"],"names":[],"mappings":"AA4BA,eAAO,MAAM,uBAAuB,uMA8DlC,CAAC"}

View File

@@ -0,0 +1,80 @@
import { getModulePath, isDirectRequireCall, isRequireMemberCall, createRule, } from '../utils/index.js';
const allowedModules = [
'n8n-workflow',
'lodash',
'moment',
'p-limit',
'luxon',
'zod',
'crypto',
'node:crypto',
];
const isModuleAllowed = (modulePath) => {
if (modulePath.startsWith('./') || modulePath.startsWith('../'))
return true;
const moduleName = modulePath.startsWith('@')
? modulePath.split('/').slice(0, 2).join('/')
: modulePath.split('/')[0];
if (!moduleName)
return true;
return allowedModules.includes(moduleName);
};
export const NoRestrictedImportsRule = createRule({
name: 'no-restricted-imports',
meta: {
type: 'problem',
docs: {
description: 'Disallow usage of restricted imports in community nodes.',
},
messages: {
restrictedImport: "Import of '{{ modulePath }}' is not allowed. n8n Cloud does not allow community nodes with dependencies.",
restrictedRequire: "Require of '{{ modulePath }}' is not allowed. n8n Cloud does not allow community nodes with dependencies.",
restrictedDynamicImport: "Dynamic import of '{{ modulePath }}' is not allowed. n8n Cloud does not allow community nodes with dependencies.",
},
schema: [],
},
defaultOptions: [],
create(context) {
return {
ImportDeclaration(node) {
const modulePath = getModulePath(node.source);
if (modulePath && !isModuleAllowed(modulePath)) {
context.report({
node,
messageId: 'restrictedImport',
data: {
modulePath,
},
});
}
},
ImportExpression(node) {
const modulePath = getModulePath(node.source);
if (modulePath && !isModuleAllowed(modulePath)) {
context.report({
node,
messageId: 'restrictedDynamicImport',
data: {
modulePath,
},
});
}
},
CallExpression(node) {
if (isDirectRequireCall(node) || isRequireMemberCall(node)) {
const modulePath = getModulePath(node.arguments[0] ?? null);
if (modulePath && !isModuleAllowed(modulePath)) {
context.report({
node,
messageId: 'restrictedRequire',
data: {
modulePath,
},
});
}
}
},
};
},
});
//# sourceMappingURL=no-restricted-imports.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"no-restricted-imports.js","sourceRoot":"","sources":["../../src/rules/no-restricted-imports.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,aAAa,EACb,mBAAmB,EACnB,mBAAmB,EACnB,UAAU,GACV,MAAM,mBAAmB,CAAC;AAE3B,MAAM,cAAc,GAAG;IACtB,cAAc;IACd,QAAQ;IACR,QAAQ;IACR,SAAS;IACT,OAAO;IACP,KAAK;IACL,QAAQ;IACR,aAAa;CACb,CAAC;AAEF,MAAM,eAAe,GAAG,CAAC,UAAkB,EAAW,EAAE;IACvD,IAAI,UAAU,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,UAAU,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAE7E,MAAM,UAAU,GAAG,UAAU,CAAC,UAAU,CAAC,GAAG,CAAC;QAC5C,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;QAC7C,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5B,IAAI,CAAC,UAAU;QAAE,OAAO,IAAI,CAAC;IAC7B,OAAO,cAAc,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;AAC5C,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,uBAAuB,GAAG,UAAU,CAAC;IACjD,IAAI,EAAE,uBAAuB;IAC7B,IAAI,EAAE;QACL,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACL,WAAW,EAAE,0DAA0D;SACvE;QACD,QAAQ,EAAE;YACT,gBAAgB,EACf,0GAA0G;YAC3G,iBAAiB,EAChB,2GAA2G;YAC5G,uBAAuB,EACtB,kHAAkH;SACnH;QACD,MAAM,EAAE,EAAE;KACV;IACD,cAAc,EAAE,EAAE;IAClB,MAAM,CAAC,OAAO;QACb,OAAO;YACN,iBAAiB,CAAC,IAAI;gBACrB,MAAM,UAAU,GAAG,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;gBAC9C,IAAI,UAAU,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,EAAE,CAAC;oBAChD,OAAO,CAAC,MAAM,CAAC;wBACd,IAAI;wBACJ,SAAS,EAAE,kBAAkB;wBAC7B,IAAI,EAAE;4BACL,UAAU;yBACV;qBACD,CAAC,CAAC;gBACJ,CAAC;YACF,CAAC;YAED,gBAAgB,CAAC,IAAI;gBACpB,MAAM,UAAU,GAAG,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;gBAC9C,IAAI,UAAU,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,EAAE,CAAC;oBAChD,OAAO,CAAC,MAAM,CAAC;wBACd,IAAI;wBACJ,SAAS,EAAE,yBAAyB;wBACpC,IAAI,EAAE;4BACL,UAAU;yBACV;qBACD,CAAC,CAAC;gBACJ,CAAC;YACF,CAAC;YAED,cAAc,CAAC,IAAI;gBAClB,IAAI,mBAAmB,CAAC,IAAI,CAAC,IAAI,mBAAmB,CAAC,IAAI,CAAC,EAAE,CAAC;oBAC5D,MAAM,UAAU,GAAG,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC;oBAC5D,IAAI,UAAU,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,EAAE,CAAC;wBAChD,OAAO,CAAC,MAAM,CAAC;4BACd,IAAI;4BACJ,SAAS,EAAE,mBAAmB;4BAC9B,IAAI,EAAE;gCACL,UAAU;6BACV;yBACD,CAAC,CAAC;oBACJ,CAAC;gBACF,CAAC;YACF,CAAC;SACD,CAAC;IACH,CAAC;CACD,CAAC,CAAC"}

View File

@@ -0,0 +1,2 @@
export declare const NodeUsableAsToolRule: import("@typescript-eslint/utils/ts-eslint").RuleModule<"missingUsableAsTool", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
//# sourceMappingURL=node-usable-as-tool.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"node-usable-as-tool.d.ts","sourceRoot":"","sources":["../../src/rules/node-usable-as-tool.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,oBAAoB,wJA8D/B,CAAC"}

View File

@@ -0,0 +1,58 @@
import { TSESTree } from '@typescript-eslint/types';
import { isNodeTypeClass, findClassProperty, findObjectProperty, createRule, } from '../utils/index.js';
export const NodeUsableAsToolRule = createRule({
name: 'node-usable-as-tool',
meta: {
type: 'problem',
docs: {
description: 'Ensure node classes have usableAsTool property',
},
messages: {
missingUsableAsTool: 'Node class should have usableAsTool property. When in doubt, set it to true.',
},
fixable: 'code',
schema: [],
},
defaultOptions: [],
create(context) {
return {
ClassDeclaration(node) {
if (!isNodeTypeClass(node)) {
return;
}
const descriptionProperty = findClassProperty(node, 'description');
if (!descriptionProperty) {
return;
}
const descriptionValue = descriptionProperty.value;
if (descriptionValue?.type !== TSESTree.AST_NODE_TYPES.ObjectExpression) {
return;
}
const usableAsToolProperty = findObjectProperty(descriptionValue, 'usableAsTool');
if (!usableAsToolProperty) {
context.report({
node,
messageId: 'missingUsableAsTool',
fix(fixer) {
if (descriptionValue?.type === TSESTree.AST_NODE_TYPES.ObjectExpression) {
const properties = descriptionValue.properties;
if (properties.length === 0) {
const openBrace = descriptionValue.range[0] + 1;
return fixer.insertTextAfterRange([openBrace, openBrace], '\n\t\tusableAsTool: true,');
}
else {
const lastProperty = properties.at(-1);
if (lastProperty) {
return fixer.insertTextAfter(lastProperty, ',\n\t\tusableAsTool: true');
}
}
}
return null;
},
});
}
},
};
},
});
//# sourceMappingURL=node-usable-as-tool.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"node-usable-as-tool.js","sourceRoot":"","sources":["../../src/rules/node-usable-as-tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEpD,OAAO,EACN,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,UAAU,GACV,MAAM,mBAAmB,CAAC;AAE3B,MAAM,CAAC,MAAM,oBAAoB,GAAG,UAAU,CAAC;IAC9C,IAAI,EAAE,qBAAqB;IAC3B,IAAI,EAAE;QACL,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACL,WAAW,EAAE,gDAAgD;SAC7D;QACD,QAAQ,EAAE;YACT,mBAAmB,EAClB,8EAA8E;SAC/E;QACD,OAAO,EAAE,MAAM;QACf,MAAM,EAAE,EAAE;KACV;IACD,cAAc,EAAE,EAAE;IAClB,MAAM,CAAC,OAAO;QACb,OAAO;YACN,gBAAgB,CAAC,IAAI;gBACpB,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC;oBAC5B,OAAO;gBACR,CAAC;gBAED,MAAM,mBAAmB,GAAG,iBAAiB,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;gBACnE,IAAI,CAAC,mBAAmB,EAAE,CAAC;oBAC1B,OAAO;gBACR,CAAC;gBAED,MAAM,gBAAgB,GAAG,mBAAmB,CAAC,KAAK,CAAC;gBACnD,IAAI,gBAAgB,EAAE,IAAI,KAAK,QAAQ,CAAC,cAAc,CAAC,gBAAgB,EAAE,CAAC;oBACzE,OAAO;gBACR,CAAC;gBAED,MAAM,oBAAoB,GAAG,kBAAkB,CAAC,gBAAgB,EAAE,cAAc,CAAC,CAAC;gBAElF,IAAI,CAAC,oBAAoB,EAAE,CAAC;oBAC3B,OAAO,CAAC,MAAM,CAAC;wBACd,IAAI;wBACJ,SAAS,EAAE,qBAAqB;wBAChC,GAAG,CAAC,KAAK;4BACR,IAAI,gBAAgB,EAAE,IAAI,KAAK,QAAQ,CAAC,cAAc,CAAC,gBAAgB,EAAE,CAAC;gCACzE,MAAM,UAAU,GAAG,gBAAgB,CAAC,UAAU,CAAC;gCAC/C,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oCAC7B,MAAM,SAAS,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;oCAChD,OAAO,KAAK,CAAC,oBAAoB,CAChC,CAAC,SAAS,EAAE,SAAS,CAAC,EACtB,2BAA2B,CAC3B,CAAC;gCACH,CAAC;qCAAM,CAAC;oCACP,MAAM,YAAY,GAAG,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;oCACvC,IAAI,YAAY,EAAE,CAAC;wCAClB,OAAO,KAAK,CAAC,eAAe,CAAC,YAAY,EAAE,2BAA2B,CAAC,CAAC;oCACzE,CAAC;gCACF,CAAC;4BACF,CAAC;4BAED,OAAO,IAAI,CAAC;wBACb,CAAC;qBACD,CAAC,CAAC;gBACJ,CAAC;YACF,CAAC;SACD,CAAC;IACH,CAAC;CACD,CAAC,CAAC"}

View File

@@ -0,0 +1,2 @@
export declare const PackageNameConventionRule: import("@typescript-eslint/utils/ts-eslint").RuleModule<"renameTo" | "invalidPackageName", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
//# sourceMappingURL=package-name-convention.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"package-name-convention.d.ts","sourceRoot":"","sources":["../../src/rules/package-name-convention.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,yBAAyB,oKA0EpC,CAAC"}

View File

@@ -0,0 +1,88 @@
import { AST_NODE_TYPES } from '@typescript-eslint/utils';
import { createRule } from '../utils/index.js';
export const PackageNameConventionRule = createRule({
name: 'package-name-convention',
meta: {
type: 'problem',
docs: {
description: 'Enforce correct package naming convention for n8n community nodes',
},
messages: {
renameTo: "Rename to '{{suggestedName}}'",
invalidPackageName: 'Package name "{{ packageName }}" must follow the convention "n8n-nodes-[PACKAGE-NAME]" or "@[AUTHOR]/n8n-nodes-[PACKAGE-NAME]"',
},
schema: [],
hasSuggestions: true,
},
defaultOptions: [],
create(context) {
if (!context.filename.endsWith('package.json')) {
return {};
}
return {
ObjectExpression(node) {
if (node.parent?.type === AST_NODE_TYPES.Property) {
return;
}
const nameProperty = node.properties.find((property) => property.type === AST_NODE_TYPES.Property &&
property.key.type === AST_NODE_TYPES.Literal &&
property.key.value === 'name');
if (!nameProperty || nameProperty.type !== AST_NODE_TYPES.Property) {
return;
}
if (nameProperty.value.type !== AST_NODE_TYPES.Literal) {
return;
}
const packageName = nameProperty.value.value;
const packageNameStr = typeof packageName === 'string' ? packageName : null;
if (!packageNameStr || !isValidPackageName(packageNameStr)) {
const suggestions = [];
// Generate package name suggestions if we have a valid string
if (packageNameStr) {
const suggestedNames = generatePackageNameSuggestions(packageNameStr);
for (const suggestedName of suggestedNames) {
suggestions.push({
messageId: 'renameTo',
data: { suggestedName },
fix(fixer) {
return fixer.replaceText(nameProperty.value, `"${suggestedName}"`);
},
});
}
}
context.report({
node: nameProperty,
messageId: 'invalidPackageName',
data: {
packageName: packageNameStr ?? 'undefined',
},
suggest: suggestions,
});
}
},
};
},
});
function isValidPackageName(name) {
const unscoped = /^n8n-nodes-.+$/;
const scoped = /^@.+\/n8n-nodes-.+$/;
return unscoped.test(name) || scoped.test(name);
}
function generatePackageNameSuggestions(invalidName) {
const cleanName = (name) => {
return name
.replace(/^nodes?-?n8n-?/, '')
.replace(/^n8n-/, '')
.replace(/^nodes?-?/, '')
.replace(/^node-/, '')
.replace(/-nodes$/, '');
};
if (invalidName.startsWith('@')) {
const [scope, packagePart] = invalidName.split('/');
const clean = cleanName(packagePart ?? '');
return clean ? [`${scope}/n8n-nodes-${clean}`] : [];
}
const clean = cleanName(invalidName);
return clean ? [`n8n-nodes-${clean}`] : [];
}
//# sourceMappingURL=package-name-convention.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"package-name-convention.js","sourceRoot":"","sources":["../../src/rules/package-name-convention.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,2 @@
export declare const ResourceOperationPatternRule: import("@typescript-eslint/utils/ts-eslint").RuleModule<"tooManyOperationsWithoutResources", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
//# sourceMappingURL=resource-operation-pattern.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"resource-operation-pattern.d.ts","sourceRoot":"","sources":["../../src/rules/resource-operation-pattern.ts"],"names":[],"mappings":"AAYA,eAAO,MAAM,4BAA4B,sKA2FvC,CAAC"}

View File

@@ -0,0 +1,79 @@
import { AST_NODE_TYPES } from '@typescript-eslint/utils';
import { isNodeTypeClass, findClassProperty, findObjectProperty, getStringLiteralValue, isFileType, createRule, } from '../utils/index.js';
export const ResourceOperationPatternRule = createRule({
name: 'resource-operation-pattern',
meta: {
type: 'problem',
docs: {
description: 'Enforce proper resource/operation pattern for better UX in n8n nodes',
},
messages: {
tooManyOperationsWithoutResources: 'Node has {{ operationCount }} operations without resources. Use resources to organize operations when there are more than 5 operations.',
},
schema: [],
},
defaultOptions: [],
create(context) {
if (!isFileType(context.filename, '.node.ts')) {
return {};
}
const analyzeNodeDescription = (descriptionValue) => {
if (!descriptionValue || descriptionValue.type !== AST_NODE_TYPES.ObjectExpression) {
return;
}
const propertiesProperty = findObjectProperty(descriptionValue, 'properties');
if (!propertiesProperty?.value ||
propertiesProperty.value.type !== AST_NODE_TYPES.ArrayExpression) {
return;
}
const propertiesArray = propertiesProperty.value;
let hasResources = false;
let operationCount = 0;
let operationNode = null;
for (const property of propertiesArray.elements) {
if (!property || property.type !== AST_NODE_TYPES.ObjectExpression) {
continue;
}
const nameProperty = findObjectProperty(property, 'name');
const typeProperty = findObjectProperty(property, 'type');
const name = nameProperty ? getStringLiteralValue(nameProperty.value) : null;
const type = typeProperty ? getStringLiteralValue(typeProperty.value) : null;
if (!name || !type) {
continue;
}
if (name === 'resource' && type === 'options') {
hasResources = true;
}
if (name === 'operation' && type === 'options') {
operationNode = property;
const optionsProperty = findObjectProperty(property, 'options');
if (optionsProperty?.value?.type === AST_NODE_TYPES.ArrayExpression) {
operationCount = optionsProperty.value.elements.length;
}
}
}
if (operationCount > 5 && !hasResources && operationNode) {
context.report({
node: operationNode,
messageId: 'tooManyOperationsWithoutResources',
data: {
operationCount: operationCount.toString(),
},
});
}
};
return {
ClassDeclaration(node) {
if (!isNodeTypeClass(node)) {
return;
}
const descriptionProperty = findClassProperty(node, 'description');
if (!descriptionProperty) {
return;
}
analyzeNodeDescription(descriptionProperty.value);
},
};
},
});
//# sourceMappingURL=resource-operation-pattern.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"resource-operation-pattern.js","sourceRoot":"","sources":["../../src/rules/resource-operation-pattern.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,26 @@
import type { TSESTree } from '@typescript-eslint/utils';
export declare function isNodeTypeClass(node: TSESTree.ClassDeclaration): boolean;
export declare function isCredentialTypeClass(node: TSESTree.ClassDeclaration): boolean;
export declare function findClassProperty(node: TSESTree.ClassDeclaration, propertyName: string): TSESTree.PropertyDefinition | null;
export declare function findObjectProperty(obj: TSESTree.ObjectExpression, propertyName: string): TSESTree.Property | null;
export declare function getLiteralValue(node: TSESTree.Node | null): string | boolean | number | null;
export declare function getStringLiteralValue(node: TSESTree.Node | null): string | null;
export declare function getModulePath(node: TSESTree.Node | null): string | null;
export declare function getBooleanLiteralValue(node: TSESTree.Node | null): boolean | null;
export declare function findArrayLiteralProperty(obj: TSESTree.ObjectExpression, propertyName: string): TSESTree.ArrayExpression | null;
export declare function hasArrayLiteralValue(node: TSESTree.PropertyDefinition, searchValue: string): boolean;
export declare function getTopLevelObjectInJson(node: TSESTree.ObjectExpression): TSESTree.ObjectExpression | null;
export declare function isFileType(filename: string, extension: string): boolean;
export declare function isDirectRequireCall(node: TSESTree.CallExpression): boolean;
export declare function isRequireMemberCall(node: TSESTree.CallExpression): boolean;
export declare function extractCredentialInfoFromArray(element: TSESTree.ArrayExpression['elements'][number]): {
name: string;
testedBy?: string;
node: TSESTree.Node;
} | null;
export declare function extractCredentialNameFromArray(element: TSESTree.ArrayExpression['elements'][number]): {
name: string;
node: TSESTree.Node;
} | null;
export declare function findSimilarStrings(target: string, candidates: Set<string>, maxDistance?: number, maxResults?: number): string[];
//# sourceMappingURL=ast-utils.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ast-utils.d.ts","sourceRoot":"","sources":["../../src/utils/ast-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAezD,wBAAgB,eAAe,CAAC,IAAI,EAAE,QAAQ,CAAC,gBAAgB,GAAG,OAAO,CAUxE;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,QAAQ,CAAC,gBAAgB,GAAG,OAAO,CAE9E;AAED,wBAAgB,iBAAiB,CAChC,IAAI,EAAE,QAAQ,CAAC,gBAAgB,EAC/B,YAAY,EAAE,MAAM,GAClB,QAAQ,CAAC,kBAAkB,GAAG,IAAI,CAQpC;AAED,wBAAgB,kBAAkB,CACjC,GAAG,EAAE,QAAQ,CAAC,gBAAgB,EAC9B,YAAY,EAAE,MAAM,GAClB,QAAQ,CAAC,QAAQ,GAAG,IAAI,CAQ1B;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,GAAG,IAAI,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,IAAI,CAK5F;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAG/E;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAevE;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,GAAG,IAAI,GAAG,OAAO,GAAG,IAAI,CAGjF;AAED,wBAAgB,wBAAwB,CACvC,GAAG,EAAE,QAAQ,CAAC,gBAAgB,EAC9B,YAAY,EAAE,MAAM,GAClB,QAAQ,CAAC,eAAe,GAAG,IAAI,CAMjC;AAED,wBAAgB,oBAAoB,CACnC,IAAI,EAAE,QAAQ,CAAC,kBAAkB,EACjC,WAAW,EAAE,MAAM,GACjB,OAAO,CAST;AAED,wBAAgB,uBAAuB,CACtC,IAAI,EAAE,QAAQ,CAAC,gBAAgB,GAC7B,QAAQ,CAAC,gBAAgB,GAAG,IAAI,CAKlC;AAED,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAEvE;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,QAAQ,CAAC,cAAc,GAAG,OAAO,CAM1E;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,QAAQ,CAAC,cAAc,GAAG,OAAO,CAO1E;AAED,wBAAgB,8BAA8B,CAC7C,OAAO,EAAE,QAAQ,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,GACnD;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAA;CAAE,GAAG,IAAI,CA6BjE;AAED,wBAAgB,8BAA8B,CAC7C,OAAO,EAAE,QAAQ,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,GACnD;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAA;CAAE,GAAG,IAAI,CAG9C;AAED,wBAAgB,kBAAkB,CACjC,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,EACvB,WAAW,GAAE,MAAU,EACvB,UAAU,GAAE,MAAU,GACpB,MAAM,EAAE,CAeV"}

View File

@@ -0,0 +1,135 @@
import { AST_NODE_TYPES } from '@typescript-eslint/utils';
import { distance } from 'fastest-levenshtein';
function implementsInterface(node, interfaceName) {
return (node.implements?.some((impl) => impl.type === AST_NODE_TYPES.TSClassImplements &&
impl.expression.type === AST_NODE_TYPES.Identifier &&
impl.expression.name === interfaceName) ?? false);
}
export function isNodeTypeClass(node) {
if (implementsInterface(node, 'INodeType')) {
return true;
}
if (node.superClass?.type === AST_NODE_TYPES.Identifier && node.superClass.name === 'Node') {
return true;
}
return false;
}
export function isCredentialTypeClass(node) {
return implementsInterface(node, 'ICredentialType');
}
export function findClassProperty(node, propertyName) {
const property = node.body.body.find((member) => member.type === AST_NODE_TYPES.PropertyDefinition &&
member.key?.type === AST_NODE_TYPES.Identifier &&
member.key.name === propertyName);
return property?.type === AST_NODE_TYPES.PropertyDefinition ? property : null;
}
export function findObjectProperty(obj, propertyName) {
const property = obj.properties.find((prop) => prop.type === AST_NODE_TYPES.Property &&
prop.key.type === AST_NODE_TYPES.Identifier &&
prop.key.name === propertyName);
return property?.type === AST_NODE_TYPES.Property ? property : null;
}
export function getLiteralValue(node) {
if (node?.type === AST_NODE_TYPES.Literal) {
return node.value;
}
return null;
}
export function getStringLiteralValue(node) {
const value = getLiteralValue(node);
return typeof value === 'string' ? value : null;
}
export function getModulePath(node) {
const stringValue = getStringLiteralValue(node);
if (stringValue) {
return stringValue;
}
if (node?.type === AST_NODE_TYPES.TemplateLiteral &&
node.expressions.length === 0 &&
node.quasis.length === 1) {
return node.quasis[0]?.value.cooked ?? null;
}
return null;
}
export function getBooleanLiteralValue(node) {
const value = getLiteralValue(node);
return typeof value === 'boolean' ? value : null;
}
export function findArrayLiteralProperty(obj, propertyName) {
const property = findObjectProperty(obj, propertyName);
if (property?.value.type === AST_NODE_TYPES.ArrayExpression) {
return property.value;
}
return null;
}
export function hasArrayLiteralValue(node, searchValue) {
if (node.value?.type !== AST_NODE_TYPES.ArrayExpression)
return false;
return node.value.elements.some((element) => element?.type === AST_NODE_TYPES.Literal &&
typeof element.value === 'string' &&
element.value === searchValue);
}
export function getTopLevelObjectInJson(node) {
if (node.parent?.type === AST_NODE_TYPES.Property) {
return null;
}
return node;
}
export function isFileType(filename, extension) {
return filename.endsWith(extension);
}
export function isDirectRequireCall(node) {
return (node.callee.type === AST_NODE_TYPES.Identifier &&
node.callee.name === 'require' &&
node.arguments.length > 0);
}
export function isRequireMemberCall(node) {
return (node.callee.type === AST_NODE_TYPES.MemberExpression &&
node.callee.object.type === AST_NODE_TYPES.Identifier &&
node.callee.object.name === 'require' &&
node.arguments.length > 0);
}
export function extractCredentialInfoFromArray(element) {
if (!element)
return null;
const stringValue = getStringLiteralValue(element);
if (stringValue) {
return { name: stringValue, node: element };
}
if (element.type === AST_NODE_TYPES.ObjectExpression) {
const nameProperty = findObjectProperty(element, 'name');
const testedByProperty = findObjectProperty(element, 'testedBy');
if (nameProperty) {
const nameValue = getStringLiteralValue(nameProperty.value);
const testedByValue = testedByProperty
? getStringLiteralValue(testedByProperty.value)
: undefined;
if (nameValue) {
return {
name: nameValue,
testedBy: testedByValue ?? undefined,
node: nameProperty.value,
};
}
}
}
return null;
}
export function extractCredentialNameFromArray(element) {
const info = extractCredentialInfoFromArray(element);
return info ? { name: info.name, node: info.node } : null;
}
export function findSimilarStrings(target, candidates, maxDistance = 3, maxResults = 3) {
const matches = [];
for (const candidate of candidates) {
const levenshteinDistance = distance(target.toLowerCase(), candidate.toLowerCase());
if (levenshteinDistance <= maxDistance) {
matches.push({ name: candidate, distance: levenshteinDistance });
}
}
return matches
.sort((a, b) => a.distance - b.distance)
.slice(0, maxResults)
.map((match) => match.name);
}
//# sourceMappingURL=ast-utils.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,26 @@
/**
* Checks if the given childPath is contained within the parentPath. Resolves
* the paths before comparing them, so that relative paths are also supported.
*/
export declare function isContainedWithin(parentPath: string, childPath: string): boolean;
/**
* Joins the given paths to the parentPath, ensuring that the resulting path
* is still contained within the parentPath. If not, it throws an error to
* prevent path traversal vulnerabilities.
*
* @throws {UnexpectedError} If the resulting path is not contained within the parentPath.
*/
export declare function safeJoinPath(parentPath: string, ...paths: string[]): string;
export declare function findPackageJson(startPath: string): string | null;
export declare function readPackageJsonCredentials(packageJsonPath: string): Set<string>;
export declare function extractCredentialNameFromFile(credentialFilePath: string): string | null;
export declare function validateIconPath(iconPath: string, baseDir: string): {
isValid: boolean;
isFile: boolean;
isSvg: boolean;
exists: boolean;
};
export declare function readPackageJsonNodes(packageJsonPath: string): string[];
export declare function areAllCredentialUsagesTestedByNodes(credentialName: string, packageDir: string): boolean;
export declare function findSimilarSvgFiles(targetPath: string, baseDir: string): string[];
//# sourceMappingURL=file-utils.d.ts.map

View File

@@ -0,0 +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;AAyCD,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,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,OAAO,CAAC;CAChB,CAcA;AAED,wBAAgB,oBAAoB,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,EAAE,CAItE;AAED,wBAAgB,mCAAmC,CAClD,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,GAChB,OAAO,CAoBT;AA+DD,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAuBjF"}

View File

@@ -0,0 +1,221 @@
import { parse, simpleTraverse, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree';
import { readFileSync, existsSync, readdirSync } from 'node:fs';
import * as path from 'node:path';
import { dirname, parse as parsePath } from 'node:path';
import { isCredentialTypeClass, isNodeTypeClass, findClassProperty, getStringLiteralValue, findArrayLiteralProperty, extractCredentialInfoFromArray, findSimilarStrings, } from './ast-utils.js';
/**
* Checks if the given childPath is contained within the parentPath. Resolves
* the paths before comparing them, so that relative paths are also supported.
*/
export function isContainedWithin(parentPath, childPath) {
parentPath = path.resolve(parentPath);
childPath = path.resolve(childPath);
if (parentPath === childPath) {
return true;
}
return childPath.startsWith(parentPath + path.sep);
}
/**
* Joins the given paths to the parentPath, ensuring that the resulting path
* is still contained within the parentPath. If not, it throws an error to
* prevent path traversal vulnerabilities.
*
* @throws {UnexpectedError} If the resulting path is not contained within the parentPath.
*/
export function safeJoinPath(parentPath, ...paths) {
const candidate = path.join(parentPath, ...paths);
if (!isContainedWithin(parentPath, candidate)) {
throw new Error(`Path traversal detected, refusing to join paths: ${parentPath} and ${JSON.stringify(paths)}`);
}
return candidate;
}
export function findPackageJson(startPath) {
let currentDir = path.dirname(startPath);
while (parsePath(currentDir).dir !== parsePath(currentDir).root) {
const testPath = safeJoinPath(currentDir, 'package.json');
if (fileExistsWithCaseSync(testPath)) {
return testPath;
}
currentDir = dirname(currentDir);
}
return null;
}
function isValidPackageJson(obj) {
return typeof obj === 'object' && obj !== null;
}
function readPackageJsonN8n(packageJsonPath) {
try {
const content = readFileSync(packageJsonPath, 'utf8');
const parsed = JSON.parse(content);
if (isValidPackageJson(parsed)) {
return parsed.n8n ?? {};
}
return {};
}
catch {
return {};
}
}
function resolveN8nFilePaths(packageJsonPath, filePaths) {
const packageDir = dirname(packageJsonPath);
const resolvedFiles = [];
for (const filePath of filePaths) {
const sourcePath = filePath.replace(/^dist\//, '').replace(/\.js$/, '.ts');
const fullSourcePath = safeJoinPath(packageDir, sourcePath);
if (existsSync(fullSourcePath)) {
resolvedFiles.push(fullSourcePath);
}
}
return resolvedFiles;
}
export function readPackageJsonCredentials(packageJsonPath) {
const n8nConfig = readPackageJsonN8n(packageJsonPath);
const credentialPaths = n8nConfig.credentials ?? [];
const credentialFiles = resolveN8nFilePaths(packageJsonPath, credentialPaths);
const credentialNames = [];
for (const credentialFile of credentialFiles) {
try {
const credentialName = extractCredentialNameFromFile(credentialFile);
if (credentialName) {
credentialNames.push(credentialName);
}
}
catch {
// Silently continue if file can't be parsed
}
}
return new Set(credentialNames);
}
export function extractCredentialNameFromFile(credentialFilePath) {
try {
const sourceCode = readFileSync(credentialFilePath, 'utf8');
const ast = parse(sourceCode, {
jsx: false,
range: true,
});
let credentialName = null;
simpleTraverse(ast, {
enter(node) {
if (node.type === AST_NODE_TYPES.ClassDeclaration && isCredentialTypeClass(node)) {
const nameProperty = findClassProperty(node, 'name');
if (nameProperty) {
const nameValue = getStringLiteralValue(nameProperty.value);
if (nameValue) {
credentialName = nameValue;
}
}
}
},
});
return credentialName;
}
catch {
return null;
}
}
export function validateIconPath(iconPath, baseDir) {
const isFile = iconPath.startsWith('file:');
const relativePath = iconPath.replace(/^file:/, '');
const isSvg = relativePath.endsWith('.svg');
// Should not use safeJoinPath here because iconPath can be outside of the node class folder
const fullPath = path.join(baseDir, relativePath);
const exists = fileExistsWithCaseSync(fullPath);
return {
isValid: isFile && isSvg && exists,
isFile,
isSvg,
exists,
};
}
export function readPackageJsonNodes(packageJsonPath) {
const n8nConfig = readPackageJsonN8n(packageJsonPath);
const nodePaths = n8nConfig.nodes ?? [];
return resolveN8nFilePaths(packageJsonPath, nodePaths);
}
export function areAllCredentialUsagesTestedByNodes(credentialName, packageDir) {
const packageJsonPath = safeJoinPath(packageDir, 'package.json');
if (!existsSync(packageJsonPath)) {
return false;
}
const nodeFiles = readPackageJsonNodes(packageJsonPath);
let hasAnyCredentialUsage = false;
for (const nodeFile of nodeFiles) {
const result = checkCredentialUsageInFile(nodeFile, credentialName);
if (result.hasUsage) {
hasAnyCredentialUsage = true;
if (!result.allTestedBy) {
return false; // Found usage without testedBy
}
}
}
return hasAnyCredentialUsage;
}
function checkCredentialUsageInFile(nodeFile, credentialName) {
try {
const sourceCode = readFileSync(nodeFile, 'utf8');
const ast = parse(sourceCode, { jsx: false, range: true });
let hasUsage = false;
let allTestedBy = true;
simpleTraverse(ast, {
enter(node) {
if (node.type === AST_NODE_TYPES.ClassDeclaration && isNodeTypeClass(node)) {
const descriptionProperty = findClassProperty(node, 'description');
if (!descriptionProperty?.value ||
descriptionProperty.value.type !== AST_NODE_TYPES.ObjectExpression) {
return;
}
const credentialsArray = findArrayLiteralProperty(descriptionProperty.value, 'credentials');
if (!credentialsArray) {
return;
}
for (const element of credentialsArray.elements) {
const credentialInfo = extractCredentialInfoFromArray(element);
if (credentialInfo?.name === credentialName) {
hasUsage = true;
if (!credentialInfo.testedBy) {
allTestedBy = false;
}
}
}
}
},
});
return { hasUsage, allTestedBy };
}
catch {
return { hasUsage: false, allTestedBy: true };
}
}
function fileExistsWithCaseSync(filePath) {
try {
const dir = path.dirname(filePath);
const file = path.basename(filePath);
const files = new Set(readdirSync(dir));
return files.has(file);
}
catch {
return false;
}
}
export function findSimilarSvgFiles(targetPath, baseDir) {
try {
const targetFileName = path.basename(targetPath, path.extname(targetPath));
const targetDir = path.dirname(targetPath);
// Should not use safeJoinPath here because iconPath can be outside of the node class folder
const searchDir = path.join(baseDir, targetDir);
if (!existsSync(searchDir)) {
return [];
}
const files = readdirSync(searchDir);
const svgFileNames = files
.filter((file) => file.endsWith('.svg'))
.map((file) => path.basename(file, '.svg'));
const candidateNames = new Set(svgFileNames);
const similarNames = findSimilarStrings(targetFileName, candidateNames);
return similarNames.map((name) => path.join(targetDir, `${name}.svg`));
}
catch {
return [];
}
}
//# sourceMappingURL=file-utils.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,4 @@
export * from './ast-utils.js';
export * from './file-utils.js';
export * from './rule-creator.js';
//# sourceMappingURL=index.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC"}

View File

@@ -0,0 +1,4 @@
export * from './ast-utils.js';
export * from './file-utils.js';
export * from './rule-creator.js';
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC"}

View File

@@ -0,0 +1,3 @@
import { ESLintUtils } from '@typescript-eslint/utils';
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>;
//# sourceMappingURL=rule-creator.d.ts.map

View File

@@ -0,0 +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,qPAA2E,CAAC"}

View File

@@ -0,0 +1,5 @@
import { ESLintUtils } from '@typescript-eslint/utils';
const REPO_URL = 'https://github.com/n8n-io/n8n';
const DOCS_PATH = 'blob/master/packages/@n8n/eslint-plugin-community-nodes/docs/rules';
export const createRule = ESLintUtils.RuleCreator((name) => `${REPO_URL}/${DOCS_PATH}/${name}.md`);
//# sourceMappingURL=rule-creator.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"rule-creator.js","sourceRoot":"","sources":["../../src/utils/rule-creator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAEvD,MAAM,QAAQ,GAAG,+BAA+B,CAAC;AACjD,MAAM,SAAS,GAAG,oEAAoE,CAAC;AAEvF,MAAM,CAAC,MAAM,UAAU,GAAG,WAAW,CAAC,WAAW,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,QAAQ,IAAI,SAAS,IAAI,IAAI,KAAK,CAAC,CAAC"}