diff --git a/credentials/ExampleCredentialsApi.credentials.ts b/credentials/ExampleCredentialsApi.credentials.ts deleted file mode 100644 index 3d7f059..0000000 --- a/credentials/ExampleCredentialsApi.credentials.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { - IAuthenticateGeneric, - ICredentialTestRequest, - ICredentialType, - INodeProperties, -} from 'n8n-workflow'; - -export class ExampleCredentialsApi implements ICredentialType { - name = 'exampleCredentialsApi'; - displayName = 'Example Credentials API'; - properties: INodeProperties[] = [ - // The credentials to get from user and save encrypted. - // Properties can be defined exactly in the same way - // as node properties. - { - displayName: 'User Name', - name: 'username', - type: 'string', - default: '', - }, - { - displayName: 'Password', - name: 'password', - type: 'string', - typeOptions: { - password: true, - }, - default: '', - }, - ]; - - // This credential is currently not used by any node directly - // but the HTTP Request node can use it to make requests. - // The credential is also testable due to the `test` property below - authenticate: IAuthenticateGeneric = { - type: 'generic', - properties: { - auth: { - username: '={{ $credentials.username }}', - password: '={{ $credentials.password }}', - }, - qs: { - // Send this as part of the query string - n8n: 'rocks', - }, - }, - }; - - // The block below tells how this credential can be tested - test: ICredentialTestRequest = { - request: { - baseURL: 'https://example.com/', - url: '', - }, - }; -} diff --git a/credentials/HttpBinApi.credentials.ts b/credentials/HttpBinApi.credentials.ts deleted file mode 100644 index a3b66f5..0000000 --- a/credentials/HttpBinApi.credentials.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { - IAuthenticateGeneric, - ICredentialTestRequest, - ICredentialType, - INodeProperties, -} from 'n8n-workflow'; - -export class HttpBinApi implements ICredentialType { - name = 'httpbinApi'; - displayName = 'HttpBin API'; - documentationUrl = ''; - properties: INodeProperties[] = [ - { - displayName: 'Token', - name: 'token', - type: 'string', - default: '', - typeOptions: { - password: true, - } - }, - { - displayName: 'Domain', - name: 'domain', - type: 'string', - default: 'https://httpbin.org', - }, - ]; - - // This allows the credential to be used by other parts of n8n - // stating how this credential is injected as part of the request - // An example is the Http Request node that can make generic calls - // reusing this credential - authenticate: IAuthenticateGeneric = { - type: 'generic', - properties: { - headers: { - Authorization: '={{"Bearer " + $credentials.token}}', - }, - }, - }; - - // The block below tells how this credential can be tested - test: ICredentialTestRequest = { - request: { - baseURL: '={{$credentials?.domain}}', - url: '/bearer', - }, - }; -} diff --git a/nodes/DropALog/DropALog.node.json b/nodes/DropALog/DropALog.node.json new file mode 100644 index 0000000..c6e8198 --- /dev/null +++ b/nodes/DropALog/DropALog.node.json @@ -0,0 +1,6 @@ +{ + "node":"n8n-nodes-drop-a-log.DropALog", + "nodeVersion": "1.0", + "codexVersion": "1.0", + "categories":["Miscellaneous"], +} diff --git a/nodes/DropALog/DropALog.node.ts b/nodes/DropALog/DropALog.node.ts new file mode 100644 index 0000000..ceaa7ac --- /dev/null +++ b/nodes/DropALog/DropALog.node.ts @@ -0,0 +1,142 @@ +import { + IExecuteFunctions, + INodeExecutionData, + INodeType, + INodeTypeDescription, + IDataObject, +} from 'n8n-workflow'; + +import { execFile } from 'child_process'; + +interface IExecReturnData { + exitCode: number; + error?: Error; + stderr: string; + stdout: string; +} + +interface ILogData { + log: string; + title: string; + date: string; + includeJson: boolean; + item: any; + out: IExecReturnData|null; +} + +/** + * Promisifiy exec manually to also get the exit code + * + * @param {string} command + * @returns {Promise} + */ +function execPromise(command: string, args: string[]): Promise { + const returnData: IExecReturnData = { + exitCode: 0, + stderr: '', + stdout: '', + }; + + return new Promise((resolve, reject) => { + execFile(command, args, { cwd: process.cwd() }, (error, stdout, stderr) => { + returnData.stdout = stdout.trim(); + returnData.stderr = stderr.trim(); + + if (error) { + returnData.error = error; + } + + resolve(returnData); + }).on('exit', code => { returnData.exitCode = code || 0; }); + }); +} + +export class DropALog implements INodeType { + description: INodeTypeDescription = { + displayName: 'Drop A Log', + name: 'dropALog', + icon: 'file:dropalog.svg', + group: ['transform'], + version: 1, + description: 'Drops data into droplog', + defaults: { + name: 'Drop A Log', + color: '#772244', + }, + inputs: ['main'], + outputs: ['main'], + properties: [ + // Node properties which the user gets displayed and + // can change on the node. + { + displayName: 'Log', + name: 'log', + type: 'string', + default: 'n8n', + placeholder: 'posts', + description: 'Name of log to use', + }, + { + displayName: 'Title', + name: 'title', + type: 'string', + default: 'New Post', + placeholder: '', + description: 'Name of title for each post' + }, + { + displayName: 'Date', + name: 'date', + type: 'string', + default: 'now', + placeholder: '', + description: 'Date to use for each post' + }, + { + displayName: 'Include entire post', + name: 'includeJson', + type: 'boolean', + default: true, + description: 'If activated, the entire item will be logged. If inactive, only title and date will be used' + } + ] + }; + + async execute(this: IExecuteFunctions): Promise { + + const items = this.getInputData(); + const returnData: IDataObject[] = []; + + for (let idx = 0; idx < items.length; idx++) { + let item = items[idx]; + + let data: ILogData = { + log: this.getNodeParameter('log', idx) as string, + title: this.getNodeParameter('title', idx) as string, + date: this.getNodeParameter('date', idx) as string, + includeJson: this.getNodeParameter('includeJson', idx) as boolean, + item: null, + out: null, + }; + + const args: string[] = ['drop']; + + args.push(data.log, data.title, '-d', data.date); + + if (data.includeJson) { + const copy = JSON.parse(JSON.stringify(item.json)); + delete copy.title; + delete copy.date; + + args.push('-j', JSON.stringify(copy)); + data.item = copy; + } + + const exit = await execPromise('my-log', args); + data.out = exit; + returnData.push(data as unknown as IDataObject); + } + + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/nodes/DropALog/dropalog.svg b/nodes/DropALog/dropalog.svg new file mode 100644 index 0000000..03202e5 --- /dev/null +++ b/nodes/DropALog/dropalog.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/nodes/ExampleNode/ExampleNode.node.ts b/nodes/ExampleNode/ExampleNode.node.ts deleted file mode 100644 index a11e428..0000000 --- a/nodes/ExampleNode/ExampleNode.node.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { - IExecuteFunctions, - INodeExecutionData, - INodeType, - INodeTypeDescription, - NodeOperationError, -} from 'n8n-workflow'; - -export class ExampleNode implements INodeType { - description: INodeTypeDescription = { - displayName: 'Example Node', - name: 'exampleNode', - group: ['transform'], - version: 1, - description: 'Basic Example Node', - defaults: { - name: 'Example Node', - }, - inputs: ['main'], - outputs: ['main'], - properties: [ - // Node properties which the user gets displayed and - // can change on the node. - { - displayName: 'My String', - name: 'myString', - type: 'string', - default: '', - placeholder: 'Placeholder value', - description: 'The description text', - }, - ], - }; - - // The function below is responsible for actually doing whatever this node - // is supposed to do. In this case, we're just appending the `myString` property - // with whatever the user has entered. - // You can make async calls and use `await`. - async execute(this: IExecuteFunctions): Promise { - const items = this.getInputData(); - - let item: INodeExecutionData; - let myString: string; - - // Iterates over all input items and add the key "myString" with the - // value the parameter "myString" resolves to. - // (This could be a different value for each item in case it contains an expression) - for (let itemIndex = 0; itemIndex < items.length; itemIndex++) { - try { - myString = this.getNodeParameter('myString', itemIndex, '') as string; - item = items[itemIndex]; - - item.json['myString'] = myString; - } catch (error) { - // This node should never fail but we want to showcase how - // to handle errors. - if (this.continueOnFail()) { - items.push({ json: this.getInputData(itemIndex)[0].json, error, pairedItem: itemIndex }); - } else { - // Adding `itemIndex` allows other workflows to handle this error - if (error.context) { - // If the error thrown already contains the context property, - // only append the itemIndex - error.context.itemIndex = itemIndex; - throw error; - } - throw new NodeOperationError(this.getNode(), error, { - itemIndex, - }); - } - } - } - - return this.prepareOutputData(items); - } -} diff --git a/nodes/HttpBin/HttpBin.node.json b/nodes/HttpBin/HttpBin.node.json deleted file mode 100644 index 2e5596c..0000000 --- a/nodes/HttpBin/HttpBin.node.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "node": "n8n-nodes-base.httpbin", - "nodeVersion": "1.0", - "codexVersion": "1.0", - "categories": ["Development", "Developer Tools"], - "resources": { - "credentialDocumentation": [ - { - "url": "http://httpbin.org/#/Auth/get_bearer" - } - ], - "primaryDocumentation": [ - { - "url": "http://httpbin.org/" - } - ] - } -} diff --git a/nodes/HttpBin/HttpBin.node.ts b/nodes/HttpBin/HttpBin.node.ts deleted file mode 100644 index a65f7a9..0000000 --- a/nodes/HttpBin/HttpBin.node.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { INodeType, INodeTypeDescription } from 'n8n-workflow'; -import { httpVerbFields, httpVerbOperations } from './HttpVerbDescription'; - -export class HttpBin implements INodeType { - description: INodeTypeDescription = { - displayName: 'HttpBin', - name: 'httpBin', - icon: 'file:httpbin.svg', - group: ['transform'], - version: 1, - subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', - description: 'Interact with HttpBin API', - defaults: { - name: 'HttpBin', - }, - inputs: ['main'], - outputs: ['main'], - credentials: [ - { - name: 'httpbinApi', - required: false, - }, - ], - requestDefaults: { - baseURL: 'https://httpbin.org', - url: '', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - }, - /** - * In the properties array we have two mandatory options objects required - * - * [Resource & Operation] - * - * https://docs.n8n.io/integrations/creating-nodes/code/create-first-node/#resources-and-operations - * - * In our example, the operations are separated into their own file (HTTPVerbDescription.ts) - * to keep this class easy to read. - * - */ - properties: [ - { - displayName: 'Resource', - name: 'resource', - type: 'options', - noDataExpression: true, - options: [ - { - name: 'HTTP Verb', - value: 'httpVerb', - }, - ], - default: 'httpVerb', - }, - - ...httpVerbOperations, - ...httpVerbFields, - ], - }; -} diff --git a/nodes/HttpBin/HttpVerbDescription.ts b/nodes/HttpBin/HttpVerbDescription.ts deleted file mode 100644 index 1a9c431..0000000 --- a/nodes/HttpBin/HttpVerbDescription.ts +++ /dev/null @@ -1,248 +0,0 @@ -import { INodeProperties } from 'n8n-workflow'; - -// When the resource `httpVerb` is selected, this `operation` parameter will be shown. -export const httpVerbOperations: INodeProperties[] = [ - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - - displayOptions: { - show: { - resource: ['httpVerb'], - }, - }, - options: [ - { - name: 'GET', - value: 'get', - description: 'Perform a GET request', - routing: { - request: { - method: 'GET', - url: '/get', - }, - }, - }, - { - name: 'DELETE', - value: 'delete', - description: 'Perform a DELETE request', - routing: { - request: { - method: 'DELETE', - url: '/delete', - }, - }, - }, - ], - default: 'get', - }, -]; - -// Here we define what to show when the `get` operation is selected. -// We do that by adding `operation: ["get"]` to `displayOptions.show` -const getOperation: INodeProperties[] = [ - { - displayName: 'Type of Data', - name: 'typeofData', - default: 'queryParameter', - description: 'Select type of data to send [Query Parameters]', - displayOptions: { - show: { - resource: ['httpVerb'], - operation: ['get'], - }, - }, - type: 'options', - options: [ - { - name: 'Query', - value: 'queryParameter', - }, - ], - required: true, - }, - { - displayName: 'Query Parameters', - name: 'arguments', - default: {}, - description: "The request's query parameters", - displayOptions: { - show: { - resource: ['httpVerb'], - operation: ['get'], - }, - }, - options: [ - { - name: 'keyvalue', - displayName: 'Key:Value', - values: [ - { - displayName: 'Key', - name: 'key', - type: 'string', - default: '', - required: true, - description: 'Key of query parameter', - }, - { - displayName: 'Value', - name: 'value', - type: 'string', - default: '', - routing: { - send: { - property: '={{$parent.key}}', - type: 'query', - }, - }, - required: true, - description: 'Value of query parameter', - }, - ], - }, - ], - type: 'fixedCollection', - typeOptions: { - multipleValues: true, - }, - }, -]; - -// Here we define what to show when the DELETE Operation is selected. -// We do that by adding `operation: ["delete"]` to `displayOptions.show` -const deleteOperation: INodeProperties[] = [ - { - displayName: 'Type of Data', - name: 'typeofData', - default: 'queryParameter', - description: 'Select type of data to send [Query Parameter Arguments, JSON-Body]', - displayOptions: { - show: { - resource: ['httpVerb'], - operation: ['delete'], - }, - }, - options: [ - { - name: 'Query', - value: 'queryParameter', - }, - { - name: 'JSON', - value: 'jsonData', - }, - ], - required: true, - type: 'options', - }, - { - displayName: 'Query Parameters', - name: 'arguments', - default: {}, - description: "The request's query parameters", - displayOptions: { - show: { - resource: ['httpVerb'], - operation: ['delete'], - typeofData: ['queryParameter'], - }, - }, - options: [ - { - name: 'keyvalue', - displayName: 'Key:Value', - values: [ - { - displayName: 'Key', - name: 'key', - type: 'string', - default: '', - required: true, - description: 'Key of query parameter', - }, - { - displayName: 'Value', - name: 'value', - type: 'string', - default: '', - routing: { - send: { - property: '={{$parent.key}}', - type: 'query', - }, - }, - required: true, - description: 'Value of query parameter', - }, - ], - }, - ], - type: 'fixedCollection', - typeOptions: { - multipleValues: true, - }, - }, - { - displayName: 'JSON Object', - name: 'arguments', - default: {}, - description: "The request's JSON properties", - displayOptions: { - show: { - resource: ['httpVerb'], - operation: ['delete'], - typeofData: ['jsonData'], - }, - }, - options: [ - { - name: 'keyvalue', - displayName: 'Key:Value', - values: [ - { - displayName: 'Key', - name: 'key', - type: 'string', - default: '', - required: true, - description: 'Key of JSON property', - }, - { - displayName: 'Value', - name: 'value', - type: 'string', - default: '', - routing: { - send: { - property: '={{$parent.key}}', - type: 'body', - }, - }, - required: true, - description: 'Value of JSON property', - }, - ], - }, - ], - type: 'fixedCollection', - typeOptions: { - multipleValues: true, - }, - }, -]; - -export const httpVerbFields: INodeProperties[] = [ - /* -------------------------------------------------------------------------- */ - /* httpVerb:get */ - /* -------------------------------------------------------------------------- */ - ...getOperation, - - /* -------------------------------------------------------------------------- */ - /* httpVerb:delete */ - /* -------------------------------------------------------------------------- */ - ...deleteOperation, -]; diff --git a/nodes/HttpBin/httpbin.svg b/nodes/HttpBin/httpbin.svg deleted file mode 100644 index aee1de1..0000000 --- a/nodes/HttpBin/httpbin.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - diff --git a/package.json b/package.json index e94475c..d2b125e 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "n8n-nodes-<...>", + "name": "n8n-nodes-drop-a-log", "version": "0.1.0", "description": "", "keywords": [ @@ -8,13 +8,10 @@ "license": "MIT", "homepage": "", "author": { - "name": "", - "email": "" - }, - "repository": { - "type": "git", - "url": "https://github.com/<...>/n8n-nodes-<...>.git" + "name": "Dan Jones", + "email": "danjones@goodevilgenius.org" }, + "repository": {}, "engines": { "node": ">=18.10", "pnpm": ">=9.1" @@ -35,16 +32,13 @@ ], "n8n": { "n8nNodesApiVersion": 1, - "credentials": [ - "dist/credentials/ExampleCredentialsApi.credentials.js", - "dist/credentials/HttpBinApi.credentials.js" - ], + "credentials": [], "nodes": [ - "dist/nodes/ExampleNode/ExampleNode.node.js", - "dist/nodes/HttpBin/HttpBin.node.js" + "dist/nodes/DropALog/DropALog.node.js" ] }, "devDependencies": { + "@types/node": "^22.7.1", "@typescript-eslint/parser": "^7.15.0", "eslint": "^8.56.0", "eslint-plugin-n8n-nodes-base": "^1.16.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 210ada7..d8813c4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: devDependencies: + '@types/node': + specifier: ^22.7.1 + version: 22.7.1 '@typescript-eslint/parser': specifier: ^7.15.0 version: 7.15.0(eslint@8.57.0)(typescript@5.5.3) @@ -88,6 +91,9 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/node@22.7.1': + resolution: {integrity: sha512-adOMRLVmleuWs/5V/w5/l7o0chDK/az+5ncCsIapTKogsu/3MVWvSgP58qVTXi5IwpfGt8pMobNq9rOWtJyu5Q==} + '@types/semver@7.5.8': resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} @@ -758,7 +764,7 @@ packages: resolution: {integrity: sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==} engines: {node: '>= 4.0'} os: [darwin] - deprecated: The v1 package contains DANGEROUS / INSECURE binaries. Upgrade to safe fsevents v2 + deprecated: Upgrade to fsevents v2 to mitigate potential security issues function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} @@ -1874,6 +1880,9 @@ packages: resolution: {integrity: sha512-/RXwi5m/Mu3H6IHQGww3GNt1PNXlbeCuclF2QYR14L/2CHPz3DFZkvB5hZ0N/QUkiXWCACML2jXViIQEQc2MLg==} engines: {node: '>= 0.10'} + undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + union-value@1.0.1: resolution: {integrity: sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==} engines: {node: '>=0.10.0'} @@ -2073,6 +2082,10 @@ snapshots: '@types/json-schema@7.0.15': {} + '@types/node@22.7.1': + dependencies: + undici-types: 6.19.8 + '@types/semver@7.5.8': {} '@typescript-eslint/parser@7.15.0(eslint@8.57.0)(typescript@5.5.3)': @@ -4096,6 +4109,8 @@ snapshots: object.reduce: 1.0.1 undertaker-registry: 1.0.1 + undici-types@6.19.8: {} + union-value@1.0.1: dependencies: arr-union: 3.1.0