fix(core): Validate task runner mode (#14376)

This commit is contained in:
Iván Ovejero
2025-04-10 13:35:13 +02:00
committed by GitHub
parent 8c417d7b1b
commit 52170f1bbc
5 changed files with 49 additions and 13 deletions

View File

@@ -22,7 +22,8 @@
],
"dependencies": {
"@n8n/di": "workspace:*",
"reflect-metadata": "catalog:"
"reflect-metadata": "catalog:",
"zod": "catalog:"
},
"devDependencies": {
"@n8n/typescript-config": "workspace:*"

View File

@@ -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 */

View File

@@ -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);
};

View File

@@ -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
View File

@@ -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:*