fix(Read/Write Files from Disk Node): Throw error when file not found, normalize windows path (#22455)

This commit is contained in:
yehorkardash
2025-12-08 10:54:11 +01:00
committed by GitHub
parent 8364430471
commit 4df815e6dd
4 changed files with 68 additions and 22 deletions
@@ -15,7 +15,7 @@ export class ReadWriteFile implements INodeType {
name: 'readWriteFile',
icon: 'file:readWriteFile.svg',
group: ['input'],
version: 1,
version: [1, 1.1],
description: 'Read or write files from the computer that runs n8n',
defaults: {
name: 'Read/Write Files from Disk',
@@ -1,5 +1,5 @@
import glob from 'fast-glob';
import { NodeApiError } from 'n8n-workflow';
import { NodeApiError, NodeOperationError } from 'n8n-workflow';
import type {
IExecuteFunctions,
INodeExecutionData,
@@ -9,7 +9,7 @@ import type {
import { updateDisplayOptions } from '@utils/utilities';
import { errorMapper, escapeSpecialCharacters } from '../helpers/utils';
import { errorMapper, normalizeFileSelector } from '../helpers/utils';
export const properties: INodeProperties[] = [
{
@@ -76,18 +76,15 @@ const displayOptions = {
export const description = updateDisplayOptions(displayOptions, properties);
export async function execute(this: IExecuteFunctions, items: INodeExecutionData[]) {
const nodeVersion = this.getNode().typeVersion;
const returnData: INodeExecutionData[] = [];
let fileSelector;
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
try {
fileSelector = String(this.getNodeParameter('fileSelector', itemIndex));
fileSelector = escapeSpecialCharacters(fileSelector);
if (/^[a-zA-Z]:/.test(fileSelector)) {
fileSelector = fileSelector.replace(/\\\\/g, '/');
}
fileSelector = normalizeFileSelector(
this.getNodeParameter('fileSelector', itemIndex) as string,
);
const options = this.getNodeParameter('options', itemIndex, {});
@@ -99,6 +96,13 @@ export async function execute(this: IExecuteFunctions, items: INodeExecutionData
const files = await glob(fileSelector);
if (files.length === 0 && nodeVersion > 1) {
throw new NodeOperationError(this.getNode(), 'No file(s) found', {
itemIndex,
description: `No file matching the selector "${fileSelector}" found`,
});
}
const newItems: INodeExecutionData[] = [];
for (const filePath of files) {
const stream = await this.helpers.createReadStream(filePath);
@@ -134,14 +138,14 @@ export async function execute(this: IExecuteFunctions, items: INodeExecutionData
}
returnData.push(...newItems);
} catch (error) {
const nodeOperatioinError = errorMapper.call(this, error, itemIndex, {
const nodeOperationError = errorMapper.call(this, error, itemIndex, {
filePath: fileSelector,
operation: 'read',
});
if (this.continueOnFail()) {
returnData.push({
json: {
error: nodeOperatioinError.message,
error: nodeOperationError.message,
},
pairedItem: {
item: itemIndex,
@@ -1,5 +1,6 @@
import type { IDataObject, IExecuteFunctions } from 'n8n-workflow';
import { NodeOperationError } from 'n8n-workflow';
import path from 'node:path';
export function errorMapper(
this: IExecuteFunctions,
@@ -39,3 +40,16 @@ export function escapeSpecialCharacters(str: string) {
return str;
}
export function normalizeFileSelector(fileSelectorRaw: string) {
let fileSelector = String(fileSelectorRaw);
const isWindows = /^[a-zA-Z]:/.test(fileSelector);
if (isWindows) {
fileSelector = path.win32.normalize(fileSelector).replace(/\\/g, '/');
}
fileSelector = escapeSpecialCharacters(fileSelector);
return fileSelector;
}
@@ -1,15 +1,43 @@
import { escapeSpecialCharacters } from '../helpers/utils';
import { escapeSpecialCharacters, normalizeFileSelector } from '../helpers/utils';
describe('Read/Write Files from Disk, escapeSpecialCharacters', () => {
it('should escape parentheses in a string', () => {
const input = '/home/michael/Desktop/test(1).txt';
const expectedOutput = '/home/michael/Desktop/test\\(1\\).txt';
expect(escapeSpecialCharacters(input)).toBe(expectedOutput);
describe('Read/Write Files from Disk', () => {
describe('escapeSpecialCharacters', () => {
it('should escape parentheses in a string', () => {
const input = '/home/michael/Desktop/test(1).txt';
const expectedOutput = '/home/michael/Desktop/test\\(1\\).txt';
expect(escapeSpecialCharacters(input)).toBe(expectedOutput);
});
it('should not modify strings that do not contain parentheses', () => {
const input = '/home/michael/Desktop/test.txt';
const expectedOutput = '/home/michael/Desktop/test.txt';
expect(escapeSpecialCharacters(input)).toBe(expectedOutput);
});
});
it('should not modify strings that do not contain parentheses', () => {
const input = '/home/michael/Desktop/test.txt';
const expectedOutput = '/home/michael/Desktop/test.txt';
expect(escapeSpecialCharacters(input)).toBe(expectedOutput);
describe('normalizeFileSelector', () => {
it('should normalize UNIX file selector with parentheses', () => {
const input = '/home/michael/Desktop/test(1).txt';
const expectedOutput = '/home/michael/Desktop/test\\(1\\).txt';
expect(normalizeFileSelector(input)).toBe(expectedOutput);
});
it('should normalize Windows file selector with \\ and parentheses', () => {
const input = 'C:\\Users\\michael\\Desktop\\test(1).txt';
const expectedOutput = 'C:/Users/michael/Desktop/test\\(1\\).txt';
expect(normalizeFileSelector(input)).toBe(expectedOutput);
});
it('should normalize Windows file selector with \\\\', () => {
const input = 'C:\\\\Users\\\\michael\\\\Desktop\\\\test.txt';
const expectedOutput = 'C:/Users/michael/Desktop/test.txt';
expect(normalizeFileSelector(input)).toBe(expectedOutput);
});
it('should normalize Windows file selector with /', () => {
const input = 'C:/Users/michael/Desktop/test.txt';
const expectedOutput = 'C:/Users/michael/Desktop/test.txt';
expect(normalizeFileSelector(input)).toBe(expectedOutput);
});
});
});