From 4f9aee14b59f219074d8bf57de3eb33565752699 Mon Sep 17 00:00:00 2001 From: Jan Date: Wed, 3 Nov 2021 09:02:20 -0600 Subject: [PATCH] :sparkles: Add Local File Trigger Node (#2375) * :sparkles: Add File System Watch Trigger Node * :zap: Improvements --- .../nodes-base/nodes/LocalFileTrigger.node.ts | 219 ++++++++++++++++++ packages/nodes-base/package.json | 2 + 2 files changed, 221 insertions(+) create mode 100644 packages/nodes-base/nodes/LocalFileTrigger.node.ts diff --git a/packages/nodes-base/nodes/LocalFileTrigger.node.ts b/packages/nodes-base/nodes/LocalFileTrigger.node.ts new file mode 100644 index 0000000000..812a3695e6 --- /dev/null +++ b/packages/nodes-base/nodes/LocalFileTrigger.node.ts @@ -0,0 +1,219 @@ +import { ITriggerFunctions } from 'n8n-core'; +import { + IDataObject, + INodeType, + INodeTypeDescription, + ITriggerResponse, +} from 'n8n-workflow'; + +import { watch } from 'chokidar'; + + +export class LocalFileTrigger implements INodeType { + description: INodeTypeDescription = { + displayName: 'Local File Trigger', + name: 'localFileTrigger', + icon: 'fa:folder-open', + group: ['trigger'], + version: 1, + subtitle: '=Path: {{$parameter["path"]}}', + description: 'Triggers a workflow on file system changes', + defaults: { + name: 'Local File Trigger', + color: '#404040', + }, + inputs: [], + outputs: ['main'], + properties: [ + { + displayName: 'Trigger on', + name: 'triggerOn', + type: 'options', + options: [ + { + name: 'Changes to a Specific File', + value: 'file', + }, + { + name: 'Changes Involving a Specific Folder', + value: 'folder', + }, + ], + required: true, + default: '', + }, + { + displayName: 'File to Watch', + name: 'path', + type: 'string', + displayOptions: { + show: { + triggerOn: [ + 'file', + ], + }, + }, + default: '', + placeholder: '/data/invoices/1.pdf', + }, + { + displayName: 'Folder to Watch', + name: 'path', + type: 'string', + displayOptions: { + show: { + triggerOn: [ + 'folder', + ], + }, + }, + default: '', + placeholder: '/data/invoices', + }, + { + displayName: 'Watch for', + name: 'events', + type: 'multiOptions', + displayOptions: { + show: { + triggerOn: [ + 'folder', + ], + }, + }, + options: [ + { + name: 'File Added', + value: 'add', + description: 'Triggers whenever a new file was added', + }, + { + name: 'File Changed', + value: 'change', + description: 'Triggers whenever a file was changed', + }, + { + name: 'File Deleted', + value: 'unlink', + description: 'Triggers whenever a file was deleted', + }, + { + name: 'Folder Added', + value: 'addDir', + description: 'Triggers whenever a new folder was added', + }, + { + name: 'Folder Deleted', + value: 'unlinkDir', + description: 'Triggers whenever a folder was deleted', + }, + ], + required: true, + default: [], + description: 'The events to listen to', + }, + + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + options: [ + { + displayName: 'Include Linked Files/Folders', + name: 'followSymlinks', + type: 'boolean', + default: true, + description: 'When activated, linked files/folders will also be watched (this includes symlinks, aliases on MacOS and shortcuts on Windows). Otherwise only the links themselves will be monitored).', + }, + { + displayName: 'Ignore', + name: 'ignored', + type: 'string', + default: '', + placeholder: '**/*.txt', + description: 'Files or paths to ignore. The whole path is tested, not just the filename. Supports Anymatch- syntax.', + }, + { + displayName: 'Max Folder Depth', + name: 'depth', + type: 'options', + options: [ + { + name: 'Unlimited', + value: -1, + }, + { + name: '5 Levels Down', + value: 5, + }, + { + name: '4 Levels Down', + value: 4, + }, + { + name: '3 Levels Down', + value: 3, + }, + { + name: '2 Levels Down', + value: 2, + }, + { + name: '1 Levels Down', + value: 1, + }, + { + name: 'Top Folder Only', + value: 0, + }, + ], + default: -1, + description: 'How deep into the folder structure to watch for changes', + }, + ], + }, + + ], + }; + + + async trigger(this: ITriggerFunctions): Promise { + const triggerOn = this.getNodeParameter('triggerOn') as string; + const path = this.getNodeParameter('path') as string; + const options = this.getNodeParameter('options', {}) as IDataObject; + + let events: string[]; + if (triggerOn === 'file') { + events = [ 'change' ]; + } else { + events = this.getNodeParameter('events', []) as string[]; + } + + const watcher = watch(path, { + ignored: options.ignored, + persistent: true, + ignoreInitial: true, + followSymlinks: options.followSymlinks === undefined ? true : options.followSymlinks as boolean, + depth: [-1, undefined].includes(options.depth as number) ? undefined : options.depth as number, + }); + + const executeTrigger = (event: string, path: string) => { + this.emit([this.helpers.returnJsonArray([{ event,path }])]); + }; + + for (const eventName of events) { + watcher.on(eventName, path => executeTrigger(eventName, path)); + } + + function closeFunction() { + return watcher.close(); + } + + return { + closeFunction, + }; + + } +} diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 2760b61294..238e4226c9 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -469,6 +469,7 @@ "dist/nodes/Line/Line.node.js", "dist/nodes/LingvaNex/LingvaNex.node.js", "dist/nodes/LinkedIn/LinkedIn.node.js", + "dist/nodes/LocalFileTrigger.node.js", "dist/nodes/Magento/Magento2.node.js", "dist/nodes/MailerLite/MailerLite.node.js", "dist/nodes/MailerLite/MailerLiteTrigger.node.js", @@ -665,6 +666,7 @@ "@types/tmp": "^0.2.0", "@types/uuid": "^8.3.0", "@types/xml2js": "^0.4.3", + "chokidar": "^3.5.2", "gulp": "^4.0.0", "jest": "^26.4.2", "n8n-workflow": "~0.74.0",