pushwork 2.0.0-a.sub.1 → 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.
Files changed (251) hide show
  1. package/dist/branches.d.ts +19 -0
  2. package/dist/branches.d.ts.map +1 -0
  3. package/dist/branches.js +111 -0
  4. package/dist/branches.js.map +1 -0
  5. package/dist/cli.d.ts +1 -1
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +238 -272
  8. package/dist/cli.js.map +1 -1
  9. package/dist/config.d.ts +17 -0
  10. package/dist/config.d.ts.map +1 -0
  11. package/dist/config.js +84 -0
  12. package/dist/config.js.map +1 -0
  13. package/dist/fs-tree.d.ts +6 -0
  14. package/dist/fs-tree.d.ts.map +1 -0
  15. package/dist/fs-tree.js +99 -0
  16. package/dist/fs-tree.js.map +1 -0
  17. package/dist/ignore.d.ts +6 -0
  18. package/dist/ignore.d.ts.map +1 -0
  19. package/dist/ignore.js +74 -0
  20. package/dist/ignore.js.map +1 -0
  21. package/dist/index.d.ts +8 -4
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +34 -4
  24. package/dist/index.js.map +1 -1
  25. package/dist/log.d.ts +3 -0
  26. package/dist/log.d.ts.map +1 -0
  27. package/dist/log.js +14 -0
  28. package/dist/log.js.map +1 -0
  29. package/dist/pushwork.d.ts +115 -0
  30. package/dist/pushwork.d.ts.map +1 -0
  31. package/dist/pushwork.js +918 -0
  32. package/dist/pushwork.js.map +1 -0
  33. package/dist/repo.d.ts +14 -0
  34. package/dist/repo.d.ts.map +1 -0
  35. package/dist/repo.js +60 -0
  36. package/dist/repo.js.map +1 -0
  37. package/dist/shapes/custom.d.ts +3 -0
  38. package/dist/shapes/custom.d.ts.map +1 -0
  39. package/dist/shapes/custom.js +57 -0
  40. package/dist/shapes/custom.js.map +1 -0
  41. package/dist/shapes/file.d.ts +20 -0
  42. package/dist/shapes/file.d.ts.map +1 -0
  43. package/dist/shapes/file.js +140 -0
  44. package/dist/shapes/file.js.map +1 -0
  45. package/dist/shapes/index.d.ts +10 -0
  46. package/dist/shapes/index.d.ts.map +1 -0
  47. package/dist/shapes/index.js +35 -0
  48. package/dist/shapes/index.js.map +1 -0
  49. package/dist/shapes/patchwork-folder.d.ts +3 -0
  50. package/dist/shapes/patchwork-folder.d.ts.map +1 -0
  51. package/dist/shapes/patchwork-folder.js +160 -0
  52. package/dist/shapes/patchwork-folder.js.map +1 -0
  53. package/dist/shapes/types.d.ts +37 -0
  54. package/dist/shapes/types.d.ts.map +1 -0
  55. package/dist/shapes/types.js +52 -0
  56. package/dist/shapes/types.js.map +1 -0
  57. package/dist/shapes/vfs.d.ts +3 -0
  58. package/dist/shapes/vfs.d.ts.map +1 -0
  59. package/dist/shapes/vfs.js +88 -0
  60. package/dist/shapes/vfs.js.map +1 -0
  61. package/dist/stash.d.ts +23 -0
  62. package/dist/stash.d.ts.map +1 -0
  63. package/dist/stash.js +118 -0
  64. package/dist/stash.js.map +1 -0
  65. package/flake.lock +128 -0
  66. package/flake.nix +66 -0
  67. package/package.json +15 -48
  68. package/patches/@automerge__automerge-repo@2.6.0-subduction.15.patch +26 -0
  69. package/pnpm-workspace.yaml +5 -0
  70. package/src/branches.ts +93 -0
  71. package/src/cli.ts +258 -408
  72. package/src/config.ts +64 -0
  73. package/src/fs-tree.ts +70 -0
  74. package/src/ignore.ts +33 -0
  75. package/src/index.ts +38 -4
  76. package/src/log.ts +8 -0
  77. package/src/pushwork.ts +1055 -0
  78. package/src/repo.ts +76 -0
  79. package/src/shapes/custom.ts +29 -0
  80. package/src/shapes/file.ts +115 -0
  81. package/src/shapes/index.ts +19 -0
  82. package/src/shapes/patchwork-folder.ts +156 -0
  83. package/src/shapes/types.ts +79 -0
  84. package/src/shapes/vfs.ts +93 -0
  85. package/src/stash.ts +106 -0
  86. package/test/integration/branches.test.ts +389 -0
  87. package/test/integration/pushwork.test.ts +547 -0
  88. package/test/setup.ts +29 -0
  89. package/test/unit/doc-shape.test.ts +612 -0
  90. package/tsconfig.json +2 -3
  91. package/vitest.config.ts +14 -0
  92. package/ARCHITECTURE-ACCORDING-TO-CLAUDE.md +0 -248
  93. package/CLAUDE.md +0 -141
  94. package/README.md +0 -221
  95. package/babel.config.js +0 -5
  96. package/dist/cli/commands.d.ts +0 -71
  97. package/dist/cli/commands.d.ts.map +0 -1
  98. package/dist/cli/commands.js +0 -794
  99. package/dist/cli/commands.js.map +0 -1
  100. package/dist/cli/index.d.ts +0 -2
  101. package/dist/cli/index.d.ts.map +0 -1
  102. package/dist/cli/index.js +0 -19
  103. package/dist/cli/index.js.map +0 -1
  104. package/dist/commands.d.ts +0 -61
  105. package/dist/commands.d.ts.map +0 -1
  106. package/dist/commands.js +0 -861
  107. package/dist/commands.js.map +0 -1
  108. package/dist/config/index.d.ts +0 -71
  109. package/dist/config/index.d.ts.map +0 -1
  110. package/dist/config/index.js +0 -314
  111. package/dist/config/index.js.map +0 -1
  112. package/dist/core/change-detection.d.ts +0 -80
  113. package/dist/core/change-detection.d.ts.map +0 -1
  114. package/dist/core/change-detection.js +0 -523
  115. package/dist/core/change-detection.js.map +0 -1
  116. package/dist/core/config.d.ts +0 -81
  117. package/dist/core/config.d.ts.map +0 -1
  118. package/dist/core/config.js +0 -258
  119. package/dist/core/config.js.map +0 -1
  120. package/dist/core/index.d.ts +0 -6
  121. package/dist/core/index.d.ts.map +0 -1
  122. package/dist/core/index.js +0 -6
  123. package/dist/core/index.js.map +0 -1
  124. package/dist/core/move-detection.d.ts +0 -34
  125. package/dist/core/move-detection.d.ts.map +0 -1
  126. package/dist/core/move-detection.js +0 -121
  127. package/dist/core/move-detection.js.map +0 -1
  128. package/dist/core/snapshot.d.ts +0 -105
  129. package/dist/core/snapshot.d.ts.map +0 -1
  130. package/dist/core/snapshot.js +0 -217
  131. package/dist/core/snapshot.js.map +0 -1
  132. package/dist/core/sync-engine.d.ts +0 -157
  133. package/dist/core/sync-engine.d.ts.map +0 -1
  134. package/dist/core/sync-engine.js +0 -1379
  135. package/dist/core/sync-engine.js.map +0 -1
  136. package/dist/types/config.d.ts +0 -99
  137. package/dist/types/config.d.ts.map +0 -1
  138. package/dist/types/config.js +0 -5
  139. package/dist/types/config.js.map +0 -1
  140. package/dist/types/documents.d.ts +0 -88
  141. package/dist/types/documents.d.ts.map +0 -1
  142. package/dist/types/documents.js +0 -20
  143. package/dist/types/documents.js.map +0 -1
  144. package/dist/types/index.d.ts +0 -4
  145. package/dist/types/index.d.ts.map +0 -1
  146. package/dist/types/index.js +0 -4
  147. package/dist/types/index.js.map +0 -1
  148. package/dist/types/snapshot.d.ts +0 -64
  149. package/dist/types/snapshot.d.ts.map +0 -1
  150. package/dist/types/snapshot.js +0 -2
  151. package/dist/types/snapshot.js.map +0 -1
  152. package/dist/utils/content-similarity.d.ts +0 -53
  153. package/dist/utils/content-similarity.d.ts.map +0 -1
  154. package/dist/utils/content-similarity.js +0 -155
  155. package/dist/utils/content-similarity.js.map +0 -1
  156. package/dist/utils/content.d.ts +0 -10
  157. package/dist/utils/content.d.ts.map +0 -1
  158. package/dist/utils/content.js +0 -31
  159. package/dist/utils/content.js.map +0 -1
  160. package/dist/utils/directory.d.ts +0 -24
  161. package/dist/utils/directory.d.ts.map +0 -1
  162. package/dist/utils/directory.js +0 -52
  163. package/dist/utils/directory.js.map +0 -1
  164. package/dist/utils/fs.d.ts +0 -74
  165. package/dist/utils/fs.d.ts.map +0 -1
  166. package/dist/utils/fs.js +0 -248
  167. package/dist/utils/fs.js.map +0 -1
  168. package/dist/utils/index.d.ts +0 -5
  169. package/dist/utils/index.d.ts.map +0 -1
  170. package/dist/utils/index.js +0 -5
  171. package/dist/utils/index.js.map +0 -1
  172. package/dist/utils/mime-types.d.ts +0 -13
  173. package/dist/utils/mime-types.d.ts.map +0 -1
  174. package/dist/utils/mime-types.js +0 -209
  175. package/dist/utils/mime-types.js.map +0 -1
  176. package/dist/utils/network-sync.d.ts +0 -36
  177. package/dist/utils/network-sync.d.ts.map +0 -1
  178. package/dist/utils/network-sync.js +0 -250
  179. package/dist/utils/network-sync.js.map +0 -1
  180. package/dist/utils/node-polyfills.d.ts +0 -9
  181. package/dist/utils/node-polyfills.d.ts.map +0 -1
  182. package/dist/utils/node-polyfills.js +0 -9
  183. package/dist/utils/node-polyfills.js.map +0 -1
  184. package/dist/utils/output.d.ts +0 -129
  185. package/dist/utils/output.d.ts.map +0 -1
  186. package/dist/utils/output.js +0 -368
  187. package/dist/utils/output.js.map +0 -1
  188. package/dist/utils/repo-factory.d.ts +0 -13
  189. package/dist/utils/repo-factory.d.ts.map +0 -1
  190. package/dist/utils/repo-factory.js +0 -46
  191. package/dist/utils/repo-factory.js.map +0 -1
  192. package/dist/utils/string-similarity.d.ts +0 -14
  193. package/dist/utils/string-similarity.d.ts.map +0 -1
  194. package/dist/utils/string-similarity.js +0 -39
  195. package/dist/utils/string-similarity.js.map +0 -1
  196. package/dist/utils/text-diff.d.ts +0 -37
  197. package/dist/utils/text-diff.d.ts.map +0 -1
  198. package/dist/utils/text-diff.js +0 -93
  199. package/dist/utils/text-diff.js.map +0 -1
  200. package/dist/utils/trace.d.ts +0 -19
  201. package/dist/utils/trace.d.ts.map +0 -1
  202. package/dist/utils/trace.js +0 -63
  203. package/dist/utils/trace.js.map +0 -1
  204. package/src/commands.ts +0 -1134
  205. package/src/core/change-detection.ts +0 -712
  206. package/src/core/config.ts +0 -313
  207. package/src/core/index.ts +0 -5
  208. package/src/core/move-detection.ts +0 -169
  209. package/src/core/snapshot.ts +0 -275
  210. package/src/core/sync-engine.ts +0 -1795
  211. package/src/types/config.ts +0 -111
  212. package/src/types/documents.ts +0 -91
  213. package/src/types/index.ts +0 -3
  214. package/src/types/snapshot.ts +0 -67
  215. package/src/utils/content.ts +0 -34
  216. package/src/utils/directory.ts +0 -73
  217. package/src/utils/fs.ts +0 -297
  218. package/src/utils/index.ts +0 -4
  219. package/src/utils/mime-types.ts +0 -244
  220. package/src/utils/network-sync.ts +0 -319
  221. package/src/utils/node-polyfills.ts +0 -8
  222. package/src/utils/output.ts +0 -450
  223. package/src/utils/repo-factory.ts +0 -73
  224. package/src/utils/string-similarity.ts +0 -54
  225. package/src/utils/text-diff.ts +0 -101
  226. package/src/utils/trace.ts +0 -70
  227. package/test/integration/README.md +0 -328
  228. package/test/integration/clone-test.sh +0 -310
  229. package/test/integration/conflict-resolution-test.sh +0 -309
  230. package/test/integration/debug-both-nested.sh +0 -74
  231. package/test/integration/debug-concurrent-nested.sh +0 -87
  232. package/test/integration/debug-nested.sh +0 -73
  233. package/test/integration/deletion-behavior-test.sh +0 -487
  234. package/test/integration/deletion-sync-test-simple.sh +0 -193
  235. package/test/integration/deletion-sync-test.sh +0 -297
  236. package/test/integration/exclude-patterns.test.ts +0 -144
  237. package/test/integration/full-integration-test.sh +0 -363
  238. package/test/integration/fuzzer.test.ts +0 -818
  239. package/test/integration/in-memory-sync.test.ts +0 -830
  240. package/test/integration/init-sync.test.ts +0 -89
  241. package/test/integration/manual-sync-test.sh +0 -84
  242. package/test/integration/sync-deletion.test.ts +0 -280
  243. package/test/integration/sync-flow.test.ts +0 -291
  244. package/test/jest.setup.ts +0 -34
  245. package/test/run-tests.sh +0 -225
  246. package/test/unit/deletion-behavior.test.ts +0 -249
  247. package/test/unit/enhanced-mime-detection.test.ts +0 -244
  248. package/test/unit/snapshot.test.ts +0 -404
  249. package/test/unit/sync-convergence.test.ts +0 -298
  250. package/test/unit/sync-timing.test.ts +0 -134
  251. package/test/unit/utils.test.ts +0 -366
