mirror of
https://github.com/n8n-io/n8n.git
synced 2026-09-24 23:22:38 +08:00
fix(Read/Write Files from Disk Node): Throw error when file not found, normalize windows path (#22455)
This commit is contained in:
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user