diff --git a/packages/@n8n/api-types/src/dto/dynamic-node-parameters/__tests__/resource-locator-request.dto.test.ts b/packages/@n8n/api-types/src/dto/dynamic-node-parameters/__tests__/resource-locator-request.dto.test.ts index 1f439a05a86b2..d64f31dec23da 100644 --- a/packages/@n8n/api-types/src/dto/dynamic-node-parameters/__tests__/resource-locator-request.dto.test.ts +++ b/packages/@n8n/api-types/src/dto/dynamic-node-parameters/__tests__/resource-locator-request.dto.test.ts @@ -42,6 +42,13 @@ describe('ResourceLocatorRequestDto', () => { currentNodeParameters: { param1: 'value1' }, }, }, + { + name: 'request with a semver node version', + request: { + ...baseValidRequest, + nodeTypeAndVersion: { name: 'TestNode', version: 1.1 }, + }, + }, ])('should validate $name', ({ request }) => { const result = ResourceLocatorRequestDto.safeParse(request); expect(result.success).toBe(true); diff --git a/packages/@n8n/api-types/src/dto/dynamic-node-parameters/base-dynamic-parameters-request.dto.ts b/packages/@n8n/api-types/src/dto/dynamic-node-parameters/base-dynamic-parameters-request.dto.ts index 02d8fcfa47655..66b9cd7629595 100644 --- a/packages/@n8n/api-types/src/dto/dynamic-node-parameters/base-dynamic-parameters-request.dto.ts +++ b/packages/@n8n/api-types/src/dto/dynamic-node-parameters/base-dynamic-parameters-request.dto.ts @@ -2,11 +2,13 @@ import type { INodeCredentials, INodeParameters, INodeTypeNameVersion } from 'n8 import { z } from 'zod'; import { Z } from 'zod-class'; +import { nodeVersionSchema } from '../../schemas/nodeVersion.schema'; + export class BaseDynamicParametersRequestDto extends Z.class({ path: z.string(), nodeTypeAndVersion: z.object({ name: z.string(), - version: z.number().int().min(1), + version: nodeVersionSchema, }) satisfies z.ZodType, currentNodeParameters: z.record(z.string(), z.any()) satisfies z.ZodType, methodName: z.string().optional(), diff --git a/packages/@n8n/api-types/src/schemas/__tests__/nodeVersion.schema.test.ts b/packages/@n8n/api-types/src/schemas/__tests__/nodeVersion.schema.test.ts new file mode 100644 index 0000000000000..098db8209697b --- /dev/null +++ b/packages/@n8n/api-types/src/schemas/__tests__/nodeVersion.schema.test.ts @@ -0,0 +1,28 @@ +import { nodeVersionSchema } from '../nodeVersion.schema'; + +describe('nodeVersionSchema', () => { + describe('valid versions', () => { + test.each([ + [1, 'single digit'], + [2, 'single digit'], + [1.0, 'major.minor with zero minor'], + [1.2, 'major.minor'], + [10.5, 'major.minor with double digits'], + ])('should accept %s as a valid version (%s)', (version) => { + const validated = nodeVersionSchema.parse(version); + expect(validated).toBe(version); + }); + }); + + describe('invalid versions', () => { + test.each([ + ['not-a-number', 'non-number input'], + ['1.2.3', 'more than two parts'], + ['1.a', 'non-numeric characters'], + ['1.2.3', 'more than two parts as string'], + ])('should reject %s as an invalid version (%s)', (version) => { + const check = () => nodeVersionSchema.parse(version); + expect(check).toThrowError(); + }); + }); +}); diff --git a/packages/@n8n/api-types/src/schemas/nodeVersion.schema.ts b/packages/@n8n/api-types/src/schemas/nodeVersion.schema.ts new file mode 100644 index 0000000000000..3edb8cc5fe6df --- /dev/null +++ b/packages/@n8n/api-types/src/schemas/nodeVersion.schema.ts @@ -0,0 +1,17 @@ +import { z } from 'zod'; + +export const nodeVersionSchema = z + .number() + .min(1) + .refine( + (val) => { + const parts = String(val).split('.'); + return ( + (parts.length === 1 && !isNaN(Number(parts[0]))) || + (parts.length === 2 && !isNaN(Number(parts[0])) && !isNaN(Number(parts[1]))) + ); + }, + { + message: 'Invalid node version. Must be in format: major.minor', + }, + );