pushwork 1.1.6 → 1.2.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.
- package/ARCHITECTURE-ACCORDING-TO-CLAUDE.md +17 -11
- package/CLAUDE.md +46 -1
- package/README.md +18 -4
- package/dist/cli.js +45 -4
- package/dist/cli.js.map +1 -1
- package/dist/commands.d.ts +1 -0
- package/dist/commands.d.ts.map +1 -1
- package/dist/commands.js +151 -38
- package/dist/commands.js.map +1 -1
- package/dist/core/change-detection.js +2 -2
- package/dist/core/change-detection.js.map +1 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +3 -0
- package/dist/core/config.js.map +1 -1
- package/dist/core/move-detection.d.ts.map +1 -1
- package/dist/core/move-detection.js +4 -1
- package/dist/core/move-detection.js.map +1 -1
- package/dist/core/sync-engine.d.ts +24 -4
- package/dist/core/sync-engine.d.ts.map +1 -1
- package/dist/core/sync-engine.js +85 -50
- package/dist/core/sync-engine.js.map +1 -1
- package/dist/types/config.d.ts +4 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +2 -1
- package/dist/types/config.js.map +1 -1
- package/dist/utils/content.js +1 -1
- package/dist/utils/content.js.map +1 -1
- package/dist/utils/network-sync.d.ts +1 -2
- package/dist/utils/network-sync.d.ts.map +1 -1
- package/dist/utils/network-sync.js +76 -7
- package/dist/utils/network-sync.js.map +1 -1
- package/dist/utils/output.js +7 -7
- package/dist/utils/output.js.map +1 -1
- package/dist/utils/repo-factory.d.ts +11 -3
- package/dist/utils/repo-factory.d.ts.map +1 -1
- package/dist/utils/repo-factory.js +112 -8
- package/dist/utils/repo-factory.js.map +1 -1
- package/flake.lock +128 -0
- package/flake.nix +66 -0
- package/package.json +98 -97
- package/scripts/roundtrip-test.sh +35 -0
- package/src/cli.ts +53 -6
- package/src/commands.ts +150 -26
- package/src/core/change-detection.ts +2 -2
- package/src/core/config.ts +4 -0
- package/src/core/move-detection.ts +3 -1
- package/src/core/sync-engine.ts +99 -59
- package/src/types/config.ts +4 -0
- package/src/utils/content.ts +1 -1
- package/src/utils/network-sync.ts +92 -8
- package/src/utils/output.ts +7 -7
- package/src/utils/repo-factory.ts +124 -10
- package/test/integration/clone-test.sh +0 -0
- package/test/integration/conflict-resolution-test.sh +0 -0
- package/test/integration/deletion-behavior-test.sh +0 -0
- package/test/integration/deletion-sync-test-simple.sh +0 -0
- package/test/integration/deletion-sync-test.sh +0 -0
- package/test/integration/full-integration-test.sh +0 -0
- package/test/integration/manual-sync-test.sh +0 -0
- package/test/integration/sub-flag.test.ts +187 -0
- package/test/run-tests.sh +0 -0
- package/test/unit/artifact-nuke-reinsert.test.ts +80 -0
- package/test/unit/network-sync-sub.test.ts +144 -0
- package/test/unit/repo-factory.test.ts +111 -0
- package/test/unit/subduction-config.test.ts +69 -0
- package/dist/cli/commands.d.ts +0 -71
- package/dist/cli/commands.d.ts.map +0 -1
- package/dist/cli/commands.js +0 -794
- 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 +0 -71
- package/dist/config/index.d.ts.map +0 -1
- package/dist/config/index.js +0 -314
- package/dist/config/index.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/node-polyfills.d.ts +0 -9
- package/dist/utils/node-polyfills.d.ts.map +0 -1
- package/dist/utils/node-polyfills.js +0 -9
- package/dist/utils/node-polyfills.js.map +0 -1
|
@@ -34,22 +34,126 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.createRepo = createRepo;
|
|
37
|
-
const automerge_repo_1 = require("@automerge/automerge-repo");
|
|
38
37
|
const automerge_repo_storage_nodefs_1 = require("@automerge/automerge-repo-storage-nodefs");
|
|
39
|
-
const
|
|
38
|
+
const fs = __importStar(require("fs/promises"));
|
|
40
39
|
const path = __importStar(require("path"));
|
|
41
40
|
/**
|
|
42
|
-
*
|
|
41
|
+
* Perform a real ESM dynamic import that tsc won't rewrite to require().
|
|
42
|
+
*
|
|
43
|
+
* TypeScript with `"module": "commonjs"` compiles `await import("x")` to
|
|
44
|
+
* `require("x")`, which resolves CJS entries instead of ESM entries. The
|
|
45
|
+
* Wasm module instance is different between the CJS and ESM module graphs,
|
|
46
|
+
* so initializing via CJS require() doesn't help the ESM /slim imports
|
|
47
|
+
* inside automerge-repo.
|
|
48
|
+
*
|
|
49
|
+
* This helper uses `new Function` to create a real `import()` expression
|
|
50
|
+
* that Node.js evaluates as ESM, sharing the same module graph as the
|
|
51
|
+
* Repo's internal imports.
|
|
43
52
|
*/
|
|
44
|
-
|
|
53
|
+
const dynamicImport = new Function("specifier", "return import(specifier)");
|
|
54
|
+
/**
|
|
55
|
+
* Initialize the Subduction Wasm module and return the Repo constructor.
|
|
56
|
+
*
|
|
57
|
+
* The Repo constructor calls set_subduction_logger() and new MemorySigner()
|
|
58
|
+
* from @automerge/automerge-subduction/slim, which require the Wasm module
|
|
59
|
+
* to be initialized first. automerge-repo exports initSubduction() to
|
|
60
|
+
* handle this — it dynamically imports the non-/slim entry (which
|
|
61
|
+
* auto-initializes the Wasm as a side effect).
|
|
62
|
+
*
|
|
63
|
+
* Both the Repo and initSubduction must be loaded via ESM dynamic import()
|
|
64
|
+
* so they share the same module graph as the Repo's internal /slim imports.
|
|
65
|
+
*/
|
|
66
|
+
let cachedRepoClass;
|
|
67
|
+
async function getRepoClass() {
|
|
68
|
+
if (cachedRepoClass)
|
|
69
|
+
return cachedRepoClass;
|
|
70
|
+
// Import Repo and initialize Subduction Wasm via automerge-repo's
|
|
71
|
+
// initSubduction() helper. This must happen before new Repo() because
|
|
72
|
+
// the constructor calls set_subduction_logger() and new MemorySigner()
|
|
73
|
+
// which require the Wasm module to be ready.
|
|
74
|
+
//
|
|
75
|
+
// Both imports use the ESM dynamic import wrapper so they share the
|
|
76
|
+
// same module graph as the Repo's internal /slim imports.
|
|
77
|
+
const repoMod = await dynamicImport("@automerge/automerge-repo");
|
|
78
|
+
await repoMod.initSubduction();
|
|
79
|
+
cachedRepoClass = repoMod.Repo;
|
|
80
|
+
return cachedRepoClass;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Scan a directory tree for 0-byte files, which indicate incomplete writes
|
|
84
|
+
* from a previous run (process exited before storage flushed). Returns true
|
|
85
|
+
* if any are found.
|
|
86
|
+
*/
|
|
87
|
+
async function hasCorruptStorage(dir) {
|
|
88
|
+
try {
|
|
89
|
+
await fs.access(dir);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
95
|
+
for (const entry of entries) {
|
|
96
|
+
const fullPath = path.join(dir, entry.name);
|
|
97
|
+
if (entry.isDirectory()) {
|
|
98
|
+
if (await hasCorruptStorage(fullPath))
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
else if (entry.isFile()) {
|
|
102
|
+
const stat = await fs.stat(fullPath);
|
|
103
|
+
if (stat.size === 0)
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Create an Automerge repository with configuration-based setup.
|
|
111
|
+
*
|
|
112
|
+
* When `sub` is true, uses the Subduction sync backend built into
|
|
113
|
+
* automerge-repo. The Repo manages its own SubductionSource internally —
|
|
114
|
+
* we just pass `subductionWebsocketEndpoints` and the Repo handles
|
|
115
|
+
* connection management, sync, and retries.
|
|
116
|
+
*
|
|
117
|
+
* When `sub` is false (default), uses the traditional WebSocket network
|
|
118
|
+
* adapter for sync via the automerge sync server.
|
|
119
|
+
*/
|
|
120
|
+
async function createRepo(workingDir, config, sub = false) {
|
|
121
|
+
const RepoClass = await getRepoClass();
|
|
45
122
|
const syncToolDir = path.join(workingDir, ".pushwork");
|
|
46
|
-
const
|
|
123
|
+
const automergeDir = path.join(syncToolDir, "automerge");
|
|
124
|
+
// Detect and recover from corrupt local storage (0-byte files left by
|
|
125
|
+
// incomplete writes from a previous run). Wipe the cache so the Repo
|
|
126
|
+
// hydrates cleanly from the sync server.
|
|
127
|
+
if (await hasCorruptStorage(automergeDir)) {
|
|
128
|
+
console.warn("[pushwork] Corrupt local storage detected, clearing cache...");
|
|
129
|
+
await fs.rm(automergeDir, { recursive: true, force: true });
|
|
130
|
+
await fs.mkdir(automergeDir, { recursive: true });
|
|
131
|
+
}
|
|
132
|
+
const storage = new automerge_repo_storage_nodefs_1.NodeFSStorageAdapter(automergeDir);
|
|
133
|
+
if (sub) {
|
|
134
|
+
const endpoints = [];
|
|
135
|
+
if (config.sync_enabled && config.sync_server) {
|
|
136
|
+
endpoints.push(config.sync_server);
|
|
137
|
+
}
|
|
138
|
+
return new RepoClass({
|
|
139
|
+
storage,
|
|
140
|
+
subductionWebsocketEndpoints: endpoints,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
// Default: WebSocket sync adapter
|
|
47
144
|
const repoConfig = { storage };
|
|
48
|
-
// Add network adapter only if sync is enabled and server is configured
|
|
49
145
|
if (config.sync_enabled && config.sync_server) {
|
|
50
|
-
|
|
146
|
+
// Load the WebSocket adapter via ESM dynamic import to stay in the
|
|
147
|
+
// same module graph as the Repo.
|
|
148
|
+
const wsMod = await dynamicImport("@automerge/automerge-repo-network-websocket");
|
|
149
|
+
// The websocket adapter package (subduction.8) hasn't updated its
|
|
150
|
+
// NetworkAdapter base-class types to match the repo's new
|
|
151
|
+
// NetworkAdapterInterface (which added state() and stricter
|
|
152
|
+
// EventEmitter generics). At runtime the adapter has all required
|
|
153
|
+
// methods; this is purely a declaration mismatch.
|
|
154
|
+
const networkAdapter = new wsMod.BrowserWebSocketClientAdapter(config.sync_server);
|
|
51
155
|
repoConfig.network = [networkAdapter];
|
|
52
156
|
}
|
|
53
|
-
return new
|
|
157
|
+
return new RepoClass(repoConfig);
|
|
54
158
|
}
|
|
55
159
|
//# sourceMappingURL=repo-factory.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repo-factory.js","sourceRoot":"","sources":["../../src/utils/repo-factory.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"repo-factory.js","sourceRoot":"","sources":["../../src/utils/repo-factory.ts"],"names":[],"mappings":"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"}
|
package/flake.lock
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
{
|
|
2
|
+
"nodes": {
|
|
3
|
+
"command-utils": {
|
|
4
|
+
"inputs": {
|
|
5
|
+
"flake-utils": "flake-utils",
|
|
6
|
+
"nixpkgs": "nixpkgs"
|
|
7
|
+
},
|
|
8
|
+
"locked": {
|
|
9
|
+
"lastModified": 1769220798,
|
|
10
|
+
"narHash": "sha256-ulD8bbh5eV4rUH61JC4gS8Ik0R2hBEEyCom3f8w2vXE=",
|
|
11
|
+
"ref": "refs/heads/main",
|
|
12
|
+
"rev": "6c72a70e0241a5af26ba664ab63f3e2d89c45cd0",
|
|
13
|
+
"revCount": 5,
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://codeberg.org/expede/nix-command-utils"
|
|
16
|
+
},
|
|
17
|
+
"original": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://codeberg.org/expede/nix-command-utils"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"flake-utils": {
|
|
23
|
+
"inputs": {
|
|
24
|
+
"systems": "systems"
|
|
25
|
+
},
|
|
26
|
+
"locked": {
|
|
27
|
+
"lastModified": 1709126324,
|
|
28
|
+
"narHash": "sha256-q6EQdSeUZOG26WelxqkmR7kArjgWCdw5sfJVHPH/7j8=",
|
|
29
|
+
"owner": "numtide",
|
|
30
|
+
"repo": "flake-utils",
|
|
31
|
+
"rev": "d465f4819400de7c8d874d50b982301f28a84605",
|
|
32
|
+
"type": "github"
|
|
33
|
+
},
|
|
34
|
+
"original": {
|
|
35
|
+
"id": "flake-utils",
|
|
36
|
+
"type": "indirect"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"flake-utils_2": {
|
|
40
|
+
"inputs": {
|
|
41
|
+
"systems": "systems_2"
|
|
42
|
+
},
|
|
43
|
+
"locked": {
|
|
44
|
+
"lastModified": 1731533236,
|
|
45
|
+
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
|
46
|
+
"owner": "numtide",
|
|
47
|
+
"repo": "flake-utils",
|
|
48
|
+
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
|
49
|
+
"type": "github"
|
|
50
|
+
},
|
|
51
|
+
"original": {
|
|
52
|
+
"owner": "numtide",
|
|
53
|
+
"repo": "flake-utils",
|
|
54
|
+
"type": "github"
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"nixpkgs": {
|
|
58
|
+
"locked": {
|
|
59
|
+
"lastModified": 1769089682,
|
|
60
|
+
"narHash": "sha256-9yA/LIuAVQq0lXelrZPjLuLVuZdm03p8tfmHhnDIkms=",
|
|
61
|
+
"owner": "NixOS",
|
|
62
|
+
"repo": "nixpkgs",
|
|
63
|
+
"rev": "078d69f03934859a181e81ba987c2bb033eebfc5",
|
|
64
|
+
"type": "github"
|
|
65
|
+
},
|
|
66
|
+
"original": {
|
|
67
|
+
"id": "nixpkgs",
|
|
68
|
+
"ref": "nixos-25.11",
|
|
69
|
+
"type": "indirect"
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"nixpkgs_2": {
|
|
73
|
+
"locked": {
|
|
74
|
+
"lastModified": 1775002709,
|
|
75
|
+
"narHash": "sha256-d3Yx83vSrN+2z/loBh4mJpyRqr9aAJqlke4TkpFmRJA=",
|
|
76
|
+
"owner": "nixos",
|
|
77
|
+
"repo": "nixpkgs",
|
|
78
|
+
"rev": "bcd464ccd2a1a7cd09aa2f8d4ffba83b761b1d0e",
|
|
79
|
+
"type": "github"
|
|
80
|
+
},
|
|
81
|
+
"original": {
|
|
82
|
+
"owner": "nixos",
|
|
83
|
+
"ref": "nixos-25.11",
|
|
84
|
+
"repo": "nixpkgs",
|
|
85
|
+
"type": "github"
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
"root": {
|
|
89
|
+
"inputs": {
|
|
90
|
+
"command-utils": "command-utils",
|
|
91
|
+
"flake-utils": "flake-utils_2",
|
|
92
|
+
"nixpkgs": "nixpkgs_2"
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
"systems": {
|
|
96
|
+
"locked": {
|
|
97
|
+
"lastModified": 1681028828,
|
|
98
|
+
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
99
|
+
"owner": "nix-systems",
|
|
100
|
+
"repo": "default",
|
|
101
|
+
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
102
|
+
"type": "github"
|
|
103
|
+
},
|
|
104
|
+
"original": {
|
|
105
|
+
"owner": "nix-systems",
|
|
106
|
+
"repo": "default",
|
|
107
|
+
"type": "github"
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
"systems_2": {
|
|
111
|
+
"locked": {
|
|
112
|
+
"lastModified": 1681028828,
|
|
113
|
+
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
114
|
+
"owner": "nix-systems",
|
|
115
|
+
"repo": "default",
|
|
116
|
+
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
117
|
+
"type": "github"
|
|
118
|
+
},
|
|
119
|
+
"original": {
|
|
120
|
+
"owner": "nix-systems",
|
|
121
|
+
"repo": "default",
|
|
122
|
+
"type": "github"
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
"root": "root",
|
|
127
|
+
"version": 7
|
|
128
|
+
}
|
package/flake.nix
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
description = "Pushwork: Bidirectional directory synchronization using Automerge CRDTs";
|
|
3
|
+
|
|
4
|
+
inputs = {
|
|
5
|
+
command-utils.url = "git+https://codeberg.org/expede/nix-command-utils";
|
|
6
|
+
flake-utils.url = "github:numtide/flake-utils";
|
|
7
|
+
nixpkgs.url = "github:nixos/nixpkgs/nixos-25.11";
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
outputs = {
|
|
11
|
+
self,
|
|
12
|
+
command-utils,
|
|
13
|
+
flake-utils,
|
|
14
|
+
nixpkgs,
|
|
15
|
+
}:
|
|
16
|
+
flake-utils.lib.eachDefaultSystem (system: let
|
|
17
|
+
pkgs = import nixpkgs {inherit system;};
|
|
18
|
+
|
|
19
|
+
nodejs = pkgs.nodejs_24;
|
|
20
|
+
pnpm-pkg = pkgs.pnpm;
|
|
21
|
+
pnpm' = "${pnpm-pkg}/bin/pnpm";
|
|
22
|
+
|
|
23
|
+
asModule = command-utils.asModule.${system};
|
|
24
|
+
cmd = command-utils.cmd.${system};
|
|
25
|
+
pnpm = command-utils.pnpm.${system};
|
|
26
|
+
|
|
27
|
+
pnpm-cfg = {pnpm = pnpm';};
|
|
28
|
+
|
|
29
|
+
menu =
|
|
30
|
+
command-utils.commands.${system}
|
|
31
|
+
[
|
|
32
|
+
(pnpm.build pnpm-cfg)
|
|
33
|
+
(pnpm.dev pnpm-cfg)
|
|
34
|
+
(pnpm.install pnpm-cfg)
|
|
35
|
+
(pnpm.lint pnpm-cfg)
|
|
36
|
+
(pnpm.test pnpm-cfg)
|
|
37
|
+
(pnpm.typecheck pnpm-cfg)
|
|
38
|
+
(asModule {
|
|
39
|
+
"clean" = cmd "Remove dist and node_modules" "rm -rf dist node_modules";
|
|
40
|
+
"start" = cmd "Run pushwork CLI" "node dist/cli.js \"$@\"";
|
|
41
|
+
"sync" = cmd "Build and run sync" "${pnpm'} build && node dist/cli.js sync \"$@\"";
|
|
42
|
+
"watch" = cmd "Watch, build, and sync loop" "node dist/cli.js watch \"$@\"";
|
|
43
|
+
})
|
|
44
|
+
];
|
|
45
|
+
in {
|
|
46
|
+
devShells.default = pkgs.mkShell {
|
|
47
|
+
name = "Pushwork Dev Shell";
|
|
48
|
+
|
|
49
|
+
nativeBuildInputs =
|
|
50
|
+
[
|
|
51
|
+
nodejs
|
|
52
|
+
pkgs.nodePackages.vscode-langservers-extracted
|
|
53
|
+
pkgs.typescript
|
|
54
|
+
pkgs.typescript-language-server
|
|
55
|
+
pnpm-pkg
|
|
56
|
+
]
|
|
57
|
+
++ menu;
|
|
58
|
+
|
|
59
|
+
shellHook = ''
|
|
60
|
+
menu
|
|
61
|
+
'';
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
formatter = pkgs.alejandra;
|
|
65
|
+
});
|
|
66
|
+
}
|
package/package.json
CHANGED
|
@@ -1,98 +1,99 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
2
|
+
"name": "pushwork",
|
|
3
|
+
"version": "1.2.2",
|
|
4
|
+
"description": "Bidirectional directory synchronization using Automerge CRDTs",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"bin": {
|
|
10
|
+
"pushwork": "./dist/cli.js"
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"dev": "tsc --watch",
|
|
15
|
+
"test": "jest",
|
|
16
|
+
"test:bail": "jest --bail",
|
|
17
|
+
"test:watch": "jest --watch",
|
|
18
|
+
"test:coverage": "jest --coverage",
|
|
19
|
+
"lint": "eslint src --ext .ts",
|
|
20
|
+
"lint:fix": "eslint src --ext .ts --fix",
|
|
21
|
+
"clean": "rm -rf dist",
|
|
22
|
+
"prepack": "pnpm run build",
|
|
23
|
+
"start": "node dist/cli.js",
|
|
24
|
+
"typecheck": "tsc --noEmit"
|
|
25
|
+
},
|
|
26
|
+
"packageManager": "pnpm@10.32.1+sha512.a706938f0e89ac1456b6563eab4edf1d1faf3368d1191fc5c59790e96dc918e4456ab2e67d613de1043d2e8c81f87303e6b40d4ffeca9df15ef1ad567348f2be",
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=24.0.0",
|
|
29
|
+
"pnpm": ">=8.0.0"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"automerge",
|
|
33
|
+
"sync",
|
|
34
|
+
"crdt",
|
|
35
|
+
"collaboration",
|
|
36
|
+
"filesystem"
|
|
37
|
+
],
|
|
38
|
+
"author": "Peter van Hardenberg",
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@automerge/automerge": "^3.2.5",
|
|
42
|
+
"@automerge/automerge-repo": "2.6.0-subduction.14",
|
|
43
|
+
"@automerge/automerge-repo-network-websocket": "2.6.0-subduction.14",
|
|
44
|
+
"@automerge/automerge-repo-storage-nodefs": "2.6.0-subduction.14",
|
|
45
|
+
"@automerge/automerge-subduction": "0.7.0",
|
|
46
|
+
"@commander-js/extra-typings": "^14.0.0",
|
|
47
|
+
"chalk": "^5.3.0",
|
|
48
|
+
"commander": "^14.0.2",
|
|
49
|
+
"diff": "^8.0.2",
|
|
50
|
+
"glob": "^10.3.0",
|
|
51
|
+
"ignore": "^5.3.0",
|
|
52
|
+
"mime-types": "^2.1.35",
|
|
53
|
+
"ora": "^7.0.1"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@babel/core": "^7.28.6",
|
|
57
|
+
"@babel/preset-env": "^7.28.6",
|
|
58
|
+
"@types/diff": "^5.0.3",
|
|
59
|
+
"@types/jest": "^29.5.0",
|
|
60
|
+
"@types/mime-types": "^2.1.1",
|
|
61
|
+
"@types/node": "^20.0.0",
|
|
62
|
+
"@types/tmp": "^0.2.4",
|
|
63
|
+
"babel-jest": "^30.2.0",
|
|
64
|
+
"fast-check": "^4.3.0",
|
|
65
|
+
"jest": "^29.7.0",
|
|
66
|
+
"tmp": "^0.2.1",
|
|
67
|
+
"ts-jest": "^29.1.0",
|
|
68
|
+
"tsx": "^4.19.2",
|
|
69
|
+
"typescript": "^5.2.0"
|
|
70
|
+
},
|
|
71
|
+
"jest": {
|
|
72
|
+
"preset": "ts-jest",
|
|
73
|
+
"testEnvironment": "node",
|
|
74
|
+
"roots": [
|
|
75
|
+
"<rootDir>/src",
|
|
76
|
+
"<rootDir>/test"
|
|
77
|
+
],
|
|
78
|
+
"testMatch": [
|
|
79
|
+
"**/__tests__/**/*.ts",
|
|
80
|
+
"**/*.(test|spec).ts"
|
|
81
|
+
],
|
|
82
|
+
"collectCoverageFrom": [
|
|
83
|
+
"src/**/*.ts",
|
|
84
|
+
"!src/**/*.d.ts"
|
|
85
|
+
],
|
|
86
|
+
"setupFilesAfterEnv": [
|
|
87
|
+
"<rootDir>/test/jest.setup.ts"
|
|
88
|
+
],
|
|
89
|
+
"maxWorkers": "75%",
|
|
90
|
+
"maxConcurrency": 10,
|
|
91
|
+
"transformIgnorePatterns": [
|
|
92
|
+
"node_modules/(?!.*@automerge)"
|
|
93
|
+
],
|
|
94
|
+
"transform": {
|
|
95
|
+
"^.+\\.tsx?$": "ts-jest",
|
|
96
|
+
"^.+\\.jsx?$": "babel-jest"
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Roundtrip-test pushwork by init-ing a directory, cloning it elsewhere,
|
|
3
|
+
# and diffing the two trees (ignoring files that aren't synced).
|
|
4
|
+
#
|
|
5
|
+
# Usage: roundtrip-test.sh <source-dir>
|
|
6
|
+
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
if [ $# -lt 1 ]; then
|
|
10
|
+
echo "usage: $0 <source-dir>" >&2
|
|
11
|
+
exit 1
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
SRC=$(cd "$1" && pwd)
|
|
15
|
+
CLONE_DIR=$(mktemp -d -t pushwork-roundtrip-XXXXXX)/clone
|
|
16
|
+
|
|
17
|
+
echo ">> init $SRC"
|
|
18
|
+
pushwork init --sub "$SRC"
|
|
19
|
+
|
|
20
|
+
URL=$(pushwork url "$SRC")
|
|
21
|
+
echo ">> url $URL"
|
|
22
|
+
|
|
23
|
+
echo ">> clone $CLONE_DIR"
|
|
24
|
+
pushwork clone "$URL" "$CLONE_DIR" --sub
|
|
25
|
+
|
|
26
|
+
echo ">> diff $SRC <-> $CLONE_DIR"
|
|
27
|
+
if diff -r \
|
|
28
|
+
--exclude=.pushwork \
|
|
29
|
+
--exclude=node_modules \
|
|
30
|
+
"$SRC" "$CLONE_DIR"; then
|
|
31
|
+
echo ">> OK: directories match"
|
|
32
|
+
else
|
|
33
|
+
echo ">> FAIL: directories differ" >&2
|
|
34
|
+
exit 1
|
|
35
|
+
fi
|