diff --git a/packages/nodes-base/credentials/Ftp.credentials.ts b/packages/nodes-base/credentials/Ftp.credentials.ts index 0592a8ecc4..8c6a6e6af7 100644 --- a/packages/nodes-base/credentials/Ftp.credentials.ts +++ b/packages/nodes-base/credentials/Ftp.credentials.ts @@ -1,4 +1,14 @@ -import type { ICredentialType, INodeProperties } from 'n8n-workflow'; +import type { ICredentialType } from 'n8n-workflow'; +import { CredentialSchema, type InferCredentialSchema } from '../utils/CredentialSchema'; + +const ftpCredentialSchema = CredentialSchema.create({ + host: CredentialSchema.string({ label: 'Host', placeholder: 'localhost' }), + port: CredentialSchema.number({ label: 'Port', default: 21 }), + username: CredentialSchema.string({ label: 'Username', optional: true }), + password: CredentialSchema.password({ optional: true }), +}); + +export type FtpCredentialSchema = InferCredentialSchema; export class Ftp implements ICredentialType { name = 'ftp'; @@ -7,36 +17,5 @@ export class Ftp implements ICredentialType { documentationUrl = 'ftp'; - properties: INodeProperties[] = [ - { - displayName: 'Host', - name: 'host', - required: true, - type: 'string', - default: '', - placeholder: 'localhost', - }, - { - displayName: 'Port', - name: 'port', - required: true, - type: 'number', - default: 21, - }, - { - displayName: 'Username', - name: 'username', - type: 'string', - default: '', - }, - { - displayName: 'Password', - name: 'password', - type: 'string', - typeOptions: { - password: true, - }, - default: '', - }, - ]; + properties = ftpCredentialSchema.toNodeProperties(); } diff --git a/packages/nodes-base/credentials/Sftp.credentials.ts b/packages/nodes-base/credentials/Sftp.credentials.ts index 3238097d6d..56a0a9501d 100644 --- a/packages/nodes-base/credentials/Sftp.credentials.ts +++ b/packages/nodes-base/credentials/Sftp.credentials.ts @@ -1,4 +1,23 @@ -import type { ICredentialType, INodeProperties } from 'n8n-workflow'; +import type { ICredentialType } from 'n8n-workflow'; +import { CredentialSchema, type InferCredentialSchema } from '../utils/CredentialSchema'; + +const sftpCredentialSchema = CredentialSchema.create({ + host: CredentialSchema.string({ label: 'Host', placeholder: 'localhost' }), + port: CredentialSchema.number({ label: 'Port', default: 22 }), + username: CredentialSchema.string({ label: 'Username' }), + password: CredentialSchema.password(), + privateKey: CredentialSchema.password({ + label: 'Private Key', + description: + 'String that contains a private key for either key-based or hostbased user authentication (OpenSSH format)', + }), + passphrase: CredentialSchema.password({ + label: 'Passphrase', + description: 'For an encrypted private key, this is the passphrase used to decrypt it', + }), +}); + +export type SftpCredentialSchema = InferCredentialSchema; export class Sftp implements ICredentialType { name = 'sftp'; @@ -7,55 +26,5 @@ export class Sftp implements ICredentialType { documentationUrl = 'ftp'; - properties: INodeProperties[] = [ - { - displayName: 'Host', - name: 'host', - required: true, - type: 'string', - default: '', - }, - { - displayName: 'Port', - name: 'port', - required: true, - type: 'number', - default: 22, - }, - { - displayName: 'Username', - name: 'username', - required: true, - type: 'string', - default: '', - }, - { - displayName: 'Password', - name: 'password', - type: 'string', - typeOptions: { - password: true, - }, - default: '', - }, - { - displayName: 'Private Key', - name: 'privateKey', - type: 'string', - typeOptions: { password: true }, - default: '', - description: - 'String that contains a private key for either key-based or hostbased user authentication (OpenSSH format)', - }, - { - displayName: 'Passphrase', - name: 'passphrase', - typeOptions: { - password: true, - }, - type: 'string', - default: '', - description: 'For an encrypted private key, this is the passphrase used to decrypt it', - }, - ]; + properties = sftpCredentialSchema.toNodeProperties(); } diff --git a/packages/nodes-base/nodes/Ftp/Ftp.node.ts b/packages/nodes-base/nodes/Ftp/Ftp.node.ts index 6e0ed48ff8..caf0a95463 100644 --- a/packages/nodes-base/nodes/Ftp/Ftp.node.ts +++ b/packages/nodes-base/nodes/Ftp/Ftp.node.ts @@ -7,7 +7,6 @@ import ftpClient from 'promise-ftp'; import sftpClient from 'ssh2-sftp-client'; import { BINARY_ENCODING, NodeApiError } from 'n8n-workflow'; import type { - ICredentialDataDecryptedObject, ICredentialsDecrypted, ICredentialTestFunctions, IDataObject, @@ -18,6 +17,8 @@ import type { INodeTypeDescription, JsonObject, } from 'n8n-workflow'; +import type { FtpCredentialSchema } from '@credentials/Ftp.credentials'; +import type { SftpCredentialSchema } from '@credentials/Sftp.credentials'; import { formatPrivateKey, generatePairedItemData } from '@utils/utilities'; interface ReturnFtpItem { @@ -439,14 +440,14 @@ export class Ftp implements INodeType { this: ICredentialTestFunctions, credential: ICredentialsDecrypted, ): Promise { - const credentials = credential.data as ICredentialDataDecryptedObject; + const credentials = credential.data as FtpCredentialSchema; const ftp = new ftpClient(); try { await ftp.connect({ - host: credentials.host as string, - port: credentials.port as number, - user: credentials.username as string, - password: credentials.password as string, + host: credentials.host, + port: credentials.port, + user: credentials.username, + password: credentials.password, }); } catch (error) { await ftp.end(); @@ -465,24 +466,24 @@ export class Ftp implements INodeType { this: ICredentialTestFunctions, credential: ICredentialsDecrypted, ): Promise { - const credentials = credential.data as ICredentialDataDecryptedObject; + const credentials = credential.data as SftpCredentialSchema; const sftp = new sftpClient(); try { if (credentials.privateKey) { await sftp.connect({ - host: credentials.host as string, - port: credentials.port as number, - username: credentials.username as string, - password: (credentials.password as string) || undefined, - privateKey: formatPrivateKey(credentials.privateKey as string), - passphrase: credentials.passphrase as string | undefined, + host: credentials.host, + port: credentials.port, + username: credentials.username, + password: credentials.password || undefined, + privateKey: formatPrivateKey(credentials.privateKey), + passphrase: credentials.passphrase, }); } else { await sftp.connect({ - host: credentials.host as string, - port: credentials.port as number, - username: credentials.username as string, - password: credentials.password as string, + host: credentials.host, + port: credentials.port, + username: credentials.username, + password: credentials.password, }); } } catch (error) { @@ -516,30 +517,30 @@ export class Ftp implements INodeType { try { if (protocol === 'sftp') { sftp = new sftpClient(); - if (credentials.privateKey) { + if ('privateKey' in credentials && credentials.privateKey) { await sftp.connect({ - host: credentials.host as string, - port: credentials.port as number, - username: credentials.username as string, - password: (credentials.password as string) || undefined, - privateKey: formatPrivateKey(credentials.privateKey as string), - passphrase: credentials.passphrase as string | undefined, + host: credentials.host, + port: credentials.port, + username: credentials.username, + password: credentials.password || undefined, + privateKey: formatPrivateKey(credentials.privateKey), + passphrase: credentials.passphrase, }); } else { await sftp.connect({ - host: credentials.host as string, - port: credentials.port as number, - username: credentials.username as string, - password: credentials.password as string, + host: credentials.host, + port: credentials.port, + username: credentials.username, + password: credentials.password, }); } } else { ftp = new ftpClient(); await ftp.connect({ - host: credentials.host as string, - port: credentials.port as number, - user: credentials.username as string, - password: credentials.password as string, + host: credentials.host, + port: credentials.port, + user: credentials.username, + password: credentials.password, }); } } catch (error) { diff --git a/packages/nodes-base/nodes/Strapi/GenericFunctions.ts b/packages/nodes-base/nodes/Strapi/GenericFunctions.ts index 03d0912860..4dcdc5466a 100644 --- a/packages/nodes-base/nodes/Strapi/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Strapi/GenericFunctions.ts @@ -9,7 +9,7 @@ import type { JsonObject, } from 'n8n-workflow'; import { NodeApiError } from 'n8n-workflow'; -import type { StrapiApiCredential } from '../../credentials/StrapiApi.credentials'; +import type { StrapiApiCredential } from '@credentials/StrapiApi.credentials'; export const removeTrailingSlash = (url: string) => { if (url.endsWith('/')) { diff --git a/packages/nodes-base/nodes/Strapi/Strapi.node.ts b/packages/nodes-base/nodes/Strapi/Strapi.node.ts index 3634b4e674..bd1d112f40 100644 --- a/packages/nodes-base/nodes/Strapi/Strapi.node.ts +++ b/packages/nodes-base/nodes/Strapi/Strapi.node.ts @@ -11,7 +11,6 @@ import type { } from 'n8n-workflow'; import { NodeOperationError } from 'n8n-workflow'; -import type { StrapiApiCredential } from '../../credentials/StrapiApi.credentials'; import { getToken, removeTrailingSlash, @@ -21,6 +20,7 @@ import { } from './GenericFunctions'; import { entryFields, entryOperations } from './EntryDescription'; +import type { StrapiApiCredential } from '@credentials/StrapiApi.credentials'; export class Strapi implements INodeType { description: INodeTypeDescription = { diff --git a/packages/nodes-base/tsconfig.json b/packages/nodes-base/tsconfig.json index 741d9491ea..e1796cdee0 100644 --- a/packages/nodes-base/tsconfig.json +++ b/packages/nodes-base/tsconfig.json @@ -4,7 +4,9 @@ "lib": ["dom", "es2020", "es2022.error"], "paths": { "@test/*": ["./test/*"], - "@utils/*": ["./utils/*"] + "@utils/*": ["./utils/*"], + "@nodes/*": ["./nodes/*"], + "@credentials/*": ["./credentials/*"] }, "tsBuildInfoFile": "dist/typecheck.tsbuildinfo", // TODO: remove all options below this line diff --git a/packages/nodes-base/types.d.ts b/packages/nodes-base/types.d.ts index a3dd627cc5..8fa358138b 100644 --- a/packages/nodes-base/types.d.ts +++ b/packages/nodes-base/types.d.ts @@ -1,9 +1,13 @@ +import type { FtpCredentialSchema } from './credentials/Ftp.credentials'; +import type { SftpCredentialSchema } from './credentials/Sftp.credentials'; import type { StrapiApiCredential } from './credentials/StrapiApi.credentials'; import type { StrapiTokenApiCredential } from './credentials/StrapiTokenApi.credentials'; type CredentialSchemaMap = { strapiApi: StrapiApiCredential; strapiTokenApi: StrapiTokenApiCredential; + ftp: FtpCredentialSchema; + sftp: SftpCredentialSchema; }; declare module 'n8n-workflow' { diff --git a/packages/nodes-base/utils/CredentialSchema.ts b/packages/nodes-base/utils/CredentialSchema.ts index a8b8e2ebc1..f2a7fd1d97 100644 --- a/packages/nodes-base/utils/CredentialSchema.ts +++ b/packages/nodes-base/utils/CredentialSchema.ts @@ -103,6 +103,26 @@ class CredentialSchemaString< } } +class CredentialSchemaNumber< + S extends ZodType, + M extends NumberMetadata, +> extends CredentialSchemaProperty { + constructor( + public metadata: M, + schema: S, + ) { + super(metadata, schema); + } + + toNodeProperties(name: string): INodeProperties { + return removeUndefinedProperties({ + ...super.toNodeProperties(name), + type: 'number', + default: this.metadata.default, + }); + } +} + class CredentialSchemaOptions< V extends string, S extends ZodType, @@ -157,6 +177,11 @@ type StringMetadata = BaseMetadata & default: string; }>; +type NumberMetadata = BaseMetadata & + Partial<{ + default: number; + }>; + type Option = { label: string; value: V; @@ -210,6 +235,10 @@ export const CredentialSchema = { string(options: M) { return new CredentialSchemaString(options, z.string()); }, + // eslint-disable-next-line id-denylist + number(options: M) { + return new CredentialSchemaNumber(options, z.number()); + }, url(options: Optional = {}) { return new CredentialSchemaString({ label: 'URL', ...options }, z.string().url()); },