mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
fix(core): Validate task runner mode (#14376)
This commit is contained in:
@@ -22,7 +22,8 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"@n8n/di": "workspace:*",
|
||||
"reflect-metadata": "catalog:"
|
||||
"reflect-metadata": "catalog:",
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@n8n/typescript-config": "workspace:*"
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { Config, Env } from '../decorators';
|
||||
|
||||
/**
|
||||
* Whether to enable task runners and how to run them
|
||||
* - internal: Task runners are run as a child process and launched by n8n
|
||||
* - external: Task runners are run as a separate program not launched by n8n
|
||||
*/
|
||||
export type TaskRunnerMode = 'internal' | 'external';
|
||||
const runnerModeSchema = z.enum(['internal', 'external']);
|
||||
|
||||
export type TaskRunnerMode = z.infer<typeof runnerModeSchema>;
|
||||
|
||||
@Config
|
||||
export class TaskRunnersConfig {
|
||||
@Env('N8N_RUNNERS_ENABLED')
|
||||
enabled: boolean = false;
|
||||
|
||||
@Env('N8N_RUNNERS_MODE')
|
||||
/**
|
||||
* Whether the task runner should run as a child process spawned by n8n (internal mode)
|
||||
* or as a separate process launched outside n8n (external mode).
|
||||
*/
|
||||
@Env('N8N_RUNNERS_MODE', runnerModeSchema)
|
||||
mode: TaskRunnerMode = 'internal';
|
||||
|
||||
/** Endpoint which task runners connect to */
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'reflect-metadata';
|
||||
import { Container, Service } from '@n8n/di';
|
||||
import { readFileSync } from 'fs';
|
||||
import { z } from 'zod';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
type Class = Function;
|
||||
@@ -10,6 +11,7 @@ type PropertyType = number | boolean | string | Class;
|
||||
interface PropertyMetadata {
|
||||
type: PropertyType;
|
||||
envName?: string;
|
||||
schema?: z.ZodType<unknown>;
|
||||
}
|
||||
|
||||
const globalMetadata = new Map<Class, Map<PropertyKey, PropertyMetadata>>();
|
||||
@@ -33,14 +35,22 @@ export const Config: ClassDecorator = (ConfigClass: Class) => {
|
||||
throw new Error('Invalid config class: ' + ConfigClass.name);
|
||||
}
|
||||
|
||||
for (const [key, { type, envName }] of classMetadata) {
|
||||
for (const [key, { type, envName, schema }] of classMetadata) {
|
||||
if (typeof type === 'function' && globalMetadata.has(type)) {
|
||||
config[key] = Container.get(type as Constructable);
|
||||
} else if (envName) {
|
||||
const value = readEnv(envName);
|
||||
if (value === undefined) continue;
|
||||
|
||||
if (type === Number) {
|
||||
if (schema) {
|
||||
const result = schema.safeParse(value);
|
||||
if (result.error) {
|
||||
console.warn(
|
||||
`Invalid value for ${envName} - ${result.error.issues[0].message}. Falling back to default value.`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
} else if (type === Number) {
|
||||
const parsed = Number(value);
|
||||
if (isNaN(parsed)) {
|
||||
console.warn(`Invalid number value for ${envName}: ${value}`);
|
||||
@@ -84,18 +94,21 @@ export const Nested: PropertyDecorator = (target: object, key: PropertyKey) => {
|
||||
};
|
||||
|
||||
export const Env =
|
||||
(envName: string): PropertyDecorator =>
|
||||
(envName: string, schema?: PropertyMetadata['schema']): PropertyDecorator =>
|
||||
(target: object, key: PropertyKey) => {
|
||||
const ConfigClass = target.constructor;
|
||||
const classMetadata =
|
||||
globalMetadata.get(ConfigClass) ?? new Map<PropertyKey, PropertyMetadata>();
|
||||
|
||||
const type = Reflect.getMetadata('design:type', target, key) as PropertyType;
|
||||
if (type === Object) {
|
||||
const isEnum = schema instanceof z.ZodEnum;
|
||||
if (type === Object && !isEnum) {
|
||||
// eslint-disable-next-line n8n-local-rules/no-plain-errors
|
||||
throw new Error(
|
||||
`Invalid decorator metadata on key "${key as string}" on ${ConfigClass.name}\n Please use explicit typing on all config fields`,
|
||||
);
|
||||
}
|
||||
classMetadata.set(key, { type, envName });
|
||||
|
||||
classMetadata.set(key, { type, envName, schema });
|
||||
globalMetadata.set(ConfigClass, classMetadata);
|
||||
};
|
||||
|
||||
@@ -399,4 +399,20 @@ describe('GlobalConfig', () => {
|
||||
'Invalid number value for DB_LOGGING_MAX_EXECUTION_TIME: abcd',
|
||||
);
|
||||
});
|
||||
|
||||
describe('string unions', () => {
|
||||
it('on invalid value, should warn and fall back to default value', () => {
|
||||
process.env = {
|
||||
N8N_RUNNERS_MODE: 'non-existing-mode',
|
||||
};
|
||||
|
||||
const globalConfig = Container.get(GlobalConfig);
|
||||
expect(globalConfig.taskRunners.mode).toEqual('internal');
|
||||
expect(consoleWarnMock).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
"Invalid value for N8N_RUNNERS_MODE - Invalid enum value. Expected 'internal' | 'external', received 'non-existing-mode'. Falling back to default value.",
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@@ -396,6 +396,9 @@ importers:
|
||||
reflect-metadata:
|
||||
specifier: 'catalog:'
|
||||
version: 0.2.2
|
||||
zod:
|
||||
specifier: 'catalog:'
|
||||
version: 3.24.1
|
||||
devDependencies:
|
||||
'@n8n/typescript-config':
|
||||
specifier: workspace:*
|
||||
|
||||
Reference in New Issue
Block a user