mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-03-29 21:53:07 +00:00
fix: correct operator names, connection types, and implement __patch_find_replace (#665, #659, #642) (#672)
Three critical fixes in n8n_update_partial_workflow: - **#665**: Replace incorrect `isNotEmpty`/`isEmpty` operator names with `notEmpty`/`empty` across validators, sanitizer, docs, and error messages. Add auto-correction in sanitizer. Unknown operators silently returned false in n8n's execution engine. - **#659**: Remap numeric `targetInput` values (e.g., "0") to "main" in addConnection. Relax sourceOutput remapping guard for redundant sourceOutput+sourceIndex combinations. Also resolves #653 (dangling connections caused by malformed type:"0" connections). - **#642**: Implement __patch_find_replace for surgical string edits in updateNode. Previously stored patch objects literally as jsCode, producing [object Object]. Now reads current value, applies find/replace sequentially, writes back the string. Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
committed by
GitHub
parent
de2abaf89d
commit
6be9ffa53e
@@ -13,6 +13,23 @@ import { INodeParameters } from 'n8n-workflow';
|
||||
import { logger } from '../utils/logger';
|
||||
import { WorkflowNode } from '../types/n8n-api';
|
||||
|
||||
/** Legacy operator names that n8n no longer recognizes, mapped to their correct names. */
|
||||
const OPERATOR_CORRECTIONS: Record<string, string> = {
|
||||
'isEmpty': 'empty',
|
||||
'isNotEmpty': 'notEmpty',
|
||||
};
|
||||
|
||||
/** Operators that take no right-hand value and require singleValue: true. */
|
||||
const UNARY_OPERATORS = new Set([
|
||||
'true',
|
||||
'false',
|
||||
'isNumeric',
|
||||
'empty',
|
||||
'notEmpty',
|
||||
'exists',
|
||||
'notExists',
|
||||
]);
|
||||
|
||||
/**
|
||||
* Sanitize a single node by adding required metadata
|
||||
*/
|
||||
@@ -162,29 +179,28 @@ function sanitizeOperator(operator: any): any {
|
||||
const sanitized = { ...operator };
|
||||
|
||||
// Fix common mistake: type field used for operation name
|
||||
// WRONG: {type: "isNotEmpty"}
|
||||
// RIGHT: {type: "string", operation: "isNotEmpty"}
|
||||
// WRONG: {type: "notEmpty"}
|
||||
// RIGHT: {type: "string", operation: "notEmpty"}
|
||||
if (sanitized.type && !sanitized.operation) {
|
||||
// Check if type value looks like an operation (lowercase, no dots)
|
||||
const typeValue = sanitized.type as string;
|
||||
if (isOperationName(typeValue)) {
|
||||
logger.debug(`Fixing operator structure: converting type="${typeValue}" to operation`);
|
||||
|
||||
// Infer data type from operation
|
||||
const dataType = inferDataType(typeValue);
|
||||
sanitized.type = dataType;
|
||||
sanitized.type = inferDataType(typeValue);
|
||||
sanitized.operation = typeValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-correct legacy operator names to n8n-recognized names
|
||||
if (sanitized.operation && OPERATOR_CORRECTIONS[sanitized.operation]) {
|
||||
sanitized.operation = OPERATOR_CORRECTIONS[sanitized.operation];
|
||||
}
|
||||
|
||||
// Set singleValue based on operator type
|
||||
if (sanitized.operation) {
|
||||
if (isUnaryOperator(sanitized.operation)) {
|
||||
// Unary operators require singleValue: true
|
||||
sanitized.singleValue = true;
|
||||
} else {
|
||||
// Binary operators should NOT have singleValue (or it should be false/undefined)
|
||||
// Remove it to prevent UI errors
|
||||
// Binary operators should NOT have singleValue — remove it to prevent UI errors
|
||||
delete sanitized.singleValue;
|
||||
}
|
||||
}
|
||||
@@ -207,7 +223,7 @@ function isOperationName(value: string): boolean {
|
||||
*/
|
||||
function inferDataType(operation: string): string {
|
||||
// Boolean operations
|
||||
const booleanOps = ['true', 'false', 'isEmpty', 'isNotEmpty'];
|
||||
const booleanOps = ['true', 'false'];
|
||||
if (booleanOps.includes(operation)) {
|
||||
return 'boolean';
|
||||
}
|
||||
@@ -225,7 +241,6 @@ function inferDataType(operation: string): string {
|
||||
}
|
||||
|
||||
// Object operations: empty/notEmpty/exists/notExists are generic object-level checks
|
||||
// (distinct from isEmpty/isNotEmpty which are boolean-typed operations)
|
||||
const objectOps = ['empty', 'notEmpty', 'exists', 'notExists'];
|
||||
if (objectOps.includes(operation)) {
|
||||
return 'object';
|
||||
@@ -239,18 +254,7 @@ function inferDataType(operation: string): string {
|
||||
* Check if operator is unary (requires singleValue: true)
|
||||
*/
|
||||
function isUnaryOperator(operation: string): boolean {
|
||||
const unaryOps = [
|
||||
'isEmpty',
|
||||
'isNotEmpty',
|
||||
'true',
|
||||
'false',
|
||||
'isNumeric',
|
||||
'empty',
|
||||
'notEmpty',
|
||||
'exists',
|
||||
'notExists'
|
||||
];
|
||||
return unaryOps.includes(operation);
|
||||
return UNARY_OPERATORS.has(operation);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user