@n8n-as-code/core 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/services/hash-utils.d.ts +22 -0
- package/dist/services/hash-utils.d.ts.map +1 -0
- package/dist/services/hash-utils.js +31 -0
- package/dist/services/hash-utils.js.map +1 -0
- package/dist/services/n8n-api-client.d.ts.map +1 -1
- package/dist/services/n8n-api-client.js +44 -50
- package/dist/services/n8n-api-client.js.map +1 -1
- package/dist/services/resolution-manager.d.ts +73 -0
- package/dist/services/resolution-manager.d.ts.map +1 -0
- package/dist/services/resolution-manager.js +149 -0
- package/dist/services/resolution-manager.js.map +1 -0
- package/dist/services/state-manager.d.ts +18 -17
- package/dist/services/state-manager.d.ts.map +1 -1
- package/dist/services/state-manager.js +22 -53
- package/dist/services/state-manager.js.map +1 -1
- package/dist/services/sync-engine.d.ts +57 -0
- package/dist/services/sync-engine.d.ts.map +1 -0
- package/dist/services/sync-engine.js +301 -0
- package/dist/services/sync-engine.js.map +1 -0
- package/dist/services/sync-manager.d.ts +19 -83
- package/dist/services/sync-manager.d.ts.map +1 -1
- package/dist/services/sync-manager.js +208 -620
- package/dist/services/sync-manager.js.map +1 -1
- package/dist/services/watcher.d.ts +121 -0
- package/dist/services/watcher.d.ts.map +1 -0
- package/dist/services/watcher.js +609 -0
- package/dist/services/watcher.js.map +1 -0
- package/dist/services/workflow-sanitizer.d.ts +9 -4
- package/dist/services/workflow-sanitizer.d.ts.map +1 -1
- package/dist/services/workflow-sanitizer.js +55 -35
- package/dist/services/workflow-sanitizer.js.map +1 -1
- package/dist/types.d.ts +10 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +8 -5
- package/dist/types.js.map +1 -1
- package/package.json +4 -2
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Read-only State Manager
|
|
5
|
+
*
|
|
6
|
+
* Responsibilities:
|
|
7
|
+
* 1. Read state from .n8n-state.json
|
|
8
|
+
* 2. Provide read-only access to workflow states
|
|
9
|
+
*
|
|
10
|
+
* Note: Write operations are handled exclusively by Watcher component
|
|
11
|
+
* to maintain single source of truth for state mutations.
|
|
12
|
+
*/
|
|
4
13
|
export class StateManager {
|
|
5
14
|
stateFilePath;
|
|
6
15
|
constructor(directory) {
|
|
7
16
|
this.stateFilePath = path.join(directory, '.n8n-state.json');
|
|
8
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Load state from disk (private method)
|
|
20
|
+
*/
|
|
9
21
|
load() {
|
|
10
22
|
if (fs.existsSync(this.stateFilePath)) {
|
|
11
23
|
try {
|
|
@@ -22,44 +34,13 @@ export class StateManager {
|
|
|
22
34
|
}
|
|
23
35
|
return { workflows: {} };
|
|
24
36
|
}
|
|
25
|
-
save(state) {
|
|
26
|
-
fs.writeFileSync(this.stateFilePath, JSON.stringify(state, null, 2));
|
|
27
|
-
}
|
|
28
37
|
/**
|
|
29
|
-
*
|
|
30
|
-
*/
|
|
31
|
-
static computeHash(workflow) {
|
|
32
|
-
// We use the cleaned version to ensure stable hashing (no dynamic IDs or timestamps)
|
|
33
|
-
const content = typeof workflow === 'string' ? workflow : JSON.stringify(workflow);
|
|
34
|
-
return crypto.createHash('sha256').update(content).digest('hex');
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Gets the last known state for a workflow.
|
|
38
|
+
* Gets the last known state (Base) for a workflow.
|
|
38
39
|
*/
|
|
39
40
|
getWorkflowState(id) {
|
|
40
41
|
const state = this.load();
|
|
41
42
|
return state.workflows[id];
|
|
42
43
|
}
|
|
43
|
-
/**
|
|
44
|
-
* Updates the last known state for a workflow.
|
|
45
|
-
*/
|
|
46
|
-
updateWorkflowState(id, workflow) {
|
|
47
|
-
const state = this.load();
|
|
48
|
-
const hash = StateManager.computeHash(workflow);
|
|
49
|
-
state.workflows[id] = {
|
|
50
|
-
lastSyncedHash: hash,
|
|
51
|
-
lastSyncedAt: new Date().toISOString()
|
|
52
|
-
};
|
|
53
|
-
this.save(state);
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Removes a workflow from state.
|
|
57
|
-
*/
|
|
58
|
-
removeWorkflowState(id) {
|
|
59
|
-
const state = this.load();
|
|
60
|
-
delete state.workflows[id];
|
|
61
|
-
this.save(state);
|
|
62
|
-
}
|
|
63
44
|
/**
|
|
64
45
|
* Gets all tracked workflow IDs.
|
|
65
46
|
*/
|
|
@@ -68,32 +49,20 @@ export class StateManager {
|
|
|
68
49
|
return Object.keys(state.workflows);
|
|
69
50
|
}
|
|
70
51
|
/**
|
|
71
|
-
* Checks if a
|
|
52
|
+
* Checks if a hash matches the last synced state.
|
|
72
53
|
*/
|
|
73
|
-
|
|
54
|
+
isSynced(id, currentHash) {
|
|
74
55
|
const state = this.getWorkflowState(id);
|
|
75
56
|
if (!state)
|
|
76
|
-
return
|
|
77
|
-
|
|
78
|
-
const result = state.lastSyncedHash === currentHash;
|
|
79
|
-
if (!result) {
|
|
80
|
-
// Changed from console.log to silent for production
|
|
81
|
-
}
|
|
82
|
-
return result;
|
|
57
|
+
return false;
|
|
58
|
+
return state.lastSyncedHash === currentHash;
|
|
83
59
|
}
|
|
84
60
|
/**
|
|
85
|
-
*
|
|
61
|
+
* Get the entire state object (for Watcher's internal use)
|
|
62
|
+
* @internal
|
|
86
63
|
*/
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (!state)
|
|
90
|
-
return true;
|
|
91
|
-
const remoteHash = StateManager.computeHash(remoteWorkflow);
|
|
92
|
-
const result = state.lastSyncedHash === remoteHash;
|
|
93
|
-
if (!result) {
|
|
94
|
-
// Changed from console.log to silent for production
|
|
95
|
-
}
|
|
96
|
-
return result;
|
|
64
|
+
getFullState() {
|
|
65
|
+
return this.load();
|
|
97
66
|
}
|
|
98
67
|
}
|
|
99
68
|
//# sourceMappingURL=state-manager.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state-manager.js","sourceRoot":"","sources":["../../src/services/state-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"state-manager.js","sourceRoot":"","sources":["../../src/services/state-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AAYxB;;;;;;;;;GASG;AACH,MAAM,OAAO,YAAY;IACb,aAAa,CAAS;IAE9B,YAAY,SAAiB;QACzB,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,iBAAiB,CAAC,CAAC;IACjE,CAAC;IAED;;OAEG;IACK,IAAI;QACR,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC;YACpC,IAAI,CAAC;gBACD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC,CAAC;gBACtE,iCAAiC;gBACjC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;oBAClB,IAAI,CAAC,SAAS,GAAG,EAAE,CAAC;gBACxB,CAAC;gBACD,OAAO,IAAI,CAAC;YAChB,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACT,OAAO,CAAC,IAAI,CAAC,8CAA8C,CAAC,CAAC;YACjE,CAAC;QACL,CAAC;QACD,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;IAC7B,CAAC;IAED;;OAEG;IACH,gBAAgB,CAAC,EAAU;QACvB,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC1B,OAAO,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IAC/B,CAAC;IAED;;OAEG;IACH,qBAAqB;QACjB,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC1B,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IACxC,CAAC;IAED;;OAEG;IACH,QAAQ,CAAC,EAAU,EAAE,WAAmB;QACpC,MAAM,KAAK,GAAG,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC;QACxC,IAAI,CAAC,KAAK;YAAE,OAAO,KAAK,CAAC;QACzB,OAAO,KAAK,CAAC,cAAc,KAAK,WAAW,CAAC;IAChD,CAAC;IAED;;;OAGG;IACH,YAAY;QACR,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC;IACvB,CAAC;CACJ"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { N8nApiClient } from './n8n-api-client.js';
|
|
2
|
+
import { Watcher } from './watcher.js';
|
|
3
|
+
import { WorkflowSyncStatus } from '../types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Sync Engine - State Mutation Component
|
|
6
|
+
*
|
|
7
|
+
* Responsibilities:
|
|
8
|
+
* 1. Execute PULL/PUSH operations based on status
|
|
9
|
+
* 2. Follow strategy tables from SPECS/REFACTO_CORE.md
|
|
10
|
+
* 3. Call Watcher.finalizeSync after successful operations
|
|
11
|
+
* 4. Handle archive operations
|
|
12
|
+
*
|
|
13
|
+
* Stateless regarding history - never writes to state file directly
|
|
14
|
+
*/
|
|
15
|
+
export declare class SyncEngine {
|
|
16
|
+
private client;
|
|
17
|
+
private watcher;
|
|
18
|
+
private directory;
|
|
19
|
+
private archiveDirectory;
|
|
20
|
+
constructor(client: N8nApiClient, watcher: Watcher, directory: string);
|
|
21
|
+
/**
|
|
22
|
+
* PULL Strategy: Remote -> Local
|
|
23
|
+
* Based on spec 5.2 PULL Strategy table
|
|
24
|
+
*/
|
|
25
|
+
pull(workflowId: string, filename: string, status: WorkflowSyncStatus): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* PUSH Strategy: Local -> Remote
|
|
28
|
+
* Based on spec 5.3 PUSH Strategy table
|
|
29
|
+
*/
|
|
30
|
+
push(filename: string, workflowId?: string, status?: WorkflowSyncStatus): Promise<string>;
|
|
31
|
+
/**
|
|
32
|
+
* Force PULL - overwrite local with remote (for conflict resolution)
|
|
33
|
+
*/
|
|
34
|
+
forcePull(workflowId: string, filename: string): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Force PUSH - overwrite remote with local (for conflict resolution and restoration)
|
|
37
|
+
* If workflow doesn't exist on remote, creates it
|
|
38
|
+
*/
|
|
39
|
+
forcePush(workflowId: string, filename: string): Promise<string>;
|
|
40
|
+
/**
|
|
41
|
+
* Delete remote workflow (for deletion validation)
|
|
42
|
+
* Note: The Watcher already archived the remote content when it detected the local deletion
|
|
43
|
+
*/
|
|
44
|
+
deleteRemote(workflowId: string, filename: string): Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* Restore from archive (for deletion validation)
|
|
47
|
+
* Moves the file from archive back to workflows directory
|
|
48
|
+
* Then DELETES the archive file (no need to keep it after restoration)
|
|
49
|
+
*/
|
|
50
|
+
restoreFromArchive(filename: string): Promise<boolean>;
|
|
51
|
+
private executePull;
|
|
52
|
+
private executeUpdate;
|
|
53
|
+
private executeCreate;
|
|
54
|
+
archive(filename: string): Promise<void>;
|
|
55
|
+
private readJsonFile;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=sync-engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-engine.d.ts","sourceRoot":"","sources":["../../src/services/sync-engine.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGnD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,kBAAkB,EAAa,MAAM,aAAa,CAAC;AAE5D;;;;;;;;;;GAUG;AACH,qBAAa,UAAU;IACnB,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,gBAAgB,CAAS;gBAG7B,MAAM,EAAE,YAAY,EACpB,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,MAAM;IAYrB;;;OAGG;IACU,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAoDlG;;;OAGG;IACU,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC;IAuDtG;;OAEG;IACU,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAa3E;;;OAGG;IACU,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAkC7E;;;OAGG;IACU,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkB9E;;;;OAIG;IACU,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;YAyBrD,WAAW;YAwBX,aAAa;YAwBb,aAAa;IAwBd,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQrD,OAAO,CAAC,YAAY;CAOvB"}
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { WorkflowSanitizer } from './workflow-sanitizer.js';
|
|
4
|
+
import { HashUtils } from './hash-utils.js';
|
|
5
|
+
import { WorkflowSyncStatus } from '../types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Sync Engine - State Mutation Component
|
|
8
|
+
*
|
|
9
|
+
* Responsibilities:
|
|
10
|
+
* 1. Execute PULL/PUSH operations based on status
|
|
11
|
+
* 2. Follow strategy tables from SPECS/REFACTO_CORE.md
|
|
12
|
+
* 3. Call Watcher.finalizeSync after successful operations
|
|
13
|
+
* 4. Handle archive operations
|
|
14
|
+
*
|
|
15
|
+
* Stateless regarding history - never writes to state file directly
|
|
16
|
+
*/
|
|
17
|
+
export class SyncEngine {
|
|
18
|
+
client;
|
|
19
|
+
watcher;
|
|
20
|
+
directory;
|
|
21
|
+
archiveDirectory;
|
|
22
|
+
constructor(client, watcher, directory) {
|
|
23
|
+
this.client = client;
|
|
24
|
+
this.watcher = watcher;
|
|
25
|
+
this.directory = directory;
|
|
26
|
+
this.archiveDirectory = path.join(directory, '.archive');
|
|
27
|
+
if (!fs.existsSync(this.archiveDirectory)) {
|
|
28
|
+
fs.mkdirSync(this.archiveDirectory, { recursive: true });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* PULL Strategy: Remote -> Local
|
|
33
|
+
* Based on spec 5.2 PULL Strategy table
|
|
34
|
+
*/
|
|
35
|
+
async pull(workflowId, filename, status) {
|
|
36
|
+
// Mark sync in progress to prevent race conditions
|
|
37
|
+
this.watcher.markSyncInProgress(workflowId);
|
|
38
|
+
this.watcher.pauseObservation(workflowId);
|
|
39
|
+
try {
|
|
40
|
+
switch (status) {
|
|
41
|
+
case WorkflowSyncStatus.EXIST_ONLY_REMOTELY:
|
|
42
|
+
// Download Remote JSON -> Write to disk
|
|
43
|
+
await this.executePull(workflowId, filename);
|
|
44
|
+
// Initialize lastSyncedHash via finalizeSync
|
|
45
|
+
await this.watcher.finalizeSync(workflowId);
|
|
46
|
+
break;
|
|
47
|
+
case WorkflowSyncStatus.MODIFIED_REMOTELY:
|
|
48
|
+
// Download Remote JSON -> Overwrite local file
|
|
49
|
+
await this.executePull(workflowId, filename);
|
|
50
|
+
// Update lastSyncedHash via finalizeSync
|
|
51
|
+
await this.watcher.finalizeSync(workflowId);
|
|
52
|
+
break;
|
|
53
|
+
case WorkflowSyncStatus.DELETED_REMOTELY:
|
|
54
|
+
// Move local file to archive
|
|
55
|
+
await this.archive(filename);
|
|
56
|
+
// Remove from state (handled by watcher after observation resumes)
|
|
57
|
+
// Watcher will detect file deletion and update status
|
|
58
|
+
break;
|
|
59
|
+
case WorkflowSyncStatus.CONFLICT:
|
|
60
|
+
// Halt - trigger conflict resolution
|
|
61
|
+
throw new Error(`Conflict detected for workflow ${workflowId}. Use resolveConflict instead.`);
|
|
62
|
+
case WorkflowSyncStatus.DELETED_LOCALLY:
|
|
63
|
+
// No action per spec
|
|
64
|
+
break;
|
|
65
|
+
case WorkflowSyncStatus.EXIST_ONLY_LOCALLY:
|
|
66
|
+
case WorkflowSyncStatus.IN_SYNC:
|
|
67
|
+
case WorkflowSyncStatus.MODIFIED_LOCALLY:
|
|
68
|
+
// No action per spec
|
|
69
|
+
break;
|
|
70
|
+
default:
|
|
71
|
+
console.warn(`[SyncEngine] Unhandled status ${status} for PULL operation`);
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
this.watcher.markSyncComplete(workflowId);
|
|
77
|
+
this.watcher.resumeObservation(workflowId);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* PUSH Strategy: Local -> Remote
|
|
82
|
+
* Based on spec 5.3 PUSH Strategy table
|
|
83
|
+
*/
|
|
84
|
+
async push(filename, workflowId, status) {
|
|
85
|
+
if (workflowId) {
|
|
86
|
+
this.watcher.markSyncInProgress(workflowId);
|
|
87
|
+
this.watcher.pauseObservation(workflowId);
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
// If no workflowId, treat as EXIST_ONLY_LOCALLY
|
|
91
|
+
if (!workflowId || status === WorkflowSyncStatus.EXIST_ONLY_LOCALLY) {
|
|
92
|
+
// POST to API (Create)
|
|
93
|
+
const newWorkflowId = await this.executeCreate(filename);
|
|
94
|
+
// Initialize lastSyncedHash via finalizeSync
|
|
95
|
+
await this.watcher.finalizeSync(newWorkflowId);
|
|
96
|
+
return newWorkflowId;
|
|
97
|
+
}
|
|
98
|
+
// With workflowId and status
|
|
99
|
+
switch (status) {
|
|
100
|
+
case WorkflowSyncStatus.MODIFIED_LOCALLY:
|
|
101
|
+
// PUT to API (Update)
|
|
102
|
+
await this.executeUpdate(workflowId, filename);
|
|
103
|
+
// Update lastSyncedHash via finalizeSync
|
|
104
|
+
await this.watcher.finalizeSync(workflowId);
|
|
105
|
+
return workflowId;
|
|
106
|
+
case WorkflowSyncStatus.DELETED_LOCALLY:
|
|
107
|
+
// Step 1: Archive Remote to _archive/
|
|
108
|
+
await this.archive(filename);
|
|
109
|
+
// Step 2: Trigger Deletion Validation (caller should handle)
|
|
110
|
+
// Note: Actual API deletion happens in ResolutionManager
|
|
111
|
+
throw new Error(`Local deletion detected for workflow ${workflowId}. Use confirmDeletion instead.`);
|
|
112
|
+
case WorkflowSyncStatus.CONFLICT:
|
|
113
|
+
// Halt - trigger conflict resolution
|
|
114
|
+
throw new Error(`Conflict detected for workflow ${workflowId}. Use resolveConflict instead.`);
|
|
115
|
+
case WorkflowSyncStatus.EXIST_ONLY_REMOTELY:
|
|
116
|
+
case WorkflowSyncStatus.IN_SYNC:
|
|
117
|
+
case WorkflowSyncStatus.MODIFIED_REMOTELY:
|
|
118
|
+
case WorkflowSyncStatus.DELETED_REMOTELY:
|
|
119
|
+
// No action per spec
|
|
120
|
+
return workflowId;
|
|
121
|
+
default:
|
|
122
|
+
console.warn(`[SyncEngine] Unhandled status ${status} for PUSH operation`);
|
|
123
|
+
return workflowId;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
finally {
|
|
127
|
+
if (workflowId) {
|
|
128
|
+
this.watcher.markSyncComplete(workflowId);
|
|
129
|
+
this.watcher.resumeObservation(workflowId);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Force PULL - overwrite local with remote (for conflict resolution)
|
|
135
|
+
*/
|
|
136
|
+
async forcePull(workflowId, filename) {
|
|
137
|
+
this.watcher.markSyncInProgress(workflowId);
|
|
138
|
+
this.watcher.pauseObservation(workflowId);
|
|
139
|
+
try {
|
|
140
|
+
await this.executePull(workflowId, filename);
|
|
141
|
+
await this.watcher.finalizeSync(workflowId);
|
|
142
|
+
}
|
|
143
|
+
finally {
|
|
144
|
+
this.watcher.markSyncComplete(workflowId);
|
|
145
|
+
this.watcher.resumeObservation(workflowId);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Force PUSH - overwrite remote with local (for conflict resolution and restoration)
|
|
150
|
+
* If workflow doesn't exist on remote, creates it
|
|
151
|
+
*/
|
|
152
|
+
async forcePush(workflowId, filename) {
|
|
153
|
+
this.watcher.markSyncInProgress(workflowId);
|
|
154
|
+
this.watcher.pauseObservation(workflowId);
|
|
155
|
+
let finalWorkflowId = workflowId;
|
|
156
|
+
try {
|
|
157
|
+
// Try to update first
|
|
158
|
+
try {
|
|
159
|
+
await this.executeUpdate(workflowId, filename);
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
// If update fails with 404, create the workflow instead
|
|
163
|
+
if (error.response?.status === 404 || error.message?.includes('404') || error.message?.includes('Not Found')) {
|
|
164
|
+
console.log(`[SyncEngine] Workflow ${workflowId} not found, creating new workflow`);
|
|
165
|
+
const newWorkflowId = await this.executeCreate(filename);
|
|
166
|
+
// Migrate state from old ID to new ID
|
|
167
|
+
if (newWorkflowId !== workflowId) {
|
|
168
|
+
await this.watcher.updateWorkflowId(workflowId, newWorkflowId);
|
|
169
|
+
finalWorkflowId = newWorkflowId;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
throw error;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
await this.watcher.finalizeSync(finalWorkflowId);
|
|
177
|
+
return finalWorkflowId;
|
|
178
|
+
}
|
|
179
|
+
finally {
|
|
180
|
+
this.watcher.markSyncComplete(finalWorkflowId);
|
|
181
|
+
this.watcher.resumeObservation(finalWorkflowId);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Delete remote workflow (for deletion validation)
|
|
186
|
+
* Note: The Watcher already archived the remote content when it detected the local deletion
|
|
187
|
+
*/
|
|
188
|
+
async deleteRemote(workflowId, filename) {
|
|
189
|
+
this.watcher.markSyncInProgress(workflowId);
|
|
190
|
+
this.watcher.pauseObservation(workflowId);
|
|
191
|
+
try {
|
|
192
|
+
// Delete from API
|
|
193
|
+
await this.client.deleteWorkflow(workflowId);
|
|
194
|
+
// Archive local file if it still exists (edge case - shouldn't happen for DELETED_LOCALLY)
|
|
195
|
+
await this.archive(filename);
|
|
196
|
+
// Note: State removal will be handled by caller (ResolutionManager)
|
|
197
|
+
}
|
|
198
|
+
finally {
|
|
199
|
+
this.watcher.markSyncComplete(workflowId);
|
|
200
|
+
this.watcher.resumeObservation(workflowId);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Restore from archive (for deletion validation)
|
|
205
|
+
* Moves the file from archive back to workflows directory
|
|
206
|
+
* Then DELETES the archive file (no need to keep it after restoration)
|
|
207
|
+
*/
|
|
208
|
+
async restoreFromArchive(filename) {
|
|
209
|
+
const archiveFiles = fs.readdirSync(this.archiveDirectory);
|
|
210
|
+
const matchingArchives = archiveFiles.filter(f => f.includes(filename));
|
|
211
|
+
if (matchingArchives.length === 0) {
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
// Get most recent archive
|
|
215
|
+
const mostRecent = matchingArchives.sort().reverse()[0];
|
|
216
|
+
const archivePath = path.join(this.archiveDirectory, mostRecent);
|
|
217
|
+
const targetPath = path.join(this.directory, filename);
|
|
218
|
+
// Read content from archive
|
|
219
|
+
const content = fs.readFileSync(archivePath, 'utf-8');
|
|
220
|
+
// Write to target location
|
|
221
|
+
fs.writeFileSync(targetPath, content);
|
|
222
|
+
// Delete the archive file (no need to keep it after restoration)
|
|
223
|
+
fs.unlinkSync(archivePath);
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
async executePull(workflowId, filename) {
|
|
227
|
+
const fullWf = await this.client.getWorkflow(workflowId);
|
|
228
|
+
if (!fullWf) {
|
|
229
|
+
// Workflow might have been deleted (DELETED_REMOTELY case)
|
|
230
|
+
// Check if local file exists - if so, archive it
|
|
231
|
+
const filePath = path.join(this.directory, filename);
|
|
232
|
+
if (fs.existsSync(filePath)) {
|
|
233
|
+
await this.archive(filename);
|
|
234
|
+
// Don't throw - archiving is the expected behavior for DELETED_REMOTELY
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
throw new Error(`Remote workflow ${workflowId} not found during pull`);
|
|
238
|
+
}
|
|
239
|
+
const clean = WorkflowSanitizer.cleanForStorage(fullWf);
|
|
240
|
+
const filePath = path.join(this.directory, filename);
|
|
241
|
+
fs.writeFileSync(filePath, JSON.stringify(clean, null, 2));
|
|
242
|
+
// Update Watcher's remote hash cache since we just fetched the workflow
|
|
243
|
+
// This ensures finalizeSync has the remote hash
|
|
244
|
+
const hash = HashUtils.computeHash(clean);
|
|
245
|
+
this.watcher.setRemoteHash(workflowId, hash);
|
|
246
|
+
}
|
|
247
|
+
async executeUpdate(workflowId, filename) {
|
|
248
|
+
const filePath = path.join(this.directory, filename);
|
|
249
|
+
const localWf = this.readJsonFile(filePath);
|
|
250
|
+
if (!localWf) {
|
|
251
|
+
throw new Error('Local file not found during push');
|
|
252
|
+
}
|
|
253
|
+
const payload = WorkflowSanitizer.cleanForPush(localWf);
|
|
254
|
+
const updatedWf = await this.client.updateWorkflow(workflowId, payload);
|
|
255
|
+
if (!updatedWf) {
|
|
256
|
+
throw new Error('Failed to update remote workflow');
|
|
257
|
+
}
|
|
258
|
+
// CRITICAL: Write the API response back to local file to ensure consistency
|
|
259
|
+
// This ensures local and remote have identical content after push
|
|
260
|
+
const clean = WorkflowSanitizer.cleanForStorage(updatedWf);
|
|
261
|
+
fs.writeFileSync(filePath, JSON.stringify(clean, null, 2));
|
|
262
|
+
// Update Watcher's remote hash cache with the updated workflow
|
|
263
|
+
const hash = HashUtils.computeHash(clean);
|
|
264
|
+
this.watcher.setRemoteHash(workflowId, hash);
|
|
265
|
+
}
|
|
266
|
+
async executeCreate(filename) {
|
|
267
|
+
const filePath = path.join(this.directory, filename);
|
|
268
|
+
const localWf = this.readJsonFile(filePath);
|
|
269
|
+
if (!localWf) {
|
|
270
|
+
throw new Error('Local file not found during creation');
|
|
271
|
+
}
|
|
272
|
+
const payload = WorkflowSanitizer.cleanForPush(localWf);
|
|
273
|
+
if (!payload.name) {
|
|
274
|
+
payload.name = path.parse(filename).name;
|
|
275
|
+
}
|
|
276
|
+
const newWf = await this.client.createWorkflow(payload);
|
|
277
|
+
if (!newWf || !newWf.id) {
|
|
278
|
+
throw new Error('Failed to create remote workflow');
|
|
279
|
+
}
|
|
280
|
+
// Update local file with new ID
|
|
281
|
+
localWf.id = newWf.id;
|
|
282
|
+
fs.writeFileSync(filePath, JSON.stringify(localWf, null, 2));
|
|
283
|
+
return newWf.id;
|
|
284
|
+
}
|
|
285
|
+
async archive(filename) {
|
|
286
|
+
const filePath = path.join(this.directory, filename);
|
|
287
|
+
if (fs.existsSync(filePath)) {
|
|
288
|
+
const archivePath = path.join(this.archiveDirectory, `${Date.now()}_${filename}`);
|
|
289
|
+
fs.renameSync(filePath, archivePath);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
readJsonFile(filePath) {
|
|
293
|
+
try {
|
|
294
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
//# sourceMappingURL=sync-engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-engine.js","sourceRoot":"","sources":["../../src/services/sync-engine.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,98 +4,34 @@ import { ISyncConfig, IWorkflowStatus } from '../types.js';
|
|
|
4
4
|
export declare class SyncManager extends EventEmitter {
|
|
5
5
|
private client;
|
|
6
6
|
private config;
|
|
7
|
-
private fileToIdMap;
|
|
8
|
-
private selfWrittenCache;
|
|
9
|
-
private isWriting;
|
|
10
|
-
private pendingDeletions;
|
|
11
|
-
private watcher;
|
|
12
|
-
private pollInterval;
|
|
13
7
|
private stateManager;
|
|
14
|
-
private
|
|
8
|
+
private watcher;
|
|
9
|
+
private syncEngine;
|
|
10
|
+
private resolutionManager;
|
|
15
11
|
constructor(client: N8nApiClient, config: ISyncConfig);
|
|
16
|
-
|
|
17
|
-
* Get the path to the instance configuration file
|
|
18
|
-
*/
|
|
19
|
-
private getInstanceConfigPath;
|
|
20
|
-
/**
|
|
21
|
-
* Load instance configuration from disk
|
|
22
|
-
*/
|
|
23
|
-
private loadInstanceConfig;
|
|
24
|
-
/**
|
|
25
|
-
* Save instance configuration to disk
|
|
26
|
-
*/
|
|
27
|
-
private saveInstanceConfig;
|
|
28
|
-
/**
|
|
29
|
-
* Ensure instance identifier is set and persistent
|
|
30
|
-
*/
|
|
31
|
-
private ensureInstanceIdentifier;
|
|
32
|
-
private initializeInstanceIdentifier;
|
|
33
|
-
getInstanceDirectory(): string;
|
|
34
|
-
private getFilePath;
|
|
35
|
-
private safeName;
|
|
36
|
-
private normalizeContent;
|
|
37
|
-
private markAsSelfWritten;
|
|
38
|
-
private isSelfWritten;
|
|
39
|
-
loadRemoteState(): Promise<void>;
|
|
40
|
-
/**
|
|
41
|
-
* Retrieves the status of all workflows (local and remote)
|
|
42
|
-
*/
|
|
12
|
+
private ensureInitialized;
|
|
43
13
|
getWorkflowsStatus(): Promise<IWorkflowStatus[]>;
|
|
44
|
-
private formatSummary;
|
|
45
|
-
/**
|
|
46
|
-
* Scans n8n instance and updates local files (Downstream Sync)
|
|
47
|
-
*/
|
|
48
14
|
syncDown(): Promise<void>;
|
|
49
|
-
/**
|
|
50
|
-
* Identifies and handles workflows deleted on n8n but still present locally and tracked in state
|
|
51
|
-
*/
|
|
52
|
-
private processRemoteDeletions;
|
|
53
|
-
/**
|
|
54
|
-
* Scans n8n instance and updates local files with conflict resolution
|
|
55
|
-
*/
|
|
56
|
-
syncDownWithConflictResolution(): Promise<void>;
|
|
57
|
-
/**
|
|
58
|
-
* Pulls a single workflow by ID and writes to filename
|
|
59
|
-
* @param force If true, overwrites local changes without checking for conflicts
|
|
60
|
-
*/
|
|
61
|
-
pullWorkflow(filename: string, id: string, force?: boolean): Promise<void>;
|
|
62
|
-
/**
|
|
63
|
-
* Pulls a single workflow with conflict resolution
|
|
64
|
-
* @returns 'updated' if file was updated, 'skipped' if no change or conflict, 'new' if file was created
|
|
65
|
-
*/
|
|
66
|
-
pullWorkflowWithConflictResolution(filename: string, id: string, remoteUpdatedAt?: string): Promise<'updated' | 'skipped' | 'new' | 'up-to-date' | 'conflict'>;
|
|
67
|
-
/**
|
|
68
|
-
* Writes file to disk only if changed
|
|
69
|
-
*/
|
|
70
|
-
private writeLocalFile;
|
|
71
|
-
private shouldIgnore;
|
|
72
|
-
/**
|
|
73
|
-
* Uploads local files that don't exist remotely (Upstream Sync - Init)
|
|
74
|
-
*/
|
|
75
|
-
syncUpMissing(): Promise<void>;
|
|
76
|
-
/**
|
|
77
|
-
* Full Upstream Sync: Updates existing and Creates new.
|
|
78
|
-
*/
|
|
79
15
|
syncUp(): Promise<void>;
|
|
16
|
+
startWatch(): Promise<void>;
|
|
80
17
|
/**
|
|
81
|
-
*
|
|
82
|
-
|
|
83
|
-
handleLocalFileDeletion(filePath: string): Promise<void>;
|
|
84
|
-
/**
|
|
85
|
-
* Actually deletes the remote workflow and cleans up local state
|
|
86
|
-
*/
|
|
87
|
-
deleteRemoteWorkflow(id: string, filename: string): Promise<boolean>;
|
|
88
|
-
/**
|
|
89
|
-
* Restore a deleted local file from remote
|
|
18
|
+
* Create or update the n8n-as-code-instance.json file
|
|
19
|
+
* This file marks the workspace as initialized and stores the instance identifier
|
|
90
20
|
*/
|
|
91
|
-
|
|
21
|
+
private ensureInstanceConfigFile;
|
|
92
22
|
/**
|
|
93
|
-
* Handle
|
|
23
|
+
* Handle automatic synchronization based on status changes
|
|
24
|
+
* Only triggered in auto mode
|
|
94
25
|
*/
|
|
95
|
-
|
|
96
|
-
private readLocalFile;
|
|
97
|
-
private readRawFile;
|
|
98
|
-
startWatch(): Promise<void>;
|
|
26
|
+
private handleAutoSync;
|
|
99
27
|
stopWatch(): void;
|
|
28
|
+
refreshState(): Promise<void>;
|
|
29
|
+
getInstanceDirectory(): string;
|
|
30
|
+
resolveConflict(id: string, filename: string, choice: 'local' | 'remote'): Promise<void>;
|
|
31
|
+
handleLocalFileChange(filePath: string): Promise<'updated' | 'created' | 'up-to-date' | 'conflict' | 'skipped'>;
|
|
32
|
+
restoreLocalFile(id: string, filename: string): Promise<boolean>;
|
|
33
|
+
deleteRemoteWorkflow(id: string, filename: string): Promise<boolean>;
|
|
34
|
+
confirmDeletion(id: string, filename: string): Promise<void>;
|
|
35
|
+
restoreRemoteWorkflow(id: string, filename: string): Promise<string>;
|
|
100
36
|
}
|
|
101
37
|
//# sourceMappingURL=sync-manager.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync-manager.d.ts","sourceRoot":"","sources":["../../src/services/sync-manager.ts"],"names":[],"mappings":"AAEA,OAAO,YAAY,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"sync-manager.d.ts","sourceRoot":"","sources":["../../src/services/sync-manager.ts"],"names":[],"mappings":"AAEA,OAAO,YAAY,MAAM,QAAQ,CAAC;AAClC,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAKnD,OAAO,EAAE,WAAW,EAAiC,eAAe,EAAE,MAAM,aAAa,CAAC;AAE1F,qBAAa,WAAY,SAAQ,YAAY;IACzC,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,UAAU,CAA2B;IAC7C,OAAO,CAAC,iBAAiB,CAAkC;gBAE/C,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,WAAW;YAUvC,iBAAiB;IAgEzB,kBAAkB,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;IAMhD,QAAQ;IAaR,MAAM;IAaN,UAAU;IAUhB;;;OAGG;IACH,OAAO,CAAC,wBAAwB;IAsBhC;;;OAGG;YACW,cAAc;IAkD5B,SAAS;IAKH,YAAY;IAOX,oBAAoB,IAAI,MAAM;IAK/B,eAAe,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,QAAQ;IASxE,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,GAAG,YAAY,GAAG,UAAU,GAAG,SAAS,CAAC;IAwB/G,gBAAgB,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAmBhE,oBAAoB,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAgBpE,eAAe,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAa5D,qBAAqB,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAI7E"}
|