diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index a7ff2a6765..94a59290e4 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -67,6 +67,7 @@ import { ITriggerFunctions, IWebhookFunctions, BinaryMetadata, + FileSystemHelperFunctions, } from 'n8n-workflow'; import { Agent } from 'https'; @@ -93,6 +94,8 @@ import axios, { } from 'axios'; import url, { URL, URLSearchParams } from 'url'; import type { Readable } from 'stream'; +import { access as fsAccess } from 'fs/promises'; +import { createReadStream } from 'fs'; import { BinaryDataManager } from './BinaryDataManager'; import type { IResponseError, IWorkflowSettings } from './Interfaces'; @@ -1997,6 +2000,21 @@ const getRequestHelperFunctions = ( }, }); +const getFileSystemHelperFunctions = (node: INode): FileSystemHelperFunctions => ({ + async createReadStream(filePath) { + try { + await fsAccess(filePath); + } catch (error) { + throw error.code === 'ENOENT' + ? new NodeOperationError(node, error, { + message: `The file "${String(filePath)}" could not be accessed.`, + }) + : error; + } + return createReadStream(filePath); + }, +}); + const getBinaryHelperFunctions = ({ executionId, }: IWorkflowExecuteAdditionalData): BinaryHelperFunctions => ({ @@ -2292,6 +2310,7 @@ export function getExecuteFunctions( }, helpers: { ...getRequestHelperFunctions(workflow, node, additionalData), + ...getFileSystemHelperFunctions(node), ...getBinaryHelperFunctions(additionalData), getBinaryDataBuffer: async (itemIndex, propertyName, inputIndex = 0) => getBinaryDataBuffer(inputData, itemIndex, propertyName, inputIndex), diff --git a/packages/nodes-base/nodes/ReadBinaryFile/ReadBinaryFile.node.ts b/packages/nodes-base/nodes/ReadBinaryFile/ReadBinaryFile.node.ts index 7494b5766c..dee0ddb233 100644 --- a/packages/nodes-base/nodes/ReadBinaryFile/ReadBinaryFile.node.ts +++ b/packages/nodes-base/nodes/ReadBinaryFile/ReadBinaryFile.node.ts @@ -1,12 +1,5 @@ -import { IExecuteFunctions } from 'n8n-core'; -import { - INodeExecutionData, - INodeType, - INodeTypeDescription, - NodeOperationError, -} from 'n8n-workflow'; - -import { createReadStream } from 'fs'; +import type { IExecuteFunctions } from 'n8n-core'; +import type { INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow'; export class ReadBinaryFile implements INodeType { description: INodeTypeDescription = { @@ -53,23 +46,6 @@ export class ReadBinaryFile implements INodeType { for (let itemIndex = 0; itemIndex < length; itemIndex++) { try { item = items[itemIndex]; - const dataPropertyName = this.getNodeParameter('dataPropertyName', itemIndex) as string; - const filePath = this.getNodeParameter('filePath', itemIndex) as string; - - let data; - try { - data = createReadStream(filePath); - } catch (error) { - if (error.code === 'ENOENT') { - throw new NodeOperationError( - this.getNode(), - `The file "${filePath}" could not be found.`, - ); - } - - throw error; - } - const newItem: INodeExecutionData = { json: item.json, binary: {}, @@ -85,7 +61,10 @@ export class ReadBinaryFile implements INodeType { Object.assign(newItem.binary, item.binary); } - newItem.binary![dataPropertyName] = await this.helpers.prepareBinaryData(data, filePath); + const filePath = this.getNodeParameter('filePath', itemIndex) as string; + const stream = await this.helpers.createReadStream(filePath); + const dataPropertyName = this.getNodeParameter('dataPropertyName', itemIndex) as string; + newItem.binary![dataPropertyName] = await this.helpers.prepareBinaryData(stream, filePath); returnData.push(newItem); } catch (error) { if (this.continueOnFail()) { diff --git a/packages/nodes-base/nodes/ReadBinaryFiles/ReadBinaryFiles.node.ts b/packages/nodes-base/nodes/ReadBinaryFiles/ReadBinaryFiles.node.ts index 9528970439..c080bb0a0c 100644 --- a/packages/nodes-base/nodes/ReadBinaryFiles/ReadBinaryFiles.node.ts +++ b/packages/nodes-base/nodes/ReadBinaryFiles/ReadBinaryFiles.node.ts @@ -1,8 +1,6 @@ -import { IExecuteFunctions } from 'n8n-core'; -import { INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow'; +import type { IExecuteFunctions } from 'n8n-core'; +import type { INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow'; import glob from 'fast-glob'; -import { createReadStream } from 'fs'; -import type { Readable } from 'stream'; export class ReadBinaryFiles implements INodeType { description: INodeTypeDescription = { @@ -46,22 +44,17 @@ export class ReadBinaryFiles implements INodeType { const files = await glob(fileSelector); const items: INodeExecutionData[] = []; - let item: INodeExecutionData; - let data: Readable; for (const filePath of files) { - data = createReadStream(filePath); - - item = { + const stream = await this.helpers.createReadStream(filePath); + items.push({ binary: { - [dataPropertyName]: await this.helpers.prepareBinaryData(data, filePath), + [dataPropertyName]: await this.helpers.prepareBinaryData(stream, filePath), }, json: {}, pairedItem: { item: 0, }, - }; - - items.push(item); + }); } return this.prepareOutputData(items); diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index c09e689a7a..3f9ecda086 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -15,6 +15,7 @@ import type { WorkflowActivationError } from './WorkflowActivationError'; import type { WorkflowOperationError } from './WorkflowErrors'; import type { NodeApiError, NodeOperationError } from './NodeErrors'; import type { ExpressionError } from './ExpressionError'; +import { PathLike } from 'fs'; export interface IAdditionalCredentialOptions { oauth2?: IOAuth2Options; @@ -640,6 +641,10 @@ export interface JsonHelperFunctions { returnJsonArray(jsonData: IDataObject | IDataObject[]): INodeExecutionData[]; } +export interface FileSystemHelperFunctions { + createReadStream(path: PathLike): Promise; +} + export interface BinaryHelperFunctions { prepareBinaryData( binaryData: Buffer | Readable, @@ -725,6 +730,7 @@ export type IExecuteFunctions = ExecuteFunctions.GetNodeParameterFn & helpers: RequestHelperFunctions & BinaryHelperFunctions & + FileSystemHelperFunctions & JsonHelperFunctions & { normalizeItems(items: INodeExecutionData | INodeExecutionData[]): INodeExecutionData[]; constructExecutionMetaData( diff --git a/packages/workflow/src/NodeErrors.ts b/packages/workflow/src/NodeErrors.ts index ceb7fcc5b2..694a9d18fc 100644 --- a/packages/workflow/src/NodeErrors.ts +++ b/packages/workflow/src/NodeErrors.ts @@ -217,6 +217,7 @@ abstract class NodeError extends ExecutionBaseError { } interface NodeOperationErrorOptions { + message?: string; description?: string; runIndex?: number; itemIndex?: number; @@ -234,6 +235,9 @@ export class NodeOperationError extends NodeError { } super(node, error); + if (options.message) { + this.message = options.message; + } this.description = options.description; this.context.runIndex = options.runIndex; this.context.itemIndex = options.itemIndex;