From 1e4aa5109a5aff8c91b5c4ef56a239fbcf02a15f Mon Sep 17 00:00:00 2001 From: Nicholas Penree Date: Fri, 6 Feb 2026 12:27:13 -0500 Subject: [PATCH] Add file name pattern filtering to FTP trigger Allow users to filter files by glob pattern (e.g. "HR_Feed*.csv") so the trigger only fires for matching files. Pattern is optional and stored in the node options. When empty, all files match (backward compatible). --- .context/notes.md | 0 .context/todos.md | 0 nodes/FtpTrigger/FtpTrigger.node.ts | 33 +++++++++++++++++++ package-lock.json | 51 ++++++++++++++++++++++++----- package.json | 2 ++ 5 files changed, 77 insertions(+), 9 deletions(-) create mode 100644 .context/notes.md create mode 100644 .context/todos.md diff --git a/.context/notes.md b/.context/notes.md new file mode 100644 index 0000000..e69de29 diff --git a/.context/todos.md b/.context/todos.md new file mode 100644 index 0000000..e69de29 diff --git a/nodes/FtpTrigger/FtpTrigger.node.ts b/nodes/FtpTrigger/FtpTrigger.node.ts index 65524e8..c33d20e 100644 --- a/nodes/FtpTrigger/FtpTrigger.node.ts +++ b/nodes/FtpTrigger/FtpTrigger.node.ts @@ -10,6 +10,7 @@ import { NodeApiError } from 'n8n-workflow'; import moment from 'moment'; import { basename } from 'path'; +import picomatch from 'picomatch'; import ftpClient from 'promise-ftp'; import sftpClient from 'ssh2-sftp-client'; @@ -251,6 +252,29 @@ export class FtpTrigger implements INodeType { }, ], }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + triggerOn: ['specificFolder'], + }, + }, + options: [ + { + displayName: 'Filename Filter', + name: 'fileNamePattern', + type: 'string', + default: '', + placeholder: '*.csv', + description: + 'Glob pattern to filter files by name (e.g. "HR_Feed*.csv"). Leave empty to match all files.', + }, + ], + }, { displayName: "Changes within subfolders won't trigger this node", name: 'asas', @@ -307,6 +331,9 @@ export class FtpTrigger implements INodeType { const triggerOn = this.getNodeParameter('triggerOn') as string; const event = this.getNodeParameter('event') as string; const isFileEvent = event.startsWith('file'); + const options = this.getNodeParameter('options', {}) as IDataObject; + const fileNamePattern = (options.fileNamePattern as string) || ''; + const isMatch = fileNamePattern ? picomatch(fileNamePattern, { dot: true }) : null; const webhookData = this.getWorkflowStaticData('node'); const now = moment().utc().format(); let responseData; @@ -363,6 +390,12 @@ export class FtpTrigger implements INodeType { : normalizeFtpItem(item as ftpClient.ListingElement, path), ) || []; + if (isMatch) { + files = files.filter((file: ReturnFtpItem) => + file.type !== '-' || isMatch(file.name), + ); + } + const updatedFileMap = files.reduce((obj: IDataObject, file: ReturnFtpItem) => { obj[file.path] = { type: file.type, diff --git a/package-lock.json b/package-lock.json index a929bc8..33c6668 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,11 +9,13 @@ "version": "1.0.1", "license": "MIT", "dependencies": { + "picomatch": "^4.0.2", "promise-ftp": "^1.3.5", "ssh2-sftp-client": "^9.1.0" }, "devDependencies": { "@types/express": "^4.17.6", + "@types/picomatch": "^3.0.1", "@types/promise-ftp": "^1.3.4", "@types/request-promise-native": "~1.0.15", "@types/ssh2-sftp-client": "^9.0.0", @@ -252,6 +254,13 @@ "integrity": "sha512-IXl7o+R9iti9eBW4Wg2hx1xQDig183jj7YLn8F7udNceyfkbn1ZxmzZXuak20gR40D7pIkIY1kYGx5VIGbaHKA==", "dev": true }, + "node_modules/@types/picomatch": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/picomatch/-/picomatch-3.0.2.tgz", + "integrity": "sha512-n0i8TD3UDB7paoMMxA3Y65vUncFJXjcUf7lQY7YyKGl6031FNjfsLs6pdLFCy2GNFxItPJG8GvvpbZc2skH7WA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/promise-ftp": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/@types/promise-ftp/-/promise-ftp-1.3.4.tgz", @@ -4341,6 +4350,19 @@ "node": ">=0.12.0" } }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/micromatch/node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -5108,12 +5130,12 @@ } }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", "engines": { - "node": ">=8.6" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" @@ -7666,6 +7688,12 @@ "integrity": "sha512-IXl7o+R9iti9eBW4Wg2hx1xQDig183jj7YLn8F7udNceyfkbn1ZxmzZXuak20gR40D7pIkIY1kYGx5VIGbaHKA==", "dev": true }, + "@types/picomatch": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/picomatch/-/picomatch-3.0.2.tgz", + "integrity": "sha512-n0i8TD3UDB7paoMMxA3Y65vUncFJXjcUf7lQY7YyKGl6031FNjfsLs6pdLFCy2GNFxItPJG8GvvpbZc2skH7WA==", + "dev": true + }, "@types/promise-ftp": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/@types/promise-ftp/-/promise-ftp-1.3.4.tgz", @@ -10842,6 +10870,12 @@ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -11451,10 +11485,9 @@ "dev": true }, "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==" }, "pify": { "version": "2.3.0", diff --git a/package.json b/package.json index cac5d9d..3f811ae 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "@types/request-promise-native": "~1.0.15", "@types/ssh2-sftp-client": "^9.0.0", "@typescript-eslint/parser": "~5.45", + "@types/picomatch": "^3.0.1", "eslint-plugin-n8n-nodes-base": "^1.11.0", "gulp": "^4.0.2", "n8n-core": "*", @@ -51,6 +52,7 @@ "typescript": "~4.8.4" }, "dependencies": { + "picomatch": "^4.0.2", "promise-ftp": "^1.3.5", "ssh2-sftp-client": "^9.1.0" }