pushwork 2.0.0-a.sub.1 → 2.0.0-preview.2

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 (234) hide show
  1. package/dist/branches.d.ts +20 -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 +245 -270
  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 +35 -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 +129 -0
  30. package/dist/pushwork.d.ts.map +1 -0
  31. package/dist/pushwork.js +1062 -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 +38 -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 +92 -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/dist/version.d.ts +11 -0
  66. package/dist/version.d.ts.map +1 -0
  67. package/dist/version.js +93 -0
  68. package/dist/version.js.map +1 -0
  69. package/package.json +19 -48
  70. package/patches/@automerge__automerge-repo@2.6.0-subduction.15.patch +26 -0
  71. package/.prettierrc +0 -9
  72. package/ARCHITECTURE-ACCORDING-TO-CLAUDE.md +0 -248
  73. package/CLAUDE.md +0 -141
  74. package/README.md +0 -221
  75. package/babel.config.js +0 -5
  76. package/dist/cli/commands.d.ts +0 -71
  77. package/dist/cli/commands.d.ts.map +0 -1
  78. package/dist/cli/commands.js +0 -794
  79. package/dist/cli/commands.js.map +0 -1
  80. package/dist/cli/index.d.ts +0 -2
  81. package/dist/cli/index.d.ts.map +0 -1
  82. package/dist/cli/index.js +0 -19
  83. package/dist/cli/index.js.map +0 -1
  84. package/dist/commands.d.ts +0 -61
  85. package/dist/commands.d.ts.map +0 -1
  86. package/dist/commands.js +0 -861
  87. package/dist/commands.js.map +0 -1
  88. package/dist/config/index.d.ts +0 -71
  89. package/dist/config/index.d.ts.map +0 -1
  90. package/dist/config/index.js +0 -314
  91. package/dist/config/index.js.map +0 -1
  92. package/dist/core/change-detection.d.ts +0 -80
  93. package/dist/core/change-detection.d.ts.map +0 -1
  94. package/dist/core/change-detection.js +0 -523
  95. package/dist/core/change-detection.js.map +0 -1
  96. package/dist/core/config.d.ts +0 -81
  97. package/dist/core/config.d.ts.map +0 -1
  98. package/dist/core/config.js +0 -258
  99. package/dist/core/config.js.map +0 -1
  100. package/dist/core/index.d.ts +0 -6
  101. package/dist/core/index.d.ts.map +0 -1
  102. package/dist/core/index.js +0 -6
  103. package/dist/core/index.js.map +0 -1
  104. package/dist/core/move-detection.d.ts +0 -34
  105. package/dist/core/move-detection.d.ts.map +0 -1
  106. package/dist/core/move-detection.js +0 -121
  107. package/dist/core/move-detection.js.map +0 -1
  108. package/dist/core/snapshot.d.ts +0 -105
  109. package/dist/core/snapshot.d.ts.map +0 -1
  110. package/dist/core/snapshot.js +0 -217
  111. package/dist/core/snapshot.js.map +0 -1
  112. package/dist/core/sync-engine.d.ts +0 -157
  113. package/dist/core/sync-engine.d.ts.map +0 -1
  114. package/dist/core/sync-engine.js +0 -1379
  115. package/dist/core/sync-engine.js.map +0 -1
  116. package/dist/types/config.d.ts +0 -99
  117. package/dist/types/config.d.ts.map +0 -1
  118. package/dist/types/config.js +0 -5
  119. package/dist/types/config.js.map +0 -1
  120. package/dist/types/documents.d.ts +0 -88
  121. package/dist/types/documents.d.ts.map +0 -1
  122. package/dist/types/documents.js +0 -20
  123. package/dist/types/documents.js.map +0 -1
  124. package/dist/types/index.d.ts +0 -4
  125. package/dist/types/index.d.ts.map +0 -1
  126. package/dist/types/index.js +0 -4
  127. package/dist/types/index.js.map +0 -1
  128. package/dist/types/snapshot.d.ts +0 -64
  129. package/dist/types/snapshot.d.ts.map +0 -1
  130. package/dist/types/snapshot.js +0 -2
  131. package/dist/types/snapshot.js.map +0 -1
  132. package/dist/utils/content-similarity.d.ts +0 -53
  133. package/dist/utils/content-similarity.d.ts.map +0 -1
  134. package/dist/utils/content-similarity.js +0 -155
  135. package/dist/utils/content-similarity.js.map +0 -1
  136. package/dist/utils/content.d.ts +0 -10
  137. package/dist/utils/content.d.ts.map +0 -1
  138. package/dist/utils/content.js +0 -31
  139. package/dist/utils/content.js.map +0 -1
  140. package/dist/utils/directory.d.ts +0 -24
  141. package/dist/utils/directory.d.ts.map +0 -1
  142. package/dist/utils/directory.js +0 -52
  143. package/dist/utils/directory.js.map +0 -1
  144. package/dist/utils/fs.d.ts +0 -74
  145. package/dist/utils/fs.d.ts.map +0 -1
  146. package/dist/utils/fs.js +0 -248
  147. package/dist/utils/fs.js.map +0 -1
  148. package/dist/utils/index.d.ts +0 -5
  149. package/dist/utils/index.d.ts.map +0 -1
  150. package/dist/utils/index.js +0 -5
  151. package/dist/utils/index.js.map +0 -1
  152. package/dist/utils/mime-types.d.ts +0 -13
  153. package/dist/utils/mime-types.d.ts.map +0 -1
  154. package/dist/utils/mime-types.js +0 -209
  155. package/dist/utils/mime-types.js.map +0 -1
  156. package/dist/utils/network-sync.d.ts +0 -36
  157. package/dist/utils/network-sync.d.ts.map +0 -1
  158. package/dist/utils/network-sync.js +0 -250
  159. package/dist/utils/network-sync.js.map +0 -1
  160. package/dist/utils/node-polyfills.d.ts +0 -9
  161. package/dist/utils/node-polyfills.d.ts.map +0 -1
  162. package/dist/utils/node-polyfills.js +0 -9
  163. package/dist/utils/node-polyfills.js.map +0 -1
  164. package/dist/utils/output.d.ts +0 -129
  165. package/dist/utils/output.d.ts.map +0 -1
  166. package/dist/utils/output.js +0 -368
  167. package/dist/utils/output.js.map +0 -1
  168. package/dist/utils/repo-factory.d.ts +0 -13
  169. package/dist/utils/repo-factory.d.ts.map +0 -1
  170. package/dist/utils/repo-factory.js +0 -46
  171. package/dist/utils/repo-factory.js.map +0 -1
  172. package/dist/utils/string-similarity.d.ts +0 -14
  173. package/dist/utils/string-similarity.d.ts.map +0 -1
  174. package/dist/utils/string-similarity.js +0 -39
  175. package/dist/utils/string-similarity.js.map +0 -1
  176. package/dist/utils/text-diff.d.ts +0 -37
  177. package/dist/utils/text-diff.d.ts.map +0 -1
  178. package/dist/utils/text-diff.js +0 -93
  179. package/dist/utils/text-diff.js.map +0 -1
  180. package/dist/utils/trace.d.ts +0 -19
  181. package/dist/utils/trace.d.ts.map +0 -1
  182. package/dist/utils/trace.js +0 -63
  183. package/dist/utils/trace.js.map +0 -1
  184. package/src/cli.ts +0 -442
  185. package/src/commands.ts +0 -1134
  186. package/src/core/change-detection.ts +0 -712
  187. package/src/core/config.ts +0 -313
  188. package/src/core/index.ts +0 -5
  189. package/src/core/move-detection.ts +0 -169
  190. package/src/core/snapshot.ts +0 -275
  191. package/src/core/sync-engine.ts +0 -1795
  192. package/src/index.ts +0 -4
  193. package/src/types/config.ts +0 -111
  194. package/src/types/documents.ts +0 -91
  195. package/src/types/index.ts +0 -3
  196. package/src/types/snapshot.ts +0 -67
  197. package/src/utils/content.ts +0 -34
  198. package/src/utils/directory.ts +0 -73
  199. package/src/utils/fs.ts +0 -297
  200. package/src/utils/index.ts +0 -4
  201. package/src/utils/mime-types.ts +0 -244
  202. package/src/utils/network-sync.ts +0 -319
  203. package/src/utils/node-polyfills.ts +0 -8
  204. package/src/utils/output.ts +0 -450
  205. package/src/utils/repo-factory.ts +0 -73
  206. package/src/utils/string-similarity.ts +0 -54
  207. package/src/utils/text-diff.ts +0 -101
  208. package/src/utils/trace.ts +0 -70
  209. package/test/integration/README.md +0 -328
  210. package/test/integration/clone-test.sh +0 -310
  211. package/test/integration/conflict-resolution-test.sh +0 -309
  212. package/test/integration/debug-both-nested.sh +0 -74
  213. package/test/integration/debug-concurrent-nested.sh +0 -87
  214. package/test/integration/debug-nested.sh +0 -73
  215. package/test/integration/deletion-behavior-test.sh +0 -487
  216. package/test/integration/deletion-sync-test-simple.sh +0 -193
  217. package/test/integration/deletion-sync-test.sh +0 -297
  218. package/test/integration/exclude-patterns.test.ts +0 -144
  219. package/test/integration/full-integration-test.sh +0 -363
  220. package/test/integration/fuzzer.test.ts +0 -818
  221. package/test/integration/in-memory-sync.test.ts +0 -830
  222. package/test/integration/init-sync.test.ts +0 -89
  223. package/test/integration/manual-sync-test.sh +0 -84
  224. package/test/integration/sync-deletion.test.ts +0 -280
  225. package/test/integration/sync-flow.test.ts +0 -291
  226. package/test/jest.setup.ts +0 -34
  227. package/test/run-tests.sh +0 -225
  228. package/test/unit/deletion-behavior.test.ts +0 -249
  229. package/test/unit/enhanced-mime-detection.test.ts +0 -244
  230. package/test/unit/snapshot.test.ts +0 -404
  231. package/test/unit/sync-convergence.test.ts +0 -298
  232. package/test/unit/sync-timing.test.ts +0 -134
  233. package/test/unit/utils.test.ts +0 -366
  234. package/tsconfig.json +0 -23
package/src/index.ts DELETED
@@ -1,4 +0,0 @@
1
- export * from "./core/index.js";
2
- export * from "./utils/index.js";
3
- export * from "./types/index.js";
4
- export * from "./cli.js";
@@ -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"