@opencode-manager/ocm-cli 0.1.3 → 0.1.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/README.md +1 -1
- package/dist/ocm.js +19 -65
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -69,6 +69,6 @@ export default [ocm]
|
|
|
69
69
|
## Requirements
|
|
70
70
|
|
|
71
71
|
- `opencode` available on `PATH`
|
|
72
|
-
- `git` and `tar` available on `PATH`
|
|
72
|
+
- `git` and `tar` (with gzip support, i.e. the `-z` flag) available on `PATH`
|
|
73
73
|
- macOS `security` CLI for Keychain-backed token storage
|
|
74
74
|
- An OpenCode Manager URL and bearer token
|
package/dist/ocm.js
CHANGED
|
@@ -165,12 +165,12 @@ class ManagerApi {
|
|
|
165
165
|
if (!res.ok)
|
|
166
166
|
throw await formatErrorResponse(res, `mirror part ${index}`);
|
|
167
167
|
}
|
|
168
|
-
async mirrorCommit(repoId, uploadId, totalParts) {
|
|
168
|
+
async mirrorCommit(repoId, uploadId, totalParts, gzip) {
|
|
169
169
|
const url = `${this.baseUrl}/api/internal/repos/${repoId}/mirror/commit`;
|
|
170
170
|
const res = await fetch(url, {
|
|
171
171
|
method: "POST",
|
|
172
172
|
headers: { ...this.headers(), "Content-Type": "application/json" },
|
|
173
|
-
body: JSON.stringify({ uploadId, totalParts })
|
|
173
|
+
body: JSON.stringify({ uploadId, totalParts, gzip })
|
|
174
174
|
});
|
|
175
175
|
if (!res.ok)
|
|
176
176
|
throw await formatErrorResponse(res, "mirror commit");
|
|
@@ -180,8 +180,9 @@ class ManagerApi {
|
|
|
180
180
|
const url = `${this.baseUrl}/api/internal/repos/${repoId}/mirror/uploads/${uploadId}`;
|
|
181
181
|
await fetch(url, { method: "DELETE", headers: this.headers() }).catch(() => {});
|
|
182
182
|
}
|
|
183
|
-
async mirrorDown(repoId) {
|
|
184
|
-
const
|
|
183
|
+
async mirrorDown(repoId, gzip) {
|
|
184
|
+
const query = gzip ? "?compress=gzip" : "";
|
|
185
|
+
const res = await fetch(`${this.baseUrl}/api/internal/repos/${repoId}/mirror${query}`, {
|
|
185
186
|
headers: this.headers()
|
|
186
187
|
});
|
|
187
188
|
if (!res.ok)
|
|
@@ -192,7 +193,7 @@ class ManagerApi {
|
|
|
192
193
|
|
|
193
194
|
// src/mirror.ts
|
|
194
195
|
import { spawnSync as spawnSync3, spawn } from "child_process";
|
|
195
|
-
import { existsSync as existsSync2
|
|
196
|
+
import { existsSync as existsSync2 } from "fs";
|
|
196
197
|
import * as fsp from "fs/promises";
|
|
197
198
|
import { Readable } from "stream";
|
|
198
199
|
import { join as join2, dirname as dirname2 } from "path";
|
|
@@ -242,6 +243,7 @@ function urlsEqual(a, b) {
|
|
|
242
243
|
var HARDCODED_EXCLUDES = ["node_modules", "dist", ".next", ".venv", "__pycache__", ".turbo", ".DS_Store", "._*"];
|
|
243
244
|
var PART_RETRIES = 3;
|
|
244
245
|
var PART_BACKOFF_MS = [500, 2000, 8000];
|
|
246
|
+
var MIRROR_GZIP = true;
|
|
245
247
|
function getGitignoreExclusions(repoRoot) {
|
|
246
248
|
const res = spawnSync3("git", ["ls-files", "--others", "--ignored", "--exclude-standard", "--directory"], {
|
|
247
249
|
cwd: repoRoot,
|
|
@@ -267,30 +269,6 @@ async function carryOverIgnored(fromDir, toDir) {
|
|
|
267
269
|
await fsp.rename(src, dest).catch(() => {});
|
|
268
270
|
}
|
|
269
271
|
}
|
|
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
|
-
}
|
|
294
272
|
|
|
295
273
|
class MirrorAbort extends Error {
|
|
296
274
|
constructor(message) {
|
|
@@ -370,9 +348,10 @@ function createPartFlusher(api, repoId, uploadId, chunkSize) {
|
|
|
370
348
|
async function mirrorUp(plan, opts) {
|
|
371
349
|
const repoId = opts.create ? 0 : plan.matched[0].repoId;
|
|
372
350
|
const begin = await opts.api.mirrorBegin(repoId, { force: opts.force, create: opts.create });
|
|
373
|
-
const totalBytes = estimateTarSize(plan.repoRoot);
|
|
374
351
|
let bytesSent = 0;
|
|
375
352
|
const tarArgs = ["-c", "-C", plan.repoRoot];
|
|
353
|
+
if (MIRROR_GZIP)
|
|
354
|
+
tarArgs.unshift("-z");
|
|
376
355
|
for (const dir of HARDCODED_EXCLUDES)
|
|
377
356
|
tarArgs.push("--exclude", dir);
|
|
378
357
|
const ignoredPaths = getGitignoreExclusions(plan.repoRoot);
|
|
@@ -406,12 +385,12 @@ async function mirrorUp(plan, opts) {
|
|
|
406
385
|
for await (const chunk of child.stdout) {
|
|
407
386
|
await flusher.push(chunk);
|
|
408
387
|
bytesSent += chunk.length;
|
|
409
|
-
opts.onProgress?.({
|
|
388
|
+
opts.onProgress?.({ bytesSent });
|
|
410
389
|
}
|
|
411
390
|
await tarExit;
|
|
412
391
|
const totalParts = await flusher.finish();
|
|
413
|
-
opts.onProgress?.({
|
|
414
|
-
const result = await opts.api.mirrorCommit(begin.repoId, begin.uploadId, totalParts);
|
|
392
|
+
opts.onProgress?.({ bytesSent });
|
|
393
|
+
const result = await opts.api.mirrorCommit(begin.repoId, begin.uploadId, totalParts, MIRROR_GZIP);
|
|
415
394
|
return result;
|
|
416
395
|
} catch (err) {
|
|
417
396
|
if (!child.killed)
|
|
@@ -431,8 +410,11 @@ async function mirrorDown(repoId, repoRoot, api, opts = { force: false }) {
|
|
|
431
410
|
const staging = `${repoRoot}.ocm-recv-${Date.now()}`;
|
|
432
411
|
await fsp.mkdir(staging, { recursive: true });
|
|
433
412
|
try {
|
|
434
|
-
const tarball = await api.mirrorDown(repoId);
|
|
435
|
-
const
|
|
413
|
+
const tarball = await api.mirrorDown(repoId, MIRROR_GZIP);
|
|
414
|
+
const tarArgs = ["-x", "-f", "-", "-C", staging];
|
|
415
|
+
if (MIRROR_GZIP)
|
|
416
|
+
tarArgs.unshift("-z");
|
|
417
|
+
const child = spawn("tar", tarArgs, { stdio: ["pipe", "pipe", "pipe"] });
|
|
436
418
|
const stderrChunks = [];
|
|
437
419
|
child.stderr.on("data", (chunk) => stderrChunks.push(chunk));
|
|
438
420
|
const tarDone = new Promise((resolve, reject) => {
|
|
@@ -501,31 +483,10 @@ function formatBytes(bytes) {
|
|
|
501
483
|
function createProgressReporter(label, out = process.stderr, now = Date.now) {
|
|
502
484
|
let finished = false;
|
|
503
485
|
let lastRenderAt = -Infinity;
|
|
504
|
-
let lastBucket = -1;
|
|
505
486
|
let lastNonTtyTickAt = -Infinity;
|
|
506
487
|
let frameIndex = 0;
|
|
507
488
|
const isTTY = out.isTTY === true;
|
|
508
489
|
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
490
|
tick(bytes) {
|
|
530
491
|
if (finished)
|
|
531
492
|
return;
|
|
@@ -585,7 +546,7 @@ function toTarget(last) {
|
|
|
585
546
|
// package.json
|
|
586
547
|
var package_default = {
|
|
587
548
|
name: "@opencode-manager/ocm-cli",
|
|
588
|
-
version: "0.1.
|
|
549
|
+
version: "0.1.4",
|
|
589
550
|
description: "OpenCode Manager CLI: attach a local OpenCode TUI to a Manager-hosted repo.",
|
|
590
551
|
license: "MIT",
|
|
591
552
|
repository: {
|
|
@@ -882,14 +843,7 @@ async function cmdPush(args) {
|
|
|
882
843
|
const api = new ManagerApi(state.managerUrl, token);
|
|
883
844
|
const repos = await fetchRepos(state.managerUrl, token);
|
|
884
845
|
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
|
-
};
|
|
846
|
+
const onProgress = (p) => progress.tick(p.bytesSent);
|
|
893
847
|
const remotes = repos.map((r) => ({
|
|
894
848
|
repoId: r.repoId,
|
|
895
849
|
name: r.name,
|