@opencode-manager/ocm-cli 0.1.1 → 0.1.3
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/ocm.js +290 -35
- package/package.json +1 -1
package/dist/ocm.js
CHANGED
|
@@ -95,6 +95,35 @@ function deleteToken(account) {
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
// src/manager-api.ts
|
|
98
|
+
class ManagerApiError extends Error {
|
|
99
|
+
status;
|
|
100
|
+
code;
|
|
101
|
+
operation;
|
|
102
|
+
constructor(message, status, code, operation) {
|
|
103
|
+
super(message);
|
|
104
|
+
this.status = status;
|
|
105
|
+
this.code = code;
|
|
106
|
+
this.operation = operation;
|
|
107
|
+
this.name = "ManagerApiError";
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async function formatErrorResponse(res, operation) {
|
|
111
|
+
const text = await res.text().catch(() => "");
|
|
112
|
+
let code = null;
|
|
113
|
+
let detail = text;
|
|
114
|
+
if (text) {
|
|
115
|
+
try {
|
|
116
|
+
const parsed = JSON.parse(text);
|
|
117
|
+
const errField = typeof parsed.error === "string" ? parsed.error : null;
|
|
118
|
+
const msgField = typeof parsed.message === "string" ? parsed.message : null;
|
|
119
|
+
code = errField;
|
|
120
|
+
detail = msgField ?? errField ?? text;
|
|
121
|
+
} catch {}
|
|
122
|
+
}
|
|
123
|
+
const message = detail ? `${operation} failed (${res.status}): ${detail}` : `${operation} failed (${res.status})`;
|
|
124
|
+
return new ManagerApiError(message, res.status, code, operation);
|
|
125
|
+
}
|
|
126
|
+
|
|
98
127
|
class ManagerApi {
|
|
99
128
|
baseUrl;
|
|
100
129
|
token;
|
|
@@ -105,49 +134,68 @@ class ManagerApi {
|
|
|
105
134
|
headers(extra = {}) {
|
|
106
135
|
return { Authorization: `Bearer ${this.token}`, ...extra };
|
|
107
136
|
}
|
|
108
|
-
async
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
params.set("force", "1");
|
|
137
|
+
async mirrorBegin(repoId, opts) {
|
|
138
|
+
const url = `${this.baseUrl}/api/internal/repos/${repoId}/mirror/begin`;
|
|
139
|
+
const body = { force: opts.force === true };
|
|
112
140
|
if (opts.create) {
|
|
113
|
-
|
|
114
|
-
|
|
141
|
+
body.create = true;
|
|
142
|
+
body.name = opts.create.name;
|
|
115
143
|
if (opts.create.originUrl)
|
|
116
|
-
|
|
144
|
+
body.originUrl = opts.create.originUrl;
|
|
117
145
|
if (opts.create.branch)
|
|
118
|
-
|
|
146
|
+
body.branch = opts.create.branch;
|
|
119
147
|
}
|
|
120
|
-
const qs = params.toString() ? `?${params.toString()}` : "";
|
|
121
|
-
const url = `${this.baseUrl}/api/internal/repos/${repoId}/mirror${qs}`;
|
|
122
148
|
const res = await fetch(url, {
|
|
123
149
|
method: "POST",
|
|
124
|
-
headers: {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
150
|
+
headers: { ...this.headers(), "Content-Type": "application/json" },
|
|
151
|
+
body: JSON.stringify(body)
|
|
152
|
+
});
|
|
153
|
+
if (!res.ok)
|
|
154
|
+
throw await formatErrorResponse(res, "mirror begin");
|
|
155
|
+
return await res.json();
|
|
156
|
+
}
|
|
157
|
+
async mirrorUploadPart(repoId, uploadId, index, chunk) {
|
|
158
|
+
const url = `${this.baseUrl}/api/internal/repos/${repoId}/mirror/parts/${uploadId}/${index}`;
|
|
159
|
+
const ab = chunk.buffer.slice(chunk.byteOffset, chunk.byteOffset + chunk.byteLength);
|
|
160
|
+
const res = await fetch(url, {
|
|
161
|
+
method: "PUT",
|
|
162
|
+
headers: { ...this.headers(), "Content-Type": "application/octet-stream" },
|
|
163
|
+
body: ab
|
|
164
|
+
});
|
|
165
|
+
if (!res.ok)
|
|
166
|
+
throw await formatErrorResponse(res, `mirror part ${index}`);
|
|
167
|
+
}
|
|
168
|
+
async mirrorCommit(repoId, uploadId, totalParts) {
|
|
169
|
+
const url = `${this.baseUrl}/api/internal/repos/${repoId}/mirror/commit`;
|
|
170
|
+
const res = await fetch(url, {
|
|
171
|
+
method: "POST",
|
|
172
|
+
headers: { ...this.headers(), "Content-Type": "application/json" },
|
|
173
|
+
body: JSON.stringify({ uploadId, totalParts })
|
|
130
174
|
});
|
|
131
175
|
if (!res.ok)
|
|
132
|
-
throw
|
|
176
|
+
throw await formatErrorResponse(res, "mirror commit");
|
|
133
177
|
return await res.json();
|
|
134
178
|
}
|
|
179
|
+
async mirrorAbort(repoId, uploadId) {
|
|
180
|
+
const url = `${this.baseUrl}/api/internal/repos/${repoId}/mirror/uploads/${uploadId}`;
|
|
181
|
+
await fetch(url, { method: "DELETE", headers: this.headers() }).catch(() => {});
|
|
182
|
+
}
|
|
135
183
|
async mirrorDown(repoId) {
|
|
136
184
|
const res = await fetch(`${this.baseUrl}/api/internal/repos/${repoId}/mirror`, {
|
|
137
185
|
headers: this.headers()
|
|
138
186
|
});
|
|
139
187
|
if (!res.ok)
|
|
140
|
-
throw
|
|
188
|
+
throw await formatErrorResponse(res, "mirror download");
|
|
141
189
|
return res.body;
|
|
142
190
|
}
|
|
143
191
|
}
|
|
144
192
|
|
|
145
193
|
// src/mirror.ts
|
|
146
194
|
import { spawnSync as spawnSync3, spawn } from "child_process";
|
|
147
|
-
import { existsSync as existsSync2 } from "fs";
|
|
195
|
+
import { existsSync as existsSync2, statSync } from "fs";
|
|
148
196
|
import * as fsp from "fs/promises";
|
|
149
197
|
import { Readable } from "stream";
|
|
150
|
-
import { join as join2 } from "path";
|
|
198
|
+
import { join as join2, dirname as dirname2 } from "path";
|
|
151
199
|
import { tmpdir } from "os";
|
|
152
200
|
|
|
153
201
|
// src/local-repo.ts
|
|
@@ -191,7 +239,9 @@ function urlsEqual(a, b) {
|
|
|
191
239
|
}
|
|
192
240
|
|
|
193
241
|
// src/mirror.ts
|
|
194
|
-
var HARDCODED_EXCLUDES = ["node_modules", "dist", ".next", ".venv", "__pycache__", ".turbo"];
|
|
242
|
+
var HARDCODED_EXCLUDES = ["node_modules", "dist", ".next", ".venv", "__pycache__", ".turbo", ".DS_Store", "._*"];
|
|
243
|
+
var PART_RETRIES = 3;
|
|
244
|
+
var PART_BACKOFF_MS = [500, 2000, 8000];
|
|
195
245
|
function getGitignoreExclusions(repoRoot) {
|
|
196
246
|
const res = spawnSync3("git", ["ls-files", "--others", "--ignored", "--exclude-standard", "--directory"], {
|
|
197
247
|
cwd: repoRoot,
|
|
@@ -202,6 +252,45 @@ function getGitignoreExclusions(repoRoot) {
|
|
|
202
252
|
return (res.stdout ?? "").split(`
|
|
203
253
|
`).filter((line) => line.length > 0);
|
|
204
254
|
}
|
|
255
|
+
async function carryOverIgnored(fromDir, toDir) {
|
|
256
|
+
if (!existsSync2(fromDir))
|
|
257
|
+
return;
|
|
258
|
+
for (const rel of getGitignoreExclusions(fromDir)) {
|
|
259
|
+
const clean = rel.replace(/\/+$/, "");
|
|
260
|
+
if (!clean)
|
|
261
|
+
continue;
|
|
262
|
+
const src = join2(fromDir, clean);
|
|
263
|
+
const dest = join2(toDir, clean);
|
|
264
|
+
if (!existsSync2(src) || existsSync2(dest))
|
|
265
|
+
continue;
|
|
266
|
+
await fsp.mkdir(dirname2(dest), { recursive: true });
|
|
267
|
+
await fsp.rename(src, dest).catch(() => {});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function listIncludedFiles(repoRoot) {
|
|
271
|
+
const tracked = spawnSync3("git", ["ls-files", "-z"], { cwd: repoRoot, encoding: "utf-8" });
|
|
272
|
+
const untracked = spawnSync3("git", ["ls-files", "--others", "--exclude-standard", "-z"], { cwd: repoRoot, encoding: "utf-8" });
|
|
273
|
+
const split = (out) => (out ?? "").split("\x00").filter((p) => p.length > 0);
|
|
274
|
+
const all = [...split(tracked.stdout), ...split(untracked.stdout)];
|
|
275
|
+
return all.filter((p) => {
|
|
276
|
+
const top = p.split("/")[0] ?? p;
|
|
277
|
+
return !HARDCODED_EXCLUDES.includes(top);
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
function estimateTarSize(repoRoot) {
|
|
281
|
+
const files = listIncludedFiles(repoRoot);
|
|
282
|
+
let total = 0;
|
|
283
|
+
for (const rel of files) {
|
|
284
|
+
let size = 0;
|
|
285
|
+
try {
|
|
286
|
+
size = statSync(join2(repoRoot, rel)).size;
|
|
287
|
+
} catch {
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
total += 512 + Math.ceil(size / 512) * 512;
|
|
291
|
+
}
|
|
292
|
+
return total + 1024;
|
|
293
|
+
}
|
|
205
294
|
|
|
206
295
|
class MirrorAbort extends Error {
|
|
207
296
|
constructor(message) {
|
|
@@ -219,7 +308,70 @@ function prepareMirror(cwd, remotes) {
|
|
|
219
308
|
const matched = remotes.filter((r) => urlsEqual(localOrigin, r.originUrl));
|
|
220
309
|
return { repoRoot, localOrigin, matched };
|
|
221
310
|
}
|
|
311
|
+
function delay(ms) {
|
|
312
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
313
|
+
}
|
|
314
|
+
function isRetryablePartError(err) {
|
|
315
|
+
if (err instanceof ManagerApiError) {
|
|
316
|
+
return err.status >= 500 || err.status === 408 || err.status === 429;
|
|
317
|
+
}
|
|
318
|
+
return true;
|
|
319
|
+
}
|
|
320
|
+
async function uploadPartWithRetry(api, repoId, uploadId, index, chunk) {
|
|
321
|
+
let lastError;
|
|
322
|
+
for (let attempt = 0;attempt < PART_RETRIES; attempt++) {
|
|
323
|
+
try {
|
|
324
|
+
await api.mirrorUploadPart(repoId, uploadId, index, chunk);
|
|
325
|
+
return;
|
|
326
|
+
} catch (err) {
|
|
327
|
+
lastError = err;
|
|
328
|
+
if (!isRetryablePartError(err))
|
|
329
|
+
break;
|
|
330
|
+
if (attempt < PART_RETRIES - 1) {
|
|
331
|
+
await delay(PART_BACKOFF_MS[attempt]);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
throw lastError instanceof Error ? lastError : new Error(`part ${index} failed: ${String(lastError)}`);
|
|
336
|
+
}
|
|
337
|
+
function createPartFlusher(api, repoId, uploadId, chunkSize) {
|
|
338
|
+
let acc = [];
|
|
339
|
+
let accLen = 0;
|
|
340
|
+
let index = 0;
|
|
341
|
+
const flush = async () => {
|
|
342
|
+
if (accLen === 0)
|
|
343
|
+
return;
|
|
344
|
+
const buf = Buffer.concat(acc, accLen);
|
|
345
|
+
acc = [];
|
|
346
|
+
accLen = 0;
|
|
347
|
+
await uploadPartWithRetry(api, repoId, uploadId, index, buf);
|
|
348
|
+
index += 1;
|
|
349
|
+
};
|
|
350
|
+
return {
|
|
351
|
+
async push(buf) {
|
|
352
|
+
let offset = 0;
|
|
353
|
+
while (offset < buf.length) {
|
|
354
|
+
const room = chunkSize - accLen;
|
|
355
|
+
const take = Math.min(room, buf.length - offset);
|
|
356
|
+
acc.push(buf.subarray(offset, offset + take));
|
|
357
|
+
accLen += take;
|
|
358
|
+
offset += take;
|
|
359
|
+
if (accLen >= chunkSize) {
|
|
360
|
+
await flush();
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
async finish() {
|
|
365
|
+
await flush();
|
|
366
|
+
return index;
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
}
|
|
222
370
|
async function mirrorUp(plan, opts) {
|
|
371
|
+
const repoId = opts.create ? 0 : plan.matched[0].repoId;
|
|
372
|
+
const begin = await opts.api.mirrorBegin(repoId, { force: opts.force, create: opts.create });
|
|
373
|
+
const totalBytes = estimateTarSize(plan.repoRoot);
|
|
374
|
+
let bytesSent = 0;
|
|
223
375
|
const tarArgs = ["-c", "-C", plan.repoRoot];
|
|
224
376
|
for (const dir of HARDCODED_EXCLUDES)
|
|
225
377
|
tarArgs.push("--exclude", dir);
|
|
@@ -232,7 +384,10 @@ async function mirrorUp(plan, opts) {
|
|
|
232
384
|
tarArgs.push("--exclude-from", excludeFile);
|
|
233
385
|
}
|
|
234
386
|
tarArgs.push(".");
|
|
235
|
-
const child = spawn("tar", tarArgs, {
|
|
387
|
+
const child = spawn("tar", tarArgs, {
|
|
388
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
389
|
+
env: { ...process.env, COPYFILE_DISABLE: "1" }
|
|
390
|
+
});
|
|
236
391
|
const stderrChunks = [];
|
|
237
392
|
child.stderr.on("data", (chunk) => stderrChunks.push(chunk));
|
|
238
393
|
const tarExit = new Promise((resolve, reject) => {
|
|
@@ -246,17 +401,23 @@ async function mirrorUp(plan, opts) {
|
|
|
246
401
|
});
|
|
247
402
|
child.on("error", reject);
|
|
248
403
|
});
|
|
249
|
-
const
|
|
250
|
-
const repoId = opts.create ? 0 : plan.matched[0].repoId;
|
|
404
|
+
const flusher = createPartFlusher(opts.api, begin.repoId, begin.uploadId, begin.chunkSize);
|
|
251
405
|
try {
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
406
|
+
for await (const chunk of child.stdout) {
|
|
407
|
+
await flusher.push(chunk);
|
|
408
|
+
bytesSent += chunk.length;
|
|
409
|
+
opts.onProgress?.({ phase: "uploading", bytesSent, totalBytes });
|
|
410
|
+
}
|
|
411
|
+
await tarExit;
|
|
412
|
+
const totalParts = await flusher.finish();
|
|
413
|
+
opts.onProgress?.({ phase: "committing", bytesSent, totalBytes });
|
|
414
|
+
const result = await opts.api.mirrorCommit(begin.repoId, begin.uploadId, totalParts);
|
|
259
415
|
return result;
|
|
416
|
+
} catch (err) {
|
|
417
|
+
if (!child.killed)
|
|
418
|
+
child.kill("SIGKILL");
|
|
419
|
+
await opts.api.mirrorAbort(begin.repoId, begin.uploadId);
|
|
420
|
+
throw err;
|
|
260
421
|
} finally {
|
|
261
422
|
if (excludeFile) {
|
|
262
423
|
await fsp.rm(excludeFile, { force: true }).catch(() => {});
|
|
@@ -286,6 +447,11 @@ async function mirrorDown(repoId, repoRoot, api, opts = { force: false }) {
|
|
|
286
447
|
child.on("error", reject);
|
|
287
448
|
});
|
|
288
449
|
const stdinWritable = Readable.fromWeb(tarball);
|
|
450
|
+
let received = 0;
|
|
451
|
+
stdinWritable.on("data", (chunk) => {
|
|
452
|
+
received += chunk.length;
|
|
453
|
+
opts.onProgress?.(received);
|
|
454
|
+
});
|
|
289
455
|
stdinWritable.pipe(child.stdin);
|
|
290
456
|
await tarDone;
|
|
291
457
|
const backupDir = `${repoRoot}.ocm-backup-${Date.now()}`;
|
|
@@ -301,6 +467,7 @@ async function mirrorDown(repoId, repoRoot, api, opts = { force: false }) {
|
|
|
301
467
|
for (const entry of stagingEntries) {
|
|
302
468
|
await fsp.rename(join2(staging, entry), join2(repoRoot, entry));
|
|
303
469
|
}
|
|
470
|
+
await carryOverIgnored(backupDir, repoRoot);
|
|
304
471
|
await fsp.rm(backupDir, { recursive: true, force: true }).catch(() => {});
|
|
305
472
|
await fsp.rm(staging, { recursive: true, force: true }).catch(() => {});
|
|
306
473
|
} catch (swapError) {
|
|
@@ -317,6 +484,77 @@ async function mirrorDown(repoId, repoRoot, api, opts = { force: false }) {
|
|
|
317
484
|
}
|
|
318
485
|
}
|
|
319
486
|
|
|
487
|
+
// src/progress.ts
|
|
488
|
+
var FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
489
|
+
function formatBytes(bytes) {
|
|
490
|
+
if (bytes < 1024) {
|
|
491
|
+
return `${bytes} B`;
|
|
492
|
+
}
|
|
493
|
+
if (bytes < 1024 * 1024) {
|
|
494
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
495
|
+
}
|
|
496
|
+
if (bytes < 1024 * 1024 * 1024) {
|
|
497
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
498
|
+
}
|
|
499
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
|
|
500
|
+
}
|
|
501
|
+
function createProgressReporter(label, out = process.stderr, now = Date.now) {
|
|
502
|
+
let finished = false;
|
|
503
|
+
let lastRenderAt = -Infinity;
|
|
504
|
+
let lastBucket = -1;
|
|
505
|
+
let lastNonTtyTickAt = -Infinity;
|
|
506
|
+
let frameIndex = 0;
|
|
507
|
+
const isTTY = out.isTTY === true;
|
|
508
|
+
return {
|
|
509
|
+
update(current, total) {
|
|
510
|
+
if (finished)
|
|
511
|
+
return;
|
|
512
|
+
if (isTTY) {
|
|
513
|
+
const t = now();
|
|
514
|
+
if (t - lastRenderAt < 80)
|
|
515
|
+
return;
|
|
516
|
+
lastRenderAt = t;
|
|
517
|
+
const pct = total > 0 ? Math.min(99, Math.floor(current / total * 100)) : 0;
|
|
518
|
+
out.write(`\r\x1B[K${label}: ${pct}% (${formatBytes(current)} / ${formatBytes(total)})`);
|
|
519
|
+
} else {
|
|
520
|
+
const pct = total > 0 ? Math.min(99, Math.floor(current / total * 100)) : 0;
|
|
521
|
+
const bucket = Math.floor(pct / 10);
|
|
522
|
+
if (bucket === lastBucket)
|
|
523
|
+
return;
|
|
524
|
+
lastBucket = bucket;
|
|
525
|
+
out.write(`${label}: ${pct}% (${formatBytes(current)} / ${formatBytes(total)})
|
|
526
|
+
`);
|
|
527
|
+
}
|
|
528
|
+
},
|
|
529
|
+
tick(bytes) {
|
|
530
|
+
if (finished)
|
|
531
|
+
return;
|
|
532
|
+
const t = now();
|
|
533
|
+
if (isTTY) {
|
|
534
|
+
if (t - lastRenderAt < 80)
|
|
535
|
+
return;
|
|
536
|
+
lastRenderAt = t;
|
|
537
|
+
out.write(`\r\x1B[K${label}: ${FRAMES[frameIndex]} ${formatBytes(bytes)}`);
|
|
538
|
+
frameIndex = (frameIndex + 1) % FRAMES.length;
|
|
539
|
+
} else {
|
|
540
|
+
if (t - lastNonTtyTickAt < 1000)
|
|
541
|
+
return;
|
|
542
|
+
lastNonTtyTickAt = t;
|
|
543
|
+
out.write(`${label}: ${formatBytes(bytes)}
|
|
544
|
+
`);
|
|
545
|
+
}
|
|
546
|
+
},
|
|
547
|
+
done() {
|
|
548
|
+
if (finished)
|
|
549
|
+
return;
|
|
550
|
+
finished = true;
|
|
551
|
+
if (isTTY) {
|
|
552
|
+
out.write("\r\x1B[K");
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
|
|
320
558
|
// src/resolve-target.ts
|
|
321
559
|
function resolveTarget(input) {
|
|
322
560
|
const repoRoot = getRepoRoot(input.cwd);
|
|
@@ -347,7 +585,7 @@ function toTarget(last) {
|
|
|
347
585
|
// package.json
|
|
348
586
|
var package_default = {
|
|
349
587
|
name: "@opencode-manager/ocm-cli",
|
|
350
|
-
version: "0.1.
|
|
588
|
+
version: "0.1.3",
|
|
351
589
|
description: "OpenCode Manager CLI: attach a local OpenCode TUI to a Manager-hosted repo.",
|
|
352
590
|
license: "MIT",
|
|
353
591
|
repository: {
|
|
@@ -643,6 +881,15 @@ async function cmdPush(args) {
|
|
|
643
881
|
const token = requireToken(state);
|
|
644
882
|
const api = new ManagerApi(state.managerUrl, token);
|
|
645
883
|
const repos = await fetchRepos(state.managerUrl, token);
|
|
884
|
+
const progress = createProgressReporter("push");
|
|
885
|
+
const onProgress = (p) => {
|
|
886
|
+
if (p.phase === "committing")
|
|
887
|
+
progress.tick(p.bytesSent);
|
|
888
|
+
else if (p.totalBytes > 0)
|
|
889
|
+
progress.update(p.bytesSent, p.totalBytes);
|
|
890
|
+
else
|
|
891
|
+
progress.tick(p.bytesSent);
|
|
892
|
+
};
|
|
646
893
|
const remotes = repos.map((r) => ({
|
|
647
894
|
repoId: r.repoId,
|
|
648
895
|
name: r.name,
|
|
@@ -672,11 +919,14 @@ async function cmdPush(args) {
|
|
|
672
919
|
const result = await mirrorUp(plan, {
|
|
673
920
|
api,
|
|
674
921
|
force,
|
|
675
|
-
create: { name, originUrl: plan.localOrigin, branch }
|
|
922
|
+
create: { name, originUrl: plan.localOrigin, branch },
|
|
923
|
+
onProgress
|
|
676
924
|
});
|
|
925
|
+
progress.done();
|
|
677
926
|
info(`pushed ${plan.repoRoot} -> ${result.created ? "created" : "updated"} (repoId=${result.repoId}, branch=${result.branch})`);
|
|
678
927
|
} else if (plan.matched.length === 1) {
|
|
679
|
-
const result = await mirrorUp(plan, { api, force });
|
|
928
|
+
const result = await mirrorUp(plan, { api, force, onProgress });
|
|
929
|
+
progress.done();
|
|
680
930
|
info(`pushed ${plan.repoRoot} -> ${plan.matched[0].name} (repoId=${result.repoId}, branch=${result.branch})`);
|
|
681
931
|
} else {
|
|
682
932
|
const names = plan.matched.map((r) => `${r.name} (id=${r.repoId})`).join(", ");
|
|
@@ -707,7 +957,12 @@ async function cmdPull(args) {
|
|
|
707
957
|
const names = plan.matched.map((r) => `${r.name} (id=${r.repoId})`).join(", ");
|
|
708
958
|
die(`multiple Manager repos match origin ${plan.localOrigin}: ${names}; disambiguate with \`ocm pull <repoId>\``);
|
|
709
959
|
}
|
|
710
|
-
|
|
960
|
+
const progress = createProgressReporter("pull");
|
|
961
|
+
try {
|
|
962
|
+
await mirrorDown(plan.matched[0].repoId, plan.repoRoot, api, { force, onProgress: (bytes) => progress.tick(bytes) });
|
|
963
|
+
} finally {
|
|
964
|
+
progress.done();
|
|
965
|
+
}
|
|
711
966
|
info(`pulled ${plan.matched[0].name} -> ${plan.repoRoot}`);
|
|
712
967
|
}
|
|
713
968
|
async function main() {
|