pushwork 1.0.5 → 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.
Files changed (204) hide show
  1. package/README.md +87 -335
  2. package/dist/.pushwork/automerge/3P/Dm3ekE2pmjGnWvDaG3vSR7ww98/snapshot/aa2349c94955ea561f698720142f9d884a6872d9f82dc332d578c216beb0df0e +0 -0
  3. package/dist/.pushwork/automerge/st/orage-adapter-id +1 -0
  4. package/dist/.pushwork/config.json +15 -0
  5. package/dist/.pushwork/snapshot.json +7 -0
  6. package/dist/cli.js +208 -213
  7. package/dist/cli.js.map +1 -1
  8. package/dist/commands.d.ts +51 -0
  9. package/dist/commands.d.ts.map +1 -0
  10. package/dist/commands.js +799 -0
  11. package/dist/commands.js.map +1 -0
  12. package/dist/core/change-detection.d.ts +2 -23
  13. package/dist/core/change-detection.d.ts.map +1 -1
  14. package/dist/core/change-detection.js +73 -115
  15. package/dist/core/change-detection.js.map +1 -1
  16. package/dist/{config/index.d.ts → core/config.d.ts} +13 -3
  17. package/dist/core/config.d.ts.map +1 -0
  18. package/dist/{config/index.js → core/config.js} +55 -73
  19. package/dist/core/config.js.map +1 -0
  20. package/dist/core/index.d.ts +1 -0
  21. package/dist/core/index.d.ts.map +1 -1
  22. package/dist/core/index.js +1 -1
  23. package/dist/core/index.js.map +1 -1
  24. package/dist/core/move-detection.d.ts +4 -3
  25. package/dist/core/move-detection.d.ts.map +1 -1
  26. package/dist/core/move-detection.js +8 -7
  27. package/dist/core/move-detection.js.map +1 -1
  28. package/dist/core/snapshot.d.ts +0 -4
  29. package/dist/core/snapshot.d.ts.map +1 -1
  30. package/dist/core/snapshot.js +2 -11
  31. package/dist/core/snapshot.js.map +1 -1
  32. package/dist/core/sync-engine.d.ts +5 -11
  33. package/dist/core/sync-engine.d.ts.map +1 -1
  34. package/dist/core/sync-engine.js +211 -308
  35. package/dist/core/sync-engine.js.map +1 -1
  36. package/dist/index.d.ts +0 -1
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +0 -6
  39. package/dist/index.js.map +1 -1
  40. package/dist/types/config.d.ts +24 -88
  41. package/dist/types/config.d.ts.map +1 -1
  42. package/dist/types/config.js +6 -0
  43. package/dist/types/config.js.map +1 -1
  44. package/dist/types/documents.d.ts +15 -2
  45. package/dist/types/documents.d.ts.map +1 -1
  46. package/dist/types/documents.js.map +1 -1
  47. package/dist/types/index.d.ts.map +1 -1
  48. package/dist/types/index.js +0 -3
  49. package/dist/types/index.js.map +1 -1
  50. package/dist/types/snapshot.d.ts +0 -21
  51. package/dist/types/snapshot.d.ts.map +1 -1
  52. package/dist/types/snapshot.js +0 -14
  53. package/dist/types/snapshot.js.map +1 -1
  54. package/dist/utils/content.d.ts.map +1 -1
  55. package/dist/utils/content.js +2 -6
  56. package/dist/utils/content.js.map +1 -1
  57. package/dist/utils/directory.d.ts +10 -0
  58. package/dist/utils/directory.d.ts.map +1 -0
  59. package/dist/utils/directory.js +37 -0
  60. package/dist/utils/directory.js.map +1 -0
  61. package/dist/utils/fs.d.ts +15 -2
  62. package/dist/utils/fs.d.ts.map +1 -1
  63. package/dist/utils/fs.js +54 -20
  64. package/dist/utils/fs.js.map +1 -1
  65. package/dist/utils/index.d.ts +1 -0
  66. package/dist/utils/index.d.ts.map +1 -1
  67. package/dist/utils/index.js +1 -3
  68. package/dist/utils/index.js.map +1 -1
  69. package/dist/utils/mime-types.d.ts.map +1 -1
  70. package/dist/utils/mime-types.js +11 -4
  71. package/dist/utils/mime-types.js.map +1 -1
  72. package/dist/utils/network-sync.d.ts +0 -6
  73. package/dist/utils/network-sync.d.ts.map +1 -1
  74. package/dist/utils/network-sync.js +55 -99
  75. package/dist/utils/network-sync.js.map +1 -1
  76. package/dist/utils/output.d.ts +129 -0
  77. package/dist/utils/output.d.ts.map +1 -0
  78. package/dist/utils/output.js +375 -0
  79. package/dist/utils/output.js.map +1 -0
  80. package/dist/utils/repo-factory.d.ts +2 -6
  81. package/dist/utils/repo-factory.d.ts.map +1 -1
  82. package/dist/utils/repo-factory.js +8 -31
  83. package/dist/utils/repo-factory.js.map +1 -1
  84. package/dist/utils/string-similarity.js +2 -2
  85. package/dist/utils/string-similarity.js.map +1 -1
  86. package/dist/utils/trace.d.ts +19 -0
  87. package/dist/utils/trace.d.ts.map +1 -0
  88. package/dist/utils/trace.js +68 -0
  89. package/dist/utils/trace.js.map +1 -0
  90. package/package.json +11 -11
  91. package/src/cli.ts +276 -308
  92. package/src/commands.ts +988 -0
  93. package/src/core/change-detection.ts +182 -240
  94. package/src/{config/index.ts → core/config.ts} +65 -82
  95. package/src/core/index.ts +1 -1
  96. package/src/core/move-detection.ts +10 -8
  97. package/src/core/snapshot.ts +2 -12
  98. package/src/core/sync-engine.ts +237 -427
  99. package/src/index.ts +0 -10
  100. package/src/types/config.ts +28 -93
  101. package/src/types/documents.ts +16 -2
  102. package/src/types/index.ts +0 -5
  103. package/src/types/snapshot.ts +0 -23
  104. package/src/utils/content.ts +2 -6
  105. package/src/utils/directory.ts +50 -0
  106. package/src/utils/fs.ts +58 -23
  107. package/src/utils/index.ts +1 -5
  108. package/src/utils/mime-types.ts +12 -4
  109. package/src/utils/network-sync.ts +79 -137
  110. package/src/utils/output.ts +450 -0
  111. package/src/utils/repo-factory.ts +13 -44
  112. package/src/utils/string-similarity.ts +2 -2
  113. package/src/utils/trace.ts +70 -0
  114. package/test/integration/exclude-patterns.test.ts +6 -15
  115. package/test/integration/fuzzer.test.ts +308 -391
  116. package/test/integration/init-sync.test.ts +89 -0
  117. package/test/integration/sync-deletion.test.ts +2 -61
  118. package/test/integration/sync-flow.test.ts +4 -24
  119. package/test/jest.setup.ts +34 -0
  120. package/test/unit/deletion-behavior.test.ts +3 -14
  121. package/test/unit/enhanced-mime-detection.test.ts +0 -22
  122. package/test/unit/snapshot.test.ts +2 -29
  123. package/test/unit/sync-convergence.test.ts +3 -198
  124. package/test/unit/sync-timing.test.ts +0 -44
  125. package/test/unit/utils.test.ts +0 -2
  126. package/tsconfig.json +3 -3
  127. package/bench/filesystem.bench.ts +0 -78
  128. package/bench/hashing.bench.ts +0 -60
  129. package/bench/move-detection.bench.ts +0 -130
  130. package/bench/runner.ts +0 -49
  131. package/dist/browser/browser-sync-engine.d.ts +0 -64
  132. package/dist/browser/browser-sync-engine.d.ts.map +0 -1
  133. package/dist/browser/browser-sync-engine.js +0 -303
  134. package/dist/browser/browser-sync-engine.js.map +0 -1
  135. package/dist/browser/filesystem-adapter.d.ts +0 -84
  136. package/dist/browser/filesystem-adapter.d.ts.map +0 -1
  137. package/dist/browser/filesystem-adapter.js +0 -413
  138. package/dist/browser/filesystem-adapter.js.map +0 -1
  139. package/dist/browser/index.d.ts +0 -36
  140. package/dist/browser/index.d.ts.map +0 -1
  141. package/dist/browser/index.js +0 -90
  142. package/dist/browser/index.js.map +0 -1
  143. package/dist/browser/types.d.ts +0 -70
  144. package/dist/browser/types.d.ts.map +0 -1
  145. package/dist/browser/types.js +0 -6
  146. package/dist/browser/types.js.map +0 -1
  147. package/dist/cli/commands.d.ts +0 -67
  148. package/dist/cli/commands.d.ts.map +0 -1
  149. package/dist/cli/commands.js +0 -794
  150. package/dist/cli/commands.js.map +0 -1
  151. package/dist/cli/index.d.ts +0 -2
  152. package/dist/cli/index.d.ts.map +0 -1
  153. package/dist/cli/index.js +0 -19
  154. package/dist/cli/index.js.map +0 -1
  155. package/dist/cli/output.d.ts +0 -75
  156. package/dist/cli/output.d.ts.map +0 -1
  157. package/dist/cli/output.js +0 -182
  158. package/dist/cli/output.js.map +0 -1
  159. package/dist/config/index.d.ts.map +0 -1
  160. package/dist/config/index.js.map +0 -1
  161. package/dist/config/remote-manager.d.ts +0 -65
  162. package/dist/config/remote-manager.d.ts.map +0 -1
  163. package/dist/config/remote-manager.js +0 -243
  164. package/dist/config/remote-manager.js.map +0 -1
  165. package/dist/core/isomorphic-snapshot.d.ts +0 -58
  166. package/dist/core/isomorphic-snapshot.d.ts.map +0 -1
  167. package/dist/core/isomorphic-snapshot.js +0 -204
  168. package/dist/core/isomorphic-snapshot.js.map +0 -1
  169. package/dist/platform/browser-filesystem.d.ts +0 -26
  170. package/dist/platform/browser-filesystem.d.ts.map +0 -1
  171. package/dist/platform/browser-filesystem.js +0 -91
  172. package/dist/platform/browser-filesystem.js.map +0 -1
  173. package/dist/platform/filesystem.d.ts +0 -29
  174. package/dist/platform/filesystem.d.ts.map +0 -1
  175. package/dist/platform/filesystem.js +0 -65
  176. package/dist/platform/filesystem.js.map +0 -1
  177. package/dist/platform/node-filesystem.d.ts +0 -21
  178. package/dist/platform/node-filesystem.d.ts.map +0 -1
  179. package/dist/platform/node-filesystem.js +0 -93
  180. package/dist/platform/node-filesystem.js.map +0 -1
  181. package/dist/utils/content-similarity.d.ts +0 -53
  182. package/dist/utils/content-similarity.d.ts.map +0 -1
  183. package/dist/utils/content-similarity.js +0 -155
  184. package/dist/utils/content-similarity.js.map +0 -1
  185. package/dist/utils/fs-browser.d.ts +0 -57
  186. package/dist/utils/fs-browser.d.ts.map +0 -1
  187. package/dist/utils/fs-browser.js +0 -311
  188. package/dist/utils/fs-browser.js.map +0 -1
  189. package/dist/utils/fs-node.d.ts +0 -53
  190. package/dist/utils/fs-node.d.ts.map +0 -1
  191. package/dist/utils/fs-node.js +0 -220
  192. package/dist/utils/fs-node.js.map +0 -1
  193. package/dist/utils/isomorphic.d.ts +0 -29
  194. package/dist/utils/isomorphic.d.ts.map +0 -1
  195. package/dist/utils/isomorphic.js +0 -139
  196. package/dist/utils/isomorphic.js.map +0 -1
  197. package/dist/utils/pure.d.ts +0 -25
  198. package/dist/utils/pure.d.ts.map +0 -1
  199. package/dist/utils/pure.js +0 -112
  200. package/dist/utils/pure.js.map +0 -1
  201. package/src/cli/commands.ts +0 -1030
  202. package/src/cli/index.ts +0 -2
  203. package/src/cli/output.ts +0 -244
  204. package/test/README-TESTING-GAPS.md +0 -174
