pushwork 2.0.0-a.sub.0 → 2.0.0-preview
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/branches.d.ts +19 -0
- package/dist/branches.d.ts.map +1 -0
- package/dist/branches.js +111 -0
- package/dist/branches.js.map +1 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +238 -272
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +17 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +84 -0
- package/dist/config.js.map +1 -0
- package/dist/fs-tree.d.ts +6 -0
- package/dist/fs-tree.d.ts.map +1 -0
- package/dist/fs-tree.js +99 -0
- package/dist/fs-tree.js.map +1 -0
- package/dist/ignore.d.ts +6 -0
- package/dist/ignore.d.ts.map +1 -0
- package/dist/ignore.js +74 -0
- package/dist/ignore.js.map +1 -0
- package/dist/index.d.ts +8 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +34 -4
- package/dist/index.js.map +1 -1
- package/dist/log.d.ts +3 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +14 -0
- package/dist/log.js.map +1 -0
- package/dist/pushwork.d.ts +115 -0
- package/dist/pushwork.d.ts.map +1 -0
- package/dist/pushwork.js +918 -0
- package/dist/pushwork.js.map +1 -0
- package/dist/repo.d.ts +14 -0
- package/dist/repo.d.ts.map +1 -0
- package/dist/repo.js +60 -0
- package/dist/repo.js.map +1 -0
- package/dist/shapes/custom.d.ts +3 -0
- package/dist/shapes/custom.d.ts.map +1 -0
- package/dist/shapes/custom.js +57 -0
- package/dist/shapes/custom.js.map +1 -0
- package/dist/shapes/file.d.ts +20 -0
- package/dist/shapes/file.d.ts.map +1 -0
- package/dist/shapes/file.js +140 -0
- package/dist/shapes/file.js.map +1 -0
- package/dist/shapes/index.d.ts +10 -0
- package/dist/shapes/index.d.ts.map +1 -0
- package/dist/shapes/index.js +35 -0
- package/dist/shapes/index.js.map +1 -0
- package/dist/shapes/patchwork-folder.d.ts +3 -0
- package/dist/shapes/patchwork-folder.d.ts.map +1 -0
- package/dist/shapes/patchwork-folder.js +160 -0
- package/dist/shapes/patchwork-folder.js.map +1 -0
- package/dist/shapes/types.d.ts +37 -0
- package/dist/shapes/types.d.ts.map +1 -0
- package/dist/shapes/types.js +52 -0
- package/dist/shapes/types.js.map +1 -0
- package/dist/shapes/vfs.d.ts +3 -0
- package/dist/shapes/vfs.d.ts.map +1 -0
- package/dist/shapes/vfs.js +88 -0
- package/dist/shapes/vfs.js.map +1 -0
- package/dist/stash.d.ts +23 -0
- package/dist/stash.d.ts.map +1 -0
- package/dist/stash.js +118 -0
- package/dist/stash.js.map +1 -0
- package/flake.lock +128 -0
- package/flake.nix +66 -0
- package/package.json +15 -48
- package/patches/@automerge__automerge-repo@2.6.0-subduction.15.patch +26 -0
- package/pnpm-workspace.yaml +5 -0
- package/src/branches.ts +93 -0
- package/src/cli.ts +258 -408
- package/src/config.ts +64 -0
- package/src/fs-tree.ts +70 -0
- package/src/ignore.ts +33 -0
- package/src/index.ts +38 -4
- package/src/log.ts +8 -0
- package/src/pushwork.ts +1055 -0
- package/src/repo.ts +76 -0
- package/src/shapes/custom.ts +29 -0
- package/src/shapes/file.ts +115 -0
- package/src/shapes/index.ts +19 -0
- package/src/shapes/patchwork-folder.ts +156 -0
- package/src/shapes/types.ts +79 -0
- package/src/shapes/vfs.ts +93 -0
- package/src/stash.ts +106 -0
- package/test/integration/branches.test.ts +389 -0
- package/test/integration/pushwork.test.ts +547 -0
- package/test/setup.ts +29 -0
- package/test/unit/doc-shape.test.ts +612 -0
- package/tsconfig.json +2 -3
- package/vitest.config.ts +14 -0
- package/ARCHITECTURE-ACCORDING-TO-CLAUDE.md +0 -248
- package/CLAUDE.md +0 -141
- package/README.md +0 -221
- package/babel.config.js +0 -5
- package/dist/cli/commands.d.ts +0 -71
- package/dist/cli/commands.d.ts.map +0 -1
- package/dist/cli/commands.js +0 -794
- package/dist/cli/commands.js.map +0 -1
- package/dist/cli/index.d.ts +0 -2
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/index.js +0 -19
- package/dist/cli/index.js.map +0 -1
- package/dist/commands.d.ts +0 -61
- package/dist/commands.d.ts.map +0 -1
- package/dist/commands.js +0 -861
- package/dist/commands.js.map +0 -1
- package/dist/config/index.d.ts +0 -71
- package/dist/config/index.d.ts.map +0 -1
- package/dist/config/index.js +0 -314
- package/dist/config/index.js.map +0 -1
- package/dist/core/change-detection.d.ts +0 -80
- package/dist/core/change-detection.d.ts.map +0 -1
- package/dist/core/change-detection.js +0 -523
- package/dist/core/change-detection.js.map +0 -1
- package/dist/core/config.d.ts +0 -81
- package/dist/core/config.d.ts.map +0 -1
- package/dist/core/config.js +0 -258
- package/dist/core/config.js.map +0 -1
- package/dist/core/index.d.ts +0 -6
- package/dist/core/index.d.ts.map +0 -1
- package/dist/core/index.js +0 -6
- package/dist/core/index.js.map +0 -1
- package/dist/core/move-detection.d.ts +0 -34
- package/dist/core/move-detection.d.ts.map +0 -1
- package/dist/core/move-detection.js +0 -121
- package/dist/core/move-detection.js.map +0 -1
- package/dist/core/snapshot.d.ts +0 -105
- package/dist/core/snapshot.d.ts.map +0 -1
- package/dist/core/snapshot.js +0 -217
- package/dist/core/snapshot.js.map +0 -1
- package/dist/core/sync-engine.d.ts +0 -151
- package/dist/core/sync-engine.d.ts.map +0 -1
- package/dist/core/sync-engine.js +0 -1346
- package/dist/core/sync-engine.js.map +0 -1
- package/dist/types/config.d.ts +0 -99
- package/dist/types/config.d.ts.map +0 -1
- package/dist/types/config.js +0 -5
- package/dist/types/config.js.map +0 -1
- package/dist/types/documents.d.ts +0 -88
- package/dist/types/documents.d.ts.map +0 -1
- package/dist/types/documents.js +0 -20
- package/dist/types/documents.js.map +0 -1
- package/dist/types/index.d.ts +0 -4
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js +0 -4
- package/dist/types/index.js.map +0 -1
- package/dist/types/snapshot.d.ts +0 -64
- package/dist/types/snapshot.d.ts.map +0 -1
- package/dist/types/snapshot.js +0 -2
- package/dist/types/snapshot.js.map +0 -1
- package/dist/utils/content-similarity.d.ts +0 -53
- package/dist/utils/content-similarity.d.ts.map +0 -1
- package/dist/utils/content-similarity.js +0 -155
- package/dist/utils/content-similarity.js.map +0 -1
- package/dist/utils/content.d.ts +0 -10
- package/dist/utils/content.d.ts.map +0 -1
- package/dist/utils/content.js +0 -31
- package/dist/utils/content.js.map +0 -1
- package/dist/utils/directory.d.ts +0 -24
- package/dist/utils/directory.d.ts.map +0 -1
- package/dist/utils/directory.js +0 -52
- package/dist/utils/directory.js.map +0 -1
- package/dist/utils/fs.d.ts +0 -74
- package/dist/utils/fs.d.ts.map +0 -1
- package/dist/utils/fs.js +0 -248
- package/dist/utils/fs.js.map +0 -1
- package/dist/utils/index.d.ts +0 -5
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js +0 -5
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/mime-types.d.ts +0 -13
- package/dist/utils/mime-types.d.ts.map +0 -1
- package/dist/utils/mime-types.js +0 -209
- package/dist/utils/mime-types.js.map +0 -1
- package/dist/utils/network-sync.d.ts +0 -36
- package/dist/utils/network-sync.d.ts.map +0 -1
- package/dist/utils/network-sync.js +0 -250
- package/dist/utils/network-sync.js.map +0 -1
- package/dist/utils/node-polyfills.d.ts +0 -9
- package/dist/utils/node-polyfills.d.ts.map +0 -1
- package/dist/utils/node-polyfills.js +0 -9
- package/dist/utils/node-polyfills.js.map +0 -1
- package/dist/utils/output.d.ts +0 -129
- package/dist/utils/output.d.ts.map +0 -1
- package/dist/utils/output.js +0 -368
- package/dist/utils/output.js.map +0 -1
- package/dist/utils/repo-factory.d.ts +0 -13
- package/dist/utils/repo-factory.d.ts.map +0 -1
- package/dist/utils/repo-factory.js +0 -46
- package/dist/utils/repo-factory.js.map +0 -1
- package/dist/utils/string-similarity.d.ts +0 -14
- package/dist/utils/string-similarity.d.ts.map +0 -1
- package/dist/utils/string-similarity.js +0 -39
- package/dist/utils/string-similarity.js.map +0 -1
- package/dist/utils/text-diff.d.ts +0 -37
- package/dist/utils/text-diff.d.ts.map +0 -1
- package/dist/utils/text-diff.js +0 -93
- package/dist/utils/text-diff.js.map +0 -1
- package/dist/utils/trace.d.ts +0 -19
- package/dist/utils/trace.d.ts.map +0 -1
- package/dist/utils/trace.js +0 -63
- package/dist/utils/trace.js.map +0 -1
- package/src/commands.ts +0 -1134
- package/src/core/change-detection.ts +0 -712
- package/src/core/config.ts +0 -313
- package/src/core/index.ts +0 -5
- package/src/core/move-detection.ts +0 -169
- package/src/core/snapshot.ts +0 -275
- package/src/core/sync-engine.ts +0 -1758
- package/src/types/config.ts +0 -111
- package/src/types/documents.ts +0 -91
- package/src/types/index.ts +0 -3
- package/src/types/snapshot.ts +0 -67
- package/src/utils/content.ts +0 -34
- package/src/utils/directory.ts +0 -73
- package/src/utils/fs.ts +0 -297
- package/src/utils/index.ts +0 -4
- package/src/utils/mime-types.ts +0 -244
- package/src/utils/network-sync.ts +0 -319
- package/src/utils/node-polyfills.ts +0 -8
- package/src/utils/output.ts +0 -450
- package/src/utils/repo-factory.ts +0 -73
- package/src/utils/string-similarity.ts +0 -54
- package/src/utils/text-diff.ts +0 -101
- package/src/utils/trace.ts +0 -70
- package/test/integration/README.md +0 -328
- package/test/integration/clone-test.sh +0 -310
- package/test/integration/conflict-resolution-test.sh +0 -309
- package/test/integration/debug-both-nested.sh +0 -74
- package/test/integration/debug-concurrent-nested.sh +0 -87
- package/test/integration/debug-nested.sh +0 -73
- package/test/integration/deletion-behavior-test.sh +0 -487
- package/test/integration/deletion-sync-test-simple.sh +0 -193
- package/test/integration/deletion-sync-test.sh +0 -297
- package/test/integration/exclude-patterns.test.ts +0 -144
- package/test/integration/full-integration-test.sh +0 -363
- package/test/integration/fuzzer.test.ts +0 -818
- package/test/integration/in-memory-sync.test.ts +0 -830
- package/test/integration/init-sync.test.ts +0 -89
- package/test/integration/manual-sync-test.sh +0 -84
- package/test/integration/sync-deletion.test.ts +0 -280
- package/test/integration/sync-flow.test.ts +0 -291
- package/test/jest.setup.ts +0 -34
- package/test/run-tests.sh +0 -225
- package/test/unit/deletion-behavior.test.ts +0 -249
- package/test/unit/enhanced-mime-detection.test.ts +0 -244
- package/test/unit/snapshot.test.ts +0 -404
- package/test/unit/sync-convergence.test.ts +0 -298
- package/test/unit/sync-timing.test.ts +0 -134
- package/test/unit/utils.test.ts +0 -366
|
@@ -1,404 +0,0 @@
|
|
|
1
|
-
import * as path from "path";
|
|
2
|
-
import * as tmp from "tmp";
|
|
3
|
-
import { SnapshotManager } from "../../src/core/snapshot";
|
|
4
|
-
import { SnapshotFileEntry, SnapshotDirectoryEntry } from "../../src/types";
|
|
5
|
-
import { UrlHeads } from "@automerge/automerge-repo";
|
|
6
|
-
|
|
7
|
-
describe("SnapshotManager", () => {
|
|
8
|
-
let tmpDir: string;
|
|
9
|
-
let cleanup: () => void;
|
|
10
|
-
let snapshotManager: SnapshotManager;
|
|
11
|
-
|
|
12
|
-
beforeEach(() => {
|
|
13
|
-
const tmpObj = tmp.dirSync({ unsafeCleanup: true });
|
|
14
|
-
tmpDir = tmpObj.name;
|
|
15
|
-
cleanup = tmpObj.removeCallback;
|
|
16
|
-
snapshotManager = new SnapshotManager(tmpDir);
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
afterEach(() => {
|
|
20
|
-
cleanup();
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
describe("exists", () => {
|
|
24
|
-
it("should return false when no snapshot exists", async () => {
|
|
25
|
-
expect(await snapshotManager.exists()).toBe(false);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it("should return true when snapshot exists", async () => {
|
|
29
|
-
const snapshot = snapshotManager.createEmpty();
|
|
30
|
-
await snapshotManager.save(snapshot);
|
|
31
|
-
|
|
32
|
-
expect(await snapshotManager.exists()).toBe(true);
|
|
33
|
-
});
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
describe("createEmpty", () => {
|
|
37
|
-
it("should create an empty snapshot", () => {
|
|
38
|
-
const snapshot = snapshotManager.createEmpty();
|
|
39
|
-
|
|
40
|
-
expect(snapshot.rootPath).toBe(tmpDir);
|
|
41
|
-
expect(snapshot.timestamp).toBeGreaterThan(0);
|
|
42
|
-
expect(snapshot.files.size).toBe(0);
|
|
43
|
-
expect(snapshot.directories.size).toBe(0);
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
describe("save and load", () => {
|
|
48
|
-
it("should save and load empty snapshot", async () => {
|
|
49
|
-
const originalSnapshot = snapshotManager.createEmpty();
|
|
50
|
-
|
|
51
|
-
await snapshotManager.save(originalSnapshot);
|
|
52
|
-
const loadedSnapshot = await snapshotManager.load();
|
|
53
|
-
|
|
54
|
-
expect(loadedSnapshot).not.toBeNull();
|
|
55
|
-
expect(loadedSnapshot!.rootPath).toBe(originalSnapshot.rootPath);
|
|
56
|
-
expect(loadedSnapshot!.timestamp).toBe(originalSnapshot.timestamp);
|
|
57
|
-
expect(loadedSnapshot!.files.size).toBe(0);
|
|
58
|
-
expect(loadedSnapshot!.directories.size).toBe(0);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it("should save and load snapshot with files", async () => {
|
|
62
|
-
const snapshot = snapshotManager.createEmpty();
|
|
63
|
-
|
|
64
|
-
const fileEntry: SnapshotFileEntry = {
|
|
65
|
-
path: path.join(tmpDir, "test.txt"),
|
|
66
|
-
url: "automerge:test-url" as any,
|
|
67
|
-
head: ["test-head"] as UrlHeads,
|
|
68
|
-
extension: "txt",
|
|
69
|
-
mimeType: "text/plain",
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
snapshotManager.updateFileEntry(snapshot, "test.txt", fileEntry);
|
|
73
|
-
|
|
74
|
-
await snapshotManager.save(snapshot);
|
|
75
|
-
const loadedSnapshot = await snapshotManager.load();
|
|
76
|
-
|
|
77
|
-
expect(loadedSnapshot).not.toBeNull();
|
|
78
|
-
expect(loadedSnapshot!.files.size).toBe(1);
|
|
79
|
-
expect(loadedSnapshot!.files.get("test.txt")).toEqual(fileEntry);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it("should save and load snapshot with directories", async () => {
|
|
83
|
-
const snapshot = snapshotManager.createEmpty();
|
|
84
|
-
|
|
85
|
-
const dirEntry: SnapshotDirectoryEntry = {
|
|
86
|
-
path: path.join(tmpDir, "subdir"),
|
|
87
|
-
url: "automerge:dir-url" as any,
|
|
88
|
-
head: ["dir-head"] as UrlHeads,
|
|
89
|
-
entries: ["file1.txt", "file2.txt"],
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
snapshotManager.updateDirectoryEntry(snapshot, "subdir", dirEntry);
|
|
93
|
-
|
|
94
|
-
await snapshotManager.save(snapshot);
|
|
95
|
-
const loadedSnapshot = await snapshotManager.load();
|
|
96
|
-
|
|
97
|
-
expect(loadedSnapshot).not.toBeNull();
|
|
98
|
-
expect(loadedSnapshot!.directories.size).toBe(1);
|
|
99
|
-
expect(loadedSnapshot!.directories.get("subdir")).toEqual(dirEntry);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it("should return null when loading non-existent snapshot", async () => {
|
|
103
|
-
const loadedSnapshot = await snapshotManager.load();
|
|
104
|
-
expect(loadedSnapshot).toBeNull();
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
describe("updateFileEntry", () => {
|
|
109
|
-
it("should add new file entry", () => {
|
|
110
|
-
const snapshot = snapshotManager.createEmpty();
|
|
111
|
-
const originalTimestamp = snapshot.timestamp;
|
|
112
|
-
|
|
113
|
-
const fileEntry: SnapshotFileEntry = {
|
|
114
|
-
path: "/test/path/test.txt",
|
|
115
|
-
url: "automerge:test-url" as any,
|
|
116
|
-
head: ["test-head"] as UrlHeads,
|
|
117
|
-
extension: "txt",
|
|
118
|
-
mimeType: "text/plain",
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
// Add small delay to ensure timestamp changes
|
|
122
|
-
const startTime = Date.now();
|
|
123
|
-
while (Date.now() === startTime) {
|
|
124
|
-
// Wait for at least 1ms
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
snapshotManager.updateFileEntry(snapshot, "test.txt", fileEntry);
|
|
128
|
-
|
|
129
|
-
expect(snapshot.files.get("test.txt")).toEqual(fileEntry);
|
|
130
|
-
expect(snapshot.timestamp).toBeGreaterThan(originalTimestamp);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it("should update existing file entry", () => {
|
|
134
|
-
const snapshot = snapshotManager.createEmpty();
|
|
135
|
-
|
|
136
|
-
const fileEntry1: SnapshotFileEntry = {
|
|
137
|
-
path: path.join(tmpDir, "test.txt"),
|
|
138
|
-
url: "automerge:test-url" as any,
|
|
139
|
-
head: ["old-head"] as UrlHeads,
|
|
140
|
-
extension: "txt",
|
|
141
|
-
mimeType: "text/plain",
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
const fileEntry2: SnapshotFileEntry = {
|
|
145
|
-
path: path.join(tmpDir, "test.txt"),
|
|
146
|
-
url: "automerge:test-url" as any,
|
|
147
|
-
head: ["new-head"] as UrlHeads,
|
|
148
|
-
extension: "txt",
|
|
149
|
-
mimeType: "text/plain",
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
snapshotManager.updateFileEntry(snapshot, "test.txt", fileEntry1);
|
|
153
|
-
snapshotManager.updateFileEntry(snapshot, "test.txt", fileEntry2);
|
|
154
|
-
|
|
155
|
-
expect(snapshot.files.get("test.txt")).toEqual(fileEntry2);
|
|
156
|
-
expect(snapshot.files.size).toBe(1);
|
|
157
|
-
});
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
describe("removeFileEntry", () => {
|
|
161
|
-
it("should remove file entry", () => {
|
|
162
|
-
const snapshot = snapshotManager.createEmpty();
|
|
163
|
-
|
|
164
|
-
const fileEntry: SnapshotFileEntry = {
|
|
165
|
-
path: path.join(tmpDir, "test.txt"),
|
|
166
|
-
url: "automerge:test-url" as any,
|
|
167
|
-
head: ["test-head"] as UrlHeads,
|
|
168
|
-
extension: "txt",
|
|
169
|
-
mimeType: "text/plain",
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
snapshotManager.updateFileEntry(snapshot, "test.txt", fileEntry);
|
|
173
|
-
expect(snapshot.files.size).toBe(1);
|
|
174
|
-
|
|
175
|
-
snapshotManager.removeFileEntry(snapshot, "test.txt");
|
|
176
|
-
expect(snapshot.files.size).toBe(0);
|
|
177
|
-
expect(snapshot.files.get("test.txt")).toBeUndefined();
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
it("should not fail when removing non-existent file", () => {
|
|
181
|
-
const snapshot = snapshotManager.createEmpty();
|
|
182
|
-
|
|
183
|
-
snapshotManager.removeFileEntry(snapshot, "nonexistent.txt");
|
|
184
|
-
expect(snapshot.files.size).toBe(0);
|
|
185
|
-
});
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
describe("getFilePaths and getDirectoryPaths", () => {
|
|
189
|
-
it("should return all file paths", () => {
|
|
190
|
-
const snapshot = snapshotManager.createEmpty();
|
|
191
|
-
|
|
192
|
-
snapshotManager.updateFileEntry(snapshot, "file1.txt", {
|
|
193
|
-
path: path.join(tmpDir, "file1.txt"),
|
|
194
|
-
url: "automerge:url1" as any,
|
|
195
|
-
head: ["head1"] as UrlHeads,
|
|
196
|
-
extension: "txt",
|
|
197
|
-
mimeType: "text/plain",
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
snapshotManager.updateFileEntry(snapshot, "file2.txt", {
|
|
201
|
-
path: path.join(tmpDir, "file2.txt"),
|
|
202
|
-
url: "automerge:url2" as any,
|
|
203
|
-
head: ["head2"] as UrlHeads,
|
|
204
|
-
extension: "txt",
|
|
205
|
-
mimeType: "text/plain",
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
const filePaths = snapshotManager.getFilePaths(snapshot);
|
|
209
|
-
expect(filePaths.sort()).toEqual(["file1.txt", "file2.txt"]);
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
it("should return all directory paths", () => {
|
|
213
|
-
const snapshot = snapshotManager.createEmpty();
|
|
214
|
-
|
|
215
|
-
snapshotManager.updateDirectoryEntry(snapshot, "dir1", {
|
|
216
|
-
path: path.join(tmpDir, "dir1"),
|
|
217
|
-
url: "automerge:url1" as any,
|
|
218
|
-
head: ["head1"] as UrlHeads,
|
|
219
|
-
entries: [],
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
snapshotManager.updateDirectoryEntry(snapshot, "dir2", {
|
|
223
|
-
path: path.join(tmpDir, "dir2"),
|
|
224
|
-
url: "automerge:url2" as any,
|
|
225
|
-
head: ["head2"] as UrlHeads,
|
|
226
|
-
entries: [],
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
const dirPaths = snapshotManager.getDirectoryPaths(snapshot);
|
|
230
|
-
expect(dirPaths.sort()).toEqual(["dir1", "dir2"]);
|
|
231
|
-
});
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
describe("isTracked", () => {
|
|
235
|
-
it("should return true for tracked files", () => {
|
|
236
|
-
const snapshot = snapshotManager.createEmpty();
|
|
237
|
-
|
|
238
|
-
snapshotManager.updateFileEntry(snapshot, "test.txt", {
|
|
239
|
-
path: path.join(tmpDir, "test.txt"),
|
|
240
|
-
url: "automerge:url" as any,
|
|
241
|
-
head: ["head"] as UrlHeads,
|
|
242
|
-
extension: "txt",
|
|
243
|
-
mimeType: "text/plain",
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
expect(snapshotManager.isTracked(snapshot, "test.txt")).toBe(true);
|
|
247
|
-
expect(snapshotManager.isTracked(snapshot, "other.txt")).toBe(false);
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
it("should return true for tracked directories", () => {
|
|
251
|
-
const snapshot = snapshotManager.createEmpty();
|
|
252
|
-
|
|
253
|
-
snapshotManager.updateDirectoryEntry(snapshot, "subdir", {
|
|
254
|
-
path: path.join(tmpDir, "subdir"),
|
|
255
|
-
url: "automerge:url" as any,
|
|
256
|
-
head: ["head"] as UrlHeads,
|
|
257
|
-
entries: [],
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
expect(snapshotManager.isTracked(snapshot, "subdir")).toBe(true);
|
|
261
|
-
expect(snapshotManager.isTracked(snapshot, "other")).toBe(false);
|
|
262
|
-
});
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
describe("getStats", () => {
|
|
266
|
-
it("should return correct statistics", () => {
|
|
267
|
-
const snapshot = snapshotManager.createEmpty();
|
|
268
|
-
|
|
269
|
-
snapshotManager.updateFileEntry(snapshot, "file1.txt", {
|
|
270
|
-
path: path.join(tmpDir, "file1.txt"),
|
|
271
|
-
url: "automerge:url1" as any,
|
|
272
|
-
head: ["head1"] as UrlHeads,
|
|
273
|
-
extension: "txt",
|
|
274
|
-
mimeType: "text/plain",
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
snapshotManager.updateDirectoryEntry(snapshot, "dir1", {
|
|
278
|
-
path: path.join(tmpDir, "dir1"),
|
|
279
|
-
url: "automerge:url2" as any,
|
|
280
|
-
head: ["head2"] as UrlHeads,
|
|
281
|
-
entries: [],
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
const stats = snapshotManager.getStats(snapshot);
|
|
285
|
-
|
|
286
|
-
expect(stats.files).toBe(1);
|
|
287
|
-
expect(stats.directories).toBe(1);
|
|
288
|
-
expect(stats.timestamp).toBeInstanceOf(Date);
|
|
289
|
-
expect(stats.timestamp.getTime()).toBe(snapshot.timestamp);
|
|
290
|
-
});
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
describe("validate", () => {
|
|
294
|
-
it("should validate correct snapshot", () => {
|
|
295
|
-
const snapshot = snapshotManager.createEmpty();
|
|
296
|
-
|
|
297
|
-
const validation = snapshotManager.validate(snapshot);
|
|
298
|
-
|
|
299
|
-
expect(validation.valid).toBe(true);
|
|
300
|
-
expect(validation.errors).toHaveLength(0);
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
it("should detect invalid timestamp", () => {
|
|
304
|
-
const snapshot = snapshotManager.createEmpty();
|
|
305
|
-
snapshot.timestamp = 0;
|
|
306
|
-
|
|
307
|
-
const validation = snapshotManager.validate(snapshot);
|
|
308
|
-
|
|
309
|
-
expect(validation.valid).toBe(false);
|
|
310
|
-
expect(validation.errors).toContain("Invalid timestamp");
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
it("should detect missing root path", () => {
|
|
314
|
-
const snapshot = snapshotManager.createEmpty();
|
|
315
|
-
snapshot.rootPath = "";
|
|
316
|
-
|
|
317
|
-
const validation = snapshotManager.validate(snapshot);
|
|
318
|
-
|
|
319
|
-
expect(validation.valid).toBe(false);
|
|
320
|
-
expect(validation.errors).toContain("Missing root path");
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
it("should detect path conflicts", () => {
|
|
324
|
-
const snapshot = snapshotManager.createEmpty();
|
|
325
|
-
|
|
326
|
-
snapshotManager.updateFileEntry(snapshot, "conflict", {
|
|
327
|
-
path: path.join(tmpDir, "conflict"),
|
|
328
|
-
url: "automerge:url1" as any,
|
|
329
|
-
head: ["head1"] as UrlHeads,
|
|
330
|
-
extension: "",
|
|
331
|
-
mimeType: "text/plain",
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
snapshotManager.updateDirectoryEntry(snapshot, "conflict", {
|
|
335
|
-
path: path.join(tmpDir, "conflict"),
|
|
336
|
-
url: "automerge:url2" as any,
|
|
337
|
-
head: ["head2"] as UrlHeads,
|
|
338
|
-
entries: [],
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
const validation = snapshotManager.validate(snapshot);
|
|
342
|
-
|
|
343
|
-
expect(validation.valid).toBe(false);
|
|
344
|
-
expect(validation.errors).toContain(
|
|
345
|
-
"Path conflict: conflict exists as both file and directory"
|
|
346
|
-
);
|
|
347
|
-
});
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
describe("clone", () => {
|
|
351
|
-
it("should create independent copy of snapshot", () => {
|
|
352
|
-
const originalSnapshot = snapshotManager.createEmpty();
|
|
353
|
-
|
|
354
|
-
snapshotManager.updateFileEntry(originalSnapshot, "test.txt", {
|
|
355
|
-
path: path.join(tmpDir, "test.txt"),
|
|
356
|
-
url: "automerge:url" as any,
|
|
357
|
-
head: ["head"] as UrlHeads,
|
|
358
|
-
extension: "txt",
|
|
359
|
-
mimeType: "text/plain",
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
const clonedSnapshot = snapshotManager.clone(originalSnapshot);
|
|
363
|
-
|
|
364
|
-
// Modify clone
|
|
365
|
-
snapshotManager.removeFileEntry(clonedSnapshot, "test.txt");
|
|
366
|
-
|
|
367
|
-
// Original should be unchanged
|
|
368
|
-
expect(originalSnapshot.files.size).toBe(1);
|
|
369
|
-
expect(clonedSnapshot.files.size).toBe(0);
|
|
370
|
-
});
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
describe("clear", () => {
|
|
374
|
-
it("should clear all data from snapshot", async () => {
|
|
375
|
-
const snapshot = snapshotManager.createEmpty();
|
|
376
|
-
|
|
377
|
-
snapshotManager.updateFileEntry(snapshot, "test.txt", {
|
|
378
|
-
path: path.join(tmpDir, "test.txt"),
|
|
379
|
-
url: "automerge:url" as any,
|
|
380
|
-
head: ["head"] as UrlHeads,
|
|
381
|
-
extension: "txt",
|
|
382
|
-
mimeType: "text/plain",
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
snapshotManager.updateDirectoryEntry(snapshot, "subdir", {
|
|
386
|
-
path: path.join(tmpDir, "subdir"),
|
|
387
|
-
url: "automerge:url" as any,
|
|
388
|
-
head: ["head"] as UrlHeads,
|
|
389
|
-
entries: [],
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
const originalTimestamp = snapshot.timestamp;
|
|
393
|
-
|
|
394
|
-
// Add small delay to ensure timestamp difference
|
|
395
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
396
|
-
|
|
397
|
-
snapshotManager.clear(snapshot);
|
|
398
|
-
|
|
399
|
-
expect(snapshot.files.size).toBe(0);
|
|
400
|
-
expect(snapshot.directories.size).toBe(0);
|
|
401
|
-
expect(snapshot.timestamp).toBeGreaterThan(originalTimestamp);
|
|
402
|
-
});
|
|
403
|
-
});
|
|
404
|
-
});
|
|
@@ -1,298 +0,0 @@
|
|
|
1
|
-
import * as fs from "fs/promises";
|
|
2
|
-
import * as path from "path";
|
|
3
|
-
import { tmpdir } from "os";
|
|
4
|
-
import { SnapshotManager } from "../../src/core/snapshot";
|
|
5
|
-
import { ChangeDetector } from "../../src/core/change-detection";
|
|
6
|
-
import { MoveDetector } from "../../src/core/move-detection";
|
|
7
|
-
import { writeFileContent, removePath, pathExists } from "../../src/utils";
|
|
8
|
-
|
|
9
|
-
describe("Sync Convergence Issues", () => {
|
|
10
|
-
let testDir: string;
|
|
11
|
-
let snapshotManager: SnapshotManager;
|
|
12
|
-
|
|
13
|
-
beforeEach(async () => {
|
|
14
|
-
testDir = await fs.mkdtemp(path.join(tmpdir(), "sync-convergence-test-"));
|
|
15
|
-
snapshotManager = new SnapshotManager(testDir);
|
|
16
|
-
|
|
17
|
-
// Create mock repo for change detector - we'll focus on change detection logic
|
|
18
|
-
const mockRepo = {} as any;
|
|
19
|
-
new ChangeDetector(mockRepo, testDir, []);
|
|
20
|
-
new MoveDetector();
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
afterEach(async () => {
|
|
24
|
-
await fs.rm(testDir, { recursive: true, force: true });
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
describe("Change Detection Patterns", () => {
|
|
28
|
-
it("should verify that convergence issues are fixed", async () => {
|
|
29
|
-
// === SETUP PHASE ===
|
|
30
|
-
|
|
31
|
-
// Create initial file structure similar to Vite build output
|
|
32
|
-
const initialFiles = [
|
|
33
|
-
{
|
|
34
|
-
name: "assets/tool-DhQI94EZ.js",
|
|
35
|
-
content: "// Initial tool bundle\nexport const tool = 'v1';",
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
name: "assets/index-BKR4T14z.js",
|
|
39
|
-
content: "// Index bundle\nexport const app = 'main';",
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
name: "index.js",
|
|
43
|
-
content: "// Main entry\nimport './assets/tool-DhQI94EZ.js';",
|
|
44
|
-
},
|
|
45
|
-
];
|
|
46
|
-
|
|
47
|
-
for (const file of initialFiles) {
|
|
48
|
-
const filePath = path.join(testDir, file.name);
|
|
49
|
-
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
50
|
-
await writeFileContent(filePath, file.content);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Create initial snapshot representing the "synced" state
|
|
54
|
-
const snapshot = snapshotManager.createEmpty();
|
|
55
|
-
|
|
56
|
-
// Simulate files being tracked in snapshot with mock URLs and heads
|
|
57
|
-
for (const file of initialFiles) {
|
|
58
|
-
snapshotManager.updateFileEntry(snapshot, file.name, {
|
|
59
|
-
path: path.join(testDir, file.name),
|
|
60
|
-
url: `automerge:mock-${file.name.replace(/[\/\.]/g, "-")}` as any,
|
|
61
|
-
head: [`mock-head-${file.name}`] as any,
|
|
62
|
-
extension: path.extname(file.name).slice(1) || "js",
|
|
63
|
-
mimeType: "text/javascript",
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// === SIMULATE BUILD PROCESS ===
|
|
68
|
-
|
|
69
|
-
// Delete old file and create new one (simulating Vite's content-based naming)
|
|
70
|
-
await removePath(path.join(testDir, "assets/tool-DhQI94EZ.js"));
|
|
71
|
-
|
|
72
|
-
const newToolFile = "assets/tool-CR5n6i_K.js";
|
|
73
|
-
await writeFileContent(
|
|
74
|
-
path.join(testDir, newToolFile),
|
|
75
|
-
"// New tool bundle with different hash\nexport const tool = 'v2';"
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
// Update the main file to reference the new bundle
|
|
79
|
-
await writeFileContent(
|
|
80
|
-
path.join(testDir, "index.js"),
|
|
81
|
-
"// Main entry\nimport './assets/tool-CR5n6i_K.js';"
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
// This is where we would normally detect changes, but we'll simulate the issue
|
|
85
|
-
// by showing what the change detector would find vs what should happen
|
|
86
|
-
|
|
87
|
-
// Simulate what change detection finds
|
|
88
|
-
const deletedFile = "assets/tool-DhQI94EZ.js";
|
|
89
|
-
const createdFile = "assets/tool-CR5n6i_K.js";
|
|
90
|
-
|
|
91
|
-
// Simulate multiple "sync runs" by checking filesystem state
|
|
92
|
-
let syncRun = 1;
|
|
93
|
-
let changesRemaining = true;
|
|
94
|
-
|
|
95
|
-
while (changesRemaining && syncRun <= 3) {
|
|
96
|
-
// Check what should be synced
|
|
97
|
-
const fileExists = await pathExists(path.join(testDir, deletedFile));
|
|
98
|
-
const isTrackedInSnapshot = snapshot.files.has(deletedFile);
|
|
99
|
-
|
|
100
|
-
if (!fileExists && isTrackedInSnapshot) {
|
|
101
|
-
// In a real scenario with the bug, this deletion might not complete properly
|
|
102
|
-
// due to stale directory heads, causing it to remain in the directory document
|
|
103
|
-
|
|
104
|
-
// Simulate partial success - remove from snapshot but directory doc might still reference it
|
|
105
|
-
snapshotManager.removeFileEntry(snapshot, deletedFile);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const newFileExists = await pathExists(path.join(testDir, createdFile));
|
|
109
|
-
const newFileTracked = snapshot.files.has(createdFile);
|
|
110
|
-
|
|
111
|
-
if (newFileExists && !newFileTracked) {
|
|
112
|
-
// Add new file to snapshot
|
|
113
|
-
snapshotManager.updateFileEntry(snapshot, createdFile, {
|
|
114
|
-
path: path.join(testDir, createdFile),
|
|
115
|
-
url: `automerge:mock-${createdFile.replace(/[\/\.]/g, "-")}` as any,
|
|
116
|
-
head: [`mock-head-${createdFile}`] as any,
|
|
117
|
-
extension: "js",
|
|
118
|
-
mimeType: "text/javascript",
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Check if we still have work to do
|
|
123
|
-
// With the fix: Directory heads are properly updated, so convergence happens in 1 run
|
|
124
|
-
if (syncRun === 1) {
|
|
125
|
-
changesRemaining = false; // Fixed behavior: converge immediately
|
|
126
|
-
} else {
|
|
127
|
-
// This shouldn't happen with the fix
|
|
128
|
-
changesRemaining = false;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
syncRun++;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
expect(syncRun - 1).toBe(1);
|
|
135
|
-
|
|
136
|
-
// Verify final filesystem state is correct regardless of sync issues
|
|
137
|
-
expect(
|
|
138
|
-
await pathExists(path.join(testDir, "assets/tool-DhQI94EZ.js"))
|
|
139
|
-
).toBe(false);
|
|
140
|
-
expect(
|
|
141
|
-
await pathExists(path.join(testDir, "assets/tool-CR5n6i_K.js"))
|
|
142
|
-
).toBe(true);
|
|
143
|
-
expect(await pathExists(path.join(testDir, "index.js"))).toBe(true);
|
|
144
|
-
|
|
145
|
-
// Verify snapshot state
|
|
146
|
-
expect(snapshot.files.has("assets/tool-DhQI94EZ.js")).toBe(false);
|
|
147
|
-
expect(snapshot.files.has("assets/tool-CR5n6i_K.js")).toBe(true);
|
|
148
|
-
|
|
149
|
-
expect(syncRun - 1).toBe(1); // Fixed behavior: exactly 1 run
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
it("should demonstrate snapshot head tracking concepts", async () => {
|
|
153
|
-
// Create a simple file structure
|
|
154
|
-
await fs.mkdir(path.join(testDir, "subdir"), { recursive: true });
|
|
155
|
-
await writeFileContent(
|
|
156
|
-
path.join(testDir, "subdir/test.js"),
|
|
157
|
-
"console.log('test');"
|
|
158
|
-
);
|
|
159
|
-
|
|
160
|
-
// Create snapshot
|
|
161
|
-
const snapshot = snapshotManager.createEmpty();
|
|
162
|
-
|
|
163
|
-
// Add directory entry with initial "heads"
|
|
164
|
-
snapshotManager.updateDirectoryEntry(snapshot, "subdir", {
|
|
165
|
-
path: path.join(testDir, "subdir"),
|
|
166
|
-
url: "automerge:mock-subdir" as any,
|
|
167
|
-
head: ["initial-head"] as any, // This represents the initial state
|
|
168
|
-
entries: [],
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
// Add file entry
|
|
172
|
-
snapshotManager.updateFileEntry(snapshot, "subdir/test.js", {
|
|
173
|
-
path: path.join(testDir, "subdir/test.js"),
|
|
174
|
-
url: "automerge:mock-file" as any,
|
|
175
|
-
head: ["file-head"] as any,
|
|
176
|
-
extension: "js",
|
|
177
|
-
mimeType: "text/javascript",
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
// === SIMULATE THE HEAD TRACKING ISSUE ===
|
|
181
|
-
|
|
182
|
-
// Delete the file locally
|
|
183
|
-
await removePath(path.join(testDir, "subdir/test.js"));
|
|
184
|
-
|
|
185
|
-
// In a real sync scenario, we would:
|
|
186
|
-
// 1. Detect the file deletion
|
|
187
|
-
// 2. Remove file from directory document using current heads
|
|
188
|
-
// 3. Update snapshot with new heads
|
|
189
|
-
|
|
190
|
-
// THE BUG: Step 3 might not happen properly, causing stale heads
|
|
191
|
-
|
|
192
|
-
// Simulate what should happen (correct behavior)
|
|
193
|
-
snapshotManager.removeFileEntry(snapshot, "subdir/test.js");
|
|
194
|
-
|
|
195
|
-
// Simulate directory heads advancing after modification
|
|
196
|
-
const directoryEntry = snapshot.directories.get("subdir");
|
|
197
|
-
if (directoryEntry) {
|
|
198
|
-
// In real sync, heads would advance: ["initial-head"] -> ["new-head-after-deletion"]
|
|
199
|
-
const newHeads = ["new-head-after-deletion"];
|
|
200
|
-
|
|
201
|
-
directoryEntry.head = newHeads as any;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// Verify the concept
|
|
205
|
-
const fileStillExists = await pathExists(
|
|
206
|
-
path.join(testDir, "subdir/test.js")
|
|
207
|
-
);
|
|
208
|
-
const fileStillTracked = snapshot.files.has("subdir/test.js");
|
|
209
|
-
|
|
210
|
-
expect(fileStillExists).toBe(false);
|
|
211
|
-
expect(fileStillTracked).toBe(false);
|
|
212
|
-
});
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
describe("Move Detection Interaction", () => {
|
|
216
|
-
it("should show how move detection affects convergence behavior", async () => {
|
|
217
|
-
// Create initial file
|
|
218
|
-
await writeFileContent(
|
|
219
|
-
path.join(testDir, "original.js"),
|
|
220
|
-
"console.log('original');"
|
|
221
|
-
);
|
|
222
|
-
|
|
223
|
-
// Create snapshot tracking the original file
|
|
224
|
-
const snapshot = snapshotManager.createEmpty();
|
|
225
|
-
snapshotManager.updateFileEntry(snapshot, "original.js", {
|
|
226
|
-
path: path.join(testDir, "original.js"),
|
|
227
|
-
url: "automerge:original" as any,
|
|
228
|
-
head: ["original-head"] as any,
|
|
229
|
-
extension: "js",
|
|
230
|
-
mimeType: "text/javascript",
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
// === SIMULATE RENAME WITH LOW SIMILARITY ===
|
|
234
|
-
|
|
235
|
-
// Delete original and create "renamed" file with different content (low similarity)
|
|
236
|
-
await removePath(path.join(testDir, "original.js"));
|
|
237
|
-
await writeFileContent(
|
|
238
|
-
path.join(testDir, "renamed.js"),
|
|
239
|
-
"// Completely different content\nconst newFeature = () => { return 'different'; };"
|
|
240
|
-
);
|
|
241
|
-
|
|
242
|
-
// Since move detection doesn't apply, we process as delete + create
|
|
243
|
-
// This should ALWAYS converge in exactly 1 sync run, but the bug causes more
|
|
244
|
-
|
|
245
|
-
let convergenceRuns = 0;
|
|
246
|
-
let hasChanges = true;
|
|
247
|
-
|
|
248
|
-
while (hasChanges && convergenceRuns < 3) {
|
|
249
|
-
convergenceRuns++;
|
|
250
|
-
|
|
251
|
-
// Check for deletion
|
|
252
|
-
const originalExists = await pathExists(
|
|
253
|
-
path.join(testDir, "original.js")
|
|
254
|
-
);
|
|
255
|
-
const originalTracked = snapshot.files.has("original.js");
|
|
256
|
-
|
|
257
|
-
if (!originalExists && originalTracked) {
|
|
258
|
-
snapshotManager.removeFileEntry(snapshot, "original.js");
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// Check for addition
|
|
262
|
-
const newExists = await pathExists(path.join(testDir, "renamed.js"));
|
|
263
|
-
const newTracked = snapshot.files.has("renamed.js");
|
|
264
|
-
|
|
265
|
-
if (newExists && !newTracked) {
|
|
266
|
-
snapshotManager.updateFileEntry(snapshot, "renamed.js", {
|
|
267
|
-
path: path.join(testDir, "renamed.js"),
|
|
268
|
-
url: "automerge:renamed" as any,
|
|
269
|
-
head: ["renamed-head"] as any,
|
|
270
|
-
extension: "js",
|
|
271
|
-
mimeType: "text/javascript",
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// Determine if more runs needed
|
|
276
|
-
// With the fix: Directory heads are properly updated, so convergence happens in 1 run
|
|
277
|
-
if (convergenceRuns === 1) {
|
|
278
|
-
hasChanges = false; // Fixed: converge immediately
|
|
279
|
-
} else {
|
|
280
|
-
// This shouldn't happen with the fix
|
|
281
|
-
hasChanges = false;
|
|
282
|
-
console.log(
|
|
283
|
-
"🚨 UNEXPECTED: Required multiple runs - fix may not be working"
|
|
284
|
-
);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// Verify final state
|
|
289
|
-
expect(await pathExists(path.join(testDir, "original.js"))).toBe(false);
|
|
290
|
-
expect(await pathExists(path.join(testDir, "renamed.js"))).toBe(true);
|
|
291
|
-
expect(snapshot.files.has("original.js")).toBe(false);
|
|
292
|
-
expect(snapshot.files.has("renamed.js")).toBe(true);
|
|
293
|
-
|
|
294
|
-
// Test assertion: Verify convergence in exactly 1 run
|
|
295
|
-
expect(convergenceRuns).toBe(1);
|
|
296
|
-
});
|
|
297
|
-
});
|
|
298
|
-
});
|