@@ -1,134 +0,0 @@
1
- import * as fs from "fs/promises";
2
- import * as path from "path";
3
- import { tmpdir } from "os";
4
-
5
- describe("Sync Timing Analysis", () => {
6
- let testDir: string;
7
-
8
- beforeEach(async () => {
9
- testDir = await fs.mkdtemp(path.join(tmpdir(), "sync-timing-"));
10
- });
11
-
12
- afterEach(async () => {
13
- await fs.rm(testDir, { recursive: true, force: true });
14
- });
15
-
16
- describe("File Operation Timing", () => {
17
- it("should measure rapid file operations timing", async () => {
18
- const startTime = Date.now();
19
-
20
- // Simulate rapid file operations similar to sync
21
- const promises: Promise<void>[] = [];
22
- for (let i = 0; i < 10; i++) {
23
- promises.push(
24
- fs.writeFile(path.join(testDir, `file${i}.txt`), `content${i}`)
25
- );
26
- }
27
-
28
- await Promise.all(promises);
29
- const totalTime = Date.now() - startTime;
30
-
31
- // Verify all files exist
32
- const files = await fs.readdir(testDir);
33
- expect(files).toHaveLength(10);
34
-
35
- // This test shows us baseline file operation timing
36
- expect(totalTime).toBeLessThan(1000); // Should be fast for local operations
37
- });
38
-
39
- it("should test file operation atomicity", async () => {
40
- const filePath = path.join(testDir, "test.txt");
41
-
42
- // Write initial content
43
- await fs.writeFile(filePath, "initial content");
44
-
45
- // Rapid successive writes to same file
46
- const writes: Promise<void>[] = [];
47
- for (let i = 0; i < 5; i++) {
48
- writes.push(fs.writeFile(filePath, `updated content ${i}`));
49
- }
50
-
51
- await Promise.all(writes);
52
-
53
- // Check final content (should be one of the updates)
54
- const finalContent = await fs.readFile(filePath, "utf8");
55
- expect(finalContent).toMatch(/updated content \d/);
56
- });
57
- });
58
-
59
- describe("Sync Completion Scenarios", () => {
60
- it("should simulate the need for sync completion detection", async () => {
61
- // This test simulates what might happen with network sync
62
- // where we need to wait for operations to complete
63
-
64
- const results: { operation: string; time: number }[] = [];
65
-
66
- // Simulate "local" operations (fast)
67
- const localStart = Date.now();
68
- await fs.writeFile(path.join(testDir, "local.txt"), "local content");
69
- const localTime = Date.now() - localStart;
70
- results.push({ operation: "local write", time: localTime });
71
-
72
- // Simulate "network" operations (slower with artificial delay)
73
- const networkStart = Date.now();
74
- await new Promise((resolve) => setTimeout(resolve, 50)); // 50ms delay
75
- await fs.writeFile(path.join(testDir, "network.txt"), "network content");
76
- const networkTime = Date.now() - networkStart;
77
- results.push({ operation: "network write", time: networkTime });
78
-
79
- // This demonstrates why we might need to wait for slower operations
80
- expect(networkTime).toBeGreaterThan(localTime);
81
- expect(networkTime).toBeGreaterThan(40); // Should include our delay
82
- });
83
-
84
- it("should test what happens without proper completion waiting", async () => {
85
- // Simulate starting an operation but not waiting for it
86
- const promises: Promise<void>[] = [];
87
-
88
- // Start operations without awaiting
89
- for (let i = 0; i < 3; i++) {
90
- promises.push(
91
- (async () => {
92
- await new Promise((resolve) => setTimeout(resolve, 10 * i)); // Varying delays
93
- await fs.writeFile(
94
- path.join(testDir, `async${i}.txt`),
95
- `content${i}`
96
- );
97
- })()
98
- );
99
- }
100
-
101
- // Check immediately (before operations complete)
102
- const filesImmediate = await fs.readdir(testDir);
103
-
104
- // Now wait for operations to complete
105
- await Promise.all(promises);
106
-
107
- // Check after completion
108
- const filesAfter = await fs.readdir(testDir);
109
-
110
- // This shows the difference between checking immediately vs waiting
111
- expect(filesAfter.length).toBeGreaterThanOrEqual(filesImmediate.length);
112
- expect(filesAfter).toHaveLength(3);
113
- });
114
- });
115
-
116
- describe("Potential Race Conditions", () => {
117
- it("should test for potential race conditions in file operations", async () => {
118
- const sharedFile = path.join(testDir, "shared.txt");
119
-
120
- // Multiple operations on the same file
121
- const operations = [
122
- fs.writeFile(sharedFile, "operation1"),
123
- fs.writeFile(sharedFile, "operation2"),
124
- fs.writeFile(sharedFile, "operation3"),
125
- ];
126
-
127
- await Promise.all(operations);
128
-
129
- // Only one operation should "win"
130
- const content = await fs.readFile(sharedFile, "utf8");
131
- expect(["operation1", "operation2", "operation3"]).toContain(content);
132
- });
133
- });
134
- });
@@ -1,366 +0,0 @@
1
- import * as fs from "fs/promises";
2
- import * as path from "path";
3
- import * as tmp from "tmp";
4
- import {
5
- pathExists,
6
- getFileSystemEntry,
7
- isTextFile,
8
- readFileContent,
9
- writeFileContent,
10
- ensureDirectoryExists,
11
- removePath,
12
- listDirectory,
13
- calculateContentHash,
14
- getMimeType,
15
- getFileExtension,
16
- normalizePath,
17
- getRelativePath,
18
- } from "../../src/utils/fs";
19
- import { FileType } from "../../src/types";
20
-
21
- describe("File System Utilities", () => {
22
- let tmpDir: string;
23
- let cleanup: () => void;
24
-
25
- beforeEach(() => {
26
- const tmpObj = tmp.dirSync({ unsafeCleanup: true });
27
- tmpDir = tmpObj.name;
28
- cleanup = tmpObj.removeCallback;
29
- });
30
-
31
- afterEach(() => {
32
- cleanup();
33
- });
34
-
35
- describe("pathExists", () => {
36
- it("should return true for existing files", async () => {
37
- const filePath = path.join(tmpDir, "test.txt");
38
- await fs.writeFile(filePath, "test content");
39
-
40
- expect(await pathExists(filePath)).toBe(true);
41
- });
42
-
43
- it("should return false for non-existing files", async () => {
44
- const filePath = path.join(tmpDir, "nonexistent.txt");
45
-
46
- expect(await pathExists(filePath)).toBe(false);
47
- });
48
-
49
- it("should return true for existing directories", async () => {
50
- expect(await pathExists(tmpDir)).toBe(true);
51
- });
52
- });
53
-
54
- describe("getFileSystemEntry", () => {
55
- it("should return metadata for files", async () => {
56
- const filePath = path.join(tmpDir, "test.txt");
57
- await fs.writeFile(filePath, "test content");
58
-
59
- const entry = await getFileSystemEntry(filePath);
60
-
61
- expect(entry).not.toBeNull();
62
- expect(entry?.path).toBe(filePath);
63
- expect(entry?.type).toBe(FileType.TEXT);
64
- expect(entry?.size).toBe(12); // 'test content'.length
65
- expect(entry?.mtime).toBeDefined();
66
- expect(entry?.mtime.getTime()).toBeGreaterThan(0);
67
- expect(typeof entry?.mtime.getTime()).toBe("number");
68
- });
69
-
70
- it("should return metadata for directories", async () => {
71
- const dirPath = path.join(tmpDir, "subdir");
72
- await fs.mkdir(dirPath);
73
-
74
- const entry = await getFileSystemEntry(dirPath);
75
-
76
- expect(entry).not.toBeNull();
77
- expect(entry?.path).toBe(dirPath);
78
- expect(entry?.type).toBe(FileType.DIRECTORY);
79
- });
80
-
81
- it("should return null for non-existing paths", async () => {
82
- const entry = await getFileSystemEntry(path.join(tmpDir, "nonexistent"));
83
- expect(entry).toBeNull();
84
- });
85
- });
86
-
87
- describe("isTextFile", () => {
88
- it("should detect text files by extension", async () => {
89
- const filePath = path.join(tmpDir, "test.txt");
90
- await fs.writeFile(filePath, "text content");
91
-
92
- expect(await isTextFile(filePath)).toBe(true);
93
- });
94
-
95
- it("should detect JSON files as text", async () => {
96
- const filePath = path.join(tmpDir, "test.json");
97
- await fs.writeFile(filePath, '{"key": "value"}');
98
-
99
- expect(await isTextFile(filePath)).toBe(true);
100
- });
101
-
102
- it("should detect binary files by content", async () => {
103
- const filePath = path.join(tmpDir, "test.bin");
104
- const binaryContent = Buffer.from([0x00, 0x01, 0x02, 0x03]);
105
- await fs.writeFile(filePath, binaryContent);
106
-
107
- expect(await isTextFile(filePath)).toBe(false);
108
- });
109
- });
110
-
111
- describe("readFileContent", () => {
112
- it("should read text files as strings", async () => {
113
- const filePath = path.join(tmpDir, "test.txt");
114
- const content = "Hello, world!";
115
- await fs.writeFile(filePath, content);
116
-
117
- const result = await readFileContent(filePath);
118
-
119
- expect(typeof result).toBe("string");
120
- expect(result).toBe(content);
121
- });
122
-
123
- it("should read TypeScript files as strings", async () => {
124
- const filePath = path.join(tmpDir, "component.ts");
125
- const content = "interface User { name: string; age: number; }";
126
- await fs.writeFile(filePath, content);
127
-
128
- const result = await readFileContent(filePath);
129
-
130
- expect(typeof result).toBe("string");
131
- expect(result).toBe(content);
132
- });
133
-
134
- it("should read TSX files as strings", async () => {
135
- const filePath = path.join(tmpDir, "Component.tsx");
136
- const content = "export const App = () => <div>Hello World</div>;";
137
- await fs.writeFile(filePath, content);
138
-
139
- const result = await readFileContent(filePath);
140
-
141
- expect(typeof result).toBe("string");
142
- expect(result).toBe(content);
143
- });
144
-
145
- it("should read Vue files as strings", async () => {
146
- const filePath = path.join(tmpDir, "App.vue");
147
- const content = "<template><div>{{ message }}</div></template>";
148
- await fs.writeFile(filePath, content);
149
-
150
- const result = await readFileContent(filePath);
151
-
152
- expect(typeof result).toBe("string");
153
- expect(result).toBe(content);
154
- });
155
-
156
- it("should read SCSS files as strings", async () => {
157
- const filePath = path.join(tmpDir, "styles.scss");
158
- const content = "$primary: #007bff; .btn { color: $primary; }";
159
- await fs.writeFile(filePath, content);
160
-
161
- const result = await readFileContent(filePath);
162
-
163
- expect(typeof result).toBe("string");
164
- expect(result).toBe(content);
165
- });
166
-
167
- it("should read binary files as Uint8Array", async () => {
168
- const filePath = path.join(tmpDir, "test.bin");
169
- const binaryContent = Buffer.from([0x00, 0x01, 0x02, 0x03]);
170
- await fs.writeFile(filePath, binaryContent);
171
-
172
- const result = await readFileContent(filePath);
173
-
174
- expect(result).toBeInstanceOf(Uint8Array);
175
- expect(Array.from(result as Uint8Array)).toEqual([
176
- 0x00, 0x01, 0x02, 0x03,
177
- ]);
178
- });
179
- });
180
-
181
- describe("writeFileContent", () => {
182
- it("should write string content to files", async () => {
183
- const filePath = path.join(tmpDir, "output.txt");
184
- const content = "Test content";
185
-
186
- await writeFileContent(filePath, content);
187
-
188
- const written = await fs.readFile(filePath, "utf8");
189
- expect(written).toBe(content);
190
- });
191
-
192
- it("should write binary content to files", async () => {
193
- const filePath = path.join(tmpDir, "output.bin");
194
- const content = new Uint8Array([0x00, 0x01, 0x02, 0x03]);
195
-
196
- await writeFileContent(filePath, content);
197
-
198
- const written = await fs.readFile(filePath);
199
- expect(Array.from(written)).toEqual([0x00, 0x01, 0x02, 0x03]);
200
- });
201
-
202
- it("should create directories if they don't exist", async () => {
203
- const filePath = path.join(tmpDir, "nested", "deep", "file.txt");
204
-
205
- await writeFileContent(filePath, "content");
206
-
207
- expect(await pathExists(filePath)).toBe(true);
208
- expect(await fs.readFile(filePath, "utf8")).toBe("content");
209
- });
210
- });
211
-
212
- describe("ensureDirectoryExists", () => {
213
- it("should create directories recursively", async () => {
214
- const dirPath = path.join(tmpDir, "nested", "deep", "directory");
215
-
216
- await ensureDirectoryExists(dirPath);
217
-
218
- expect(await pathExists(dirPath)).toBe(true);
219
- const stats = await fs.stat(dirPath);
220
- expect(stats.isDirectory()).toBe(true);
221
- });
222
-
223
- it("should not fail if directory already exists", async () => {
224
- await ensureDirectoryExists(tmpDir);
225
- await ensureDirectoryExists(tmpDir); // Should not throw
226
-
227
- expect(await pathExists(tmpDir)).toBe(true);
228
- });
229
- });
230
-
231
- describe("removePath", () => {
232
- it("should remove files", async () => {
233
- const filePath = path.join(tmpDir, "toremove.txt");
234
- await fs.writeFile(filePath, "content");
235
-
236
- await removePath(filePath);
237
-
238
- expect(await pathExists(filePath)).toBe(false);
239
- });
240
-
241
- it("should remove directories recursively", async () => {
242
- const dirPath = path.join(tmpDir, "toremove");
243
- const filePath = path.join(dirPath, "file.txt");
244
- await fs.mkdir(dirPath);
245
- await fs.writeFile(filePath, "content");
246
-
247
- await removePath(dirPath);
248
-
249
- expect(await pathExists(dirPath)).toBe(false);
250
- });
251
-
252
- it("should not fail if path doesn't exist", async () => {
253
- const nonExistentPath = path.join(tmpDir, "nonexistent");
254
-
255
- await removePath(nonExistentPath); // Should not throw
256
-
257
- expect(await pathExists(nonExistentPath)).toBe(false);
258
- });
259
- });
260
-
261
- describe("listDirectory", () => {
262
- beforeEach(async () => {
263
- // Create test directory structure
264
- await fs.mkdir(path.join(tmpDir, "subdir"));
265
- await fs.writeFile(path.join(tmpDir, "file1.txt"), "content1");
266
- await fs.writeFile(path.join(tmpDir, "file2.txt"), "content2");
267
- await fs.writeFile(path.join(tmpDir, "subdir", "file3.txt"), "content3");
268
- });
269
-
270
- it("should list directory contents non-recursively", async () => {
271
- const entries = await listDirectory(tmpDir, false);
272
-
273
- const names = entries.map((e) => path.basename(e.path)).sort();
274
- expect(names).toEqual(["file1.txt", "file2.txt", "subdir"]);
275
- });
276
-
277
- it("should list directory contents recursively", async () => {
278
- const entries = await listDirectory(tmpDir, true);
279
-
280
- const relativePaths = entries
281
- .map((e) => path.relative(tmpDir, e.path))
282
- .sort();
283
-
284
- expect(relativePaths).toContain("file1.txt");
285
- expect(relativePaths).toContain("file2.txt");
286
- expect(relativePaths).toContain("subdir");
287
- expect(relativePaths).toContain(path.join("subdir", "file3.txt"));
288
- });
289
- });
290
-
291
- describe("calculateContentHash", () => {
292
- it("should generate consistent hashes for string content", async () => {
293
- const content = "test content";
294
-
295
- const hash1 = await calculateContentHash(content);
296
- const hash2 = await calculateContentHash(content);
297
-
298
- expect(hash1).toBe(hash2);
299
- expect(hash1).toHaveLength(64); // SHA-256 hex string
300
- });
301
-
302
- it("should generate consistent hashes for binary content", async () => {
303
- const content = new Uint8Array([0x00, 0x01, 0x02, 0x03]);
304
-
305
- const hash1 = await calculateContentHash(content);
306
- const hash2 = await calculateContentHash(content);
307
-
308
- expect(hash1).toBe(hash2);
309
- expect(hash1).toHaveLength(64);
310
- });
311
-
312
- it("should generate different hashes for different content", async () => {
313
- const content1 = "content1";
314
- const content2 = "content2";
315
-
316
- const hash1 = await calculateContentHash(content1);
317
- const hash2 = await calculateContentHash(content2);
318
-
319
- expect(hash1).not.toBe(hash2);
320
- });
321
- });
322
-
323
- describe("getMimeType", () => {
324
- it("should return correct MIME type for text files", () => {
325
- expect(getMimeType("test.txt")).toBe("text/plain");
326
- expect(getMimeType("test.json")).toBe("application/json");
327
- expect(getMimeType("test.html")).toBe("text/html");
328
- });
329
-
330
- it("should return default MIME type for unknown extensions", () => {
331
- expect(getMimeType("test.unknown")).toBe("application/octet-stream");
332
- });
333
- });
334
-
335
- describe("getFileExtension", () => {
336
- it("should extract file extensions", () => {
337
- expect(getFileExtension("test.txt")).toBe("txt");
338
- expect(getFileExtension("archive.tar.gz")).toBe("gz");
339
- expect(getFileExtension("noextension")).toBe("");
340
- });
341
- });
342
-
343
- describe("normalizePath", () => {
344
- it("should normalize path separators", () => {
345
- expect(normalizePath("path\\to\\file")).toBe("path/to/file");
346
- expect(normalizePath("path/to/file")).toBe("path/to/file");
347
- expect(normalizePath("path//to//file")).toBe("path/to/file");
348
- });
349
- });
350
-
351
- describe("getRelativePath", () => {
352
- it("should return relative paths", () => {
353
- const base = "/home/user/project";
354
- const target = "/home/user/project/src/file.txt";
355
-
356
- expect(getRelativePath(base, target)).toBe("src/file.txt");
357
- });
358
-
359
- it("should handle same directory", () => {
360
- const base = "/home/user/project";
361
- const target = "/home/user/project";
362
-
363
- expect(getRelativePath(base, target)).toBe(".");
364
- });
365
- });
366
- });