feat(editor): Add HTTP request nodes for credentials without a node (#7157)
Github issue / Community forum post (link here to close automatically): --------- Co-authored-by: Giulio Andreini <g.andreini@gmail.com> Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>
This commit is contained in:
@@ -2,7 +2,8 @@
|
||||
|
||||
const path = require('path');
|
||||
const glob = require('fast-glob');
|
||||
const { LoggerProxy } = require('n8n-workflow');
|
||||
const uniq = require('lodash/uniq');
|
||||
const { LoggerProxy, getCredentialsForNode } = require('n8n-workflow');
|
||||
const { packageDir, writeJSON } = require('./common');
|
||||
const { loadClassInIsolation } = require('../dist/ClassLoader');
|
||||
|
||||
@@ -19,48 +20,80 @@ const loadClass = (sourcePath) => {
|
||||
}
|
||||
};
|
||||
|
||||
const nodesToTestWith = {};
|
||||
|
||||
const generate = async (kind) => {
|
||||
const data = glob
|
||||
.sync(`dist/${kind}/**/*.${kind === 'nodes' ? 'node' : kind}.js`, {
|
||||
cwd: packageDir,
|
||||
})
|
||||
const generateKnownNodes = async () => {
|
||||
const nodeClasses = glob
|
||||
.sync('dist/nodes/**/*.node.js', { cwd: packageDir })
|
||||
.map(loadClass)
|
||||
.filter((data) => !!data)
|
||||
.reduce((obj, { className, sourcePath, instance }) => {
|
||||
const name = kind === 'nodes' ? instance.description.name : instance.name;
|
||||
if (!/[vV]\d.node\.js$/.test(sourcePath)) {
|
||||
if (name in obj) console.error('already loaded', kind, name, sourcePath);
|
||||
else obj[name] = { className, sourcePath };
|
||||
// Ignore node versions
|
||||
.filter((nodeClass) => nodeClass && !/[vV]\d.node\.js$/.test(nodeClass.sourcePath));
|
||||
|
||||
const nodes = {};
|
||||
const nodesByCredential = {};
|
||||
|
||||
for (const { className, sourcePath, instance } of nodeClasses) {
|
||||
const nodeName = instance.description.name;
|
||||
nodes[nodeName] = { className, sourcePath };
|
||||
|
||||
for (const credential of getCredentialsForNode(instance)) {
|
||||
if (!nodesByCredential[credential.name]) {
|
||||
nodesByCredential[credential.name] = [];
|
||||
}
|
||||
|
||||
if (kind === 'credentials' && Array.isArray(instance.extends)) {
|
||||
obj[name].extends = instance.extends;
|
||||
nodesByCredential[credential.name].push(nodeName);
|
||||
}
|
||||
}
|
||||
|
||||
LoggerProxy.info(`Detected ${Object.keys(nodes).length} nodes`);
|
||||
await writeJSON('known/nodes.json', nodes);
|
||||
return { nodes, nodesByCredential };
|
||||
};
|
||||
|
||||
const generateKnownCredentials = async (nodesByCredential) => {
|
||||
const credentialClasses = glob
|
||||
.sync(`dist/credentials/**/*.credentials.js`, { cwd: packageDir })
|
||||
.map(loadClass)
|
||||
.filter((data) => !!data);
|
||||
|
||||
for (const { instance } of credentialClasses) {
|
||||
if (Array.isArray(instance.extends)) {
|
||||
for (const extendedCredential of instance.extends) {
|
||||
nodesByCredential[extendedCredential] = [
|
||||
...(nodesByCredential[extendedCredential] ?? []),
|
||||
...(nodesByCredential[instance.name] ?? []),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const credentials = credentialClasses.reduce(
|
||||
(credentials, { className, sourcePath, instance }) => {
|
||||
const credentialName = instance.name;
|
||||
const credential = {
|
||||
className,
|
||||
sourcePath,
|
||||
};
|
||||
|
||||
if (Array.isArray(instance.extends)) {
|
||||
credential.extends = instance.extends;
|
||||
}
|
||||
|
||||
if (kind === 'nodes') {
|
||||
const { credentials } = instance.description;
|
||||
if (credentials && credentials.length) {
|
||||
for (const credential of credentials) {
|
||||
nodesToTestWith[credential.name] = nodesToTestWith[credential.name] || [];
|
||||
nodesToTestWith[credential.name].push(name);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (name in nodesToTestWith) {
|
||||
obj[name].nodesToTestWith = nodesToTestWith[name];
|
||||
}
|
||||
if (nodesByCredential[credentialName]) {
|
||||
credential.supportedNodes = Array.from(new Set(nodesByCredential[credentialName]));
|
||||
}
|
||||
return obj;
|
||||
}, {});
|
||||
|
||||
LoggerProxy.info(`Detected ${Object.keys(data).length} ${kind}`);
|
||||
await writeJSON(`known/${kind}.json`, data);
|
||||
return data;
|
||||
credentials[credentialName] = credential;
|
||||
|
||||
return credentials;
|
||||
},
|
||||
{},
|
||||
);
|
||||
|
||||
LoggerProxy.info(`Detected ${Object.keys(credentials).length} credentials`);
|
||||
await writeJSON('known/credentials.json', credentials);
|
||||
return credentials;
|
||||
};
|
||||
|
||||
(async () => {
|
||||
await generate('nodes');
|
||||
await generate('credentials');
|
||||
const { nodesByCredential } = await generateKnownNodes();
|
||||
await generateKnownCredentials(nodesByCredential);
|
||||
})();
|
||||
|
||||
@@ -45,7 +45,14 @@ function addWebhookLifecycle(nodeType) {
|
||||
const loader = new PackageDirectoryLoader(packageDir);
|
||||
await loader.loadAll();
|
||||
|
||||
const credentialTypes = Object.values(loader.credentialTypes).map((data) => data.type);
|
||||
const knownCredentials = loader.known.credentials;
|
||||
const credentialTypes = Object.values(loader.credentialTypes).map((data) => {
|
||||
const credentialType = data.type;
|
||||
if (knownCredentials[credentialType.name].supportedNodes?.length > 0) {
|
||||
delete credentialType.httpRequestNode;
|
||||
}
|
||||
return credentialType;
|
||||
});
|
||||
|
||||
const loaderNodeTypes = Object.values(loader.nodeTypes);
|
||||
|
||||
@@ -75,13 +82,12 @@ function addWebhookLifecycle(nodeType) {
|
||||
addWebhookLifecycle(nodeType);
|
||||
return data.type;
|
||||
})
|
||||
.flatMap((nodeData) => {
|
||||
return NodeHelpers.getVersionedNodeTypeAll(nodeData).map((item) => {
|
||||
.flatMap((nodeType) =>
|
||||
NodeHelpers.getVersionedNodeTypeAll(nodeType).map((item) => {
|
||||
const { __loadOptionsMethods, ...rest } = item.description;
|
||||
|
||||
return rest;
|
||||
});
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
const referencedMethods = findReferencedMethods(nodeTypes);
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import * as path from 'path';
|
||||
import { readFile } from 'fs/promises';
|
||||
import glob from 'fast-glob';
|
||||
import { jsonParse, getVersionedNodeTypeAll, LoggerProxy as Logger } from 'n8n-workflow';
|
||||
import { readFile } from 'fs/promises';
|
||||
import type {
|
||||
CodexData,
|
||||
DocumentationLink,
|
||||
@@ -9,15 +7,22 @@ import type {
|
||||
ICredentialTypeData,
|
||||
INodeType,
|
||||
INodeTypeBaseDescription,
|
||||
INodeTypeDescription,
|
||||
INodeTypeData,
|
||||
INodeTypeDescription,
|
||||
INodeTypeNameVersion,
|
||||
IVersionedNodeType,
|
||||
KnownNodesAndCredentials,
|
||||
} from 'n8n-workflow';
|
||||
import {
|
||||
LoggerProxy as Logger,
|
||||
getCredentialsForNode,
|
||||
getVersionedNodeTypeAll,
|
||||
jsonParse,
|
||||
} from 'n8n-workflow';
|
||||
import * as path from 'path';
|
||||
import { loadClassInIsolation } from './ClassLoader';
|
||||
import { CUSTOM_NODES_CATEGORY } from './Constants';
|
||||
import type { n8n } from './Interfaces';
|
||||
import { loadClassInIsolation } from './ClassLoader';
|
||||
|
||||
function toJSON(this: ICredentialType) {
|
||||
return {
|
||||
@@ -44,6 +49,8 @@ export abstract class DirectoryLoader {
|
||||
|
||||
types: Types = { nodes: [], credentials: [] };
|
||||
|
||||
protected nodesByCredential: Record<string, string[]> = {};
|
||||
|
||||
constructor(
|
||||
readonly directory: string,
|
||||
protected readonly excludeNodes: string[] = [],
|
||||
@@ -140,6 +147,13 @@ export abstract class DirectoryLoader {
|
||||
getVersionedNodeTypeAll(tempNode).forEach(({ description }) => {
|
||||
this.types.nodes.push(description);
|
||||
});
|
||||
|
||||
for (const credential of getCredentialsForNode(tempNode)) {
|
||||
if (!this.nodesByCredential[credential.name]) {
|
||||
this.nodesByCredential[credential.name] = [];
|
||||
}
|
||||
this.nodesByCredential[credential.name].push(fullNodeName);
|
||||
}
|
||||
}
|
||||
|
||||
protected loadCredentialFromFile(credentialName: string, filePath: string): void {
|
||||
@@ -168,6 +182,7 @@ export abstract class DirectoryLoader {
|
||||
className: credentialName,
|
||||
sourcePath: filePath,
|
||||
extends: tempCredential.extends,
|
||||
supportedNodes: this.nodesByCredential[tempCredential.name],
|
||||
};
|
||||
|
||||
this.credentialTypes[tempCredential.name] = {
|
||||
@@ -276,19 +291,24 @@ export class CustomDirectoryLoader extends DirectoryLoader {
|
||||
packageName = 'CUSTOM';
|
||||
|
||||
override async loadAll() {
|
||||
const filePaths = await glob('**/*.@(node|credentials).js', {
|
||||
const nodes = await glob('**/*.node.js', {
|
||||
cwd: this.directory,
|
||||
absolute: true,
|
||||
});
|
||||
|
||||
for (const filePath of filePaths) {
|
||||
const [fileName, type] = path.parse(filePath).name.split('.');
|
||||
for (const nodePath of nodes) {
|
||||
const [fileName] = path.parse(nodePath).name.split('.');
|
||||
this.loadNodeFromFile(fileName, nodePath);
|
||||
}
|
||||
|
||||
if (type === 'node') {
|
||||
this.loadNodeFromFile(fileName, filePath);
|
||||
} else if (type === 'credentials') {
|
||||
this.loadCredentialFromFile(fileName, filePath);
|
||||
}
|
||||
const credentials = await glob('**/*.credentials.js', {
|
||||
cwd: this.directory,
|
||||
absolute: true,
|
||||
});
|
||||
|
||||
for (const credentialPath of credentials) {
|
||||
const [fileName] = path.parse(credentialPath).name.split('.');
|
||||
this.loadCredentialFromFile(fileName, credentialPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -315,15 +335,6 @@ export class PackageDirectoryLoader extends DirectoryLoader {
|
||||
|
||||
const { nodes, credentials } = n8n;
|
||||
|
||||
if (Array.isArray(credentials)) {
|
||||
for (const credential of credentials) {
|
||||
const filePath = this.resolvePath(credential);
|
||||
const [credentialName] = path.parse(credential).name.split('.');
|
||||
|
||||
this.loadCredentialFromFile(credentialName, filePath);
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(nodes)) {
|
||||
for (const node of nodes) {
|
||||
const filePath = this.resolvePath(node);
|
||||
@@ -333,6 +344,15 @@ export class PackageDirectoryLoader extends DirectoryLoader {
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(credentials)) {
|
||||
for (const credential of credentials) {
|
||||
const filePath = this.resolvePath(credential);
|
||||
const [credentialName] = path.parse(credential).name.split('.');
|
||||
|
||||
this.loadCredentialFromFile(credentialName, filePath);
|
||||
}
|
||||
}
|
||||
|
||||
Logger.debug(`Loaded all credentials and nodes from ${this.packageName}`, {
|
||||
credentials: credentials?.length ?? 0,
|
||||
nodes: nodes?.length ?? 0,
|
||||
|
||||
Reference in New Issue
Block a user