chore(core): Allow undefined in the node output value (#18198)

This commit is contained in:
Guillaume Jacquart
2025-08-11 15:25:52 +02:00
committed by GitHub
parent 409085e8fe
commit 660f2ff09b
3 changed files with 40 additions and 52 deletions

View File

@@ -37,7 +37,7 @@
"optimize-svg": "find ./packages -name '*.svg' ! -name 'pipedrive.svg' -print0 | xargs -0 -P16 -L20 npx svgo", "optimize-svg": "find ./packages -name '*.svg' ! -name 'pipedrive.svg' -print0 | xargs -0 -P16 -L20 npx svgo",
"setup-backend-module": "node scripts/ensure-zx.mjs && zx scripts/backend-module/setup.mjs", "setup-backend-module": "node scripts/ensure-zx.mjs && zx scripts/backend-module/setup.mjs",
"start": "run-script-os", "start": "run-script-os",
"start:default": "cd packages/cli/bin && ./n8n", "start:default": "cd packages/cli/bin && node --inspect ./n8n",
"start:tunnel": "./packages/cli/bin/n8n start --tunnel", "start:tunnel": "./packages/cli/bin/n8n start --tunnel",
"start:windows": "cd packages/cli/bin && n8n", "start:windows": "cd packages/cli/bin && n8n",
"test": "JEST_JUNIT_CLASSNAME={filepath} turbo run test", "test": "JEST_JUNIT_CLASSNAME={filepath} turbo run test",

View File

@@ -99,12 +99,6 @@ describe('isJsonCompatible', () => {
errorPath: 'value.nan', errorPath: 'value.nan',
errorMessage: 'is NaN, which is not JSON-compatible', errorMessage: 'is NaN, which is not JSON-compatible',
}, },
{
name: 'undefined',
value: { undefined },
errorPath: 'value.undefined',
errorMessage: 'is of unknown type (undefined) with value undefined',
},
{ {
name: 'an object with symbol keys', name: 'an object with symbol keys',
value: { [Symbol.for('key')]: 1 }, value: { [Symbol.for('key')]: 1 },
@@ -125,6 +119,7 @@ describe('isJsonCompatible', () => {
const objectRef = {}; const objectRef = {};
test.each([ test.each([
{ name: 'null', value: { null: null } }, { name: 'null', value: { null: null } },
{ name: 'undefined', value: { noValue: undefined } },
{ name: 'an array of primitives', value: { array: [1, 'string', true, false] } }, { name: 'an array of primitives', value: { array: [1, 'string', true, false] } },
{ {
name: 'an object without a prototype chain', name: 'an object without a prototype chain',

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line complexity
const check = ( const check = (
val: unknown, val: unknown,
path = 'value', path = 'value',
@@ -6,7 +7,7 @@ const check = (
): { isValid: true } | { isValid: false; errorPath: string; errorMessage: string } => { ): { isValid: true } | { isValid: false; errorPath: string; errorMessage: string } => {
const type = typeof val; const type = typeof val;
if (val === null || type === 'boolean' || type === 'string') { if (val === null || type === 'boolean' || type === 'string' || type === 'undefined') {
return { isValid: true }; return { isValid: true };
} }
@@ -46,52 +47,44 @@ const check = (
return { isValid: true }; return { isValid: true };
} }
if (type === 'object') { if (stack.has(val)) {
if (stack.has(val)) { return {
return { isValid: false,
isValid: false, errorPath: path,
errorPath: path, errorMessage: 'contains a circular reference',
errorMessage: 'contains a circular reference', };
};
}
stack.add(val);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const proto = Object.getPrototypeOf(val);
if (proto !== Object.prototype && proto !== null) {
return {
isValid: false,
errorPath: path,
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
errorMessage: `has non-plain prototype (${proto?.constructor?.name || 'unknown'})`,
};
}
for (const key of Reflect.ownKeys(val as object)) {
if (typeof key === 'symbol') {
return {
isValid: false,
errorPath: `${path}.${key.toString()}`,
errorMessage: `has a symbol key (${String(key)}), which is not JSON-compatible`,
};
}
if (keysToIgnore.has(key)) {
continue;
}
const subVal = (val as Record<string, unknown>)[key];
const result = check(subVal, `${path}.${key}`, stack, keysToIgnore);
if (!result.isValid) return result;
}
stack.delete(val);
return { isValid: true };
} }
stack.add(val);
return { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
isValid: false, const proto = Object.getPrototypeOf(val);
errorPath: path, if (proto !== Object.prototype && proto !== null) {
errorMessage: `is of unknown type (${type}) with value ${JSON.stringify(val)}`, return {
}; isValid: false,
errorPath: path,
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
errorMessage: `has non-plain prototype (${proto?.constructor?.name || 'unknown'})`,
};
}
for (const key of Reflect.ownKeys(val as object)) {
if (typeof key === 'symbol') {
return {
isValid: false,
errorPath: `${path}.${key.toString()}`,
errorMessage: `has a symbol key (${String(key)}), which is not JSON-compatible`,
};
}
if (keysToIgnore.has(key)) {
continue;
}
const subVal = (val as Record<string, unknown>)[key];
const result = check(subVal, `${path}.${key}`, stack, keysToIgnore);
if (!result.isValid) return result;
}
stack.delete(val);
return { isValid: true };
}; };
/** /**