diff --git a/packages/core/src/execution-engine/node-execution-context/__tests__/node-execution-context.test.ts b/packages/core/src/execution-engine/node-execution-context/__tests__/node-execution-context.test.ts index 6f5724b68c4a6..a07b196a89309 100644 --- a/packages/core/src/execution-engine/node-execution-context/__tests__/node-execution-context.test.ts +++ b/packages/core/src/execution-engine/node-execution-context/__tests__/node-execution-context.test.ts @@ -1,4 +1,5 @@ import { Container } from '@n8n/di'; +import fsPromises from 'fs/promises'; import { mock } from 'jest-mock-extended'; import type { Expression, @@ -404,4 +405,42 @@ describe('NodeExecutionContext', () => { ); }); }); + + describe('getN8nVersion', () => { + it('should return the version of the n8n', async () => { + const spy = jest.spyOn(fsPromises, 'readFile'); + spy.mockResolvedValue('{ "version": "1.114.0" }'); + + const result = await testContext.getN8nVersion(); + + expect(result).toBe('1.114.0'); + }); + + it('should return 0.0.0 when the package.json file is not found', async () => { + const spy = jest.spyOn(fsPromises, 'readFile'); + spy.mockRejectedValue(new Error('File not found')); + + const result = await testContext.getN8nVersion(); + + expect(result).toBe('0.0.0'); + }); + + it('should return 0.0.0 when the package.json file is not valid', async () => { + const spy = jest.spyOn(fsPromises, 'readFile'); + spy.mockResolvedValue('invalid'); + + const result = await testContext.getN8nVersion(); + + expect(result).toBe('0.0.0'); + }); + + it('should return 0.0.0 when the package.json file does not contain the version', async () => { + const spy = jest.spyOn(fsPromises, 'readFile'); + spy.mockResolvedValue('{ "foo": "bar" }'); + + const result = await testContext.getN8nVersion(); + + expect(result).toBe('0.0.0'); + }); + }); }); diff --git a/packages/core/src/execution-engine/node-execution-context/node-execution-context.ts b/packages/core/src/execution-engine/node-execution-context/node-execution-context.ts index f1ebe182e2baa..37815ca38e2c1 100644 --- a/packages/core/src/execution-engine/node-execution-context/node-execution-context.ts +++ b/packages/core/src/execution-engine/node-execution-context/node-execution-context.ts @@ -1,6 +1,7 @@ import { Logger } from '@n8n/backend-common'; import { Memoized } from '@n8n/decorators'; import { Container } from '@n8n/di'; +import { readFile } from 'fs/promises'; import get from 'lodash/get'; import type { FunctionsBase, @@ -28,10 +29,12 @@ import { CHAT_TRIGGER_NODE_TYPE, deepCopy, ExpressionError, + jsonParse, NodeHelpers, NodeOperationError, UnexpectedError, } from 'n8n-workflow'; +import { join, resolve } from 'path'; import { HTTP_REQUEST_AS_TOOL_NODE_TYPE, @@ -214,6 +217,18 @@ export abstract class NodeExecutionContext implements Omit(await readFile(packageJsonPath, 'utf8')); + return packageJson?.version ?? fallbackValue; + } catch { + return fallbackValue; + } + } + setSignatureValidationRequired() { if (this.runExecutionData) this.runExecutionData.validateSignature = true; } diff --git a/packages/workflow/src/interfaces.ts b/packages/workflow/src/interfaces.ts index 8557522439fbb..978a36b21c57f 100644 --- a/packages/workflow/src/interfaces.ts +++ b/packages/workflow/src/interfaces.ts @@ -894,6 +894,7 @@ export interface FunctionsBase { getRestApiUrl(): string; getInstanceBaseUrl(): string; getInstanceId(): string; + getN8nVersion(): Promise; /** Get the waiting resume url signed with the signature token */ getSignedResumeUrl(parameters?: Record): string; /** Set requirement in the execution for signature token validation */