pushwork 1.0.4 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +87 -328
- package/dist/.pushwork/automerge/3P/Dm3ekE2pmjGnWvDaG3vSR7ww98/snapshot/aa2349c94955ea561f698720142f9d884a6872d9f82dc332d578c216beb0df0e +0 -0
- package/dist/.pushwork/automerge/st/orage-adapter-id +1 -0
- package/dist/.pushwork/config.json +15 -0
- package/dist/.pushwork/snapshot.json +7 -0
- package/dist/cli.js +231 -170
- package/dist/cli.js.map +1 -1
- package/dist/commands.d.ts +51 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +799 -0
- package/dist/commands.js.map +1 -0
- package/dist/core/change-detection.d.ts +6 -19
- package/dist/core/change-detection.d.ts.map +1 -1
- package/dist/core/change-detection.js +101 -80
- package/dist/core/change-detection.js.map +1 -1
- package/dist/{config/index.d.ts → core/config.d.ts} +13 -3
- package/dist/core/config.d.ts.map +1 -0
- package/dist/{config/index.js → core/config.js} +55 -73
- package/dist/core/config.js.map +1 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/move-detection.d.ts +12 -50
- package/dist/core/move-detection.d.ts.map +1 -1
- package/dist/core/move-detection.js +58 -139
- package/dist/core/move-detection.js.map +1 -1
- package/dist/core/snapshot.d.ts +0 -4
- package/dist/core/snapshot.d.ts.map +1 -1
- package/dist/core/snapshot.js +2 -11
- package/dist/core/snapshot.js.map +1 -1
- package/dist/core/sync-engine.d.ts +5 -11
- package/dist/core/sync-engine.d.ts.map +1 -1
- package/dist/core/sync-engine.js +220 -362
- package/dist/core/sync-engine.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -6
- package/dist/index.js.map +1 -1
- package/dist/types/config.d.ts +43 -67
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +6 -0
- package/dist/types/config.js.map +1 -1
- package/dist/types/documents.d.ts +15 -3
- package/dist/types/documents.d.ts.map +1 -1
- package/dist/types/documents.js.map +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +0 -3
- package/dist/types/index.js.map +1 -1
- package/dist/types/snapshot.d.ts +3 -21
- package/dist/types/snapshot.d.ts.map +1 -1
- package/dist/types/snapshot.js +0 -14
- package/dist/types/snapshot.js.map +1 -1
- package/dist/utils/content.d.ts.map +1 -1
- package/dist/utils/content.js +2 -6
- package/dist/utils/content.js.map +1 -1
- package/dist/utils/directory.d.ts +10 -0
- package/dist/utils/directory.d.ts.map +1 -0
- package/dist/utils/directory.js +37 -0
- package/dist/utils/directory.js.map +1 -0
- package/dist/utils/fs.d.ts +15 -2
- package/dist/utils/fs.d.ts.map +1 -1
- package/dist/utils/fs.js +63 -53
- package/dist/utils/fs.js.map +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -4
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/mime-types.d.ts.map +1 -1
- package/dist/utils/mime-types.js +11 -4
- package/dist/utils/mime-types.js.map +1 -1
- package/dist/utils/network-sync.d.ts +0 -6
- package/dist/utils/network-sync.d.ts.map +1 -1
- package/dist/utils/network-sync.js +55 -99
- package/dist/utils/network-sync.js.map +1 -1
- package/dist/utils/output.d.ts +129 -0
- package/dist/utils/output.d.ts.map +1 -0
- package/dist/utils/output.js +375 -0
- package/dist/utils/output.js.map +1 -0
- package/dist/utils/repo-factory.d.ts +2 -6
- package/dist/utils/repo-factory.d.ts.map +1 -1
- package/dist/utils/repo-factory.js +8 -22
- package/dist/utils/repo-factory.js.map +1 -1
- package/dist/utils/string-similarity.d.ts +14 -0
- package/dist/utils/string-similarity.d.ts.map +1 -0
- package/dist/utils/string-similarity.js +43 -0
- package/dist/utils/string-similarity.js.map +1 -0
- package/dist/utils/trace.d.ts +19 -0
- package/dist/utils/trace.d.ts.map +1 -0
- package/dist/utils/trace.js +68 -0
- package/dist/utils/trace.js.map +1 -0
- package/package.json +17 -12
- package/src/cli.ts +326 -252
- package/src/commands.ts +988 -0
- package/src/core/change-detection.ts +199 -162
- package/src/{config/index.ts → core/config.ts} +65 -82
- package/src/core/index.ts +1 -1
- package/src/core/move-detection.ts +74 -180
- package/src/core/snapshot.ts +2 -12
- package/src/core/sync-engine.ts +248 -499
- package/src/index.ts +0 -10
- package/src/types/config.ts +50 -72
- package/src/types/documents.ts +16 -3
- package/src/types/index.ts +0 -5
- package/src/types/snapshot.ts +1 -23
- package/src/utils/content.ts +2 -6
- package/src/utils/directory.ts +50 -0
- package/src/utils/fs.ts +67 -56
- package/src/utils/index.ts +1 -6
- package/src/utils/mime-types.ts +12 -4
- package/src/utils/network-sync.ts +79 -137
- package/src/utils/output.ts +450 -0
- package/src/utils/repo-factory.ts +13 -31
- package/src/utils/string-similarity.ts +54 -0
- package/src/utils/trace.ts +70 -0
- package/test/integration/exclude-patterns.test.ts +6 -15
- package/test/integration/fuzzer.test.ts +308 -391
- package/test/integration/init-sync.test.ts +89 -0
- package/test/integration/sync-deletion.test.ts +2 -61
- package/test/integration/sync-flow.test.ts +4 -24
- package/test/jest.setup.ts +34 -0
- package/test/unit/deletion-behavior.test.ts +3 -14
- package/test/unit/enhanced-mime-detection.test.ts +0 -22
- package/test/unit/snapshot.test.ts +2 -29
- package/test/unit/sync-convergence.test.ts +3 -198
- package/test/unit/sync-timing.test.ts +0 -44
- package/test/unit/utils.test.ts +0 -2
- package/tsconfig.json +3 -3
- package/dist/browser/browser-sync-engine.d.ts +0 -64
- package/dist/browser/browser-sync-engine.d.ts.map +0 -1
- package/dist/browser/browser-sync-engine.js +0 -303
- package/dist/browser/browser-sync-engine.js.map +0 -1
- package/dist/browser/filesystem-adapter.d.ts +0 -84
- package/dist/browser/filesystem-adapter.d.ts.map +0 -1
- package/dist/browser/filesystem-adapter.js +0 -413
- package/dist/browser/filesystem-adapter.js.map +0 -1
- package/dist/browser/index.d.ts +0 -36
- package/dist/browser/index.d.ts.map +0 -1
- package/dist/browser/index.js +0 -90
- package/dist/browser/index.js.map +0 -1
- package/dist/browser/types.d.ts +0 -70
- package/dist/browser/types.d.ts.map +0 -1
- package/dist/browser/types.js +0 -6
- package/dist/browser/types.js.map +0 -1
- package/dist/cli/commands.d.ts +0 -77
- package/dist/cli/commands.d.ts.map +0 -1
- package/dist/cli/commands.js +0 -904
- package/dist/cli/commands.js.map +0 -1
- package/dist/cli/index.d.ts +0 -2
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/index.js +0 -19
- package/dist/cli/index.js.map +0 -1
- package/dist/config/index.d.ts.map +0 -1
- package/dist/config/index.js.map +0 -1
- package/dist/core/isomorphic-snapshot.d.ts +0 -58
- package/dist/core/isomorphic-snapshot.d.ts.map +0 -1
- package/dist/core/isomorphic-snapshot.js +0 -204
- package/dist/core/isomorphic-snapshot.js.map +0 -1
- package/dist/platform/browser-filesystem.d.ts +0 -26
- package/dist/platform/browser-filesystem.d.ts.map +0 -1
- package/dist/platform/browser-filesystem.js +0 -91
- package/dist/platform/browser-filesystem.js.map +0 -1
- package/dist/platform/filesystem.d.ts +0 -29
- package/dist/platform/filesystem.d.ts.map +0 -1
- package/dist/platform/filesystem.js +0 -65
- package/dist/platform/filesystem.js.map +0 -1
- package/dist/platform/node-filesystem.d.ts +0 -21
- package/dist/platform/node-filesystem.d.ts.map +0 -1
- package/dist/platform/node-filesystem.js +0 -93
- package/dist/platform/node-filesystem.js.map +0 -1
- package/dist/utils/content-similarity.d.ts +0 -53
- package/dist/utils/content-similarity.d.ts.map +0 -1
- package/dist/utils/content-similarity.js +0 -155
- package/dist/utils/content-similarity.js.map +0 -1
- package/dist/utils/fs-browser.d.ts +0 -57
- package/dist/utils/fs-browser.d.ts.map +0 -1
- package/dist/utils/fs-browser.js +0 -311
- package/dist/utils/fs-browser.js.map +0 -1
- package/dist/utils/fs-node.d.ts +0 -53
- package/dist/utils/fs-node.d.ts.map +0 -1
- package/dist/utils/fs-node.js +0 -220
- package/dist/utils/fs-node.js.map +0 -1
- package/dist/utils/isomorphic.d.ts +0 -29
- package/dist/utils/isomorphic.d.ts.map +0 -1
- package/dist/utils/isomorphic.js +0 -139
- package/dist/utils/isomorphic.js.map +0 -1
- package/dist/utils/pure.d.ts +0 -25
- package/dist/utils/pure.d.ts.map +0 -1
- package/dist/utils/pure.js +0 -112
- package/dist/utils/pure.js.map +0 -1
- package/src/cli/commands.ts +0 -1207
- package/src/cli/index.ts +0 -2
- package/src/utils/content-similarity.ts +0 -194
- package/test/README-TESTING-GAPS.md +0 -174
- package/test/unit/content-similarity.test.ts +0 -236
package/src/index.ts
CHANGED
|
@@ -1,14 +1,4 @@
|
|
|
1
|
-
// Core sync functionality
|
|
2
1
|
export * from "./core";
|
|
3
|
-
|
|
4
|
-
// Utilities
|
|
5
2
|
export * from "./utils";
|
|
6
|
-
|
|
7
|
-
// Configuration
|
|
8
|
-
export * from "./config";
|
|
9
|
-
|
|
10
|
-
// Types
|
|
11
3
|
export * from "./types";
|
|
12
|
-
|
|
13
|
-
// CLI commands (for programmatic use)
|
|
14
4
|
export * from "./cli";
|
package/src/types/config.ts
CHANGED
|
@@ -1,91 +1,37 @@
|
|
|
1
|
-
|
|
2
|
-
* Global configuration options
|
|
3
|
-
*/
|
|
4
|
-
export interface GlobalConfig {
|
|
5
|
-
sync_server?: string;
|
|
6
|
-
sync_server_storage_id?: string;
|
|
7
|
-
exclude_patterns?: string[];
|
|
8
|
-
large_file_threshold?: string;
|
|
9
|
-
diff?: {
|
|
10
|
-
external_tool?: string;
|
|
11
|
-
show_binary?: boolean;
|
|
12
|
-
};
|
|
13
|
-
sync?: {
|
|
14
|
-
move_detection_threshold?: number;
|
|
15
|
-
prompt_threshold?: number;
|
|
16
|
-
auto_sync?: boolean;
|
|
17
|
-
parallel_operations?: number;
|
|
18
|
-
};
|
|
19
|
-
}
|
|
1
|
+
import { StorageId } from "@automerge/automerge-repo";
|
|
20
2
|
|
|
21
3
|
/**
|
|
22
|
-
* Default
|
|
4
|
+
* Default sync server configuration
|
|
23
5
|
*/
|
|
24
|
-
export
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
large_file_threshold: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Diff tool settings
|
|
32
|
-
*/
|
|
33
|
-
export interface DiffSettings {
|
|
34
|
-
external_tool?: string;
|
|
35
|
-
show_binary: boolean;
|
|
36
|
-
}
|
|
6
|
+
export const DEFAULT_SYNC_SERVER = "wss://sync3.automerge.org";
|
|
7
|
+
export const DEFAULT_SYNC_SERVER_STORAGE_ID =
|
|
8
|
+
"3760df37-a4c6-4f66-9ecd-732039a9385d" as StorageId;
|
|
37
9
|
|
|
38
10
|
/**
|
|
39
|
-
*
|
|
11
|
+
* Global configuration options
|
|
40
12
|
*/
|
|
41
|
-
export interface
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
13
|
+
export interface GlobalConfig {
|
|
14
|
+
sync_server?: string;
|
|
15
|
+
sync_server_storage_id?: StorageId;
|
|
16
|
+
exclude_patterns: string[];
|
|
17
|
+
sync: {
|
|
18
|
+
move_detection_threshold: number;
|
|
19
|
+
};
|
|
46
20
|
}
|
|
47
21
|
|
|
48
22
|
/**
|
|
49
23
|
* Per-directory configuration
|
|
50
24
|
*/
|
|
51
|
-
export interface DirectoryConfig {
|
|
52
|
-
|
|
53
|
-
sync_server_storage_id?: string;
|
|
25
|
+
export interface DirectoryConfig extends GlobalConfig {
|
|
26
|
+
root_directory_url?: string;
|
|
54
27
|
sync_enabled: boolean;
|
|
55
|
-
root_directory_url?: string; // AutomergeUrl of the root directory document
|
|
56
|
-
defaults: {
|
|
57
|
-
exclude_patterns: string[];
|
|
58
|
-
large_file_threshold: string;
|
|
59
|
-
};
|
|
60
|
-
diff: {
|
|
61
|
-
external_tool?: string;
|
|
62
|
-
show_binary: boolean;
|
|
63
|
-
};
|
|
64
|
-
sync: {
|
|
65
|
-
move_detection_threshold: number;
|
|
66
|
-
prompt_threshold: number;
|
|
67
|
-
auto_sync: boolean;
|
|
68
|
-
parallel_operations: number;
|
|
69
|
-
};
|
|
70
28
|
}
|
|
71
29
|
|
|
72
30
|
/**
|
|
73
31
|
* CLI command options
|
|
74
32
|
*/
|
|
75
33
|
export interface CommandOptions {
|
|
76
|
-
dryRun?: boolean;
|
|
77
34
|
verbose?: boolean;
|
|
78
|
-
tool?: string;
|
|
79
|
-
nameOnly?: boolean;
|
|
80
|
-
oneline?: boolean;
|
|
81
|
-
remote?: string;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Init command specific options
|
|
86
|
-
*/
|
|
87
|
-
export interface InitOptions extends CommandOptions {
|
|
88
|
-
// No additional options needed - init creates a new sync directory
|
|
89
35
|
}
|
|
90
36
|
|
|
91
37
|
/**
|
|
@@ -94,22 +40,21 @@ export interface InitOptions extends CommandOptions {
|
|
|
94
40
|
export interface CloneOptions extends CommandOptions {
|
|
95
41
|
force?: boolean; // Overwrite existing directory
|
|
96
42
|
syncServer?: string; // Custom sync server URL
|
|
97
|
-
syncServerStorageId?:
|
|
43
|
+
syncServerStorageId?: StorageId; // Custom sync server storage ID
|
|
98
44
|
}
|
|
99
45
|
|
|
100
46
|
/**
|
|
101
47
|
* Sync command specific options
|
|
102
48
|
*/
|
|
103
49
|
export interface SyncOptions extends CommandOptions {
|
|
104
|
-
dryRun: boolean;
|
|
105
50
|
force?: boolean;
|
|
51
|
+
dryRun?: boolean;
|
|
106
52
|
}
|
|
107
53
|
|
|
108
54
|
/**
|
|
109
55
|
* Diff command specific options
|
|
110
56
|
*/
|
|
111
57
|
export interface DiffOptions extends CommandOptions {
|
|
112
|
-
tool?: string;
|
|
113
58
|
nameOnly: boolean;
|
|
114
59
|
}
|
|
115
60
|
|
|
@@ -128,3 +73,36 @@ export interface LogOptions extends CommandOptions {
|
|
|
128
73
|
export interface CheckoutOptions extends CommandOptions {
|
|
129
74
|
force?: boolean;
|
|
130
75
|
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Init command specific options
|
|
79
|
+
*/
|
|
80
|
+
export interface InitOptions extends CommandOptions {
|
|
81
|
+
syncServer?: string;
|
|
82
|
+
syncServerStorageId?: StorageId;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Config command specific options
|
|
87
|
+
*/
|
|
88
|
+
export interface ConfigOptions extends CommandOptions {
|
|
89
|
+
list?: boolean;
|
|
90
|
+
get?: string;
|
|
91
|
+
set?: string;
|
|
92
|
+
value?: string;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Status command specific options
|
|
97
|
+
*/
|
|
98
|
+
export interface StatusOptions extends CommandOptions {
|
|
99
|
+
verbose?: boolean;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Watch command specific options
|
|
104
|
+
*/
|
|
105
|
+
export interface WatchOptions extends CommandOptions {
|
|
106
|
+
script?: string; // Script to run before syncing
|
|
107
|
+
watchDir?: string; // Directory to watch (relative to working dir)
|
|
108
|
+
}
|
package/src/types/documents.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { AutomergeUrl } from "@automerge/automerge-repo";
|
|
1
|
+
import { AutomergeUrl, UrlHeads } from "@automerge/automerge-repo";
|
|
2
|
+
import * as A from "@automerge/automerge";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Entry in a directory document
|
|
@@ -26,7 +27,7 @@ export interface FileDocument {
|
|
|
26
27
|
name: string;
|
|
27
28
|
extension: string;
|
|
28
29
|
mimeType: string;
|
|
29
|
-
content:
|
|
30
|
+
content: A.ImmutableString | Uint8Array;
|
|
30
31
|
metadata: {
|
|
31
32
|
permissions: number;
|
|
32
33
|
};
|
|
@@ -69,6 +70,18 @@ export interface MoveCandidate {
|
|
|
69
70
|
fromPath: string;
|
|
70
71
|
toPath: string;
|
|
71
72
|
similarity: number;
|
|
72
|
-
confidence: "auto" | "prompt" | "low";
|
|
73
73
|
newContent?: string | Uint8Array; // Content at destination (may differ from source if modified during move)
|
|
74
74
|
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Represents a detected change
|
|
78
|
+
*/
|
|
79
|
+
export interface DetectedChange {
|
|
80
|
+
path: string;
|
|
81
|
+
changeType: ChangeType;
|
|
82
|
+
fileType: FileType;
|
|
83
|
+
localContent: string | Uint8Array | null;
|
|
84
|
+
remoteContent: string | Uint8Array | null;
|
|
85
|
+
localHead?: UrlHeads;
|
|
86
|
+
remoteHead?: UrlHeads;
|
|
87
|
+
}
|
package/src/types/index.ts
CHANGED
package/src/types/snapshot.ts
CHANGED
|
@@ -52,6 +52,7 @@ export interface SyncResult {
|
|
|
52
52
|
directoriesChanged: number;
|
|
53
53
|
errors: SyncError[];
|
|
54
54
|
warnings: string[];
|
|
55
|
+
timings?: { [key: string]: number };
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
/**
|
|
@@ -63,26 +64,3 @@ export interface SyncError {
|
|
|
63
64
|
error: Error;
|
|
64
65
|
recoverable: boolean;
|
|
65
66
|
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Sync operation type
|
|
69
|
-
*/
|
|
70
|
-
export enum SyncOperation {
|
|
71
|
-
CREATE_FILE = "create_file",
|
|
72
|
-
UPDATE_FILE = "update_file",
|
|
73
|
-
DELETE_FILE = "delete_file",
|
|
74
|
-
MOVE_FILE = "move_file",
|
|
75
|
-
CREATE_DIRECTORY = "create_directory",
|
|
76
|
-
DELETE_DIRECTORY = "delete_directory",
|
|
77
|
-
MOVE_DIRECTORY = "move_directory",
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Pending sync operation
|
|
82
|
-
*/
|
|
83
|
-
export interface PendingSyncOperation {
|
|
84
|
-
operation: SyncOperation;
|
|
85
|
-
path: string;
|
|
86
|
-
newPath?: string;
|
|
87
|
-
priority: number;
|
|
88
|
-
}
|
package/src/utils/content.ts
CHANGED
|
@@ -13,16 +13,12 @@ export function isContentEqual(
|
|
|
13
13
|
if (typeof content1 === "string") {
|
|
14
14
|
return content1 === content2;
|
|
15
15
|
} else {
|
|
16
|
-
// Compare Uint8Array
|
|
16
|
+
// Compare Uint8Array using native Buffer.equals() for better performance
|
|
17
17
|
const buf1 = content1 as Uint8Array;
|
|
18
18
|
const buf2 = content2 as Uint8Array;
|
|
19
19
|
|
|
20
20
|
if (buf1.length !== buf2.length) return false;
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
if (buf1[i] !== buf2[i]) return false;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
return true;
|
|
22
|
+
return Buffer.from(buf1).equals(Buffer.from(buf2));
|
|
27
23
|
}
|
|
28
24
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { AutomergeUrl, Repo } from "@automerge/automerge-repo";
|
|
2
|
+
import { DirectoryDocument } from "../types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Find a file in the directory hierarchy by path
|
|
6
|
+
*/
|
|
7
|
+
export async function findFileInDirectoryHierarchy(
|
|
8
|
+
repo: Repo,
|
|
9
|
+
directoryUrl: AutomergeUrl,
|
|
10
|
+
filePath: string
|
|
11
|
+
): Promise<{ name: string; type: string; url: AutomergeUrl } | null> {
|
|
12
|
+
try {
|
|
13
|
+
const pathParts = filePath.split("/");
|
|
14
|
+
let currentDirUrl = directoryUrl;
|
|
15
|
+
|
|
16
|
+
// Navigate through directories to find the parent directory
|
|
17
|
+
for (let i = 0; i < pathParts.length - 1; i++) {
|
|
18
|
+
const dirName = pathParts[i];
|
|
19
|
+
const dirHandle = await repo.find<DirectoryDocument>(currentDirUrl);
|
|
20
|
+
const dirDoc = await dirHandle.doc();
|
|
21
|
+
|
|
22
|
+
if (!dirDoc) return null;
|
|
23
|
+
|
|
24
|
+
const subDirEntry = dirDoc.docs.find(
|
|
25
|
+
(entry: { name: string; type: string; url: AutomergeUrl }) =>
|
|
26
|
+
entry.name === dirName && entry.type === "folder"
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
if (!subDirEntry) return null;
|
|
30
|
+
currentDirUrl = subDirEntry.url;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Now look for the file in the final directory
|
|
34
|
+
const fileName = pathParts[pathParts.length - 1];
|
|
35
|
+
const finalDirHandle = await repo.find<DirectoryDocument>(currentDirUrl);
|
|
36
|
+
const finalDirDoc = await finalDirHandle.doc();
|
|
37
|
+
|
|
38
|
+
if (!finalDirDoc) return null;
|
|
39
|
+
|
|
40
|
+
const fileEntry = finalDirDoc.docs.find(
|
|
41
|
+
(entry: { name: string; type: string; url: AutomergeUrl }) =>
|
|
42
|
+
entry.name === fileName && entry.type === "file"
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
return fileEntry || null;
|
|
46
|
+
} catch (error) {
|
|
47
|
+
// Failed to find file in hierarchy
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
package/src/utils/fs.ts
CHANGED
|
@@ -3,6 +3,8 @@ import * as path from "path";
|
|
|
3
3
|
import * as crypto from "crypto";
|
|
4
4
|
import { glob } from "glob";
|
|
5
5
|
import * as mimeTypes from "mime-types";
|
|
6
|
+
import * as ignore from "ignore";
|
|
7
|
+
import * as A from "@automerge/automerge";
|
|
6
8
|
import { FileSystemEntry, FileType } from "../types";
|
|
7
9
|
import { isEnhancedTextFile } from "./mime-types";
|
|
8
10
|
|
|
@@ -94,12 +96,15 @@ export async function readFileContent(
|
|
|
94
96
|
*/
|
|
95
97
|
export async function writeFileContent(
|
|
96
98
|
filePath: string,
|
|
97
|
-
content: string | Uint8Array
|
|
99
|
+
content: string | A.ImmutableString | Uint8Array
|
|
98
100
|
): Promise<void> {
|
|
99
101
|
await ensureDirectoryExists(path.dirname(filePath));
|
|
100
102
|
|
|
101
103
|
if (typeof content === "string") {
|
|
102
104
|
await fs.writeFile(filePath, content, "utf8");
|
|
105
|
+
} else if (A.isImmutableString(content)) {
|
|
106
|
+
// Convert ImmutableString to regular string for filesystem operations
|
|
107
|
+
await fs.writeFile(filePath, content.toString(), "utf8");
|
|
103
108
|
} else {
|
|
104
109
|
await fs.writeFile(filePath, content);
|
|
105
110
|
}
|
|
@@ -125,7 +130,7 @@ export async function removePath(filePath: string): Promise<void> {
|
|
|
125
130
|
try {
|
|
126
131
|
const stats = await fs.stat(filePath);
|
|
127
132
|
if (stats.isDirectory()) {
|
|
128
|
-
await fs.
|
|
133
|
+
await fs.rm(filePath, { recursive: true });
|
|
129
134
|
} else {
|
|
130
135
|
await fs.unlink(filePath);
|
|
131
136
|
}
|
|
@@ -137,48 +142,23 @@ export async function removePath(filePath: string): Promise<void> {
|
|
|
137
142
|
}
|
|
138
143
|
|
|
139
144
|
/**
|
|
140
|
-
* Check if a path matches any of the exclude patterns
|
|
145
|
+
* Check if a path matches any of the exclude patterns using the ignore library
|
|
146
|
+
* Supports proper gitignore-style patterns (e.g., "node_modules", "*.tmp", ".git")
|
|
141
147
|
*/
|
|
142
148
|
function isExcluded(
|
|
143
149
|
filePath: string,
|
|
144
150
|
basePath: string,
|
|
145
151
|
excludePatterns: string[]
|
|
146
152
|
): boolean {
|
|
153
|
+
if (excludePatterns.length === 0) return false;
|
|
154
|
+
|
|
147
155
|
const relativePath = path.relative(basePath, filePath);
|
|
148
156
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
// Directory pattern like ".pushwork" or ".git"
|
|
153
|
-
if (
|
|
154
|
-
relativePath.startsWith(pattern) ||
|
|
155
|
-
relativePath.includes(`/${pattern}/`) ||
|
|
156
|
-
relativePath.includes(`\\${pattern}\\`)
|
|
157
|
-
) {
|
|
158
|
-
return true;
|
|
159
|
-
}
|
|
160
|
-
} else if (pattern.includes("*")) {
|
|
161
|
-
// Glob pattern like "*.tmp"
|
|
162
|
-
// CRITICAL FIX: Properly escape dots and anchor the pattern
|
|
163
|
-
// Convert glob to regex: *.tmp -> ^.*\.tmp$ (not /.*.tmp/ which matches "fuftmp.ts"!)
|
|
164
|
-
const regexPattern = pattern
|
|
165
|
-
.replace(/\./g, "\\.") // Escape dots first
|
|
166
|
-
.replace(/\*/g, ".*") // Then convert * to .*
|
|
167
|
-
.replace(/\?/g, "."); // And ? to single char
|
|
168
|
-
const regex = new RegExp(`^${regexPattern}$`); // Anchor to match full path
|
|
169
|
-
if (regex.test(relativePath)) {
|
|
170
|
-
return true;
|
|
171
|
-
}
|
|
172
|
-
} else {
|
|
173
|
-
// Exact directory name like "node_modules"
|
|
174
|
-
const parts = relativePath.split(/[/\\]/);
|
|
175
|
-
if (parts.includes(pattern)) {
|
|
176
|
-
return true;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
}
|
|
157
|
+
// Use the ignore library which implements proper .gitignore semantics
|
|
158
|
+
// This is the same library used by ESLint and other major tools
|
|
159
|
+
const ig = ignore.default().add(excludePatterns);
|
|
180
160
|
|
|
181
|
-
return
|
|
161
|
+
return ig.ignores(relativePath);
|
|
182
162
|
}
|
|
183
163
|
|
|
184
164
|
/**
|
|
@@ -192,33 +172,34 @@ export async function listDirectory(
|
|
|
192
172
|
const entries: FileSystemEntry[] = [];
|
|
193
173
|
|
|
194
174
|
try {
|
|
175
|
+
// Construct pattern using path.join for proper cross-platform handling
|
|
195
176
|
const pattern = recursive
|
|
196
177
|
? path.join(dirPath, "**/*")
|
|
197
178
|
: path.join(dirPath, "*");
|
|
179
|
+
|
|
180
|
+
// CRITICAL: glob expects forward slashes, even on Windows
|
|
181
|
+
// Convert backslashes to forward slashes for glob pattern
|
|
182
|
+
const normalizedPattern = pattern.replace(/\\/g, "/");
|
|
198
183
|
|
|
199
|
-
//
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
// Directory patterns
|
|
203
|
-
return `${pattern}/**`;
|
|
204
|
-
}
|
|
205
|
-
return pattern;
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
const paths = await glob(pattern, {
|
|
184
|
+
// Use glob to get all paths (with dot files)
|
|
185
|
+
// Note: We don't use glob's ignore option because it doesn't support gitignore semantics
|
|
186
|
+
const paths = await glob(normalizedPattern, {
|
|
209
187
|
dot: true,
|
|
210
|
-
ignore: ignorePatterns,
|
|
211
188
|
});
|
|
212
189
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
if (
|
|
218
|
-
|
|
190
|
+
// Parallelize all stat calls for better performance
|
|
191
|
+
const allEntries = await Promise.all(
|
|
192
|
+
paths.map(async (filePath) => {
|
|
193
|
+
// Filter using proper gitignore semantics from the ignore library
|
|
194
|
+
if (isExcluded(filePath, dirPath, excludePatterns)) {
|
|
195
|
+
return null;
|
|
219
196
|
}
|
|
220
|
-
|
|
221
|
-
|
|
197
|
+
return await getFileSystemEntry(filePath);
|
|
198
|
+
})
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
// Filter out null entries (excluded files or files that couldn't be read)
|
|
202
|
+
entries.push(...allEntries.filter((e): e is FileSystemEntry => e !== null));
|
|
222
203
|
} catch {
|
|
223
204
|
// Return empty array if directory doesn't exist or can't be read
|
|
224
205
|
}
|
|
@@ -256,10 +237,14 @@ export async function movePath(
|
|
|
256
237
|
* Calculate content hash for change detection
|
|
257
238
|
*/
|
|
258
239
|
export async function calculateContentHash(
|
|
259
|
-
content: string | Uint8Array
|
|
240
|
+
content: string | A.ImmutableString | Uint8Array
|
|
260
241
|
): Promise<string> {
|
|
261
242
|
const hash = crypto.createHash("sha256");
|
|
262
|
-
|
|
243
|
+
if (A.isImmutableString(content)) {
|
|
244
|
+
hash.update(content.toString());
|
|
245
|
+
} else {
|
|
246
|
+
hash.update(content);
|
|
247
|
+
}
|
|
263
248
|
return hash.digest("hex");
|
|
264
249
|
}
|
|
265
250
|
|
|
@@ -280,14 +265,40 @@ export function getFileExtension(filePath: string): string {
|
|
|
280
265
|
|
|
281
266
|
/**
|
|
282
267
|
* Normalize path separators for cross-platform compatibility
|
|
268
|
+
* Converts all path separators to forward slashes for consistent storage
|
|
283
269
|
*/
|
|
284
270
|
export function normalizePath(filePath: string): string {
|
|
285
271
|
return path.posix.normalize(filePath.replace(/\\/g, "/"));
|
|
286
272
|
}
|
|
287
273
|
|
|
274
|
+
/**
|
|
275
|
+
* Join paths and normalize separators for cross-platform compatibility
|
|
276
|
+
* Use this instead of string concatenation to ensure proper path handling on Windows
|
|
277
|
+
*/
|
|
278
|
+
export function joinAndNormalizePath(...paths: string[]): string {
|
|
279
|
+
// Use path.join to properly handle path construction (handles Windows drive letters, etc.)
|
|
280
|
+
const joined = path.join(...paths);
|
|
281
|
+
// Then normalize to forward slashes for consistent storage/comparison
|
|
282
|
+
return normalizePath(joined);
|
|
283
|
+
}
|
|
284
|
+
|
|
288
285
|
/**
|
|
289
286
|
* Get relative path from base directory
|
|
290
287
|
*/
|
|
291
288
|
export function getRelativePath(basePath: string, filePath: string): string {
|
|
292
289
|
return normalizePath(path.relative(basePath, filePath));
|
|
293
290
|
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Format a path as a relative path with proper prefix
|
|
294
|
+
* Ensures paths like "src" become "./src" for clarity
|
|
295
|
+
* Leaves absolute paths and paths already starting with . or .. unchanged
|
|
296
|
+
*/
|
|
297
|
+
export function formatRelativePath(filePath: string): string {
|
|
298
|
+
// Already starts with . or / - leave as-is
|
|
299
|
+
if (filePath.startsWith(".") || filePath.startsWith("/")) {
|
|
300
|
+
return filePath;
|
|
301
|
+
}
|
|
302
|
+
// Add ./ prefix for clarity
|
|
303
|
+
return `./${filePath}`;
|
|
304
|
+
}
|
package/src/utils/index.ts
CHANGED
package/src/utils/mime-types.ts
CHANGED
|
@@ -109,8 +109,9 @@ const FORCE_TEXT_EXTENSIONS = new Set([
|
|
|
109
109
|
* Get enhanced MIME type for file with custom dev file support
|
|
110
110
|
*/
|
|
111
111
|
export function getEnhancedMimeType(filePath: string): string {
|
|
112
|
-
const
|
|
113
|
-
const
|
|
112
|
+
const normalized = normalizePathSeparators(filePath);
|
|
113
|
+
const filename = normalized.split("/").pop() || "";
|
|
114
|
+
const extension = getFileExtension(normalized);
|
|
114
115
|
|
|
115
116
|
// Check custom definitions first (by extension)
|
|
116
117
|
if (extension && CUSTOM_MIME_TYPES[extension]) {
|
|
@@ -123,7 +124,7 @@ export function getEnhancedMimeType(filePath: string): string {
|
|
|
123
124
|
}
|
|
124
125
|
|
|
125
126
|
// Fall back to standard mime-types library
|
|
126
|
-
const standardMime = mimeTypes.lookup(
|
|
127
|
+
const standardMime = mimeTypes.lookup(normalized);
|
|
127
128
|
if (standardMime) {
|
|
128
129
|
return standardMime;
|
|
129
130
|
}
|
|
@@ -145,7 +146,14 @@ export function shouldForceAsText(filePath: string): boolean {
|
|
|
145
146
|
*/
|
|
146
147
|
function getFileExtension(filePath: string): string {
|
|
147
148
|
const match = filePath.match(/\.[^.]*$/);
|
|
148
|
-
return match ? match[0] : "";
|
|
149
|
+
return match ? match[0].toLowerCase() : "";
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Normalize path separators to forward slashes for cross-platform consistency
|
|
154
|
+
*/
|
|
155
|
+
function normalizePathSeparators(p: string): string {
|
|
156
|
+
return p.replace(/\\/g, "/");
|
|
149
157
|
}
|
|
150
158
|
|
|
151
159
|
/**
|