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.
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 -151
  133. package/dist/core/sync-engine.d.ts.map +0 -1
  134. package/dist/core/sync-engine.js +0 -1346
  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 -1758
  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,111 +0,0 @@
1
- /**
2
- * Default sync server configuration
3
- */
4
- export const DEFAULT_SYNC_SERVER = "wss://subduction.sync.inkandswitch.com";
5
-
6
- /**
7
- * Global configuration options
8
- */
9
- export interface GlobalConfig {
10
- sync_server?: string;
11
- exclude_patterns: string[];
12
- artifact_directories: string[];
13
- sync: {
14
- move_detection_threshold: number;
15
- };
16
- }
17
-
18
- /**
19
- * Per-directory configuration
20
- */
21
- export interface DirectoryConfig extends GlobalConfig {
22
- root_directory_url?: string;
23
- sync_enabled: boolean;
24
- }
25
-
26
- /**
27
- * CLI command options
28
- */
29
- export interface CommandOptions {
30
- verbose?: boolean;
31
- }
32
-
33
- /**
34
- * Clone command specific options
35
- */
36
- export interface CloneOptions extends CommandOptions {
37
- force?: boolean; // Overwrite existing directory
38
- syncServer?: string; // Custom sync server URL
39
- }
40
-
41
- /**
42
- * Sync command specific options
43
- */
44
- export interface SyncOptions extends CommandOptions {
45
- force?: boolean;
46
- nuclear?: boolean;
47
- gentle?: boolean;
48
- dryRun?: boolean;
49
- }
50
-
51
- /**
52
- * Diff command specific options
53
- */
54
- export interface DiffOptions extends CommandOptions {
55
- nameOnly: boolean;
56
- }
57
-
58
- /**
59
- * Log command specific options
60
- */
61
- export interface LogOptions extends CommandOptions {
62
- oneline: boolean;
63
- since?: string;
64
- limit?: number;
65
- }
66
-
67
- /**
68
- * Checkout command specific options
69
- */
70
- export interface CheckoutOptions extends CommandOptions {
71
- force?: boolean;
72
- }
73
-
74
- /**
75
- * Init command specific options
76
- */
77
- export interface InitOptions extends CommandOptions {
78
- syncServer?: string;
79
- }
80
-
81
- /**
82
- * Config command specific options
83
- */
84
- export interface ConfigOptions extends CommandOptions {
85
- list?: boolean;
86
- get?: string;
87
- set?: string;
88
- value?: string;
89
- }
90
-
91
- /**
92
- * Status command specific options
93
- */
94
- export interface StatusOptions extends CommandOptions {
95
- verbose?: boolean;
96
- }
97
-
98
- /**
99
- * Watch command specific options
100
- */
101
- export interface WatchOptions extends CommandOptions {
102
- script?: string; // Script to run before syncing
103
- watchDir?: string; // Directory to watch (relative to working dir)
104
- }
105
-
106
- /**
107
- * Read command specific options
108
- */
109
- export interface ReadOptions extends CommandOptions {
110
- remote?: boolean; // Read from sync server instead of local storage
111
- }
@@ -1,91 +0,0 @@
1
- import {AutomergeUrl, UrlHeads} from "@automerge/automerge-repo"
2
-
3
- /**
4
- * Entry in a directory document
5
- */
6
- export interface DirectoryEntry {
7
- name: string
8
- type: "file" | "folder"
9
- url: AutomergeUrl
10
- }
11
-
12
- /**
13
- * Directory document structure
14
- */
15
- export interface DirectoryDocument {
16
- "@patchwork": {type: "folder"}
17
- name: string
18
- title: string
19
- docs: DirectoryEntry[]
20
- lastSyncAt?: number // Timestamp of last sync operation that made changes
21
- with?: string // Tool identifier that last synced, e.g. "pushwork@1.0.19"
22
- }
23
-
24
- /**
25
- * File document structure
26
- */
27
- export interface FileDocument {
28
- "@patchwork": {type: "file"}
29
- name: string
30
- extension: string
31
- mimeType: string
32
- content: string | Uint8Array
33
- metadata: {
34
- permissions: number
35
- }
36
- }
37
-
38
- /**
39
- * File type classification
40
- */
41
- export enum FileType {
42
- TEXT = "text",
43
- BINARY = "binary",
44
- DIRECTORY = "directory"
45
- }
46
-
47
- /**
48
- * Change type classification for sync operations
49
- */
50
- export enum ChangeType {
51
- NO_CHANGE = "no_change",
52
- LOCAL_ONLY = "local_only",
53
- REMOTE_ONLY = "remote_only",
54
- BOTH_CHANGED = "both_changed"
55
- }
56
-
57
- /**
58
- * File system entry metadata
59
- */
60
- export interface FileSystemEntry {
61
- path: string
62
- type: FileType
63
- size: number
64
- mtime: Date
65
- permissions: number
66
- }
67
-
68
- /**
69
- * Move detection result
70
- */
71
- export interface MoveCandidate {
72
- fromPath: string
73
- toPath: string
74
- similarity: number
75
- newContent?: string | Uint8Array // Content at destination (may differ from source if modified during move)
76
- }
77
-
78
- /**
79
- * Represents a detected change
80
- */
81
- export interface DetectedChange {
82
- path: string
83
- changeType: ChangeType
84
- fileType: FileType
85
- localContent: string | Uint8Array | null
86
- remoteContent: string | Uint8Array | null
87
- localHead?: UrlHeads
88
- remoteHead?: UrlHeads
89
- /** New remote URL when the remote document was replaced (artifact URL change) */
90
- remoteUrl?: AutomergeUrl
91
- }
@@ -1,3 +0,0 @@
1
- export * from "./documents.js";
2
- export * from "./snapshot.js";
3
- export * from "./config.js";
@@ -1,67 +0,0 @@
1
- import { AutomergeUrl, UrlHeads } from "@automerge/automerge-repo";
2
-
3
- /**
4
- * Tracked file entry in the sync snapshot
5
- */
6
- export interface SnapshotFileEntry {
7
- path: string; // Full filesystem path for mapping
8
- url: AutomergeUrl; // Automerge document URL
9
- head: UrlHeads; // Document head at last sync
10
- extension: string; // File extension
11
- mimeType: string; // MIME type
12
- contentHash?: string; // SHA-256 of content at last sync (used by artifact files to skip remote reads)
13
- }
14
-
15
- /**
16
- * Tracked directory entry in the sync snapshot
17
- */
18
- export interface SnapshotDirectoryEntry {
19
- path: string; // Full filesystem path for mapping
20
- url: AutomergeUrl; // Automerge document URL
21
- head: UrlHeads; // Document head at last sync
22
- entries: string[]; // List of child entry names
23
- }
24
-
25
- /**
26
- * Sync snapshot for local state management
27
- */
28
- export interface SyncSnapshot {
29
- timestamp: number;
30
- rootPath: string;
31
- rootDirectoryUrl?: AutomergeUrl; // URL of the root directory document
32
- files: Map<string, SnapshotFileEntry>;
33
- directories: Map<string, SnapshotDirectoryEntry>;
34
- }
35
-
36
- /**
37
- * Serializable version of sync snapshot for storage
38
- */
39
- export interface SerializableSyncSnapshot {
40
- timestamp: number;
41
- rootPath: string;
42
- rootDirectoryUrl?: AutomergeUrl; // URL of the root directory document
43
- files: Array<[string, SnapshotFileEntry]>;
44
- directories: Array<[string, SnapshotDirectoryEntry]>;
45
- }
46
-
47
- /**
48
- * Sync operation result
49
- */
50
- export interface SyncResult {
51
- success: boolean;
52
- filesChanged: number;
53
- directoriesChanged: number;
54
- errors: SyncError[];
55
- warnings: string[];
56
- timings?: { [key: string]: number };
57
- }
58
-
59
- /**
60
- * Sync error details
61
- */
62
- export interface SyncError {
63
- path: string;
64
- operation: string;
65
- error: Error;
66
- recoverable: boolean;
67
- }
@@ -1,34 +0,0 @@
1
- import { createHash } from "crypto";
2
-
3
- /**
4
- * Compute a SHA-256 hash of file content.
5
- * Used to detect local changes for artifact files without reading remote docs.
6
- */
7
- export function contentHash(content: string | Uint8Array): string {
8
- return createHash("sha256").update(content).digest("hex");
9
- }
10
-
11
- /**
12
- * Compare two content pieces for equality
13
- */
14
- export function isContentEqual(
15
- content1: string | Uint8Array | null,
16
- content2: string | Uint8Array | null
17
- ): boolean {
18
- if (content1 === content2) return true;
19
- if (!content1 || !content2) return false;
20
-
21
- if (typeof content1 !== typeof content2) return false;
22
-
23
- if (typeof content1 === "string") {
24
- return content1 === content2;
25
- } else {
26
- // Compare Uint8Array using native Buffer.equals() for better performance
27
- const buf1 = content1 as Uint8Array;
28
- const buf2 = content2 as Uint8Array;
29
-
30
- if (buf1.length !== buf2.length) return false;
31
-
32
- return Buffer.from(buf1).equals(Buffer.from(buf2));
33
- }
34
- }
@@ -1,73 +0,0 @@
1
- import {
2
- AutomergeUrl,
3
- Repo,
4
- parseAutomergeUrl,
5
- stringifyAutomergeUrl,
6
- } from "@automerge/automerge-repo";
7
- import { DirectoryDocument } from "../types/index.js";
8
-
9
- /**
10
- * Get a plain URL (without heads) from any URL.
11
- * Versioned URLs with heads return view handles, which show a frozen point in time.
12
- * For internal navigation, we always want to see the CURRENT state of documents.
13
- */
14
- export function getPlainUrl(url: AutomergeUrl): AutomergeUrl {
15
- const { documentId } = parseAutomergeUrl(url);
16
- return stringifyAutomergeUrl({ documentId });
17
- }
18
-
19
- /**
20
- * Find a file in the directory hierarchy by path.
21
- *
22
- * IMPORTANT: This function strips heads from all URLs before navigation.
23
- * This ensures we always see the CURRENT state of directories, not a frozen
24
- * point-in-time view. This is critical because:
25
- * 1. Directory documents store versioned URLs for subdirectories
26
- * 2. These URLs may have been captured when the subdirectory was empty
27
- * 3. Using versioned URLs would make files appear to not exist
28
- * 4. This would trigger false "remote deletion" detection
29
- */
30
- export async function findFileInDirectoryHierarchy(
31
- repo: Repo,
32
- directoryUrl: AutomergeUrl,
33
- filePath: string
34
- ): Promise<{ name: string; type: string; url: AutomergeUrl } | null> {
35
- try {
36
- const pathParts = filePath.split("/");
37
- let currentDirUrl = getPlainUrl(directoryUrl);
38
-
39
- // Navigate through directories to find the parent directory
40
- for (let i = 0; i < pathParts.length - 1; i++) {
41
- const dirName = pathParts[i];
42
- const dirHandle = await repo.find<DirectoryDocument>(currentDirUrl);
43
- const dirDoc = await dirHandle.doc();
44
-
45
- if (!dirDoc) return null;
46
-
47
- const subDirEntry = dirDoc.docs.find(
48
- (entry: { name: string; type: string; url: AutomergeUrl }) =>
49
- entry.name === dirName && entry.type === "folder"
50
- );
51
-
52
- if (!subDirEntry) return null;
53
- currentDirUrl = getPlainUrl(subDirEntry.url);
54
- }
55
-
56
- // Now look for the file in the final directory
57
- const fileName = pathParts[pathParts.length - 1];
58
- const finalDirHandle = await repo.find<DirectoryDocument>(currentDirUrl);
59
- const finalDirDoc = await finalDirHandle.doc();
60
-
61
- if (!finalDirDoc) return null;
62
-
63
- const fileEntry = finalDirDoc.docs.find(
64
- (entry: { name: string; type: string; url: AutomergeUrl }) =>
65
- entry.name === fileName && entry.type === "file"
66
- );
67
-
68
- return fileEntry || null;
69
- } catch (error) {
70
- // Failed to find file in hierarchy
71
- return null;
72
- }
73
- }
package/src/utils/fs.ts DELETED
@@ -1,297 +0,0 @@
1
- import * as fs from "fs/promises"
2
- import * as path from "path"
3
- import * as crypto from "crypto"
4
- import {glob} from "glob"
5
- import * as mimeTypes from "mime-types"
6
- import ignoreModule from "ignore"
7
- // CJS default export compat under nodenext
8
- const ignore = (ignoreModule as any).default || ignoreModule
9
- import {FileSystemEntry, FileType} from "../types/index.js"
10
- import {isEnhancedTextFile} from "./mime-types.js"
11
-
12
- /**
13
- * Check if a path exists
14
- */
15
- export async function pathExists(filePath: string): Promise<boolean> {
16
- try {
17
- await fs.access(filePath)
18
- return true
19
- } catch {
20
- return false
21
- }
22
- }
23
-
24
- /**
25
- * Get file system entry metadata
26
- */
27
- export async function getFileSystemEntry(
28
- filePath: string
29
- ): Promise<FileSystemEntry | null> {
30
- try {
31
- const stats = await fs.stat(filePath)
32
- const type = stats.isDirectory()
33
- ? FileType.DIRECTORY
34
- : (await isEnhancedTextFile(filePath))
35
- ? FileType.TEXT
36
- : FileType.BINARY
37
-
38
- return {
39
- path: filePath,
40
- type,
41
- size: stats.size,
42
- mtime: stats.mtime,
43
- permissions: stats.mode & parseInt("777", 8),
44
- }
45
- } catch {
46
- return null
47
- }
48
- }
49
-
50
- /**
51
- * Determine if a file is text or binary
52
- */
53
- export async function isTextFile(filePath: string): Promise<boolean> {
54
- try {
55
- const mimeType = mimeTypes.lookup(filePath)
56
- if (mimeType) {
57
- return (
58
- mimeType.startsWith("text/") ||
59
- mimeType === "application/json" ||
60
- mimeType === "application/xml" ||
61
- mimeType.includes("javascript") ||
62
- mimeType.includes("typescript")
63
- )
64
- }
65
-
66
- // Sample first 8KB to detect binary content
67
- const handle = await fs.open(filePath, "r")
68
- const buffer = Buffer.alloc(Math.min(8192, (await handle.stat()).size))
69
- await handle.read(buffer, 0, buffer.length, 0)
70
- await handle.close()
71
-
72
- // Check for null bytes which indicate binary content
73
- return !buffer.includes(0)
74
- } catch {
75
- return false
76
- }
77
- }
78
-
79
- /**
80
- * Read file content as string or buffer
81
- */
82
- export async function readFileContent(
83
- filePath: string
84
- ): Promise<string | Uint8Array> {
85
- const isText = await isEnhancedTextFile(filePath)
86
-
87
- if (isText) {
88
- return await fs.readFile(filePath, "utf8")
89
- } else {
90
- const buffer = await fs.readFile(filePath)
91
- return new Uint8Array(buffer)
92
- }
93
- }
94
-
95
- /**
96
- * Write file content from string or buffer
97
- */
98
- export async function writeFileContent(
99
- filePath: string,
100
- content: string | Uint8Array
101
- ): Promise<void> {
102
- await ensureDirectoryExists(path.dirname(filePath))
103
-
104
- if (typeof content === "string") {
105
- await fs.writeFile(filePath, content, "utf8")
106
- } else {
107
- await fs.writeFile(filePath, content)
108
- }
109
- }
110
-
111
- /**
112
- * Ensure directory exists, creating it if necessary
113
- */
114
- export async function ensureDirectoryExists(dirPath: string): Promise<void> {
115
- try {
116
- await fs.mkdir(dirPath, {recursive: true})
117
- } catch (error: any) {
118
- if (error.code !== "EEXIST") {
119
- throw error
120
- }
121
- }
122
- }
123
-
124
- /**
125
- * Remove file or directory
126
- */
127
- export async function removePath(filePath: string): Promise<void> {
128
- try {
129
- const stats = await fs.stat(filePath)
130
- if (stats.isDirectory()) {
131
- await fs.rm(filePath, {recursive: true})
132
- } else {
133
- await fs.unlink(filePath)
134
- }
135
- } catch (error: any) {
136
- if (error.code !== "ENOENT") {
137
- throw error
138
- }
139
- }
140
- }
141
-
142
- /**
143
- * Check if a path matches any of the exclude patterns using the ignore library
144
- * Supports proper gitignore-style patterns (e.g., "node_modules", "*.tmp", ".git")
145
- */
146
- function isExcluded(
147
- filePath: string,
148
- basePath: string,
149
- excludePatterns: string[]
150
- ): boolean {
151
- if (excludePatterns.length === 0) return false
152
-
153
- const relativePath = path.relative(basePath, filePath)
154
-
155
- // Use the ignore library which implements proper .gitignore semantics
156
- // This is the same library used by ESLint and other major tools
157
- const ig = ignore().add(excludePatterns)
158
-
159
- return ig.ignores(relativePath)
160
- }
161
-
162
- /**
163
- * List directory contents with metadata
164
- */
165
- export async function listDirectory(
166
- dirPath: string,
167
- recursive = false,
168
- excludePatterns: string[] = []
169
- ): Promise<FileSystemEntry[]> {
170
- const entries: FileSystemEntry[] = []
171
-
172
- try {
173
- // Construct pattern using path.join for proper cross-platform handling
174
- const pattern = recursive
175
- ? path.join(dirPath, "**/*")
176
- : path.join(dirPath, "*")
177
-
178
- // glob expects forward slashes, even on Windows
179
- const normalizedPattern = pattern.replace(/\\/g, "/")
180
-
181
- // Use glob to get all paths (with dot files)
182
- // Note: We don't use glob's ignore option because it doesn't support gitignore semantics
183
- const paths = await glob(normalizedPattern, {
184
- dot: true,
185
- })
186
-
187
- // Parallelize all stat calls for better performance
188
- const allEntries = await Promise.all(
189
- paths.map(async filePath => {
190
- // Filter using proper gitignore semantics from the ignore library
191
- if (isExcluded(filePath, dirPath, excludePatterns)) {
192
- return null
193
- }
194
- return await getFileSystemEntry(filePath)
195
- })
196
- )
197
-
198
- // Filter out null entries (excluded files or files that couldn't be read)
199
- entries.push(...allEntries.filter((e): e is FileSystemEntry => e !== null))
200
- } catch {
201
- // Return empty array if directory doesn't exist or can't be read
202
- }
203
-
204
- return entries
205
- }
206
-
207
- /**
208
- * Copy file with metadata preservation
209
- */
210
- export async function copyFile(
211
- sourcePath: string,
212
- destPath: string
213
- ): Promise<void> {
214
- await ensureDirectoryExists(path.dirname(destPath))
215
- await fs.copyFile(sourcePath, destPath)
216
-
217
- // Preserve file permissions
218
- const stats = await fs.stat(sourcePath)
219
- await fs.chmod(destPath, stats.mode)
220
- }
221
-
222
- /**
223
- * Move/rename file or directory
224
- */
225
- export async function movePath(
226
- sourcePath: string,
227
- destPath: string
228
- ): Promise<void> {
229
- await ensureDirectoryExists(path.dirname(destPath))
230
- await fs.rename(sourcePath, destPath)
231
- }
232
-
233
- /**
234
- * Calculate content hash for change detection
235
- */
236
- export async function calculateContentHash(
237
- content: string | Uint8Array
238
- ): Promise<string> {
239
- const hash = crypto.createHash("sha256")
240
- hash.update(content)
241
- return hash.digest("hex")
242
- }
243
-
244
- /**
245
- * Get MIME type for file
246
- */
247
- export function getMimeType(filePath: string): string {
248
- return mimeTypes.lookup(filePath) || "application/octet-stream"
249
- }
250
-
251
- /**
252
- * Get file extension
253
- */
254
- export function getFileExtension(filePath: string): string {
255
- const ext = path.extname(filePath)
256
- return ext.startsWith(".") ? ext.slice(1) : ext
257
- }
258
-
259
- /**
260
- * Normalize path separators for cross-platform compatibility
261
- * Converts all path separators to forward slashes for consistent storage
262
- */
263
- export function normalizePath(filePath: string): string {
264
- return path.posix.normalize(filePath.replace(/\\/g, "/"))
265
- }
266
-
267
- /**
268
- * Join paths and normalize separators for cross-platform compatibility
269
- * Use this instead of string concatenation to ensure proper path handling on Windows
270
- */
271
- export function joinAndNormalizePath(...paths: string[]): string {
272
- // Use path.join to properly handle path construction (handles Windows drive letters, etc.)
273
- const joined = path.join(...paths)
274
- // Then normalize to forward slashes for consistent storage/comparison
275
- return normalizePath(joined)
276
- }
277
-
278
- /**
279
- * Get relative path from base directory
280
- */
281
- export function getRelativePath(basePath: string, filePath: string): string {
282
- return normalizePath(path.relative(basePath, filePath))
283
- }
284
-
285
- /**
286
- * Format a path as a relative path with proper prefix
287
- * Ensures paths like "src" become "./src" for clarity
288
- * Leaves absolute paths and paths already starting with . or .. unchanged
289
- */
290
- export function formatRelativePath(filePath: string): string {
291
- // Already starts with . or / - leave as-is
292
- if (filePath.startsWith(".") || filePath.startsWith("/")) {
293
- return filePath
294
- }
295
- // Add ./ prefix for clarity
296
- return `./${filePath}`
297
- }
@@ -1,4 +0,0 @@
1
- export * from "./fs.js"
2
- export * from "./mime-types.js"
3
- export * from "./directory.js"
4
- export * from "./text-diff.js"