@@ -1,794 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.setupCommandContext = setupCommandContext;
37
- exports.safeRepoShutdown = safeRepoShutdown;
38
- exports.init = init;
39
- exports.sync = sync;
40
- exports.diff = diff;
41
- exports.status = status;
42
- exports.log = log;
43
- exports.checkout = checkout;
44
- exports.clone = clone;
45
- exports.url = url;
46
- exports.commit = commit;
47
- exports.debug = debug;
48
- exports.ls = ls;
49
- exports.config = config;
50
- const path = __importStar(require("path"));
51
- const fs = __importStar(require("fs/promises"));
52
- const diffLib = __importStar(require("diff"));
53
- const core_1 = require("../core");
54
- const utils_1 = require("../utils");
55
- const config_1 = require("../config");
56
- const repo_factory_1 = require("../utils/repo-factory");
57
- const output_1 = require("./output");
58
- /**
59
- * Simple key transformation for debug output: snake_case -> Title Case
60
- */
61
- function prettifyKey(key) {
62
- return (key
63
- .split("_")
64
- .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
65
- .join(" ") + ":");
66
- }
67
- /**
68
- * Format timing value with percentage and optional metadata
69
- */
70
- function formatTimingValue(value, key, total, timings) {
71
- // Skip non-timing values
72
- if (key === "documents_to_sync")
73
- return "";
74
- if (key === "total")
75
- return `${(value / 1000).toFixed(3)}s`;
76
- const timeStr = `${(value / 1000).toFixed(3)}s`;
77
- const pctStr = `(${((value / total) * 100).toFixed(1)}%)`;
78
- return `${timeStr} ${pctStr}`;
79
- }
80
- /**
81
- * Validate that sync server options are used together
82
- */
83
- function validateSyncServerOptions(syncServer, syncServerStorageId) {
84
- const hasSyncServer = !!syncServer;
85
- const hasSyncServerStorageId = !!syncServerStorageId;
86
- if (hasSyncServer && !hasSyncServerStorageId) {
87
- throw new Error("--sync-server requires --sync-server-storage-id\nBoth arguments must be provided together.");
88
- }
89
- if (hasSyncServerStorageId && !hasSyncServer) {
90
- throw new Error("--sync-server-storage-id requires --sync-server\nBoth arguments must be provided together.");
91
- }
92
- }
93
- /**
94
- * Shared pre-action that ensures repository and sync engine are properly initialized
95
- * This function always works, with or without network connectivity
96
- */
97
- async function setupCommandContext(workingDir = process.cwd(), customSyncServer, customStorageId, enableNetwork = true) {
98
- const resolvedPath = path.resolve(workingDir);
99
- // Check if initialized
100
- const syncToolDir = path.join(resolvedPath, ".pushwork");
101
- if (!(await (0, utils_1.pathExists)(syncToolDir))) {
102
- throw new Error('Directory not initialized for sync. Run "pushwork init" first.');
103
- }
104
- // Load configuration
105
- const configManager = new config_1.ConfigManager(resolvedPath);
106
- const config = await configManager.getMerged();
107
- // Create repo with configurable network setting
108
- const repo = await (0, repo_factory_1.createRepo)(resolvedPath, {
109
- enableNetwork,
110
- syncServer: customSyncServer,
111
- syncServerStorageId: customStorageId,
112
- });
113
- // Create sync engine with configurable network sync
114
- const syncEngine = new core_1.SyncEngine(repo, resolvedPath, config.defaults.exclude_patterns, enableNetwork, config.sync_server_storage_id);
115
- return {
116
- repo,
117
- syncEngine,
118
- config,
119
- workingDir: resolvedPath,
120
- };
121
- }
122
- /**
123
- * Safely shutdown a repository with proper error handling
124
- */
125
- async function safeRepoShutdown(repo, context) {
126
- try {
127
- await repo.shutdown();
128
- }
129
- catch (shutdownError) {
130
- // WebSocket errors during shutdown are common and non-critical
131
- // Silently ignore them - they don't affect data integrity
132
- const errorMessage = shutdownError instanceof Error
133
- ? shutdownError.message
134
- : String(shutdownError);
135
- // Ignore WebSocket-related errors entirely
136
- if (errorMessage.includes("WebSocket") ||
137
- errorMessage.includes("connection was established") ||
138
- errorMessage.includes("was closed")) {
139
- // Silently ignore WebSocket shutdown errors
140
- return;
141
- }
142
- // Only warn about truly unexpected shutdown errors
143
- console.warn(`Warning: Repository shutdown failed${context ? ` (${context})` : ""}: ${shutdownError}`);
144
- }
145
- }
146
- /**
147
- * Initialize sync in a directory
148
- */
149
- async function init(targetPath, options = {}) {
150
- // Validate sync server options
151
- validateSyncServerOptions(options.syncServer, options.syncServerStorageId);
152
- const out = new output_1.Output();
153
- try {
154
- const resolvedPath = path.resolve(targetPath);
155
- out.task(`Initializing ${resolvedPath}`);
156
- await (0, utils_1.ensureDirectoryExists)(resolvedPath);
157
- // Check if already initialized
158
- const syncToolDir = path.join(resolvedPath, ".pushwork");
159
- if (await (0, utils_1.pathExists)(syncToolDir)) {
160
- out.error("Directory already initialized for sync");
161
- out.exit(1);
162
- }
163
- out.update("Creating sync directory");
164
- await (0, utils_1.ensureDirectoryExists)(syncToolDir);
165
- await (0, utils_1.ensureDirectoryExists)(path.join(syncToolDir, "automerge"));
166
- out.update("Setting up configuration");
167
- const configManager = new config_1.ConfigManager(resolvedPath);
168
- const defaultSyncServer = options.syncServer || "wss://sync3.automerge.org";
169
- const defaultStorageId = options.syncServerStorageId || "3760df37-a4c6-4f66-9ecd-732039a9385d";
170
- const config = {
171
- sync_server: defaultSyncServer,
172
- sync_server_storage_id: defaultStorageId,
173
- sync_enabled: true,
174
- defaults: {
175
- exclude_patterns: [".git", "node_modules", "*.tmp", ".pushwork"],
176
- large_file_threshold: "100MB",
177
- },
178
- diff: {
179
- show_binary: false,
180
- },
181
- sync: {
182
- move_detection_threshold: 0.8,
183
- prompt_threshold: 0.5,
184
- auto_sync: false,
185
- parallel_operations: 4,
186
- },
187
- };
188
- await configManager.save(config);
189
- out.update("Creating root directory");
190
- const repo = await (0, repo_factory_1.createRepo)(resolvedPath, {
191
- enableNetwork: true,
192
- syncServer: options.syncServer,
193
- syncServerStorageId: options.syncServerStorageId,
194
- });
195
- const rootDoc = {
196
- "@patchwork": { type: "folder" },
197
- docs: [],
198
- };
199
- const rootHandle = repo.create(rootDoc);
200
- out.update("Scanning existing files");
201
- const syncEngine = new core_1.SyncEngine(repo, resolvedPath, config.defaults.exclude_patterns, true, config.sync_server_storage_id);
202
- await syncEngine.setRootDirectoryUrl(rootHandle.url);
203
- const result = await syncEngine.sync(false);
204
- out.update("Writing to disk");
205
- await safeRepoShutdown(repo, "init");
206
- out.done();
207
- out.pair("Sync", defaultSyncServer);
208
- if (result.filesChanged > 0) {
209
- out.pair("Files", `${result.filesChanged} added`);
210
- }
211
- out.success("INITIALIZED", rootHandle.url);
212
- // Show timing breakdown if debug mode is enabled
213
- if (options.debug &&
214
- result.timings &&
215
- Object.keys(result.timings).length > 0) {
216
- const total = result.timings.total || 0;
217
- out.log("");
218
- out.special("TIMING", "");
219
- out.obj(result.timings, prettifyKey, (value, key) => formatTimingValue(value, key, total, result.timings));
220
- }
221
- out.log("");
222
- out.log(`Run 'pushwork sync' to start synchronizing`);
223
- }
224
- catch (error) {
225
- out.error("FAILED", "Initialization failed");
226
- out.log(` ${error}`);
227
- out.exit(1);
228
- }
229
- process.exit();
230
- }
231
- /**
232
- * Run bidirectional sync
233
- */
234
- async function sync(targetPath = ".", options) {
235
- const out = new output_1.Output();
236
- try {
237
- out.task("Syncing");
238
- const { repo, syncEngine, workingDir } = await setupCommandContext(targetPath);
239
- if (options.dryRun) {
240
- out.update("Analyzing changes");
241
- const preview = await syncEngine.previewChanges();
242
- out.done();
243
- if (preview.changes.length === 0 && preview.moves.length === 0) {
244
- out.info("No changes detected");
245
- out.log("Everything is already in sync");
246
- return;
247
- }
248
- out.info("CHANGES", "Pending");
249
- out.pair("Directory", workingDir);
250
- out.pair("Changes", preview.changes.length.toString());
251
- if (preview.moves.length > 0) {
252
- out.pair("Moves", preview.moves.length.toString());
253
- }
254
- out.log("");
255
- out.log("Files:");
256
- for (const change of preview.changes.slice(0, 10)) {
257
- const prefix = change.changeType === "local_only"
258
- ? "[local] "
259
- : change.changeType === "remote_only"
260
- ? "[remote] "
261
- : "[conflict]";
262
- out.log(` ${prefix} ${change.path}`);
263
- }
264
- if (preview.changes.length > 10) {
265
- out.log(` ... and ${preview.changes.length - 10} more`);
266
- }
267
- if (preview.moves.length > 0) {
268
- out.log("");
269
- out.log("Moves:");
270
- for (const move of preview.moves.slice(0, 5)) {
271
- out.log(` ${move.fromPath} → ${move.toPath}`);
272
- }
273
- if (preview.moves.length > 5) {
274
- out.log(` ... and ${preview.moves.length - 5} more`);
275
- }
276
- }
277
- out.log("");
278
- out.log("Run without --dry-run to apply these changes");
279
- }
280
- else {
281
- out.update("Synchronizing");
282
- const result = await syncEngine.sync(false);
283
- out.update("Writing to disk");
284
- await safeRepoShutdown(repo, "sync");
285
- if (result.success) {
286
- if (result.filesChanged === 0 && result.directoriesChanged === 0) {
287
- out.done();
288
- out.success("Already in sync");
289
- }
290
- else {
291
- out.done();
292
- out.success("SYNCED", `${result.filesChanged} file${result.filesChanged !== 1 ? "s" : ""} updated`);
293
- out.pair("Files", result.filesChanged.toString());
294
- }
295
- if (result.warnings.length > 0) {
296
- out.log("");
297
- out.warn("WARNINGS", `${result.warnings.length} warnings`);
298
- for (const warning of result.warnings.slice(0, 5)) {
299
- out.log(` ${warning}`);
300
- }
301
- if (result.warnings.length > 5) {
302
- out.log(` ... and ${result.warnings.length - 5} more`);
303
- }
304
- }
305
- // Show timing breakdown if debug mode is enabled
306
- if (options.debug &&
307
- result.timings &&
308
- Object.keys(result.timings).length > 0) {
309
- const total = result.timings.total || 0;
310
- out.log("");
311
- out.special("TIMING", "");
312
- out.obj(result.timings, prettifyKey, (value, key) => formatTimingValue(value, key, total, result.timings));
313
- }
314
- }
315
- else {
316
- out.done("partial", false);
317
- out.warn("PARTIAL", `${result.filesChanged} updated, ${result.errors.length} errors`);
318
- out.pair("Files", result.filesChanged.toString());
319
- out.pair("Errors", result.errors.length.toString());
320
- out.log("");
321
- for (const error of result.errors.slice(0, 5)) {
322
- out.error("ERROR", error.path);
323
- out.log(` ${error.error.message}`);
324
- out.log("");
325
- }
326
- if (result.errors.length > 5) {
327
- out.log(`... and ${result.errors.length - 5} more errors`);
328
- }
329
- }
330
- }
331
- }
332
- catch (error) {
333
- out.error("FAILED", "Sync failed");
334
- out.log(` ${error}`);
335
- out.exit(1);
336
- }
337
- process.exit();
338
- }
339
- /**
340
- * Show differences between local and remote
341
- */
342
- async function diff(targetPath = ".", options) {
343
- const out = new output_1.Output();
344
- try {
345
- out.task("Analyzing changes");
346
- const { repo, syncEngine } = await setupCommandContext(targetPath, undefined, undefined, false);
347
- const preview = await syncEngine.previewChanges();
348
- out.done();
349
- if (options.nameOnly) {
350
- for (const change of preview.changes) {
351
- console.log(change.path);
352
- }
353
- return;
354
- }
355
- if (preview.changes.length === 0) {
356
- out.success("No changes detected");
357
- await safeRepoShutdown(repo, "diff");
358
- out.exit();
359
- return;
360
- }
361
- out.warn(`${preview.changes.length} changes detected`);
362
- for (const change of preview.changes) {
363
- const prefix = change.changeType === "local_only"
364
- ? "[local] "
365
- : change.changeType === "remote_only"
366
- ? "[remote] "
367
- : "[conflict]";
368
- if (!options.tool) {
369
- try {
370
- // Get old content (from snapshot/remote)
371
- const oldContent = change.remoteContent || "";
372
- // Get new content (current local)
373
- const newContent = change.localContent || "";
374
- // Convert binary content to string representation if needed
375
- const oldText = typeof oldContent === "string"
376
- ? oldContent
377
- : `<binary content: ${oldContent.length} bytes>`;
378
- const newText = typeof newContent === "string"
379
- ? newContent
380
- : `<binary content: ${newContent.length} bytes>`;
381
- // Generate unified diff
382
- const diffResult = diffLib.createPatch(change.path, oldText, newText, "previous", "current");
383
- // Skip the header lines and process the diff
384
- const lines = diffResult.split("\n").slice(4); // Skip index, ===, ---, +++ lines
385
- if (lines.length === 0 || (lines.length === 1 && lines[0] === "")) {
386
- out.log(`${prefix}${change.path} (content identical)`, "cyan");
387
- continue;
388
- }
389
- // Extract first hunk header and show inline with path
390
- let firstHunk = "";
391
- let diffLines = lines;
392
- if (lines[0]?.startsWith("@@")) {
393
- firstHunk = ` ${lines[0]}`;
394
- diffLines = lines.slice(1);
395
- }
396
- out.log(`${prefix}${change.path}${firstHunk}`, "cyan");
397
- for (const line of diffLines) {
398
- if (line.startsWith("@@")) {
399
- // Additional hunk headers
400
- out.log(line, "dim");
401
- }
402
- else if (line.startsWith("+")) {
403
- // Added line
404
- out.log(line, "green");
405
- }
406
- else if (line.startsWith("-")) {
407
- // Removed line
408
- out.log(line, "red");
409
- }
410
- else if (line.startsWith(" ") || line === "") {
411
- // Context line or empty
412
- out.log(line, "dim");
413
- }
414
- }
415
- }
416
- catch (error) {
417
- out.log(`${prefix}${change.path} (diff error: ${error})`, "cyan");
418
- }
419
- }
420
- else {
421
- out.log(`${prefix} ${change.path}`);
422
- }
423
- }
424
- await safeRepoShutdown(repo, "diff");
425
- }
426
- catch (error) {
427
- out.error(`Diff failed: ${error}`);
428
- out.exit(1);
429
- }
430
- }
431
- /**
432
- * Show sync status
433
- */
434
- async function status(targetPath = ".", options = {}) {
435
- const out = new output_1.Output();
436
- try {
437
- out.task("Loading status");
438
- const { repo, syncEngine, workingDir, config } = await setupCommandContext(targetPath, undefined, undefined, false);
439
- const syncStatus = await syncEngine.getStatus();
440
- out.done();
441
- out.info("STATUS", workingDir);
442
- if (syncStatus.snapshot?.rootDirectoryUrl) {
443
- out.pair("URL", syncStatus.snapshot.rootDirectoryUrl);
444
- }
445
- if (syncStatus.snapshot) {
446
- const fileCount = syncStatus.snapshot.files.size;
447
- out.pair("Files", `${fileCount} tracked`);
448
- }
449
- out.pair("Sync", config?.sync_server || "wss://sync3.automerge.org");
450
- if (syncStatus.hasChanges) {
451
- out.pair("Changes", `${syncStatus.changeCount} pending`);
452
- out.log("");
453
- out.log("Run 'pushwork diff' to see changes");
454
- }
455
- else {
456
- out.pair("Status", "up to date");
457
- }
458
- await safeRepoShutdown(repo, "status");
459
- }
460
- catch (error) {
461
- out.error(`Status check failed: ${error}`);
462
- out.exit(1);
463
- }
464
- }
465
- /**
466
- * Show sync history
467
- */
468
- async function log(targetPath = ".", options) {
469
- const out = new output_1.Output();
470
- try {
471
- const { repo: logRepo, workingDir } = await setupCommandContext(targetPath, undefined, undefined, false);
472
- // TODO: Implement history tracking
473
- const snapshotPath = path.join(workingDir, ".pushwork", "snapshot.json");
474
- if (await (0, utils_1.pathExists)(snapshotPath)) {
475
- const stats = await fs.stat(snapshotPath);
476
- out.info("HISTORY", "Sync history (stub)");
477
- out.pair("Last sync", stats.mtime.toISOString());
478
- }
479
- else {
480
- out.info("No sync history found");
481
- }
482
- await safeRepoShutdown(logRepo, "log");
483
- }
484
- catch (error) {
485
- out.error(`Log failed: ${error}`);
486
- out.exit(1);
487
- }
488
- }
489
- /**
490
- * Checkout/restore from previous sync
491
- */
492
- async function checkout(syncId, targetPath = ".", options) {
493
- const out = new output_1.Output();
494
- try {
495
- const { workingDir } = await setupCommandContext(targetPath);
496
- // TODO: Implement checkout functionality
497
- out.warn("NOT IMPLEMENTED", "Checkout not yet implemented");
498
- out.pair("Sync ID", syncId);
499
- out.pair("Path", workingDir);
500
- }
501
- catch (error) {
502
- out.error(`Checkout failed: ${error}`);
503
- out.exit(1);
504
- }
505
- }
506
- /**
507
- * Clone an existing synced directory from an AutomergeUrl
508
- */
509
- async function clone(rootUrl, targetPath, options) {
510
- // Validate sync server options
511
- validateSyncServerOptions(options.syncServer, options.syncServerStorageId);
512
- const out = new output_1.Output();
513
- try {
514
- const resolvedPath = path.resolve(targetPath);
515
- out.task(`Cloning ${rootUrl}`);
516
- // Check if directory exists and handle --force
517
- if (await (0, utils_1.pathExists)(resolvedPath)) {
518
- const files = await fs.readdir(resolvedPath);
519
- if (files.length > 0 && !options.force) {
520
- out.error("Target directory is not empty. Use --force to overwrite");
521
- out.exit(1);
522
- }
523
- }
524
- else {
525
- await (0, utils_1.ensureDirectoryExists)(resolvedPath);
526
- }
527
- // Check if already initialized
528
- const syncToolDir = path.join(resolvedPath, ".pushwork");
529
- if (await (0, utils_1.pathExists)(syncToolDir)) {
530
- if (!options.force) {
531
- out.error("Directory already initialized. Use --force to overwrite");
532
- out.exit(1);
533
- }
534
- await fs.rm(syncToolDir, { recursive: true, force: true });
535
- }
536
- out.update("Creating sync directory");
537
- await (0, utils_1.ensureDirectoryExists)(syncToolDir);
538
- await (0, utils_1.ensureDirectoryExists)(path.join(syncToolDir, "automerge"));
539
- out.update("Setting up configuration");
540
- const configManager = new config_1.ConfigManager(resolvedPath);
541
- const defaultSyncServer = options.syncServer || "wss://sync3.automerge.org";
542
- const defaultStorageId = options.syncServerStorageId || "3760df37-a4c6-4f66-9ecd-732039a9385d";
543
- const config = {
544
- sync_server: defaultSyncServer,
545
- sync_server_storage_id: defaultStorageId,
546
- sync_enabled: true,
547
- defaults: {
548
- exclude_patterns: [".git", "node_modules", "*.tmp", ".pushwork"],
549
- large_file_threshold: "100MB",
550
- },
551
- diff: {
552
- show_binary: false,
553
- },
554
- sync: {
555
- move_detection_threshold: 0.8,
556
- prompt_threshold: 0.5,
557
- auto_sync: false,
558
- parallel_operations: 4,
559
- },
560
- };
561
- await configManager.save(config);
562
- out.update("Connecting to sync server");
563
- const repo = await (0, repo_factory_1.createRepo)(resolvedPath, {
564
- enableNetwork: true,
565
- syncServer: options.syncServer,
566
- syncServerStorageId: options.syncServerStorageId,
567
- });
568
- out.update("Downloading files");
569
- const syncEngine = new core_1.SyncEngine(repo, resolvedPath, config.defaults.exclude_patterns, true, defaultStorageId);
570
- await syncEngine.setRootDirectoryUrl(rootUrl);
571
- const result = await syncEngine.sync(false);
572
- out.update("Writing to disk");
573
- await safeRepoShutdown(repo, "clone");
574
- out.done();
575
- out.pair("Path", resolvedPath);
576
- out.pair("Files", `${result.filesChanged} downloaded`);
577
- out.pair("Sync", defaultSyncServer);
578
- out.success("CLONED", rootUrl);
579
- }
580
- catch (error) {
581
- out.error("FAILED", "Clone failed");
582
- out.log(` ${error}`);
583
- out.exit(1);
584
- }
585
- process.exit();
586
- }
587
- /**
588
- * Get the root URL for the current pushwork repository
589
- */
590
- async function url(targetPath = ".", options = {}) {
591
- const out = new output_1.Output();
592
- try {
593
- const resolvedPath = path.resolve(targetPath);
594
- const syncToolDir = path.join(resolvedPath, ".pushwork");
595
- if (!(await (0, utils_1.pathExists)(syncToolDir))) {
596
- out.error("Directory not initialized for sync");
597
- out.exit(1);
598
- }
599
- const snapshotPath = path.join(syncToolDir, "snapshot.json");
600
- if (!(await (0, utils_1.pathExists)(snapshotPath))) {
601
- out.error("No snapshot found");
602
- out.exit(1);
603
- }
604
- const snapshotData = await fs.readFile(snapshotPath, "utf-8");
605
- const snapshot = JSON.parse(snapshotData);
606
- if (snapshot.rootDirectoryUrl) {
607
- // Output just the URL for easy use in scripts
608
- console.log(snapshot.rootDirectoryUrl);
609
- }
610
- else {
611
- out.error("No root URL found in snapshot");
612
- out.exit(1);
613
- }
614
- }
615
- catch (error) {
616
- out.error(`Failed to get URL: ${error}`);
617
- out.exit(1);
618
- }
619
- }
620
- async function commit(targetPath, options = {}) {
621
- const out = new output_1.Output();
622
- try {
623
- out.task("Committing local changes");
624
- const { repo, syncEngine } = await setupCommandContext(targetPath, undefined, undefined, false);
625
- const result = await syncEngine.commitLocal(options.dryRun || false);
626
- await safeRepoShutdown(repo, "commit");
627
- out.done();
628
- if (result.errors.length > 0) {
629
- out.error("ERRORS", `${result.errors.length} errors`);
630
- result.errors.forEach((error) => {
631
- out.log(` ${error.path}: ${error.error.message}`);
632
- });
633
- out.exit(1);
634
- }
635
- out.success("COMMITTED", `${result.filesChanged} files committed`);
636
- out.pair("Files", result.filesChanged.toString());
637
- out.pair("Directories", result.directoriesChanged.toString());
638
- if (result.warnings.length > 0) {
639
- out.log("");
640
- out.warn("WARNINGS", `${result.warnings.length} warnings`);
641
- result.warnings.forEach((warning) => out.log(` ${warning}`));
642
- }
643
- }
644
- catch (error) {
645
- out.error(`Commit failed: ${error}`);
646
- out.exit(1);
647
- }
648
- process.exit();
649
- }
650
- /**
651
- * Debug command to inspect internal document state
652
- */
653
- async function debug(targetPath = ".", options = {}) {
654
- const out = new output_1.Output();
655
- try {
656
- out.task("Loading debug info");
657
- const { repo, syncEngine, workingDir } = await setupCommandContext(targetPath, undefined, undefined, false);
658
- const debugStatus = await syncEngine.getStatus();
659
- out.done("done");
660
- out.info("DEBUG", workingDir);
661
- if (debugStatus.snapshot?.rootDirectoryUrl) {
662
- out.pair("URL", debugStatus.snapshot.rootDirectoryUrl);
663
- try {
664
- const rootHandle = await repo.find(debugStatus.snapshot.rootDirectoryUrl);
665
- const rootDoc = await rootHandle.doc();
666
- if (rootDoc) {
667
- out.pair("Entries", rootDoc.docs.length.toString());
668
- if (rootDoc.lastSyncAt) {
669
- const lastSyncDate = new Date(rootDoc.lastSyncAt);
670
- out.pair("Last sync", lastSyncDate.toISOString());
671
- }
672
- if (options.verbose) {
673
- out.log("");
674
- out.log("Document:");
675
- out.log(JSON.stringify(rootDoc, null, 2));
676
- out.log("");
677
- out.log("Heads:");
678
- out.log(JSON.stringify(rootHandle.heads(), null, 2));
679
- }
680
- }
681
- }
682
- catch (error) {
683
- out.warn(`Error loading root document: ${error}`);
684
- }
685
- }
686
- if (debugStatus.snapshot) {
687
- out.pair("Files", debugStatus.snapshot.files.size.toString());
688
- out.pair("Directories", debugStatus.snapshot.directories.size.toString());
689
- if (options.verbose) {
690
- out.log("");
691
- out.log("All tracked files:");
692
- debugStatus.snapshot.files.forEach((entry, filePath) => {
693
- out.log(` ${filePath} -> ${entry.url}`);
694
- });
695
- }
696
- }
697
- await safeRepoShutdown(repo, "debug");
698
- }
699
- catch (error) {
700
- out.error(`Debug failed: ${error}`);
701
- out.exit(1);
702
- }
703
- }
704
- /**
705
- * List tracked files
706
- */
707
- async function ls(targetPath = ".", options = {}) {
708
- const out = new output_1.Output();
709
- try {
710
- const { repo, syncEngine } = await setupCommandContext(targetPath, undefined, undefined, false);
711
- const syncStatus = await syncEngine.getStatus();
712
- if (!syncStatus.snapshot) {
713
- out.error("No snapshot found");
714
- await safeRepoShutdown(repo, "ls");
715
- out.exit(1);
716
- return;
717
- }
718
- const files = Array.from(syncStatus.snapshot.files.entries()).sort(([pathA], [pathB]) => pathA.localeCompare(pathB));
719
- if (files.length === 0) {
720
- out.info("No tracked files");
721
- await safeRepoShutdown(repo, "ls");
722
- return;
723
- }
724
- if (options.long) {
725
- // Long format with URLs
726
- for (const [filePath, entry] of files) {
727
- const url = entry?.url || "unknown";
728
- console.log(`${filePath} -> ${url}`);
729
- }
730
- }
731
- else {
732
- // Simple list
733
- for (const [filePath] of files) {
734
- console.log(filePath);
735
- }
736
- }
737
- await safeRepoShutdown(repo, "ls");
738
- }
739
- catch (error) {
740
- out.error(`List failed: ${error}`);
741
- out.exit(1);
742
- }
743
- }
744
- /**
745
- * View or edit configuration
746
- */
747
- async function config(targetPath = ".", options = {}) {
748
- const out = new output_1.Output();
749
- try {
750
- const resolvedPath = path.resolve(targetPath);
751
- const syncToolDir = path.join(resolvedPath, ".pushwork");
752
- if (!(await (0, utils_1.pathExists)(syncToolDir))) {
753
- out.error("Directory not initialized for sync");
754
- out.exit(1);
755
- }
756
- const configManager = new config_1.ConfigManager(resolvedPath);
757
- const config = await configManager.getMerged();
758
- if (options.list) {
759
- // List all configuration
760
- out.info("CONFIGURATION", "Full configuration");
761
- out.log(JSON.stringify(config, null, 2));
762
- }
763
- else if (options.get) {
764
- // Get specific config value
765
- const keys = options.get.split(".");
766
- let value = config;
767
- for (const key of keys) {
768
- value = value?.[key];
769
- }
770
- if (value !== undefined) {
771
- console.log(typeof value === "object" ? JSON.stringify(value, null, 2) : value);
772
- }
773
- else {
774
- out.error(`Config key not found: ${options.get}`);
775
- out.exit(1);
776
- }
777
- }
778
- else {
779
- // Show basic config info
780
- out.info("CONFIGURATION", resolvedPath);
781
- out.pair("Sync server", config.sync_server || "default");
782
- out.pair("Sync enabled", config.sync_enabled ? "yes" : "no");
783
- out.pair("Exclusions", config.defaults.exclude_patterns.length.toString());
784
- out.log("");
785
- out.log("Use --list to see full configuration");
786
- }
787
- }
788
- catch (error) {
789
- out.error(`Config failed: ${error}`);
790
- out.exit(1);
791
- }
792
- }
793
- // TODO: Add push and pull commands later
794
- //# sourceMappingURL=commands.js.map