mnemonad-cli 0.3.1 → 0.3.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/README.md +24 -2
- package/bin/mnemonad.js +2 -1
- package/lib/commands/info.js +19 -0
- package/lib/commands/push.js +83 -0
- package/lib/jobs.js +98 -19
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -118,7 +118,7 @@ Every command prints the installed CLI version as its first line of output.
|
|
|
118
118
|
| `--pinata-jwt <jwt>` | Pinata JWT for IPFS offload of large files (or `PINATA_JWT` env var) |
|
|
119
119
|
| `--manifest` | Write/use `.mnemonad` manifest for faster change detection. With no stream-id given, every command recovers it from an existing `.mnemonad` in the folder instead of requiring it explicitly |
|
|
120
120
|
| `--force-snapshot` | Push a full snapshot regardless of prior history (repairs a corrupt stream) |
|
|
121
|
-
| `--detach` | `push`: build the next version here, send it from a background process, and return right away. Existing streams only. See "Push in the background" below |
|
|
121
|
+
| `--detach` | `push`: build the next version here, send it from a background process, and return right away. With `--index`, indexing and building move to the background too. Existing streams only. See "Push in the background" below |
|
|
122
122
|
| `--index` | `push`/`watch`: update the folder's search index before every push (takes `--db`/`--model`/`--chunk-size`/`--chunk-overlap`); if indexing fails, nothing is pushed |
|
|
123
123
|
| `--poll-interval <s>` | `watch`: seconds between remote version checks (default: `2`) |
|
|
124
124
|
| `--debounce <ms>` | `watch`: quiet period in ms before pushing after a local change (default: `1000`) |
|
|
@@ -231,7 +231,7 @@ mnemonad pull 123 ~/restored --version 1
|
|
|
231
231
|
### Inspect a stream
|
|
232
232
|
|
|
233
233
|
```bash
|
|
234
|
-
# check your wallet
|
|
234
|
+
# check your wallet, its MON balance, and the chain (no stream id needed)
|
|
235
235
|
mnemonad info
|
|
236
236
|
|
|
237
237
|
# full stream metadata + local sync status
|
|
@@ -241,6 +241,7 @@ mnemonad info 123 ~/my-data
|
|
|
241
241
|
```
|
|
242
242
|
chain: testnet
|
|
243
243
|
your wallet: 0x06bc6420b37a4898424429dcfa236f0065e12279
|
|
244
|
+
balance: 4.2183 MON
|
|
244
245
|
|
|
245
246
|
stream: 0x68d9736257f0d2da3c848666a4b36c4b9f18eda7e4dfba655599c47560d8a4b5
|
|
246
247
|
owner: 0x06bc6420b37a4898424429dcfa236f0065e12279 (you)
|
|
@@ -389,6 +390,27 @@ mnemonad push ~/my-data --manifest --detach
|
|
|
389
390
|
IPFS upload, can't be resumed. A paid but unused upload can be refunded after the gateway's
|
|
390
391
|
time limit, with the SDK's `MnemonadUploadGateway.refund()` (the CLI has no command for it).
|
|
391
392
|
|
|
393
|
+
**With `--index`, indexing goes to the background too.** The updated index is part of the
|
|
394
|
+
version being pushed, so the version can only be built once indexing is done, and sent once
|
|
395
|
+
it is built. `push --detach --index` therefore hands off all three, and returns after only the
|
|
396
|
+
checks that need no folder scan: the signer owns the stream, the credential unlocks it, and the
|
|
397
|
+
wallet isn't empty.
|
|
398
|
+
|
|
399
|
+
```bash
|
|
400
|
+
mnemonad push ~/my-data --manifest --index --detach
|
|
401
|
+
# stream: 0x68d9…
|
|
402
|
+
# indexing, building and sending in the background: job 3b9e07c1 (pid 41388)
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
The trade-offs, compared with a plain `--detach`:
|
|
406
|
+
|
|
407
|
+
- No version number, size or cost estimate is printed. `mnemonad info` shows them once the job
|
|
408
|
+
has built the version, and shows "no changes, nothing pushed" if the folder already matched.
|
|
409
|
+
- An indexing error, a model download that fails, and "no changes" show up in the job's status,
|
|
410
|
+
not in the terminal.
|
|
411
|
+
- The folder is not frozen when the command runs. The job reads it when it starts building, so
|
|
412
|
+
edits made before then are part of this version.
|
|
413
|
+
|
|
392
414
|
Jobs live in `~/.cache/mnemonad/jobs/` (set `MNEMONAD_JOBS_DIR` to move them). Finished ones are
|
|
393
415
|
removed after 14 days.
|
|
394
416
|
|
package/bin/mnemonad.js
CHANGED
|
@@ -108,7 +108,8 @@ Options:
|
|
|
108
108
|
--detach push: build the next version here, send it from a background process,
|
|
109
109
|
and return right away. Existing streams only (an id, or --manifest).
|
|
110
110
|
Later pushes and pulls of that stream wait for it; \`mnemonad info\`
|
|
111
|
-
shows how it went
|
|
111
|
+
shows how it went. With --index, indexing and building the version
|
|
112
|
+
move to the background too
|
|
112
113
|
--index push/watch: update the folder's search index (search_index.db)
|
|
113
114
|
before every push, so the stream is searchable right after a pull.
|
|
114
115
|
Takes --db/--model/--chunk-size/--chunk-overlap like \`index\`
|
package/lib/commands/info.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Mnemonad, { kekMethodOf } from 'mnemonad';
|
|
2
2
|
import { MonadSync } from 'monadsync';
|
|
3
|
+
import { formatEther } from 'viem';
|
|
3
4
|
import { makeChainClient } from '../chainClient.js';
|
|
4
5
|
import {
|
|
5
6
|
resolvePath,
|
|
@@ -19,6 +20,9 @@ export async function info(args) {
|
|
|
19
20
|
|
|
20
21
|
console.log(' chain: ', args.chain);
|
|
21
22
|
console.log(' your wallet: ', client.account?.address || '(none)');
|
|
23
|
+
if (client.account) {
|
|
24
|
+
console.log(' balance: ', await walletBalance(client));
|
|
25
|
+
}
|
|
22
26
|
|
|
23
27
|
// Peek at .mnemonad whenever a path is given, regardless of --manifest and regardless
|
|
24
28
|
// of whether an id was given — info is read-only, so surfacing what's there costs
|
|
@@ -170,3 +174,18 @@ export async function info(args) {
|
|
|
170
174
|
console.log(' latest remote: version', totalVersions);
|
|
171
175
|
}
|
|
172
176
|
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* The signing wallet's native balance, in the chain's own unit (MON on Monad, ETH on a local
|
|
180
|
+
* Hardhat node). A failed RPC read is shown as such rather than failing `info` — the balance
|
|
181
|
+
* is a convenience next to the stream report, not something the rest depends on.
|
|
182
|
+
*/
|
|
183
|
+
async function walletBalance(client) {
|
|
184
|
+
const symbol = client.chain.nativeCurrency?.symbol || 'MON';
|
|
185
|
+
try {
|
|
186
|
+
const wei = await client.publicClient.getBalance({ address: client.account.address });
|
|
187
|
+
return `${formatEther(wei)} ${symbol}`;
|
|
188
|
+
} catch {
|
|
189
|
+
return '(could not read)';
|
|
190
|
+
}
|
|
191
|
+
}
|
package/lib/commands/push.js
CHANGED
|
@@ -60,6 +60,10 @@ export async function push(args) {
|
|
|
60
60
|
if (args.rebuild && !args.index) {
|
|
61
61
|
throw userError('--rebuild only applies with --index (it starts the search index over)');
|
|
62
62
|
}
|
|
63
|
+
if (args.detach && args.index) {
|
|
64
|
+
await detachPrepare(args, destPath, streamId);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
63
67
|
if (args.index) {
|
|
64
68
|
console.log(args.rebuild ? ' rebuilding search index...' : ' updating search index...');
|
|
65
69
|
try {
|
|
@@ -190,6 +194,85 @@ export async function push(args) {
|
|
|
190
194
|
}
|
|
191
195
|
}
|
|
192
196
|
|
|
197
|
+
/**
|
|
198
|
+
* `push --detach --index`: indexing can't run here and still be "detached" — the updated index
|
|
199
|
+
* is part of the version being pushed, so building the patch has to wait for it, and the
|
|
200
|
+
* sending waits for the patch. So this hands off all three, and keeps only the checks that
|
|
201
|
+
* need no folder scan and no chain replay: the signer owns the stream, the credential unlocks
|
|
202
|
+
* it, and the wallet isn't empty. Everything else — "no changes", an indexing error, the
|
|
203
|
+
* version number — shows up in the job (`mnemonad info`).
|
|
204
|
+
*
|
|
205
|
+
* Unlike a plain `--detach`, the folder isn't frozen here: the job reads it when its own turn
|
|
206
|
+
* comes, so edits made before then go into this version.
|
|
207
|
+
*/
|
|
208
|
+
async function detachPrepare(args, destPath, streamId) {
|
|
209
|
+
const client = await makeChainClient(args);
|
|
210
|
+
requireWalletClient(client);
|
|
211
|
+
await waitForJobs(
|
|
212
|
+
{ signer: client.account.address, chain: args.chain },
|
|
213
|
+
{ reason: 'so the two pushes don\'t race for the same wallet nonce' },
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
// Flag combinations are checked here too, not left for the job to trip over.
|
|
217
|
+
resolveEncryptMethod(args);
|
|
218
|
+
|
|
219
|
+
const mn = new Mnemonad({
|
|
220
|
+
publicClient: client.publicClient,
|
|
221
|
+
walletClient: client.walletClient,
|
|
222
|
+
contractAddress: client.contractAddress,
|
|
223
|
+
id: streamId,
|
|
224
|
+
...resolveOffloadParams(args, client.account),
|
|
225
|
+
});
|
|
226
|
+
await assertStreamOwner(mn, client.account.address);
|
|
227
|
+
await unlockStream(mn, { password: args.password, signer: client.walletClient });
|
|
228
|
+
|
|
229
|
+
const balance = await client.publicClient.getBalance({ address: client.account.address });
|
|
230
|
+
if (balance === 0n) {
|
|
231
|
+
throw userError(`${client.account.address} has no MON to pay for the push. Nothing was started.`);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const job = await startPushJob(
|
|
235
|
+
{
|
|
236
|
+
mode: 'prepare',
|
|
237
|
+
streamId: formatStreamId(streamId),
|
|
238
|
+
streamKey: streamKey({ chain: args.chain, contractAddress: args.contractAddress, streamId }),
|
|
239
|
+
chain: args.chain,
|
|
240
|
+
signer: client.account.address,
|
|
241
|
+
folder: destPath,
|
|
242
|
+
baseLength: null,
|
|
243
|
+
version: null,
|
|
244
|
+
manifest: args.manifest ? {} : null,
|
|
245
|
+
args: {
|
|
246
|
+
chain: args.chain,
|
|
247
|
+
rpcUrl: args.rpcUrl,
|
|
248
|
+
contractAddress: args.contractAddress,
|
|
249
|
+
gatewayUrl: args.gatewayUrl,
|
|
250
|
+
presignUrl: args.presignUrl,
|
|
251
|
+
exclude: args.exclude,
|
|
252
|
+
compress: args.compress,
|
|
253
|
+
forceSnapshot: args.forceSnapshot,
|
|
254
|
+
searchDb: args.searchDb,
|
|
255
|
+
model: args.model,
|
|
256
|
+
chunkSize: args.chunkSize,
|
|
257
|
+
chunkOverlap: args.chunkOverlap,
|
|
258
|
+
rebuild: args.rebuild,
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
null,
|
|
262
|
+
{
|
|
263
|
+
key: args.key || process.env.MNEMONAD_KEY || null,
|
|
264
|
+
phrase: args.phrase,
|
|
265
|
+
password: args.password,
|
|
266
|
+
pinataJwt: args.pinataJwt,
|
|
267
|
+
},
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
console.log(' stream:', formatStreamId(streamId));
|
|
271
|
+
console.log(' indexing, building and sending in the background: job', job.id, '(pid ' + job.pid + ')');
|
|
272
|
+
console.log(' log: ', formatPath(job.dir + '/push.log'));
|
|
273
|
+
console.log(' check: mnemonad info', formatPath(destPath) + (args.manifest ? ' --manifest' : ''));
|
|
274
|
+
}
|
|
275
|
+
|
|
193
276
|
/**
|
|
194
277
|
* `push --detach`: everything up to a finished patch runs here, so every error a user can act
|
|
195
278
|
* on still surfaces before this command exits. Only the sending — transactions, receipts, an
|
package/lib/jobs.js
CHANGED
|
@@ -29,22 +29,36 @@ import { fileURLToPath } from 'node:url';
|
|
|
29
29
|
import Mnemonad from 'mnemonad';
|
|
30
30
|
import { MonadSync } from 'monadsync';
|
|
31
31
|
import { makeChainClient, requireWalletClient } from './chainClient.js';
|
|
32
|
+
import { FSFolder } from './FSFolder.js';
|
|
32
33
|
import {
|
|
33
34
|
formatStreamId,
|
|
34
35
|
formatPath,
|
|
35
36
|
unlockStream,
|
|
37
|
+
assertStreamOwner,
|
|
36
38
|
resolveOffloadParams,
|
|
37
39
|
writeManifest,
|
|
40
|
+
makeExcludes,
|
|
41
|
+
hashLocalTree,
|
|
42
|
+
localTreeHash,
|
|
38
43
|
userError,
|
|
39
44
|
} from './commands/shared.js';
|
|
45
|
+
import { updateIndex } from './commands/buildIndex.js';
|
|
40
46
|
|
|
41
47
|
const BIN_PATH = join(dirname(fileURLToPath(import.meta.url)), '..', 'bin', 'mnemonad.js');
|
|
42
48
|
|
|
43
49
|
/** pending: written, child not started yet · waiting: queued behind an earlier job from the
|
|
44
|
-
* same signer ·
|
|
45
|
-
|
|
50
|
+
* same signer · preparing: indexing and building the patch (`push --detach --index` only) ·
|
|
51
|
+
* sending: transactions in flight · done / failed: finished. */
|
|
52
|
+
export const JOB_STATUS = {
|
|
53
|
+
PENDING: 'pending', WAITING: 'waiting', PREPARING: 'preparing', SENDING: 'sending', DONE: 'done', FAILED: 'failed',
|
|
54
|
+
};
|
|
46
55
|
|
|
47
|
-
const ACTIVE = new Set([JOB_STATUS.PENDING, JOB_STATUS.WAITING, JOB_STATUS.SENDING]);
|
|
56
|
+
const ACTIVE = new Set([JOB_STATUS.PENDING, JOB_STATUS.WAITING, JOB_STATUS.PREPARING, JOB_STATUS.SENDING]);
|
|
57
|
+
|
|
58
|
+
/** "version 8", or what a job that hasn't built its version yet is doing instead. */
|
|
59
|
+
function versionLabel(job) {
|
|
60
|
+
return job.version != null ? `version ${job.version}` : 'the next version';
|
|
61
|
+
}
|
|
48
62
|
|
|
49
63
|
/** A job still `pending` with no pid after this long never got its child started. */
|
|
50
64
|
const SPAWN_GRACE_MS = 60_000;
|
|
@@ -176,7 +190,7 @@ export async function waitForJobs(selector, { reason = 'before continuing' } = {
|
|
|
176
190
|
|
|
177
191
|
const job = first[first.length - 1];
|
|
178
192
|
console.log(
|
|
179
|
-
` waiting for background push ${job.id} (
|
|
193
|
+
` waiting for background push ${job.id} (${versionLabel(job)}) to finish ${reason}...\n` +
|
|
180
194
|
` log: ${formatPath(join(job.dir, 'push.log'))}`
|
|
181
195
|
);
|
|
182
196
|
while (first.length) {
|
|
@@ -195,7 +209,7 @@ export function reportFailedJob(key) {
|
|
|
195
209
|
const job = latestJob(key);
|
|
196
210
|
if (!job || job.status !== JOB_STATUS.FAILED || job.reported) return;
|
|
197
211
|
console.log(
|
|
198
|
-
` warning: background push ${job.id} (
|
|
212
|
+
` warning: background push ${job.id} (${versionLabel(job)}) failed: ${job.error}\n` +
|
|
199
213
|
` nothing from it reached the stream. Log: ${formatPath(join(job.dir, 'push.log'))}`
|
|
200
214
|
);
|
|
201
215
|
writeJobSync(job.dir, { ...readJobSync(job.dir), reported: true });
|
|
@@ -224,7 +238,7 @@ export async function settleStreamJobs(args, { wait = true, reason } = {}) {
|
|
|
224
238
|
if (running.length) {
|
|
225
239
|
const job = running[running.length - 1];
|
|
226
240
|
console.log(
|
|
227
|
-
` note: background push ${job.id} (
|
|
241
|
+
` note: background push ${job.id} (${versionLabel(job)}) is still running —\n` +
|
|
228
242
|
' this compares against the chain as it is before that version lands'
|
|
229
243
|
);
|
|
230
244
|
}
|
|
@@ -235,13 +249,17 @@ export function describeJob(job) {
|
|
|
235
249
|
const log = formatPath(join(job.dir, 'push.log'));
|
|
236
250
|
switch (job.status) {
|
|
237
251
|
case JOB_STATUS.DONE:
|
|
238
|
-
return
|
|
252
|
+
return job.noChanges
|
|
253
|
+
? `no changes, nothing pushed ${job.finishedAt} (job ${job.id})`
|
|
254
|
+
: `version ${job.version} sent ${job.finishedAt} (job ${job.id})`;
|
|
239
255
|
case JOB_STATUS.FAILED:
|
|
240
|
-
return
|
|
256
|
+
return `${versionLabel(job)} FAILED: ${job.error} (job ${job.id}, log: ${log})`;
|
|
241
257
|
case JOB_STATUS.WAITING:
|
|
242
|
-
return
|
|
258
|
+
return `${versionLabel(job)} queued behind another push from the same wallet (job ${job.id}, log: ${log})`;
|
|
259
|
+
case JOB_STATUS.PREPARING:
|
|
260
|
+
return `indexing and building the next version (job ${job.id}, pid ${job.pid ?? '?'}, log: ${log})`;
|
|
243
261
|
default:
|
|
244
|
-
return
|
|
262
|
+
return `${versionLabel(job)} still sending (job ${job.id}, pid ${job.pid ?? '?'}, log: ${log})`;
|
|
245
263
|
}
|
|
246
264
|
}
|
|
247
265
|
|
|
@@ -257,8 +275,9 @@ function pruneFinishedJobs() {
|
|
|
257
275
|
/**
|
|
258
276
|
* Writes a job and starts its detached child.
|
|
259
277
|
*
|
|
260
|
-
* @param {Object} job - everything the child needs except secrets; see push.js's detachPush
|
|
261
|
-
*
|
|
278
|
+
* @param {Object} job - everything the child needs except secrets; see push.js's detachPush.
|
|
279
|
+
* `mode: 'prepare'` (push.js's detachPrepare) has the child index and build the patch too.
|
|
280
|
+
* @param {?Uint8Array} patchBytes - the prepared patch; null in `prepare` mode
|
|
262
281
|
* @param {{key?: ?string, phrase?: ?string, password?: ?string, pinataJwt?: ?string}} secrets
|
|
263
282
|
* @returns {Promise<{id: string, dir: string, pid: number}>}
|
|
264
283
|
*/
|
|
@@ -268,7 +287,7 @@ export async function startPushJob(job, patchBytes, secrets) {
|
|
|
268
287
|
const id = randomBytes(4).toString('hex');
|
|
269
288
|
const dir = join(jobsRoot(), id);
|
|
270
289
|
await mkdir(dir, { recursive: true });
|
|
271
|
-
await writeFile(join(dir, 'patch.bin'), patchBytes);
|
|
290
|
+
if (patchBytes) await writeFile(join(dir, 'patch.bin'), patchBytes);
|
|
272
291
|
writeJobSync(dir, { ...job, id, status: JOB_STATUS.PENDING, pid: null, createdAt: new Date().toISOString() });
|
|
273
292
|
|
|
274
293
|
const logFd = openSync(join(dir, 'push.log'), 'a');
|
|
@@ -331,9 +350,6 @@ export async function runPushJob(dir) {
|
|
|
331
350
|
while (earlier().length) await new Promise((r) => setTimeout(r, POLL_MS));
|
|
332
351
|
}
|
|
333
352
|
|
|
334
|
-
update({ status: JOB_STATUS.SENDING, startedAt: new Date().toISOString() });
|
|
335
|
-
log(`sending version ${job.version} of ${job.streamId} on ${job.chain}...`);
|
|
336
|
-
|
|
337
353
|
const args = { ...job.args, key: secrets.key ?? null, phrase: secrets.phrase ?? null, password: secrets.password ?? null, pinataJwt: secrets.pinataJwt ?? null, passkey: false };
|
|
338
354
|
const client = await makeChainClient(args);
|
|
339
355
|
requireWalletClient(client);
|
|
@@ -347,11 +363,31 @@ export async function runPushJob(dir) {
|
|
|
347
363
|
});
|
|
348
364
|
await unlockStream(mn, { password: args.password, signer: client.walletClient });
|
|
349
365
|
|
|
350
|
-
|
|
351
|
-
|
|
366
|
+
let sync;
|
|
367
|
+
let prepared;
|
|
368
|
+
let files = job.manifest?.files || null;
|
|
369
|
+
if (job.mode === 'prepare') {
|
|
370
|
+
update({ status: JOB_STATUS.PREPARING, startedAt: new Date().toISOString() });
|
|
371
|
+
await assertStreamOwner(mn, client.account.address);
|
|
372
|
+
const built = await prepareInBackground(job, args, mn);
|
|
373
|
+
if (!built) {
|
|
374
|
+
update({ status: JOB_STATUS.DONE, noChanges: true, version: mn.length, finishedAt: new Date().toISOString() });
|
|
375
|
+
log('no changes, nothing to push');
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
({ sync, prepared, files } = built);
|
|
379
|
+
update({ version: prepared.version, baseLength: prepared.baseLength, kind: prepared.kind, bytes: prepared.patchBytes.length });
|
|
380
|
+
} else {
|
|
381
|
+
sync = new MonadSync({ mnemonad: mn });
|
|
382
|
+
prepared = { patchBytes: new Uint8Array(await readFile(join(dir, 'patch.bin'))), baseLength: job.baseLength };
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
update({ status: JOB_STATUS.SENDING, sendingAt: new Date().toISOString() });
|
|
386
|
+
log(`sending version ${job.version} of ${job.streamId} on ${job.chain}...`);
|
|
387
|
+
const result = await sync.commitPush(prepared);
|
|
352
388
|
|
|
353
389
|
if (job.manifest) {
|
|
354
|
-
await writeManifest(job.folder, { streamId: job.streamId, version: result.version, files
|
|
390
|
+
await writeManifest(job.folder, { streamId: job.streamId, version: result.version, files });
|
|
355
391
|
}
|
|
356
392
|
update({ status: JOB_STATUS.DONE, version: result.version, finishedAt: new Date().toISOString() });
|
|
357
393
|
log(`version ${result.version} pushed`);
|
|
@@ -367,6 +403,49 @@ export async function runPushJob(dir) {
|
|
|
367
403
|
}
|
|
368
404
|
}
|
|
369
405
|
|
|
406
|
+
/**
|
|
407
|
+
* The part of a foreground push that `push --detach --index` hands off along with the sending:
|
|
408
|
+
* update the search index, replay the chain, and build the patch — same steps, same order, as
|
|
409
|
+
* push.js. Runs after any earlier job from the same wallet has landed, so it builds on the
|
|
410
|
+
* real tip.
|
|
411
|
+
*
|
|
412
|
+
* @returns {Promise<?{sync: MonadSync, prepared: Object, files: ?Object}>} null when the folder
|
|
413
|
+
* already matches the latest version
|
|
414
|
+
*/
|
|
415
|
+
async function prepareInBackground(job, args, mn) {
|
|
416
|
+
log('updating search index...');
|
|
417
|
+
try {
|
|
418
|
+
await updateIndex(job.folder, args, { quiet: true });
|
|
419
|
+
} catch (err) {
|
|
420
|
+
throw new Error(`indexing failed, so nothing was pushed: ${err.message}`);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const fsFolder = new FSFolder(job.folder, makeExcludes(args));
|
|
424
|
+
const sync = new MonadSync({ mnemonad: mn, compress: args.compress === false ? false : 'gzip' });
|
|
425
|
+
|
|
426
|
+
if (args.forceSnapshot) {
|
|
427
|
+
// Same escape hatch push.js uses — see monadsync/README.md "Repairing a corrupt chain".
|
|
428
|
+
await mn.initialize();
|
|
429
|
+
sync._isInitialized = true;
|
|
430
|
+
sync._lastSnapshot = null;
|
|
431
|
+
sync._replayedCount = mn.length;
|
|
432
|
+
} else {
|
|
433
|
+
await sync.initialize();
|
|
434
|
+
if (mn.length > 0) {
|
|
435
|
+
const localHash = await localTreeHash(job.folder);
|
|
436
|
+
const remoteHash = await sync.getTreeHash(mn.length);
|
|
437
|
+
if (localHash && remoteHash.length === localHash.length && localHash.every((b, i) => b === remoteHash[i])) {
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
log('building patch...');
|
|
444
|
+
const prepared = await sync.preparePush(fsFolder);
|
|
445
|
+
const files = job.manifest ? await hashLocalTree(fsFolder) : null;
|
|
446
|
+
return { sync, prepared, files };
|
|
447
|
+
}
|
|
448
|
+
|
|
370
449
|
/** For tests: blocks until a job has finished, and returns it. */
|
|
371
450
|
export async function waitForJob(dir, { timeoutMs = 60_000 } = {}) {
|
|
372
451
|
const deadline = Date.now() + timeoutMs;
|