pushwork 2.0.0-a.sub.1 → 2.0.0-preview
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/branches.d.ts +19 -0
- package/dist/branches.d.ts.map +1 -0
- package/dist/branches.js +111 -0
- package/dist/branches.js.map +1 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +238 -272
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +17 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +84 -0
- package/dist/config.js.map +1 -0
- package/dist/fs-tree.d.ts +6 -0
- package/dist/fs-tree.d.ts.map +1 -0
- package/dist/fs-tree.js +99 -0
- package/dist/fs-tree.js.map +1 -0
- package/dist/ignore.d.ts +6 -0
- package/dist/ignore.d.ts.map +1 -0
- package/dist/ignore.js +74 -0
- package/dist/ignore.js.map +1 -0
- package/dist/index.d.ts +8 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +34 -4
- package/dist/index.js.map +1 -1
- package/dist/log.d.ts +3 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +14 -0
- package/dist/log.js.map +1 -0
- package/dist/pushwork.d.ts +115 -0
- package/dist/pushwork.d.ts.map +1 -0
- package/dist/pushwork.js +918 -0
- package/dist/pushwork.js.map +1 -0
- package/dist/repo.d.ts +14 -0
- package/dist/repo.d.ts.map +1 -0
- package/dist/repo.js +60 -0
- package/dist/repo.js.map +1 -0
- package/dist/shapes/custom.d.ts +3 -0
- package/dist/shapes/custom.d.ts.map +1 -0
- package/dist/shapes/custom.js +57 -0
- package/dist/shapes/custom.js.map +1 -0
- package/dist/shapes/file.d.ts +20 -0
- package/dist/shapes/file.d.ts.map +1 -0
- package/dist/shapes/file.js +140 -0
- package/dist/shapes/file.js.map +1 -0
- package/dist/shapes/index.d.ts +10 -0
- package/dist/shapes/index.d.ts.map +1 -0
- package/dist/shapes/index.js +35 -0
- package/dist/shapes/index.js.map +1 -0
- package/dist/shapes/patchwork-folder.d.ts +3 -0
- package/dist/shapes/patchwork-folder.d.ts.map +1 -0
- package/dist/shapes/patchwork-folder.js +160 -0
- package/dist/shapes/patchwork-folder.js.map +1 -0
- package/dist/shapes/types.d.ts +37 -0
- package/dist/shapes/types.d.ts.map +1 -0
- package/dist/shapes/types.js +52 -0
- package/dist/shapes/types.js.map +1 -0
- package/dist/shapes/vfs.d.ts +3 -0
- package/dist/shapes/vfs.d.ts.map +1 -0
- package/dist/shapes/vfs.js +88 -0
- package/dist/shapes/vfs.js.map +1 -0
- package/dist/stash.d.ts +23 -0
- package/dist/stash.d.ts.map +1 -0
- package/dist/stash.js +118 -0
- package/dist/stash.js.map +1 -0
- package/flake.lock +128 -0
- package/flake.nix +66 -0
- package/package.json +15 -48
- package/patches/@automerge__automerge-repo@2.6.0-subduction.15.patch +26 -0
- package/pnpm-workspace.yaml +5 -0
- package/src/branches.ts +93 -0
- package/src/cli.ts +258 -408
- package/src/config.ts +64 -0
- package/src/fs-tree.ts +70 -0
- package/src/ignore.ts +33 -0
- package/src/index.ts +38 -4
- package/src/log.ts +8 -0
- package/src/pushwork.ts +1055 -0
- package/src/repo.ts +76 -0
- package/src/shapes/custom.ts +29 -0
- package/src/shapes/file.ts +115 -0
- package/src/shapes/index.ts +19 -0
- package/src/shapes/patchwork-folder.ts +156 -0
- package/src/shapes/types.ts +79 -0
- package/src/shapes/vfs.ts +93 -0
- package/src/stash.ts +106 -0
- package/test/integration/branches.test.ts +389 -0
- package/test/integration/pushwork.test.ts +547 -0
- package/test/setup.ts +29 -0
- package/test/unit/doc-shape.test.ts +612 -0
- package/tsconfig.json +2 -3
- package/vitest.config.ts +14 -0
- package/ARCHITECTURE-ACCORDING-TO-CLAUDE.md +0 -248
- package/CLAUDE.md +0 -141
- package/README.md +0 -221
- package/babel.config.js +0 -5
- 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/commands.d.ts +0 -61
- package/dist/commands.d.ts.map +0 -1
- package/dist/commands.js +0 -861
- package/dist/commands.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/core/change-detection.d.ts +0 -80
- package/dist/core/change-detection.d.ts.map +0 -1
- package/dist/core/change-detection.js +0 -523
- package/dist/core/change-detection.js.map +0 -1
- package/dist/core/config.d.ts +0 -81
- package/dist/core/config.d.ts.map +0 -1
- package/dist/core/config.js +0 -258
- package/dist/core/config.js.map +0 -1
- package/dist/core/index.d.ts +0 -6
- package/dist/core/index.d.ts.map +0 -1
- package/dist/core/index.js +0 -6
- package/dist/core/index.js.map +0 -1
- package/dist/core/move-detection.d.ts +0 -34
- package/dist/core/move-detection.d.ts.map +0 -1
- package/dist/core/move-detection.js +0 -121
- package/dist/core/move-detection.js.map +0 -1
- package/dist/core/snapshot.d.ts +0 -105
- package/dist/core/snapshot.d.ts.map +0 -1
- package/dist/core/snapshot.js +0 -217
- package/dist/core/snapshot.js.map +0 -1
- package/dist/core/sync-engine.d.ts +0 -157
- package/dist/core/sync-engine.d.ts.map +0 -1
- package/dist/core/sync-engine.js +0 -1379
- package/dist/core/sync-engine.js.map +0 -1
- package/dist/types/config.d.ts +0 -99
- package/dist/types/config.d.ts.map +0 -1
- package/dist/types/config.js +0 -5
- package/dist/types/config.js.map +0 -1
- package/dist/types/documents.d.ts +0 -88
- package/dist/types/documents.d.ts.map +0 -1
- package/dist/types/documents.js +0 -20
- package/dist/types/documents.js.map +0 -1
- package/dist/types/index.d.ts +0 -4
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js +0 -4
- package/dist/types/index.js.map +0 -1
- package/dist/types/snapshot.d.ts +0 -64
- package/dist/types/snapshot.d.ts.map +0 -1
- package/dist/types/snapshot.js +0 -2
- package/dist/types/snapshot.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/content.d.ts +0 -10
- package/dist/utils/content.d.ts.map +0 -1
- package/dist/utils/content.js +0 -31
- package/dist/utils/content.js.map +0 -1
- package/dist/utils/directory.d.ts +0 -24
- package/dist/utils/directory.d.ts.map +0 -1
- package/dist/utils/directory.js +0 -52
- package/dist/utils/directory.js.map +0 -1
- package/dist/utils/fs.d.ts +0 -74
- package/dist/utils/fs.d.ts.map +0 -1
- package/dist/utils/fs.js +0 -248
- package/dist/utils/fs.js.map +0 -1
- package/dist/utils/index.d.ts +0 -5
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js +0 -5
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/mime-types.d.ts +0 -13
- package/dist/utils/mime-types.d.ts.map +0 -1
- package/dist/utils/mime-types.js +0 -209
- package/dist/utils/mime-types.js.map +0 -1
- package/dist/utils/network-sync.d.ts +0 -36
- package/dist/utils/network-sync.d.ts.map +0 -1
- package/dist/utils/network-sync.js +0 -250
- package/dist/utils/network-sync.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
- package/dist/utils/output.d.ts +0 -129
- package/dist/utils/output.d.ts.map +0 -1
- package/dist/utils/output.js +0 -368
- package/dist/utils/output.js.map +0 -1
- package/dist/utils/repo-factory.d.ts +0 -13
- package/dist/utils/repo-factory.d.ts.map +0 -1
- package/dist/utils/repo-factory.js +0 -46
- package/dist/utils/repo-factory.js.map +0 -1
- package/dist/utils/string-similarity.d.ts +0 -14
- package/dist/utils/string-similarity.d.ts.map +0 -1
- package/dist/utils/string-similarity.js +0 -39
- package/dist/utils/string-similarity.js.map +0 -1
- package/dist/utils/text-diff.d.ts +0 -37
- package/dist/utils/text-diff.d.ts.map +0 -1
- package/dist/utils/text-diff.js +0 -93
- package/dist/utils/text-diff.js.map +0 -1
- package/dist/utils/trace.d.ts +0 -19
- package/dist/utils/trace.d.ts.map +0 -1
- package/dist/utils/trace.js +0 -63
- package/dist/utils/trace.js.map +0 -1
- package/src/commands.ts +0 -1134
- package/src/core/change-detection.ts +0 -712
- package/src/core/config.ts +0 -313
- package/src/core/index.ts +0 -5
- package/src/core/move-detection.ts +0 -169
- package/src/core/snapshot.ts +0 -275
- package/src/core/sync-engine.ts +0 -1795
- package/src/types/config.ts +0 -111
- package/src/types/documents.ts +0 -91
- package/src/types/index.ts +0 -3
- package/src/types/snapshot.ts +0 -67
- package/src/utils/content.ts +0 -34
- package/src/utils/directory.ts +0 -73
- package/src/utils/fs.ts +0 -297
- package/src/utils/index.ts +0 -4
- package/src/utils/mime-types.ts +0 -244
- package/src/utils/network-sync.ts +0 -319
- package/src/utils/node-polyfills.ts +0 -8
- package/src/utils/output.ts +0 -450
- package/src/utils/repo-factory.ts +0 -73
- package/src/utils/string-similarity.ts +0 -54
- package/src/utils/text-diff.ts +0 -101
- package/src/utils/trace.ts +0 -70
- package/test/integration/README.md +0 -328
- package/test/integration/clone-test.sh +0 -310
- package/test/integration/conflict-resolution-test.sh +0 -309
- package/test/integration/debug-both-nested.sh +0 -74
- package/test/integration/debug-concurrent-nested.sh +0 -87
- package/test/integration/debug-nested.sh +0 -73
- package/test/integration/deletion-behavior-test.sh +0 -487
- package/test/integration/deletion-sync-test-simple.sh +0 -193
- package/test/integration/deletion-sync-test.sh +0 -297
- package/test/integration/exclude-patterns.test.ts +0 -144
- package/test/integration/full-integration-test.sh +0 -363
- package/test/integration/fuzzer.test.ts +0 -818
- package/test/integration/in-memory-sync.test.ts +0 -830
- package/test/integration/init-sync.test.ts +0 -89
- package/test/integration/manual-sync-test.sh +0 -84
- package/test/integration/sync-deletion.test.ts +0 -280
- package/test/integration/sync-flow.test.ts +0 -291
- package/test/jest.setup.ts +0 -34
- package/test/run-tests.sh +0 -225
- package/test/unit/deletion-behavior.test.ts +0 -249
- package/test/unit/enhanced-mime-detection.test.ts +0 -244
- package/test/unit/snapshot.test.ts +0 -404
- package/test/unit/sync-convergence.test.ts +0 -298
- package/test/unit/sync-timing.test.ts +0 -134
- package/test/unit/utils.test.ts +0 -366
package/src/cli.ts
CHANGED
|
@@ -1,442 +1,292 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { Command
|
|
4
|
-
import
|
|
2
|
+
import "./log.js"; // sets up DEBUG=true → DEBUG=* before anything else
|
|
3
|
+
import { Command } from "@commander-js/extra-typings";
|
|
4
|
+
import * as path from "path";
|
|
5
5
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
6
|
+
clone,
|
|
7
|
+
createBranch,
|
|
8
|
+
currentBranch,
|
|
9
|
+
cutWorkdir,
|
|
10
|
+
diff,
|
|
11
|
+
init,
|
|
12
|
+
listBranches,
|
|
13
|
+
mergeBranch,
|
|
14
|
+
pasteStash,
|
|
15
|
+
previewMerge,
|
|
16
|
+
save,
|
|
17
|
+
showStashes,
|
|
18
|
+
status,
|
|
19
|
+
switchBranch,
|
|
20
|
+
sync,
|
|
21
|
+
url,
|
|
22
|
+
} from "./pushwork.js";
|
|
23
|
+
import { log } from "./log.js";
|
|
24
|
+
|
|
25
|
+
const dlog = log("cli");
|
|
26
|
+
|
|
27
|
+
const collect = (value: string, prev: string[] | undefined) =>
|
|
28
|
+
(prev ?? []).concat(value);
|
|
22
29
|
|
|
23
|
-
import { createRequire } from "module";
|
|
24
|
-
const require = createRequire(import.meta.url);
|
|
25
|
-
const version = require("../package.json").version;
|
|
26
30
|
const program = new Command()
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
.version(version, "-V, --version", "output the version number");
|
|
31
|
+
.name("pushwork")
|
|
32
|
+
.description("Bidirectional directory synchronization using Automerge CRDTs");
|
|
30
33
|
|
|
31
|
-
// Init command
|
|
32
34
|
program
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
35
|
+
.command("init")
|
|
36
|
+
.description("Initialize pushwork in a directory")
|
|
37
|
+
.argument("[dir]", "Directory to initialize", ".")
|
|
38
|
+
.option("--sub", "Use subduction backend")
|
|
39
|
+
.option(
|
|
40
|
+
"--shape <shape>",
|
|
41
|
+
"Document shape: vfs, patchwork-folder, or path to a custom shape module",
|
|
42
|
+
"vfs",
|
|
43
|
+
)
|
|
44
|
+
.option(
|
|
45
|
+
"--artifact-dir <dir>",
|
|
46
|
+
"Directory whose contents are stored as ImmutableString and pinned with heads in the root doc. Repeatable.",
|
|
47
|
+
collect,
|
|
48
|
+
undefined as string[] | undefined,
|
|
49
|
+
)
|
|
50
|
+
.option("--no-branches", "Skip wrapping the root doc in a branches doc")
|
|
51
|
+
.action(async (dir, opts) => {
|
|
52
|
+
dlog("init dir=%s opts=%o", dir, opts);
|
|
53
|
+
const u = await init({
|
|
54
|
+
dir: path.resolve(dir),
|
|
55
|
+
backend: opts.sub ? "subduction" : "legacy",
|
|
56
|
+
shape: opts.shape,
|
|
57
|
+
artifactDirectories: opts.artifactDir,
|
|
58
|
+
branches: opts.branches,
|
|
59
|
+
});
|
|
60
|
+
process.stderr.write(`initialized ${u}\n`);
|
|
61
|
+
});
|
|
52
62
|
|
|
53
63
|
program
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
64
|
+
.command("clone")
|
|
65
|
+
.description("Clone an automerge URL into a directory")
|
|
66
|
+
.argument("<url>", "automerge: URL")
|
|
67
|
+
.argument("[dir]", "Target directory", ".")
|
|
68
|
+
.option("--sub", "Use subduction backend")
|
|
69
|
+
.option(
|
|
70
|
+
"--shape <shape>",
|
|
71
|
+
"Document shape: vfs, patchwork-folder, or path to a custom shape module",
|
|
72
|
+
"vfs",
|
|
73
|
+
)
|
|
74
|
+
.option(
|
|
75
|
+
"--artifact-dir <dir>",
|
|
76
|
+
"Directory whose contents are stored as ImmutableString and pinned with heads in the root doc. Repeatable.",
|
|
77
|
+
collect,
|
|
78
|
+
undefined as string[] | undefined,
|
|
79
|
+
)
|
|
80
|
+
.option("--branch <name>", "Branch to track when cloning a branches doc")
|
|
81
|
+
.action(async (u, dir, opts) => {
|
|
82
|
+
dlog("clone url=%s dir=%s opts=%o", u, dir, opts);
|
|
83
|
+
await clone({
|
|
84
|
+
url: u,
|
|
85
|
+
dir: path.resolve(dir),
|
|
86
|
+
backend: opts.sub ? "subduction" : "legacy",
|
|
87
|
+
shape: opts.shape,
|
|
88
|
+
artifactDirectories: opts.artifactDir,
|
|
89
|
+
branch: opts.branch,
|
|
90
|
+
});
|
|
91
|
+
process.stderr.write(`cloned into ${path.resolve(dir)}\n`);
|
|
92
|
+
});
|
|
69
93
|
|
|
70
|
-
// Hidden alias for backwards compatibility
|
|
71
94
|
program
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
95
|
+
.command("url")
|
|
96
|
+
.description("Print the automerge URL of this pushwork repo")
|
|
97
|
+
.action(async () => {
|
|
98
|
+
dlog("url cwd=%s", process.cwd());
|
|
99
|
+
const u = await url(process.cwd());
|
|
100
|
+
process.stdout.write(u + "\n");
|
|
101
|
+
});
|
|
79
102
|
|
|
80
|
-
// Clone command
|
|
81
103
|
program
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
.option("-f, --force", "Overwrite existing directory", false)
|
|
90
|
-
.option(
|
|
91
|
-
"--sync-server <url>",
|
|
92
|
-
"Custom sync server URL"
|
|
93
|
-
)
|
|
94
|
-
.option("-v, --verbose", "Verbose output", false)
|
|
95
|
-
.action(async (url, path, opts) => {
|
|
96
|
-
await clone(url, path, {
|
|
97
|
-
force: opts.force,
|
|
98
|
-
verbose: opts.verbose,
|
|
99
|
-
syncServer: opts.syncServer,
|
|
100
|
-
});
|
|
101
|
-
});
|
|
104
|
+
.command("sync")
|
|
105
|
+
.description("Sync local changes with peers")
|
|
106
|
+
.action(async () => {
|
|
107
|
+
dlog("sync cwd=%s", process.cwd());
|
|
108
|
+
await sync(process.cwd());
|
|
109
|
+
process.stderr.write("synced\n");
|
|
110
|
+
});
|
|
102
111
|
|
|
103
|
-
// Commit command
|
|
104
112
|
program
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
await commit(path);
|
|
114
|
-
});
|
|
113
|
+
.command("save")
|
|
114
|
+
.alias("commit")
|
|
115
|
+
.description("Commit local changes to the current branch without contacting the sync server")
|
|
116
|
+
.action(async () => {
|
|
117
|
+
dlog("save cwd=%s", process.cwd());
|
|
118
|
+
await save(process.cwd());
|
|
119
|
+
process.stderr.write("saved\n");
|
|
120
|
+
});
|
|
115
121
|
|
|
116
|
-
// Sync command
|
|
117
122
|
program
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
"--nuclear",
|
|
137
|
-
"Recreate all Automerge documents from scratch",
|
|
138
|
-
false
|
|
139
|
-
)
|
|
140
|
-
.addOption(new Option("-f, --force", "Accepted for backwards compatibility").default(false).hideHelp())
|
|
141
|
-
.option("-v, --verbose", "Verbose output", false)
|
|
142
|
-
.action(async (path, opts) => {
|
|
143
|
-
await sync(path, {
|
|
144
|
-
dryRun: opts.dryRun,
|
|
145
|
-
force: opts.force,
|
|
146
|
-
gentle: opts.gentle,
|
|
147
|
-
nuclear: opts.nuclear,
|
|
148
|
-
verbose: opts.verbose,
|
|
149
|
-
});
|
|
150
|
-
});
|
|
123
|
+
.command("status")
|
|
124
|
+
.description("Show current branch and changes against it")
|
|
125
|
+
.action(async () => {
|
|
126
|
+
const { branch, diff: d } = await status(process.cwd());
|
|
127
|
+
const lines: string[] = [];
|
|
128
|
+
if (branch) lines.push(`On branch ${branch}`);
|
|
129
|
+
else lines.push("(no branches)");
|
|
130
|
+
const total = d.added.length + d.modified.length + d.deleted.length;
|
|
131
|
+
if (total === 0) {
|
|
132
|
+
lines.push("nothing to save, working tree clean");
|
|
133
|
+
} else {
|
|
134
|
+
lines.push("Changes:");
|
|
135
|
+
for (const p of d.modified) lines.push(` modified: ${p}`);
|
|
136
|
+
for (const p of d.added) lines.push(` added: ${p}`);
|
|
137
|
+
for (const p of d.deleted) lines.push(` deleted: ${p}`);
|
|
138
|
+
}
|
|
139
|
+
process.stdout.write(lines.join("\n") + "\n");
|
|
140
|
+
});
|
|
151
141
|
|
|
152
|
-
// Diff command
|
|
153
142
|
program
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
143
|
+
.command("diff")
|
|
144
|
+
.description("Show textual diffs of local changes against the current branch")
|
|
145
|
+
.argument("[path]", "Limit to a specific path")
|
|
146
|
+
.action(async (limitPath) => {
|
|
147
|
+
const entries = await diff(process.cwd(), limitPath);
|
|
148
|
+
if (entries.length === 0) {
|
|
149
|
+
process.stdout.write("(no changes)\n");
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const { createPatch } = await import("diff");
|
|
153
|
+
const td = new TextDecoder("utf-8", { fatal: false });
|
|
154
|
+
for (const e of entries) {
|
|
155
|
+
const before = e.before ? td.decode(e.before) : "";
|
|
156
|
+
const after = e.after ? td.decode(e.after) : "";
|
|
157
|
+
const header =
|
|
158
|
+
e.kind === "added" ? `+++ ${e.path}` :
|
|
159
|
+
e.kind === "deleted" ? `--- ${e.path}` :
|
|
160
|
+
`*** ${e.path}`;
|
|
161
|
+
process.stdout.write(header + "\n");
|
|
162
|
+
process.stdout.write(createPatch(e.path, before, after, "", "") + "\n");
|
|
163
|
+
}
|
|
164
|
+
});
|
|
167
165
|
|
|
168
|
-
// Status command
|
|
169
166
|
program
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
});
|
|
167
|
+
.command("branch")
|
|
168
|
+
.description("With no arg: print the current branch. With <name>: create a new branch from the current one (offline).")
|
|
169
|
+
.argument("[name]", "Name of the new branch")
|
|
170
|
+
.action(async (name) => {
|
|
171
|
+
if (!name) {
|
|
172
|
+
const cur = await currentBranch(process.cwd());
|
|
173
|
+
process.stdout.write((cur ?? "(none)") + "\n");
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const newUrl = await createBranch(process.cwd(), name);
|
|
177
|
+
process.stderr.write(`created branch ${name} → ${newUrl}\n`);
|
|
178
|
+
});
|
|
183
179
|
|
|
184
|
-
// Log command
|
|
185
180
|
program
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
limit: parseInt(opts.limit),
|
|
201
|
-
});
|
|
202
|
-
});
|
|
181
|
+
.command("switch")
|
|
182
|
+
.description("Switch to a branch (offline). With no name: list branches.")
|
|
183
|
+
.argument("[name]", "Name of the branch to switch to")
|
|
184
|
+
.action(async (name) => {
|
|
185
|
+
if (!name) {
|
|
186
|
+
const { current, names } = await listBranches(process.cwd());
|
|
187
|
+
for (const n of names) {
|
|
188
|
+
process.stdout.write(`${n === current ? "* " : " "}${n}\n`);
|
|
189
|
+
}
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
await switchBranch(process.cwd(), name);
|
|
193
|
+
process.stderr.write(`switched to ${name}\n`);
|
|
194
|
+
});
|
|
203
195
|
|
|
204
|
-
// Checkout command
|
|
205
196
|
program
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
197
|
+
.command("merge")
|
|
198
|
+
.description("Apply changes from <source> branch onto the current branch (offline)")
|
|
199
|
+
.argument("<source>", "Branch to merge into the current one")
|
|
200
|
+
.option("--dry", "Preview the merge without applying")
|
|
201
|
+
.action(async (source, opts) => {
|
|
202
|
+
if (opts.dry) {
|
|
203
|
+
const preview = await previewMerge(process.cwd(), source);
|
|
204
|
+
const lines: string[] = [];
|
|
205
|
+
lines.push(`Merging ${preview.source} into ${preview.target} (preview)`);
|
|
206
|
+
if (preview.entries.length === 0) {
|
|
207
|
+
lines.push("(no changes)");
|
|
208
|
+
process.stdout.write(lines.join("\n") + "\n");
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const { createPatch } = await import("diff");
|
|
212
|
+
const td = new TextDecoder("utf-8", { fatal: false });
|
|
213
|
+
for (const e of preview.entries) {
|
|
214
|
+
const before = e.before ? td.decode(e.before) : "";
|
|
215
|
+
const after = td.decode(e.after);
|
|
216
|
+
const tag = e.kind === "added" ? "added" : "merged";
|
|
217
|
+
lines.push(` ${tag}: ${e.path}`);
|
|
218
|
+
lines.push(createPatch(e.path, before, after, "", ""));
|
|
219
|
+
}
|
|
220
|
+
process.stdout.write(lines.join("\n") + "\n");
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
const report = await mergeBranch(process.cwd(), source);
|
|
224
|
+
const lines: string[] = [];
|
|
225
|
+
lines.push(`Merging ${report.source} into ${report.target}`);
|
|
226
|
+
if (report.merged.length === 0 && report.added.length === 0) {
|
|
227
|
+
lines.push("(no changes)");
|
|
228
|
+
} else {
|
|
229
|
+
for (const p of report.merged) lines.push(` merged: ${p}`);
|
|
230
|
+
for (const p of report.added) lines.push(` added: ${p}`);
|
|
231
|
+
}
|
|
232
|
+
process.stdout.write(lines.join("\n") + "\n");
|
|
233
|
+
});
|
|
224
234
|
|
|
225
|
-
// URL command
|
|
226
235
|
program
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
236
|
+
.command("cut")
|
|
237
|
+
.description("Stash working-tree changes against the current branch and reset the tree to clean (offline)")
|
|
238
|
+
.argument("[name]", "Optional name for the stash entry")
|
|
239
|
+
.action(async (name) => {
|
|
240
|
+
const result = await cutWorkdir(process.cwd(), { name });
|
|
241
|
+
process.stderr.write(`cut #${result.id}: ${result.entries} entr${result.entries === 1 ? "y" : "ies"}\n`);
|
|
242
|
+
});
|
|
233
243
|
|
|
234
|
-
// Remove command
|
|
235
244
|
program
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
245
|
+
.command("paste")
|
|
246
|
+
.description("Re-apply a stashed set of changes; default is the most recent (offline)")
|
|
247
|
+
.argument("[id-or-name]", "Stash id or name")
|
|
248
|
+
.action(async (selector) => {
|
|
249
|
+
const result = await pasteStash(process.cwd(), selector);
|
|
250
|
+
process.stderr.write(
|
|
251
|
+
`pasted #${result.id}${result.name ? ` (${result.name})` : ""}: ${result.entries} entr${result.entries === 1 ? "y" : "ies"}\n`,
|
|
252
|
+
);
|
|
253
|
+
});
|
|
242
254
|
|
|
243
|
-
// List command
|
|
244
255
|
program
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
256
|
+
.command("cuts")
|
|
257
|
+
.description("List stashed change sets (newest first)")
|
|
258
|
+
.action(async () => {
|
|
259
|
+
const stashes = await showStashes(process.cwd());
|
|
260
|
+
if (stashes.length === 0) {
|
|
261
|
+
process.stdout.write("(no stashes)\n");
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
for (const s of stashes) {
|
|
265
|
+
const ts = new Date(s.createdAt).toISOString();
|
|
266
|
+
const label = s.name ? `"${s.name}"` : "";
|
|
267
|
+
const branch = s.branch ? ` on ${s.branch}` : "";
|
|
268
|
+
process.stdout.write(
|
|
269
|
+
`#${s.id}${label ? " " + label : ""}${branch} ${s.entries.length} entr${s.entries.length === 1 ? "y" : "ies"} ${ts}\n`,
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
});
|
|
254
273
|
|
|
255
|
-
// Config command
|
|
256
274
|
program
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
.action(async (path, opts) => {
|
|
266
|
-
await config(path, {
|
|
267
|
-
list: opts.list,
|
|
268
|
-
get: opts.get,
|
|
269
|
-
});
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
// Watch command
|
|
273
|
-
program
|
|
274
|
-
.command("watch")
|
|
275
|
-
.summary("Watch directory for changes, build, and sync")
|
|
276
|
-
.argument(
|
|
277
|
-
"[path]",
|
|
278
|
-
"Directory path to sync (default: current directory)",
|
|
279
|
-
"."
|
|
280
|
-
)
|
|
281
|
-
.option(
|
|
282
|
-
"--script <command>",
|
|
283
|
-
"Build script to run before syncing",
|
|
284
|
-
"pnpm build"
|
|
285
|
-
)
|
|
286
|
-
.option(
|
|
287
|
-
"--dir <dir>",
|
|
288
|
-
"Directory to watch for changes (relative to working directory)",
|
|
289
|
-
"src"
|
|
290
|
-
)
|
|
291
|
-
.option("-v, --verbose", "Show build script output", false)
|
|
292
|
-
.action(async (path, opts) => {
|
|
293
|
-
await watch(path, {
|
|
294
|
-
script: opts.script,
|
|
295
|
-
watchDir: opts.dir,
|
|
296
|
-
verbose: opts.verbose,
|
|
297
|
-
});
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
// Read command
|
|
301
|
-
program
|
|
302
|
-
.command("read")
|
|
303
|
-
.summary("Read a file document by its Automerge URL")
|
|
304
|
-
.argument(
|
|
305
|
-
"<url>",
|
|
306
|
-
"AutomergeUrl of file document (format: automerge:XXXXX)"
|
|
307
|
-
)
|
|
308
|
-
.option("-r, --remote", "Read from sync server instead of local storage", false)
|
|
309
|
-
.action(async (url, opts) => {
|
|
310
|
-
await read(url, {
|
|
311
|
-
remote: opts.remote,
|
|
312
|
-
});
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
// Completion command (hidden from help)
|
|
316
|
-
program.command("completion", { hidden: true }).action(() => {
|
|
317
|
-
// Generate completion dynamically from registered commands
|
|
318
|
-
const commands = program.commands
|
|
319
|
-
.filter((cmd) => cmd.name() !== "completion") // Exclude self
|
|
320
|
-
.map((cmd) => {
|
|
321
|
-
const name = cmd.name();
|
|
322
|
-
const desc = (cmd.summary() || cmd.description() || "").replace(
|
|
323
|
-
/'/g,
|
|
324
|
-
"\\'"
|
|
325
|
-
);
|
|
326
|
-
return `'${name}:${desc}'`;
|
|
327
|
-
})
|
|
328
|
-
.join(" ");
|
|
329
|
-
|
|
330
|
-
// Generate option completions for each command
|
|
331
|
-
const commandCases = program.commands
|
|
332
|
-
.filter((cmd) => cmd.name() !== "completion")
|
|
333
|
-
.map((cmd) => {
|
|
334
|
-
const options = cmd.options
|
|
335
|
-
.filter((opt) => opt.flags !== "-h, --help") // Exclude help
|
|
336
|
-
.map((opt) => {
|
|
337
|
-
// Parse flags like "-v, --verbose" or "--dry-run"
|
|
338
|
-
const flags = opt.flags.split(",").map((f) => f.trim());
|
|
339
|
-
const desc = (opt.description || "")
|
|
340
|
-
.replace(/'/g, "\\'")
|
|
341
|
-
.replace(/\n/g, " ");
|
|
342
|
-
|
|
343
|
-
// For options with arguments like "--sync-server <url>"
|
|
344
|
-
// Extract just the flag part
|
|
345
|
-
const cleanFlags = flags.map((f) => f.split(/\s+/)[0]);
|
|
275
|
+
.command("branches")
|
|
276
|
+
.description("List branches")
|
|
277
|
+
.action(async () => {
|
|
278
|
+
const { current, names } = await listBranches(process.cwd());
|
|
279
|
+
for (const n of names) {
|
|
280
|
+
process.stdout.write(`${n === current ? "* " : " "}${n}\n`);
|
|
281
|
+
}
|
|
282
|
+
});
|
|
346
283
|
|
|
347
|
-
if (cleanFlags.length > 1) {
|
|
348
|
-
// Multiple flags (short and long): '(-v --verbose)'{-v,--verbose}'[description]'
|
|
349
|
-
const short = cleanFlags[0];
|
|
350
|
-
const long = cleanFlags[1];
|
|
351
|
-
return `'(${short} ${long})'{${short},${long}}'[${desc}]'`;
|
|
352
|
-
} else {
|
|
353
|
-
// Single flag: '--flag[description]'
|
|
354
|
-
return `'${cleanFlags[0]}[${desc}]'`;
|
|
355
|
-
}
|
|
356
|
-
})
|
|
357
|
-
.join(" \\\n ");
|
|
358
|
-
|
|
359
|
-
return options
|
|
360
|
-
? ` ${cmd.name()})
|
|
361
|
-
_arguments \\
|
|
362
|
-
${options}
|
|
363
|
-
;;`
|
|
364
|
-
: "";
|
|
365
|
-
})
|
|
366
|
-
.filter(Boolean)
|
|
367
|
-
.join("\n");
|
|
368
|
-
|
|
369
|
-
const completionScript = `
|
|
370
|
-
# pushwork completion for zsh
|
|
371
|
-
_pushwork() {
|
|
372
|
-
local -a commands
|
|
373
|
-
commands=(${commands})
|
|
374
|
-
|
|
375
|
-
_arguments -C \\
|
|
376
|
-
'1: :->command' \\
|
|
377
|
-
'*::arg:->args'
|
|
378
|
-
|
|
379
|
-
case $state in
|
|
380
|
-
command)
|
|
381
|
-
_describe 'command' commands
|
|
382
|
-
;;
|
|
383
|
-
args)
|
|
384
|
-
case $words[1] in
|
|
385
|
-
${commandCases}
|
|
386
|
-
esac
|
|
387
|
-
;;
|
|
388
|
-
esac
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
compdef _pushwork pushwork
|
|
392
|
-
`.trim();
|
|
393
|
-
|
|
394
|
-
console.log(completionScript);
|
|
395
|
-
});
|
|
396
|
-
|
|
397
|
-
process.on("unhandledRejection", (error) => {
|
|
398
|
-
console.log(chalk.bgRed.white(" ERROR "));
|
|
399
|
-
if (error instanceof Error && error.stack) {
|
|
400
|
-
console.log(chalk.red(error.stack));
|
|
401
|
-
} else {
|
|
402
|
-
console.error(chalk.red(error));
|
|
403
|
-
}
|
|
404
|
-
process.exit(1);
|
|
405
|
-
});
|
|
406
|
-
|
|
407
|
-
// Configure help colors using Commander v13's built-in color support
|
|
408
284
|
program
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
.filter((opt) => opt.flags !== "-h, --help")
|
|
418
|
-
.map((opt) => opt.short || opt.long)
|
|
419
|
-
.join(", ");
|
|
420
|
-
|
|
421
|
-
const name = chalk.white(cmd.name());
|
|
422
|
-
const args = cmd.registeredArguments
|
|
423
|
-
.map((arg) =>
|
|
424
|
-
arg.required
|
|
425
|
-
? chalk.cyan(`<${arg.name()}>`)
|
|
426
|
-
: chalk.dim(`[${arg.name()}]`)
|
|
427
|
-
)
|
|
428
|
-
.join(" ");
|
|
429
|
-
|
|
430
|
-
return [name, args, opts && chalk.dim(`[${opts}]`)]
|
|
431
|
-
.filter(Boolean)
|
|
432
|
-
.join(" ");
|
|
433
|
-
},
|
|
434
|
-
})
|
|
435
|
-
.addHelpText(
|
|
436
|
-
"after",
|
|
437
|
-
chalk.dim(
|
|
438
|
-
'\nEnable tab completion by adding this to your ~/.zshrc:\neval "$(pushwork completion)"'
|
|
439
|
-
)
|
|
440
|
-
);
|
|
441
|
-
|
|
442
|
-
program.parseAsync();
|
|
285
|
+
.parseAsync(process.argv)
|
|
286
|
+
.then(() => process.exit(0))
|
|
287
|
+
.catch((err) => {
|
|
288
|
+
process.stderr.write(
|
|
289
|
+
`pushwork: ${err instanceof Error ? err.message : String(err)}\n`,
|
|
290
|
+
);
|
|
291
|
+
process.exit(1);
|
|
292
|
+
});
|