mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-02-06 13:33:11 +00:00
chore: add pre-built dist folder for npx usage
This commit is contained in:
committed by
Romuald Członkowski
parent
a70d96a373
commit
5057481e70
264
dist/services/workflow-versioning-service.js
vendored
Normal file
264
dist/services/workflow-versioning-service.js
vendored
Normal file
@@ -0,0 +1,264 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.WorkflowVersioningService = void 0;
|
||||
const workflow_validator_1 = require("./workflow-validator");
|
||||
const enhanced_config_validator_1 = require("./enhanced-config-validator");
|
||||
class WorkflowVersioningService {
|
||||
constructor(nodeRepository, apiClient) {
|
||||
this.nodeRepository = nodeRepository;
|
||||
this.apiClient = apiClient;
|
||||
this.DEFAULT_MAX_VERSIONS = 10;
|
||||
}
|
||||
async createBackup(workflowId, workflow, context) {
|
||||
const versions = this.nodeRepository.getWorkflowVersions(workflowId, 1);
|
||||
const nextVersion = versions.length > 0 ? versions[0].versionNumber + 1 : 1;
|
||||
const versionId = this.nodeRepository.createWorkflowVersion({
|
||||
workflowId,
|
||||
versionNumber: nextVersion,
|
||||
workflowName: workflow.name || 'Unnamed Workflow',
|
||||
workflowSnapshot: workflow,
|
||||
trigger: context.trigger,
|
||||
operations: context.operations,
|
||||
fixTypes: context.fixTypes,
|
||||
metadata: context.metadata
|
||||
});
|
||||
const pruned = this.nodeRepository.pruneWorkflowVersions(workflowId, this.DEFAULT_MAX_VERSIONS);
|
||||
return {
|
||||
versionId,
|
||||
versionNumber: nextVersion,
|
||||
pruned,
|
||||
message: pruned > 0
|
||||
? `Backup created (version ${nextVersion}), pruned ${pruned} old version(s)`
|
||||
: `Backup created (version ${nextVersion})`
|
||||
};
|
||||
}
|
||||
async getVersionHistory(workflowId, limit = 10) {
|
||||
const versions = this.nodeRepository.getWorkflowVersions(workflowId, limit);
|
||||
return versions.map(v => ({
|
||||
id: v.id,
|
||||
workflowId: v.workflowId,
|
||||
versionNumber: v.versionNumber,
|
||||
workflowName: v.workflowName,
|
||||
trigger: v.trigger,
|
||||
operationCount: v.operations ? v.operations.length : undefined,
|
||||
fixTypesApplied: v.fixTypes || undefined,
|
||||
createdAt: v.createdAt,
|
||||
size: JSON.stringify(v.workflowSnapshot).length
|
||||
}));
|
||||
}
|
||||
async getVersion(versionId) {
|
||||
return this.nodeRepository.getWorkflowVersion(versionId);
|
||||
}
|
||||
async restoreVersion(workflowId, versionId, validateBefore = true) {
|
||||
if (!this.apiClient) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'API client not configured - cannot restore workflow',
|
||||
workflowId,
|
||||
toVersionId: versionId || 0,
|
||||
backupCreated: false
|
||||
};
|
||||
}
|
||||
let versionToRestore = null;
|
||||
if (versionId) {
|
||||
versionToRestore = this.nodeRepository.getWorkflowVersion(versionId);
|
||||
}
|
||||
else {
|
||||
versionToRestore = this.nodeRepository.getLatestWorkflowVersion(workflowId);
|
||||
}
|
||||
if (!versionToRestore) {
|
||||
return {
|
||||
success: false,
|
||||
message: versionId
|
||||
? `Version ${versionId} not found`
|
||||
: `No backup versions found for workflow ${workflowId}`,
|
||||
workflowId,
|
||||
toVersionId: versionId || 0,
|
||||
backupCreated: false
|
||||
};
|
||||
}
|
||||
if (validateBefore) {
|
||||
const validator = new workflow_validator_1.WorkflowValidator(this.nodeRepository, enhanced_config_validator_1.EnhancedConfigValidator);
|
||||
const validationResult = await validator.validateWorkflow(versionToRestore.workflowSnapshot, {
|
||||
validateNodes: true,
|
||||
validateConnections: true,
|
||||
validateExpressions: false,
|
||||
profile: 'runtime'
|
||||
});
|
||||
if (validationResult.errors.length > 0) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Cannot restore - version ${versionToRestore.versionNumber} has validation errors`,
|
||||
workflowId,
|
||||
toVersionId: versionToRestore.id,
|
||||
backupCreated: false,
|
||||
validationErrors: validationResult.errors.map(e => e.message || 'Unknown error')
|
||||
};
|
||||
}
|
||||
}
|
||||
let backupResult;
|
||||
try {
|
||||
const currentWorkflow = await this.apiClient.getWorkflow(workflowId);
|
||||
backupResult = await this.createBackup(workflowId, currentWorkflow, {
|
||||
trigger: 'partial_update',
|
||||
metadata: {
|
||||
reason: 'Backup before rollback',
|
||||
restoringToVersion: versionToRestore.versionNumber
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Failed to create backup before restore: ${error.message}`,
|
||||
workflowId,
|
||||
toVersionId: versionToRestore.id,
|
||||
backupCreated: false
|
||||
};
|
||||
}
|
||||
try {
|
||||
await this.apiClient.updateWorkflow(workflowId, versionToRestore.workflowSnapshot);
|
||||
return {
|
||||
success: true,
|
||||
message: `Successfully restored workflow to version ${versionToRestore.versionNumber}`,
|
||||
workflowId,
|
||||
fromVersion: backupResult.versionNumber,
|
||||
toVersionId: versionToRestore.id,
|
||||
backupCreated: true,
|
||||
backupVersionId: backupResult.versionId
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Failed to restore workflow: ${error.message}`,
|
||||
workflowId,
|
||||
toVersionId: versionToRestore.id,
|
||||
backupCreated: true,
|
||||
backupVersionId: backupResult.versionId
|
||||
};
|
||||
}
|
||||
}
|
||||
async deleteVersion(versionId) {
|
||||
const version = this.nodeRepository.getWorkflowVersion(versionId);
|
||||
if (!version) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Version ${versionId} not found`
|
||||
};
|
||||
}
|
||||
this.nodeRepository.deleteWorkflowVersion(versionId);
|
||||
return {
|
||||
success: true,
|
||||
message: `Deleted version ${version.versionNumber} for workflow ${version.workflowId}`
|
||||
};
|
||||
}
|
||||
async deleteAllVersions(workflowId) {
|
||||
const count = this.nodeRepository.getWorkflowVersionCount(workflowId);
|
||||
if (count === 0) {
|
||||
return {
|
||||
deleted: 0,
|
||||
message: `No versions found for workflow ${workflowId}`
|
||||
};
|
||||
}
|
||||
const deleted = this.nodeRepository.deleteWorkflowVersionsByWorkflowId(workflowId);
|
||||
return {
|
||||
deleted,
|
||||
message: `Deleted ${deleted} version(s) for workflow ${workflowId}`
|
||||
};
|
||||
}
|
||||
async pruneVersions(workflowId, maxVersions = 10) {
|
||||
const pruned = this.nodeRepository.pruneWorkflowVersions(workflowId, maxVersions);
|
||||
const remaining = this.nodeRepository.getWorkflowVersionCount(workflowId);
|
||||
return { pruned, remaining };
|
||||
}
|
||||
async truncateAllVersions(confirm) {
|
||||
if (!confirm) {
|
||||
return {
|
||||
deleted: 0,
|
||||
message: 'Truncate operation not confirmed - no action taken'
|
||||
};
|
||||
}
|
||||
const deleted = this.nodeRepository.truncateWorkflowVersions();
|
||||
return {
|
||||
deleted,
|
||||
message: `Truncated workflow_versions table - deleted ${deleted} version(s)`
|
||||
};
|
||||
}
|
||||
async getStorageStats() {
|
||||
const stats = this.nodeRepository.getVersionStorageStats();
|
||||
return {
|
||||
totalVersions: stats.totalVersions,
|
||||
totalSize: stats.totalSize,
|
||||
totalSizeFormatted: this.formatBytes(stats.totalSize),
|
||||
byWorkflow: stats.byWorkflow.map((w) => ({
|
||||
workflowId: w.workflowId,
|
||||
workflowName: w.workflowName,
|
||||
versionCount: w.versionCount,
|
||||
totalSize: w.totalSize,
|
||||
totalSizeFormatted: this.formatBytes(w.totalSize),
|
||||
lastBackup: w.lastBackup
|
||||
}))
|
||||
};
|
||||
}
|
||||
async compareVersions(versionId1, versionId2) {
|
||||
const v1 = this.nodeRepository.getWorkflowVersion(versionId1);
|
||||
const v2 = this.nodeRepository.getWorkflowVersion(versionId2);
|
||||
if (!v1 || !v2) {
|
||||
throw new Error(`One or both versions not found: ${versionId1}, ${versionId2}`);
|
||||
}
|
||||
const nodes1 = new Set(v1.workflowSnapshot.nodes?.map((n) => n.id) || []);
|
||||
const nodes2 = new Set(v2.workflowSnapshot.nodes?.map((n) => n.id) || []);
|
||||
const addedNodes = [...nodes2].filter(id => !nodes1.has(id));
|
||||
const removedNodes = [...nodes1].filter(id => !nodes2.has(id));
|
||||
const commonNodes = [...nodes1].filter(id => nodes2.has(id));
|
||||
const modifiedNodes = [];
|
||||
for (const nodeId of commonNodes) {
|
||||
const node1 = v1.workflowSnapshot.nodes?.find((n) => n.id === nodeId);
|
||||
const node2 = v2.workflowSnapshot.nodes?.find((n) => n.id === nodeId);
|
||||
if (JSON.stringify(node1) !== JSON.stringify(node2)) {
|
||||
modifiedNodes.push(nodeId);
|
||||
}
|
||||
}
|
||||
const conn1Str = JSON.stringify(v1.workflowSnapshot.connections || {});
|
||||
const conn2Str = JSON.stringify(v2.workflowSnapshot.connections || {});
|
||||
const connectionChanges = conn1Str !== conn2Str ? 1 : 0;
|
||||
const settings1 = v1.workflowSnapshot.settings || {};
|
||||
const settings2 = v2.workflowSnapshot.settings || {};
|
||||
const settingChanges = this.diffObjects(settings1, settings2);
|
||||
return {
|
||||
versionId1,
|
||||
versionId2,
|
||||
version1Number: v1.versionNumber,
|
||||
version2Number: v2.versionNumber,
|
||||
addedNodes,
|
||||
removedNodes,
|
||||
modifiedNodes,
|
||||
connectionChanges,
|
||||
settingChanges
|
||||
};
|
||||
}
|
||||
formatBytes(bytes) {
|
||||
if (bytes === 0)
|
||||
return '0 Bytes';
|
||||
const k = 1024;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
|
||||
}
|
||||
diffObjects(obj1, obj2) {
|
||||
const changes = {};
|
||||
const allKeys = new Set([...Object.keys(obj1), ...Object.keys(obj2)]);
|
||||
for (const key of allKeys) {
|
||||
if (JSON.stringify(obj1[key]) !== JSON.stringify(obj2[key])) {
|
||||
changes[key] = {
|
||||
before: obj1[key],
|
||||
after: obj2[key]
|
||||
};
|
||||
}
|
||||
}
|
||||
return changes;
|
||||
}
|
||||
}
|
||||
exports.WorkflowVersioningService = WorkflowVersioningService;
|
||||
//# sourceMappingURL=workflow-versioning-service.js.map
|
||||
Reference in New Issue
Block a user