"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.NodeMigrationService = void 0;
const uuid_1 = require("uuid");
class NodeMigrationService {
constructor(versionService, breakingChangeDetector) {
this.versionService = versionService;
this.breakingChangeDetector = breakingChangeDetector;
}
async migrateNode(node, fromVersion, toVersion) {
const nodeId = node.id || 'unknown';
const nodeName = node.name || 'Unknown Node';
const nodeType = node.type;
const analysis = await this.breakingChangeDetector.analyzeVersionUpgrade(nodeType, fromVersion, toVersion);
const migratedNode = JSON.parse(JSON.stringify(node));
migratedNode.typeVersion = this.parseVersion(toVersion);
const appliedMigrations = [];
const remainingIssues = [];
for (const change of analysis.changes.filter(c => c.autoMigratable)) {
const migration = this.applyMigration(migratedNode, change);
if (migration) {
appliedMigrations.push(migration);
}
}
for (const change of analysis.changes.filter(c => !c.autoMigratable)) {
remainingIssues.push(`Manual action required for "${change.propertyName}": ${change.migrationHint}`);
}
let confidence = 'HIGH';
if (remainingIssues.length > 0) {
confidence = remainingIssues.length > 3 ? 'LOW' : 'MEDIUM';
}
return {
success: remainingIssues.length === 0,
nodeId,
nodeName,
fromVersion,
toVersion,
appliedMigrations,
remainingIssues,
confidence,
updatedNode: migratedNode
};
}
applyMigration(node, change) {
if (!change.migrationStrategy)
return null;
const { type, defaultValue, sourceProperty, targetProperty } = change.migrationStrategy;
switch (type) {
case 'add_property':
return this.addProperty(node, change.propertyName, defaultValue, change);
case 'remove_property':
return this.removeProperty(node, change.propertyName, change);
case 'rename_property':
return this.renameProperty(node, sourceProperty, targetProperty, change);
case 'set_default':
return this.setDefault(node, change.propertyName, defaultValue, change);
default:
return null;
}
}
addProperty(node, propertyPath, defaultValue, change) {
const value = this.resolveDefaultValue(propertyPath, defaultValue, node);
const parts = propertyPath.split('.');
let target = node;
for (let i = 0; i < parts.length - 1; i++) {
const part = parts[i];
if (!target[part]) {
target[part] = {};
}
target = target[part];
}
const finalKey = parts[parts.length - 1];
target[finalKey] = value;
return {
propertyName: propertyPath,
action: 'Added property',
newValue: value,
description: `Added "${propertyPath}" with default value`
};
}
removeProperty(node, propertyPath, change) {
const parts = propertyPath.split('.');
let target = node;
for (let i = 0; i < parts.length - 1; i++) {
const part = parts[i];
if (!target[part])
return null;
target = target[part];
}
const finalKey = parts[parts.length - 1];
const oldValue = target[finalKey];
if (oldValue !== undefined) {
delete target[finalKey];
return {
propertyName: propertyPath,
action: 'Removed property',
oldValue,
description: `Removed deprecated property "${propertyPath}"`
};
}
return null;
}
renameProperty(node, sourcePath, targetPath, change) {
const sourceParts = sourcePath.split('.');
let sourceTarget = node;
for (let i = 0; i < sourceParts.length - 1; i++) {
if (!sourceTarget[sourceParts[i]])
return null;
sourceTarget = sourceTarget[sourceParts[i]];
}
const sourceKey = sourceParts[sourceParts.length - 1];
const oldValue = sourceTarget[sourceKey];
if (oldValue === undefined)
return null;
const targetParts = targetPath.split('.');
let targetTarget = node;
for (let i = 0; i < targetParts.length - 1; i++) {
if (!targetTarget[targetParts[i]]) {
targetTarget[targetParts[i]] = {};
}
targetTarget = targetTarget[targetParts[i]];
}
const targetKey = targetParts[targetParts.length - 1];
targetTarget[targetKey] = oldValue;
delete sourceTarget[sourceKey];
return {
propertyName: targetPath,
action: 'Renamed property',
oldValue: `${sourcePath}: ${JSON.stringify(oldValue)}`,
newValue: `${targetPath}: ${JSON.stringify(oldValue)}`,
description: `Renamed "${sourcePath}" to "${targetPath}"`
};
}
setDefault(node, propertyPath, defaultValue, change) {
const parts = propertyPath.split('.');
let target = node;
for (let i = 0; i < parts.length - 1; i++) {
if (!target[parts[i]]) {
target[parts[i]] = {};
}
target = target[parts[i]];
}
const finalKey = parts[parts.length - 1];
if (target[finalKey] === undefined) {
const value = this.resolveDefaultValue(propertyPath, defaultValue, node);
target[finalKey] = value;
return {
propertyName: propertyPath,
action: 'Set default value',
newValue: value,
description: `Set default value for "${propertyPath}"`
};
}
return null;
}
resolveDefaultValue(propertyPath, defaultValue, node) {
if (propertyPath === 'webhookId' || propertyPath.endsWith('.webhookId')) {
return (0, uuid_1.v4)();
}
if (propertyPath === 'path' || propertyPath.endsWith('.path')) {
if (node.type === 'n8n-nodes-base.webhook') {
return `/webhook-${Date.now()}`;
}
}
return defaultValue !== null && defaultValue !== undefined ? defaultValue : null;
}
parseVersion(version) {
const parts = version.split('.').map(Number);
if (parts.length === 1)
return parts[0];
if (parts.length === 2)
return parts[0] + parts[1] / 10;
return parts[0];
}
async validateMigratedNode(node, nodeType) {
const errors = [];
const warnings = [];
if (!node.typeVersion) {
errors.push('Missing typeVersion after migration');
}
if (!node.parameters) {
errors.push('Missing parameters object');
}
if (nodeType === 'n8n-nodes-base.webhook') {
if (!node.parameters?.path) {
errors.push('Webhook node missing required "path" parameter');
}
if (node.typeVersion >= 2.1 && !node.webhookId) {
warnings.push('Webhook v2.1+ typically requires webhookId');
}
}
if (nodeType === 'n8n-nodes-base.executeWorkflow') {
if (node.typeVersion >= 1.1 && !node.parameters?.inputFieldMapping) {
errors.push('Execute Workflow v1.1+ requires inputFieldMapping');
}
}
return {
valid: errors.length === 0,
errors,
warnings
};
}
async migrateWorkflowNodes(workflow, targetVersions) {
const results = [];
for (const node of workflow.nodes || []) {
const targetVersion = targetVersions[node.id];
if (targetVersion && node.typeVersion) {
const currentVersion = node.typeVersion.toString();
const result = await this.migrateNode(node, currentVersion, targetVersion);
results.push(result);
Object.assign(node, result.updatedNode);
}
}
const confidences = results.map(r => r.confidence);
let overallConfidence = 'HIGH';
if (confidences.includes('LOW')) {
overallConfidence = 'LOW';
}
else if (confidences.includes('MEDIUM')) {
overallConfidence = 'MEDIUM';
}
const success = results.every(r => r.success);
return {
success,
results,
overallConfidence
};
}
}
exports.NodeMigrationService = NodeMigrationService;
//# sourceMappingURL=node-migration-service.js.map