pushwork 1.2.3 → 1.2.4
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/browser/browser-sync-engine.d.ts +64 -0
- package/dist/browser/browser-sync-engine.d.ts.map +1 -0
- package/dist/browser/browser-sync-engine.js +303 -0
- package/dist/browser/browser-sync-engine.js.map +1 -0
- package/dist/browser/filesystem-adapter.d.ts +84 -0
- package/dist/browser/filesystem-adapter.d.ts.map +1 -0
- package/dist/browser/filesystem-adapter.js +413 -0
- package/dist/browser/filesystem-adapter.js.map +1 -0
- package/dist/browser/index.d.ts +36 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +90 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/types.d.ts +70 -0
- package/dist/browser/types.d.ts.map +1 -0
- package/dist/browser/types.js +6 -0
- package/dist/browser/types.js.map +1 -0
- package/dist/cli/commands.d.ts +6 -0
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +114 -4
- package/dist/cli/commands.js.map +1 -1
- package/dist/cli/output.d.ts +75 -0
- package/dist/cli/output.d.ts.map +1 -0
- package/dist/cli/output.js +182 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/config/remote-manager.d.ts +65 -0
- package/dist/config/remote-manager.d.ts.map +1 -0
- package/dist/config/remote-manager.js +243 -0
- package/dist/config/remote-manager.js.map +1 -0
- package/dist/config.d.ts +16 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +83 -0
- package/dist/config.js.map +1 -0
- package/dist/core/isomorphic-snapshot.d.ts +58 -0
- package/dist/core/isomorphic-snapshot.d.ts.map +1 -0
- package/dist/core/isomorphic-snapshot.js +204 -0
- package/dist/core/isomorphic-snapshot.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/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/platform/browser-filesystem.d.ts +26 -0
- package/dist/platform/browser-filesystem.d.ts.map +1 -0
- package/dist/platform/browser-filesystem.js +91 -0
- package/dist/platform/browser-filesystem.js.map +1 -0
- package/dist/platform/filesystem.d.ts +29 -0
- package/dist/platform/filesystem.d.ts.map +1 -0
- package/dist/platform/filesystem.js +65 -0
- package/dist/platform/filesystem.js.map +1 -0
- package/dist/platform/node-filesystem.d.ts +21 -0
- package/dist/platform/node-filesystem.d.ts.map +1 -0
- package/dist/platform/node-filesystem.js +93 -0
- package/dist/platform/node-filesystem.js.map +1 -0
- package/dist/polyfill-node-window.d.ts +13 -0
- package/dist/polyfill-node-window.d.ts.map +1 -0
- package/dist/polyfill-node-window.js +17 -0
- package/dist/polyfill-node-window.js.map +1 -0
- package/dist/pushwork.d.ts +97 -0
- package/dist/pushwork.d.ts.map +1 -0
- package/dist/pushwork.js +726 -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 +38 -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 +92 -0
- package/dist/shapes/vfs.js.map +1 -0
- package/dist/snarf.d.ts +21 -0
- package/dist/snarf.d.ts.map +1 -0
- package/dist/snarf.js +117 -0
- package/dist/snarf.js.map +1 -0
- package/dist/utils/fs-browser.d.ts +57 -0
- package/dist/utils/fs-browser.d.ts.map +1 -0
- package/dist/utils/fs-browser.js +311 -0
- package/dist/utils/fs-browser.js.map +1 -0
- package/dist/utils/fs-node.d.ts +53 -0
- package/dist/utils/fs-node.d.ts.map +1 -0
- package/dist/utils/fs-node.js +220 -0
- package/dist/utils/fs-node.js.map +1 -0
- package/dist/utils/isomorphic.d.ts +29 -0
- package/dist/utils/isomorphic.d.ts.map +1 -0
- package/dist/utils/isomorphic.js +139 -0
- package/dist/utils/isomorphic.js.map +1 -0
- package/dist/utils/pure.d.ts +25 -0
- package/dist/utils/pure.d.ts.map +1 -0
- package/dist/utils/pure.js +112 -0
- package/dist/utils/pure.js.map +1 -0
- package/dist/utils/repo-factory.d.ts.map +1 -1
- package/dist/utils/repo-factory.js +0 -1
- package/dist/utils/repo-factory.js.map +1 -1
- package/dist/version.d.ts +11 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +93 -0
- package/dist/version.js.map +1 -0
- package/package.json +5 -5
- package/src/utils/repo-factory.ts +78 -85
package/dist/pushwork.js
ADDED
|
@@ -0,0 +1,726 @@
|
|
|
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.init = init;
|
|
37
|
+
exports.clone = clone;
|
|
38
|
+
exports.url = url;
|
|
39
|
+
exports.sync = sync;
|
|
40
|
+
exports.nuclearizeRepo = nuclearizeRepo;
|
|
41
|
+
exports.save = save;
|
|
42
|
+
exports.heads = heads;
|
|
43
|
+
exports.status = status;
|
|
44
|
+
exports.diff = diff;
|
|
45
|
+
exports.cutWorkdir = cutWorkdir;
|
|
46
|
+
exports.pasteSnarf = pasteSnarf;
|
|
47
|
+
exports.showSnarfs = showSnarfs;
|
|
48
|
+
const fs = __importStar(require("fs/promises"));
|
|
49
|
+
const path = __importStar(require("path"));
|
|
50
|
+
const Automerge = __importStar(require("@automerge/automerge"));
|
|
51
|
+
const automerge_repo_1 = require("@automerge/automerge-repo");
|
|
52
|
+
const config_js_1 = require("./config.js");
|
|
53
|
+
const ignore_js_1 = require("./ignore.js");
|
|
54
|
+
const fs_tree_js_1 = require("./fs-tree.js");
|
|
55
|
+
const log_js_1 = require("./log.js");
|
|
56
|
+
const repo_js_1 = require("./repo.js");
|
|
57
|
+
const snarf_js_1 = require("./snarf.js");
|
|
58
|
+
const index_js_1 = require("./shapes/index.js");
|
|
59
|
+
const dlog = (0, log_js_1.log)("pushwork");
|
|
60
|
+
const DEFAULT_ARTIFACT_DIRECTORIES = ["dist"];
|
|
61
|
+
async function init(opts) {
|
|
62
|
+
const root = path.resolve(opts.dir);
|
|
63
|
+
const online = opts.online ?? true;
|
|
64
|
+
dlog("init root=%s backend=%s shape=%s online=%s", root, opts.backend, opts.shape, online);
|
|
65
|
+
if (await (0, config_js_1.configExists)(root)) {
|
|
66
|
+
throw new Error(`pushwork already initialized at ${root}`);
|
|
67
|
+
}
|
|
68
|
+
const artifactDirs = normalizeDirs(opts.artifactDirectories ?? DEFAULT_ARTIFACT_DIRECTORIES);
|
|
69
|
+
dlog("init artifactDirs=%o", artifactDirs);
|
|
70
|
+
await fs.mkdir((0, config_js_1.pushworkDir)(root), { recursive: true });
|
|
71
|
+
const repo = await (0, repo_js_1.openRepo)(opts.backend, (0, config_js_1.storageDir)(root), { offline: !online });
|
|
72
|
+
try {
|
|
73
|
+
const shape = await (0, index_js_1.resolveShape)(opts.shape);
|
|
74
|
+
const ig = await (0, ignore_js_1.loadIgnore)(root);
|
|
75
|
+
const fsFiles = await (0, fs_tree_js_1.walkDir)(root, ig);
|
|
76
|
+
dlog("init walked %d files", fsFiles.size);
|
|
77
|
+
const title = path.basename(root) || undefined;
|
|
78
|
+
const tree = await pushFiles(repo, fsFiles, undefined, artifactDirs);
|
|
79
|
+
const folderUrl = await shape.encode({ repo, tree, title });
|
|
80
|
+
dlog("init encoded folder=%s title=%s", folderUrl, title);
|
|
81
|
+
const folderHandle = await repo.find(folderUrl);
|
|
82
|
+
if (online) {
|
|
83
|
+
await (0, repo_js_1.waitForSync)(folderHandle, { minMs: 3000, idleMs: 1500, maxMs: 15000 });
|
|
84
|
+
stampLastSyncAt(folderHandle);
|
|
85
|
+
await (0, repo_js_1.waitForSync)(folderHandle, { idleMs: 1500, maxMs: 10000 });
|
|
86
|
+
}
|
|
87
|
+
await (0, config_js_1.writeConfig)(root, {
|
|
88
|
+
version: config_js_1.CONFIG_VERSION,
|
|
89
|
+
rootUrl: folderUrl,
|
|
90
|
+
backend: opts.backend,
|
|
91
|
+
shape: opts.shape,
|
|
92
|
+
artifactDirectories: artifactDirs,
|
|
93
|
+
});
|
|
94
|
+
dlog("init complete: rootUrl=%s", folderUrl);
|
|
95
|
+
return folderUrl;
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
await repo.shutdown();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async function clone(opts) {
|
|
102
|
+
if (!(0, automerge_repo_1.isValidAutomergeUrl)(opts.url)) {
|
|
103
|
+
throw new Error(`invalid automerge URL: ${opts.url}`);
|
|
104
|
+
}
|
|
105
|
+
const root = path.resolve(opts.dir);
|
|
106
|
+
dlog("clone url=%s root=%s backend=%s shape=%s", opts.url, root, opts.backend, opts.shape);
|
|
107
|
+
await fs.mkdir(root, { recursive: true });
|
|
108
|
+
if (await (0, config_js_1.configExists)(root)) {
|
|
109
|
+
throw new Error(`pushwork already initialized at ${root}`);
|
|
110
|
+
}
|
|
111
|
+
const artifactDirs = normalizeDirs(opts.artifactDirectories ?? DEFAULT_ARTIFACT_DIRECTORIES);
|
|
112
|
+
await fs.mkdir((0, config_js_1.pushworkDir)(root), { recursive: true });
|
|
113
|
+
const online = opts.online ?? true;
|
|
114
|
+
const repo = await (0, repo_js_1.openRepo)(opts.backend, (0, config_js_1.storageDir)(root), { offline: !online });
|
|
115
|
+
try {
|
|
116
|
+
const shape = await (0, index_js_1.resolveShape)(opts.shape);
|
|
117
|
+
let folderHandle = await repo.find(opts.url);
|
|
118
|
+
if (online) {
|
|
119
|
+
await (0, repo_js_1.waitForSync)(folderHandle, { idleMs: 1500, maxMs: 15000 });
|
|
120
|
+
}
|
|
121
|
+
let storedUrl = opts.url;
|
|
122
|
+
const branchesDoc = asBranchesDoc(folderHandle.doc());
|
|
123
|
+
if (branchesDoc) {
|
|
124
|
+
if (!opts.onBranchesDoc) {
|
|
125
|
+
throw new Error(`URL ${opts.url} is a legacy branches doc; pushwork no longer supports branches. Provide an onBranchesDoc callback (or use the CLI, which will prompt you to pick a branch).`);
|
|
126
|
+
}
|
|
127
|
+
const branches = Object.entries(branchesDoc.branches).map(([name, url]) => ({ name, url }));
|
|
128
|
+
const chosenUrl = await opts.onBranchesDoc({
|
|
129
|
+
title: branchesDoc.title,
|
|
130
|
+
branches,
|
|
131
|
+
});
|
|
132
|
+
dlog("clone branches doc → chose %s", chosenUrl);
|
|
133
|
+
folderHandle = await repo.find(chosenUrl);
|
|
134
|
+
if (online) {
|
|
135
|
+
await (0, repo_js_1.waitForSync)(folderHandle, { idleMs: 1500, maxMs: 15000 });
|
|
136
|
+
}
|
|
137
|
+
storedUrl = chosenUrl;
|
|
138
|
+
}
|
|
139
|
+
const tree = await shape.decode({ repo, root: folderHandle });
|
|
140
|
+
await materializeTree(repo, root, tree);
|
|
141
|
+
await (0, config_js_1.writeConfig)(root, {
|
|
142
|
+
version: config_js_1.CONFIG_VERSION,
|
|
143
|
+
rootUrl: storedUrl,
|
|
144
|
+
backend: opts.backend,
|
|
145
|
+
shape: opts.shape,
|
|
146
|
+
artifactDirectories: artifactDirs,
|
|
147
|
+
});
|
|
148
|
+
dlog("clone complete");
|
|
149
|
+
}
|
|
150
|
+
finally {
|
|
151
|
+
await repo.shutdown();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function asBranchesDoc(doc) {
|
|
155
|
+
if (!doc || typeof doc !== "object")
|
|
156
|
+
return null;
|
|
157
|
+
const meta = doc["@patchwork"];
|
|
158
|
+
if (!meta || typeof meta !== "object")
|
|
159
|
+
return null;
|
|
160
|
+
if (meta.type !== "branches")
|
|
161
|
+
return null;
|
|
162
|
+
const branches = doc.branches;
|
|
163
|
+
if (!branches || typeof branches !== "object")
|
|
164
|
+
return null;
|
|
165
|
+
return {
|
|
166
|
+
title: meta.title,
|
|
167
|
+
branches: branches,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
async function url(cwd) {
|
|
171
|
+
const config = await (0, config_js_1.readConfig)(path.resolve(cwd));
|
|
172
|
+
return config.rootUrl;
|
|
173
|
+
}
|
|
174
|
+
async function sync(cwd, opts = {}) {
|
|
175
|
+
if (opts.nuclear) {
|
|
176
|
+
await nuclearizeRepo(cwd);
|
|
177
|
+
await publishCurrentTree(cwd);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
await commitWorkdir(cwd, { online: true });
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Open an online repo, subscribe the root folder and every file leaf so the
|
|
184
|
+
* network adapter announces them to peers, then wait for the local heads to
|
|
185
|
+
* settle. No decode/diff/encode — used after nuclearizeRepo, where every doc
|
|
186
|
+
* is freshly created locally and the server has nothing to merge in.
|
|
187
|
+
*/
|
|
188
|
+
async function publishCurrentTree(cwd) {
|
|
189
|
+
const root = path.resolve(cwd);
|
|
190
|
+
const config = await (0, config_js_1.readConfig)(root);
|
|
191
|
+
dlog("publish root=%s", root);
|
|
192
|
+
const repo = await (0, repo_js_1.openRepo)(config.backend, (0, config_js_1.storageDir)(root), { offline: false });
|
|
193
|
+
try {
|
|
194
|
+
const shape = await (0, index_js_1.resolveShape)(config.shape);
|
|
195
|
+
const folderHandle = await repo.find(config.rootUrl);
|
|
196
|
+
const tree = await shape.decode({ repo, root: folderHandle });
|
|
197
|
+
// Touch every leaf so the network adapter knows to push it.
|
|
198
|
+
for (const [, fileUrl] of (0, index_js_1.flattenLeaves)(tree)) {
|
|
199
|
+
await repo.find(fileUrl);
|
|
200
|
+
}
|
|
201
|
+
stampLastSyncAt(folderHandle);
|
|
202
|
+
await (0, repo_js_1.waitForSync)(folderHandle, {
|
|
203
|
+
minMs: 3000,
|
|
204
|
+
idleMs: 1500,
|
|
205
|
+
maxMs: 15000,
|
|
206
|
+
});
|
|
207
|
+
dlog("publish complete");
|
|
208
|
+
}
|
|
209
|
+
finally {
|
|
210
|
+
await repo.shutdown();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Re-create every UnixFileEntry doc this repo references with a fresh URL,
|
|
215
|
+
* then rewrite the existing folder doc's leaves to point at the new file
|
|
216
|
+
* URLs. The folder doc URL itself is preserved so anyone holding it keeps
|
|
217
|
+
* tracking this repo. Offline; the next sync publishes the new file docs
|
|
218
|
+
* and the rewritten folder doc to the server.
|
|
219
|
+
*
|
|
220
|
+
* The previous file-doc URLs are orphaned from this repo's perspective.
|
|
221
|
+
* Anyone holding one of those URLs directly continues to work from it;
|
|
222
|
+
* this client just stops referencing them.
|
|
223
|
+
*/
|
|
224
|
+
async function nuclearizeRepo(cwd) {
|
|
225
|
+
const root = path.resolve(cwd);
|
|
226
|
+
const config = await (0, config_js_1.readConfig)(root);
|
|
227
|
+
dlog("nuclear root=%s rootUrl=%s", root, config.rootUrl);
|
|
228
|
+
const repo = await (0, repo_js_1.openRepo)(config.backend, (0, config_js_1.storageDir)(root), { offline: true });
|
|
229
|
+
try {
|
|
230
|
+
const shape = await (0, index_js_1.resolveShape)(config.shape);
|
|
231
|
+
const folderHandle = await repo.find(config.rootUrl);
|
|
232
|
+
const title = path.basename(root) || undefined;
|
|
233
|
+
const oldTree = await shape.decode({ repo, root: folderHandle });
|
|
234
|
+
// For each leaf: read content, create a fresh UnixFileEntry doc.
|
|
235
|
+
const newTree = (0, index_js_1.newDir)();
|
|
236
|
+
for (const [posixPath, fileUrl] of (0, index_js_1.flattenLeaves)(oldTree)) {
|
|
237
|
+
const bare = (0, index_js_1.stripHeads)(fileUrl);
|
|
238
|
+
const oldFileHandle = await repo.find(bare);
|
|
239
|
+
const oldDoc = oldFileHandle.doc();
|
|
240
|
+
const newFileHandle = repo.create({
|
|
241
|
+
"@patchwork": { type: "file" },
|
|
242
|
+
name: oldDoc.name,
|
|
243
|
+
extension: oldDoc.extension,
|
|
244
|
+
mimeType: oldDoc.mimeType,
|
|
245
|
+
content: oldDoc.content,
|
|
246
|
+
});
|
|
247
|
+
let finalUrl = newFileHandle.url;
|
|
248
|
+
if ((0, index_js_1.isInArtifactDir)(posixPath, config.artifactDirectories)) {
|
|
249
|
+
finalUrl = (0, index_js_1.pinUrl)(newFileHandle);
|
|
250
|
+
}
|
|
251
|
+
(0, index_js_1.setFileAt)(newTree, posixPath.split("/").filter(Boolean), finalUrl);
|
|
252
|
+
}
|
|
253
|
+
// Mutate the existing folder doc in place — same URL, new file leaves.
|
|
254
|
+
await shape.encode({
|
|
255
|
+
repo,
|
|
256
|
+
tree: newTree,
|
|
257
|
+
previousRoot: folderHandle,
|
|
258
|
+
title,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
finally {
|
|
262
|
+
await repo.shutdown();
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
async function save(cwd) {
|
|
266
|
+
await commitWorkdir(cwd, { online: false });
|
|
267
|
+
}
|
|
268
|
+
async function commitWorkdir(cwd, { online }) {
|
|
269
|
+
const root = path.resolve(cwd);
|
|
270
|
+
const config = await (0, config_js_1.readConfig)(root);
|
|
271
|
+
dlog("commit online=%s root=%s", online, root);
|
|
272
|
+
const repo = await (0, repo_js_1.openRepo)(config.backend, (0, config_js_1.storageDir)(root), {
|
|
273
|
+
offline: !online,
|
|
274
|
+
});
|
|
275
|
+
try {
|
|
276
|
+
const shape = await (0, index_js_1.resolveShape)(config.shape);
|
|
277
|
+
const folderHandle = await repo.find(config.rootUrl);
|
|
278
|
+
const previousTree = await shape.decode({ repo, root: folderHandle });
|
|
279
|
+
const previousFiles = await readFileBytes(repo, previousTree);
|
|
280
|
+
const ig = await (0, ignore_js_1.loadIgnore)(root);
|
|
281
|
+
const fsFiles = await (0, fs_tree_js_1.walkDir)(root, ig);
|
|
282
|
+
const newTree = await pushFiles(repo, fsFiles, previousFiles, config.artifactDirectories);
|
|
283
|
+
const changed = !sameTree(previousTree, newTree);
|
|
284
|
+
dlog("commit tree changed: %s", changed);
|
|
285
|
+
if (changed) {
|
|
286
|
+
await shape.encode({ repo, tree: newTree, previousRoot: folderHandle });
|
|
287
|
+
}
|
|
288
|
+
if (online) {
|
|
289
|
+
await (0, repo_js_1.waitForSync)(folderHandle, {
|
|
290
|
+
minMs: 3000,
|
|
291
|
+
idleMs: 1500,
|
|
292
|
+
maxMs: 15000,
|
|
293
|
+
});
|
|
294
|
+
// After peer changes have settled, refresh the folder doc so its
|
|
295
|
+
// pinned (artifact) leaves reference each file doc's current
|
|
296
|
+
// heads. Bare URLs already track current heads implicitly.
|
|
297
|
+
const refreshed = await refreshFolderPins(repo, folderHandle, shape, config.artifactDirectories);
|
|
298
|
+
// Always stamp lastSyncAt — a sync is also a checkpoint that
|
|
299
|
+
// "we reconciled with the server at this time" — and let any
|
|
300
|
+
// resulting changes flush.
|
|
301
|
+
stampLastSyncAt(folderHandle);
|
|
302
|
+
await (0, repo_js_1.waitForSync)(folderHandle, {
|
|
303
|
+
idleMs: 1500,
|
|
304
|
+
maxMs: refreshed ? 10000 : 5000,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
const finalTree = await shape.decode({ repo, root: folderHandle });
|
|
308
|
+
await materializeTree(repo, root, finalTree);
|
|
309
|
+
dlog("commit complete");
|
|
310
|
+
}
|
|
311
|
+
finally {
|
|
312
|
+
await repo.shutdown();
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* List the current Automerge heads for the root folder doc and every file
|
|
317
|
+
* leaf it references. Offline; never contacts a sync server.
|
|
318
|
+
*
|
|
319
|
+
* `pathspec` filters results: exact match, or prefix match against a folder
|
|
320
|
+
* (e.g. "src" or "src/" matches "src/index.ts"). Pass "/" to show only the
|
|
321
|
+
* root folder doc.
|
|
322
|
+
*/
|
|
323
|
+
async function heads(cwd, pathspec) {
|
|
324
|
+
const root = path.resolve(cwd);
|
|
325
|
+
const config = await (0, config_js_1.readConfig)(root);
|
|
326
|
+
const repo = await (0, repo_js_1.openRepo)(config.backend, (0, config_js_1.storageDir)(root), { offline: true });
|
|
327
|
+
try {
|
|
328
|
+
const shape = await (0, index_js_1.resolveShape)(config.shape);
|
|
329
|
+
const folderHandle = await repo.find(config.rootUrl);
|
|
330
|
+
const tree = await shape.decode({ repo, root: folderHandle });
|
|
331
|
+
const out = [];
|
|
332
|
+
const matches = (p) => matchesPathspec(p, pathspec);
|
|
333
|
+
if (matches("/")) {
|
|
334
|
+
out.push({
|
|
335
|
+
path: "/",
|
|
336
|
+
url: config.rootUrl,
|
|
337
|
+
heads: folderHandle.heads() ?? [],
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
for (const [posixPath, fileUrl] of (0, index_js_1.flattenLeaves)(tree)) {
|
|
341
|
+
if (!matches(posixPath))
|
|
342
|
+
continue;
|
|
343
|
+
const handle = await repo.find(fileUrl);
|
|
344
|
+
out.push({
|
|
345
|
+
path: posixPath,
|
|
346
|
+
url: fileUrl,
|
|
347
|
+
heads: handle.heads() ?? [],
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
out.sort((a, b) => a.path.localeCompare(b.path));
|
|
351
|
+
return out;
|
|
352
|
+
}
|
|
353
|
+
finally {
|
|
354
|
+
await repo.shutdown();
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
function matchesPathspec(path, spec) {
|
|
358
|
+
if (!spec)
|
|
359
|
+
return true;
|
|
360
|
+
if (spec === "/")
|
|
361
|
+
return path === "/";
|
|
362
|
+
const trimmed = spec.endsWith("/") ? spec.slice(0, -1) : spec;
|
|
363
|
+
if (path === trimmed)
|
|
364
|
+
return true;
|
|
365
|
+
return path.startsWith(trimmed + "/");
|
|
366
|
+
}
|
|
367
|
+
async function status(cwd) {
|
|
368
|
+
const root = path.resolve(cwd);
|
|
369
|
+
const config = await (0, config_js_1.readConfig)(root);
|
|
370
|
+
const repo = await (0, repo_js_1.openRepo)(config.backend, (0, config_js_1.storageDir)(root), { offline: true });
|
|
371
|
+
try {
|
|
372
|
+
const shape = await (0, index_js_1.resolveShape)(config.shape);
|
|
373
|
+
const folderHandle = await repo.find(config.rootUrl);
|
|
374
|
+
const previousTree = await shape.decode({ repo, root: folderHandle });
|
|
375
|
+
const previousFiles = await readFileBytes(repo, previousTree);
|
|
376
|
+
const ig = await (0, ignore_js_1.loadIgnore)(root);
|
|
377
|
+
const fsFiles = await (0, fs_tree_js_1.walkDir)(root, ig);
|
|
378
|
+
const diff = computeDiff(previousFiles, fsFiles);
|
|
379
|
+
return { diff };
|
|
380
|
+
}
|
|
381
|
+
finally {
|
|
382
|
+
await repo.shutdown();
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
async function diff(cwd, limitToPath) {
|
|
386
|
+
const root = path.resolve(cwd);
|
|
387
|
+
const config = await (0, config_js_1.readConfig)(root);
|
|
388
|
+
const repo = await (0, repo_js_1.openRepo)(config.backend, (0, config_js_1.storageDir)(root), { offline: true });
|
|
389
|
+
try {
|
|
390
|
+
const shape = await (0, index_js_1.resolveShape)(config.shape);
|
|
391
|
+
const folderHandle = await repo.find(config.rootUrl);
|
|
392
|
+
const previousTree = await shape.decode({ repo, root: folderHandle });
|
|
393
|
+
const previousFiles = await readFileBytes(repo, previousTree);
|
|
394
|
+
const ig = await (0, ignore_js_1.loadIgnore)(root);
|
|
395
|
+
const fsFiles = await (0, fs_tree_js_1.walkDir)(root, ig);
|
|
396
|
+
const out = [];
|
|
397
|
+
for (const [p, bytes] of fsFiles) {
|
|
398
|
+
if (limitToPath && p !== limitToPath)
|
|
399
|
+
continue;
|
|
400
|
+
const prev = previousFiles.get(p);
|
|
401
|
+
if (!prev) {
|
|
402
|
+
out.push({ path: p, kind: "added", after: bytes });
|
|
403
|
+
}
|
|
404
|
+
else if (!(0, fs_tree_js_1.byteEq)(prev.bytes, bytes)) {
|
|
405
|
+
out.push({ path: p, kind: "modified", before: prev.bytes, after: bytes });
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
for (const [p, prev] of previousFiles) {
|
|
409
|
+
if (limitToPath && p !== limitToPath)
|
|
410
|
+
continue;
|
|
411
|
+
if (!fsFiles.has(p))
|
|
412
|
+
out.push({ path: p, kind: "deleted", before: prev.bytes });
|
|
413
|
+
}
|
|
414
|
+
return out;
|
|
415
|
+
}
|
|
416
|
+
finally {
|
|
417
|
+
await repo.shutdown();
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Capture the working tree's changes against the saved state into a local
|
|
422
|
+
* snarf, then reset the working tree to the saved state. Snarfs live in
|
|
423
|
+
* `.pushwork/snarf/` and are never synced.
|
|
424
|
+
*/
|
|
425
|
+
async function cutWorkdir(cwd, opts = {}) {
|
|
426
|
+
const root = path.resolve(cwd);
|
|
427
|
+
const config = await (0, config_js_1.readConfig)(root);
|
|
428
|
+
dlog("cut root=%s name=%s", root, opts.name ?? "(unnamed)");
|
|
429
|
+
const repo = await (0, repo_js_1.openRepo)(config.backend, (0, config_js_1.storageDir)(root), { offline: true });
|
|
430
|
+
try {
|
|
431
|
+
const shape = await (0, index_js_1.resolveShape)(config.shape);
|
|
432
|
+
const folderHandle = await repo.find(config.rootUrl);
|
|
433
|
+
const previousTree = await shape.decode({ repo, root: folderHandle });
|
|
434
|
+
const previousFiles = await readFileBytes(repo, previousTree);
|
|
435
|
+
const ig = await (0, ignore_js_1.loadIgnore)(root);
|
|
436
|
+
const fsFiles = await (0, fs_tree_js_1.walkDir)(root, ig);
|
|
437
|
+
const entries = [];
|
|
438
|
+
for (const [p, bytes] of fsFiles) {
|
|
439
|
+
const prev = previousFiles.get(p);
|
|
440
|
+
if (!prev) {
|
|
441
|
+
entries.push({ path: p, kind: "added", contentBase64: (0, snarf_js_1.encodeBytes)(bytes) });
|
|
442
|
+
}
|
|
443
|
+
else if (!(0, fs_tree_js_1.byteEq)(prev.bytes, bytes)) {
|
|
444
|
+
entries.push({
|
|
445
|
+
path: p,
|
|
446
|
+
kind: "modified",
|
|
447
|
+
contentBase64: (0, snarf_js_1.encodeBytes)(bytes),
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
for (const [p] of previousFiles) {
|
|
452
|
+
if (!fsFiles.has(p))
|
|
453
|
+
entries.push({ path: p, kind: "deleted" });
|
|
454
|
+
}
|
|
455
|
+
if (entries.length === 0) {
|
|
456
|
+
throw new Error("nothing to cut: working tree clean");
|
|
457
|
+
}
|
|
458
|
+
entries.sort((a, b) => a.path.localeCompare(b.path));
|
|
459
|
+
const snarf = await (0, snarf_js_1.appendSnarf)(root, {
|
|
460
|
+
name: opts.name,
|
|
461
|
+
entries,
|
|
462
|
+
});
|
|
463
|
+
// Reset working tree to the saved state.
|
|
464
|
+
await materializeTree(repo, root, previousTree);
|
|
465
|
+
dlog("cut complete id=%d entries=%d", snarf.id, entries.length);
|
|
466
|
+
return { id: snarf.id, entries: entries.length };
|
|
467
|
+
}
|
|
468
|
+
finally {
|
|
469
|
+
await repo.shutdown();
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Apply a snarf on top of the current working tree, then remove the snarf
|
|
474
|
+
* entry. Refuses if the working tree has uncommitted changes (caller can
|
|
475
|
+
* `pushwork save` or `pushwork cut` first).
|
|
476
|
+
*/
|
|
477
|
+
async function pasteSnarf(cwd, selector) {
|
|
478
|
+
const root = path.resolve(cwd);
|
|
479
|
+
const config = await (0, config_js_1.readConfig)(root);
|
|
480
|
+
// Check the working tree is clean against the saved state.
|
|
481
|
+
const repo = await (0, repo_js_1.openRepo)(config.backend, (0, config_js_1.storageDir)(root), { offline: true });
|
|
482
|
+
try {
|
|
483
|
+
const shape = await (0, index_js_1.resolveShape)(config.shape);
|
|
484
|
+
const folderHandle = await repo.find(config.rootUrl);
|
|
485
|
+
const previousTree = await shape.decode({ repo, root: folderHandle });
|
|
486
|
+
const previousFiles = await readFileBytes(repo, previousTree);
|
|
487
|
+
const ig = await (0, ignore_js_1.loadIgnore)(root);
|
|
488
|
+
const fsFiles = await (0, fs_tree_js_1.walkDir)(root, ig);
|
|
489
|
+
const dirty = computeDiff(previousFiles, fsFiles);
|
|
490
|
+
if (dirty.added.length || dirty.modified.length || dirty.deleted.length) {
|
|
491
|
+
throw new Error("refusing to paste: working tree has uncommitted changes. run `pushwork save` or `pushwork cut` first.");
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
finally {
|
|
495
|
+
await repo.shutdown();
|
|
496
|
+
}
|
|
497
|
+
const snarf = await (0, snarf_js_1.takeSnarf)(root, selector);
|
|
498
|
+
if (!snarf) {
|
|
499
|
+
throw new Error(selector
|
|
500
|
+
? `no snarf matches "${selector}"`
|
|
501
|
+
: "nothing to paste: no snarfs");
|
|
502
|
+
}
|
|
503
|
+
for (const entry of snarf.entries) {
|
|
504
|
+
const target = path.join(root, fromPosix(entry.path));
|
|
505
|
+
if (entry.kind === "deleted") {
|
|
506
|
+
try {
|
|
507
|
+
await fs.unlink(target);
|
|
508
|
+
}
|
|
509
|
+
catch {
|
|
510
|
+
// already gone
|
|
511
|
+
}
|
|
512
|
+
await pruneEmptyDirs(root, path.dirname(fromPosix(entry.path)));
|
|
513
|
+
}
|
|
514
|
+
else if (entry.contentBase64 != null) {
|
|
515
|
+
const bytes = (0, snarf_js_1.decodeBytes)(entry.contentBase64);
|
|
516
|
+
await (0, fs_tree_js_1.writeFileAtomic)(target, bytes);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
dlog("paste complete id=%d entries=%d", snarf.id, snarf.entries.length);
|
|
520
|
+
return { id: snarf.id, name: snarf.name, entries: snarf.entries.length };
|
|
521
|
+
}
|
|
522
|
+
async function showSnarfs(cwd) {
|
|
523
|
+
return (0, snarf_js_1.listSnarfs)(path.resolve(cwd));
|
|
524
|
+
}
|
|
525
|
+
function stampLastSyncAt(handle) {
|
|
526
|
+
handle.change((d) => {
|
|
527
|
+
d.lastSyncAt = Date.now();
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
function normalizeDirs(dirs) {
|
|
531
|
+
const seen = new Set();
|
|
532
|
+
const out = [];
|
|
533
|
+
for (const d of dirs) {
|
|
534
|
+
const norm = (0, index_js_1.normalizeArtifactDir)(d);
|
|
535
|
+
if (!norm || seen.has(norm))
|
|
536
|
+
continue;
|
|
537
|
+
seen.add(norm);
|
|
538
|
+
out.push(norm);
|
|
539
|
+
}
|
|
540
|
+
return out;
|
|
541
|
+
}
|
|
542
|
+
function computeDiff(previous, current) {
|
|
543
|
+
const added = [];
|
|
544
|
+
const modified = [];
|
|
545
|
+
const deleted = [];
|
|
546
|
+
for (const [p, bytes] of current) {
|
|
547
|
+
const prev = previous.get(p);
|
|
548
|
+
if (!prev)
|
|
549
|
+
added.push(p);
|
|
550
|
+
else if (!(0, fs_tree_js_1.byteEq)(prev.bytes, bytes))
|
|
551
|
+
modified.push(p);
|
|
552
|
+
}
|
|
553
|
+
for (const p of previous.keys()) {
|
|
554
|
+
if (!current.has(p))
|
|
555
|
+
deleted.push(p);
|
|
556
|
+
}
|
|
557
|
+
added.sort();
|
|
558
|
+
modified.sort();
|
|
559
|
+
deleted.sort();
|
|
560
|
+
return { added, modified, deleted };
|
|
561
|
+
}
|
|
562
|
+
async function pushFiles(repo, fsFiles, previous, artifactDirs) {
|
|
563
|
+
const root = (0, index_js_1.newDir)();
|
|
564
|
+
let created = 0;
|
|
565
|
+
let updated = 0;
|
|
566
|
+
let unchanged = 0;
|
|
567
|
+
for (const [posixPath, bytes] of fsFiles) {
|
|
568
|
+
const segments = posixPath.split("/").filter(Boolean);
|
|
569
|
+
const isArtifact = (0, index_js_1.isInArtifactDir)(posixPath, artifactDirs);
|
|
570
|
+
const fresh = (0, index_js_1.makeFileEntry)(posixPath, bytes, isArtifact);
|
|
571
|
+
const prev = previous?.get(posixPath);
|
|
572
|
+
let baseUrl;
|
|
573
|
+
if (prev && (0, fs_tree_js_1.byteEq)(prev.bytes, bytes)) {
|
|
574
|
+
// Unchanged path: keep the existing file-doc URL. For artifacts
|
|
575
|
+
// we'll re-pin from the current heads below.
|
|
576
|
+
baseUrl = (0, index_js_1.stripHeads)(prev.url);
|
|
577
|
+
unchanged++;
|
|
578
|
+
}
|
|
579
|
+
else if (prev) {
|
|
580
|
+
// Changed path: mutate the existing file doc in place. This keeps
|
|
581
|
+
// the file URL stable across edits and avoids the propagation
|
|
582
|
+
// race where a brand-new file doc URL is referenced by the folder
|
|
583
|
+
// before its bytes have reached the sync server.
|
|
584
|
+
//
|
|
585
|
+
// For string content (text files) we use Automerge.updateText so
|
|
586
|
+
// concurrent character-level edits merge correctly. Bytes and
|
|
587
|
+
// ImmutableString are atomic — last writer wins on the field.
|
|
588
|
+
const refreshUrl = (0, index_js_1.stripHeads)(prev.url);
|
|
589
|
+
const handle = await repo.find(refreshUrl);
|
|
590
|
+
handle.change((d) => {
|
|
591
|
+
if (!(0, index_js_1.contentEquals)(d.content, fresh.content)) {
|
|
592
|
+
if (typeof d.content === "string" &&
|
|
593
|
+
typeof fresh.content === "string") {
|
|
594
|
+
Automerge.updateText(d, ["content"], fresh.content);
|
|
595
|
+
}
|
|
596
|
+
else {
|
|
597
|
+
d.content = fresh.content;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
if (d.extension !== fresh.extension)
|
|
601
|
+
d.extension = fresh.extension;
|
|
602
|
+
if (d.mimeType !== fresh.mimeType)
|
|
603
|
+
d.mimeType = fresh.mimeType;
|
|
604
|
+
if (d.name !== fresh.name)
|
|
605
|
+
d.name = fresh.name;
|
|
606
|
+
if (!d["@patchwork"])
|
|
607
|
+
d["@patchwork"] = { type: "file" };
|
|
608
|
+
});
|
|
609
|
+
baseUrl = refreshUrl;
|
|
610
|
+
updated++;
|
|
611
|
+
dlog("pushFiles updated %s url=%s artifact=%s bytes=%d", posixPath, baseUrl, isArtifact, bytes.length);
|
|
612
|
+
}
|
|
613
|
+
else {
|
|
614
|
+
// New path: create a fresh file doc.
|
|
615
|
+
const handle = repo.create(fresh);
|
|
616
|
+
baseUrl = handle.url;
|
|
617
|
+
created++;
|
|
618
|
+
dlog("pushFiles created %s url=%s artifact=%s bytes=%d", posixPath, baseUrl, isArtifact, bytes.length);
|
|
619
|
+
}
|
|
620
|
+
const finalUrl = isArtifact
|
|
621
|
+
? (0, index_js_1.pinUrl)(await repo.find(baseUrl))
|
|
622
|
+
: baseUrl;
|
|
623
|
+
(0, index_js_1.setFileAt)(root, segments, finalUrl);
|
|
624
|
+
}
|
|
625
|
+
dlog("pushFiles done: %d created, %d updated, %d unchanged", created, updated, unchanged);
|
|
626
|
+
return root;
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Re-pin every artifact leaf in the folder doc to its file doc's current
|
|
630
|
+
* heads. Bare (non-artifact) URLs are left as-is since they already track
|
|
631
|
+
* current heads implicitly. Returns true if any leaf URL was rewritten.
|
|
632
|
+
*/
|
|
633
|
+
async function refreshFolderPins(repo, folderHandle, shape, artifactDirs) {
|
|
634
|
+
const tree = await shape.decode({ repo, root: folderHandle });
|
|
635
|
+
const refreshed = (0, index_js_1.newDir)();
|
|
636
|
+
let changed = false;
|
|
637
|
+
for (const [posixPath, currentUrl] of (0, index_js_1.flattenLeaves)(tree)) {
|
|
638
|
+
const segments = posixPath.split("/").filter(Boolean);
|
|
639
|
+
let finalUrl = currentUrl;
|
|
640
|
+
if ((0, index_js_1.isInArtifactDir)(posixPath, artifactDirs)) {
|
|
641
|
+
const handle = await repo.find((0, index_js_1.stripHeads)(currentUrl));
|
|
642
|
+
const repinned = (0, index_js_1.pinUrl)(handle);
|
|
643
|
+
if (repinned !== currentUrl) {
|
|
644
|
+
finalUrl = repinned;
|
|
645
|
+
changed = true;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
(0, index_js_1.setFileAt)(refreshed, segments, finalUrl);
|
|
649
|
+
}
|
|
650
|
+
if (changed) {
|
|
651
|
+
dlog("refreshFolderPins: re-pinned artifacts to current heads");
|
|
652
|
+
await shape.encode({ repo, tree: refreshed, previousRoot: folderHandle });
|
|
653
|
+
}
|
|
654
|
+
return changed;
|
|
655
|
+
}
|
|
656
|
+
async function readFileBytes(repo, tree) {
|
|
657
|
+
const out = new Map();
|
|
658
|
+
for (const [posixPath, fileUrl] of (0, index_js_1.flattenLeaves)(tree)) {
|
|
659
|
+
const handle = await repo.find(fileUrl);
|
|
660
|
+
out.set(posixPath, {
|
|
661
|
+
url: fileUrl,
|
|
662
|
+
bytes: (0, index_js_1.contentToBytes)(handle.doc().content),
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
return out;
|
|
666
|
+
}
|
|
667
|
+
async function materializeTree(repo, root, tree) {
|
|
668
|
+
const desired = new Map();
|
|
669
|
+
for (const [posixPath, fileUrl] of (0, index_js_1.flattenLeaves)(tree)) {
|
|
670
|
+
const handle = await repo.find(fileUrl);
|
|
671
|
+
desired.set(posixPath, (0, index_js_1.contentToBytes)(handle.doc().content));
|
|
672
|
+
}
|
|
673
|
+
dlog("materialize desired: %d files", desired.size);
|
|
674
|
+
const ig = await (0, ignore_js_1.loadIgnore)(root);
|
|
675
|
+
const present = await (0, fs_tree_js_1.walkDir)(root, ig);
|
|
676
|
+
let written = 0;
|
|
677
|
+
let removed = 0;
|
|
678
|
+
for (const [posixPath, bytes] of desired) {
|
|
679
|
+
if ((0, fs_tree_js_1.byteEq)(present.get(posixPath), bytes))
|
|
680
|
+
continue;
|
|
681
|
+
await (0, fs_tree_js_1.writeFileAtomic)(path.join(root, fromPosix(posixPath)), bytes);
|
|
682
|
+
written++;
|
|
683
|
+
}
|
|
684
|
+
for (const posixPath of present.keys()) {
|
|
685
|
+
if (desired.has(posixPath))
|
|
686
|
+
continue;
|
|
687
|
+
try {
|
|
688
|
+
await fs.unlink(path.join(root, fromPosix(posixPath)));
|
|
689
|
+
removed++;
|
|
690
|
+
}
|
|
691
|
+
catch {
|
|
692
|
+
// already gone
|
|
693
|
+
}
|
|
694
|
+
await pruneEmptyDirs(root, path.dirname(fromPosix(posixPath)));
|
|
695
|
+
}
|
|
696
|
+
dlog("materialize done: %d written, %d removed", written, removed);
|
|
697
|
+
}
|
|
698
|
+
const fromPosix = (p) => p.split("/").join(path.sep);
|
|
699
|
+
async function pruneEmptyDirs(root, relDir) {
|
|
700
|
+
let dir = relDir;
|
|
701
|
+
while (dir && dir !== "." && dir !== path.sep) {
|
|
702
|
+
const full = path.join(root, dir);
|
|
703
|
+
try {
|
|
704
|
+
const entries = await fs.readdir(full);
|
|
705
|
+
if (entries.length > 0)
|
|
706
|
+
return;
|
|
707
|
+
await fs.rmdir(full);
|
|
708
|
+
}
|
|
709
|
+
catch {
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
dir = path.dirname(dir);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
function sameTree(a, b) {
|
|
716
|
+
const av = (0, index_js_1.flattenLeaves)(a);
|
|
717
|
+
const bv = (0, index_js_1.flattenLeaves)(b);
|
|
718
|
+
if (av.size !== bv.size)
|
|
719
|
+
return false;
|
|
720
|
+
for (const [k, v] of av) {
|
|
721
|
+
if (bv.get(k) !== v)
|
|
722
|
+
return false;
|
|
723
|
+
}
|
|
724
|
+
return true;
|
|
725
|
+
}
|
|
726
|
+
//# sourceMappingURL=pushwork.js.map
|