@coderook/cli 0.21.0 → 0.22.0
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.
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "coderook",
|
|
3
3
|
"displayName": "CodeRook",
|
|
4
4
|
"description": "Save, browse and restore whole-snapshot versions of a project on CodeRook, from Claude Code.",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.22.0",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "ACCA Gaming Productions",
|
|
8
8
|
"url": "https://coderook.com"
|
|
@@ -718,7 +718,14 @@ class Uploader {
|
|
|
718
718
|
});
|
|
719
719
|
});
|
|
720
720
|
inFlight.delete(index);
|
|
721
|
-
|
|
721
|
+
/*
|
|
722
|
+
A chunk is not a file. Counting every reused piece here made a
|
|
723
|
+
one-file save report a negative number of sent files. The selected
|
|
724
|
+
file counts as already stored only when every one of its pieces was
|
|
725
|
+
reused and no payload crossed the wire.
|
|
726
|
+
*/
|
|
727
|
+
if (pieces.sentBytes === 0)
|
|
728
|
+
alreadyOnAccount += 1;
|
|
722
729
|
uploaded.push({
|
|
723
730
|
path: declaration.path,
|
|
724
731
|
sha256: declaration.sha256,
|
|
@@ -988,7 +995,7 @@ class Uploader {
|
|
|
988
995
|
...(completed.repeated ? { repeated: true } : {}),
|
|
989
996
|
sourceBytes: completed.version.sourceSize ?? sourceBytes,
|
|
990
997
|
storedBytes: completed.version.storedSize ?? storedBytes,
|
|
991
|
-
sentBytes,
|
|
998
|
+
sentBytes: transferred,
|
|
992
999
|
sentFiles: uploaded.length - alreadyOnAccount,
|
|
993
1000
|
reusedFiles: reused.length,
|
|
994
1001
|
/** Selected, but the service already held the content. */
|
|
@@ -1301,8 +1308,8 @@ class Uploader {
|
|
|
1301
1308
|
*/
|
|
1302
1309
|
const CHUNK_LANES = 4;
|
|
1303
1310
|
const active = new Set();
|
|
1304
|
-
const dispatch = async (piece, at) => {
|
|
1305
|
-
const task = this.sendChunk(repositoryId, declaration, piece, chunks, at, note).finally(() => {
|
|
1311
|
+
const dispatch = async (piece, at, digest) => {
|
|
1312
|
+
const task = this.sendChunk(repositoryId, declaration, piece, digest, chunks, at, note).finally(() => {
|
|
1306
1313
|
active.delete(task);
|
|
1307
1314
|
done += piece.length;
|
|
1308
1315
|
onOffset(done);
|
|
@@ -1311,6 +1318,51 @@ class Uploader {
|
|
|
1311
1318
|
if (active.size >= CHUNK_LANES)
|
|
1312
1319
|
await Promise.race(active);
|
|
1313
1320
|
};
|
|
1321
|
+
/*
|
|
1322
|
+
Ask about a bounded group of chunks before sending any of them.
|
|
1323
|
+
|
|
1324
|
+
The old path asked once per chunk, with four GETs in flight. Besides
|
|
1325
|
+
spending one network round trip per piece, an unavailable lookup was
|
|
1326
|
+
deliberately treated as a miss, so a transient failure retransmitted
|
|
1327
|
+
every otherwise reusable chunk. The website already uses the bulk
|
|
1328
|
+
endpoint. Reusing it here gives Desktop and CLI the same wire behaviour
|
|
1329
|
+
while retaining the safe "send it if unsure" fallback.
|
|
1330
|
+
|
|
1331
|
+
A batch is no larger than the existing chunk lane count, so this does
|
|
1332
|
+
not increase the memory ceiling: at most four bounded pieces are held.
|
|
1333
|
+
*/
|
|
1334
|
+
const batch = [];
|
|
1335
|
+
const flush = async () => {
|
|
1336
|
+
if (!batch.length)
|
|
1337
|
+
return;
|
|
1338
|
+
const ready = batch.splice(0, batch.length);
|
|
1339
|
+
const held = await this.storedInBulk(repositoryId, ready.map((item) => item.digest));
|
|
1340
|
+
for (const item of ready) {
|
|
1341
|
+
const stored = held.get(item.digest);
|
|
1342
|
+
if (stored) {
|
|
1343
|
+
note("reused", 0);
|
|
1344
|
+
chunks[item.at] = {
|
|
1345
|
+
objectId: stored.objectId,
|
|
1346
|
+
sourceSize: item.piece.length,
|
|
1347
|
+
storedSize: stored.size,
|
|
1348
|
+
};
|
|
1349
|
+
done += item.piece.length;
|
|
1350
|
+
onOffset(done);
|
|
1351
|
+
continue;
|
|
1352
|
+
}
|
|
1353
|
+
await dispatch(item.piece, item.at, item.digest);
|
|
1354
|
+
}
|
|
1355
|
+
await Promise.all(active);
|
|
1356
|
+
};
|
|
1357
|
+
const queue = async (piece) => {
|
|
1358
|
+
batch.push({
|
|
1359
|
+
piece,
|
|
1360
|
+
at: ordinal++,
|
|
1361
|
+
digest: (0, node_crypto_1.createHash)("sha256").update(piece).digest("hex"),
|
|
1362
|
+
});
|
|
1363
|
+
if (batch.length >= CHUNK_LANES)
|
|
1364
|
+
await flush();
|
|
1365
|
+
};
|
|
1314
1366
|
let ordinal = 0;
|
|
1315
1367
|
let pending = Buffer.alloc(0);
|
|
1316
1368
|
for await (const block of (0, node_fs_1.createReadStream)(declaration.full, {
|
|
@@ -1321,7 +1373,7 @@ class Uploader {
|
|
|
1321
1373
|
pending = pending.length ? Buffer.concat([pending, incoming]) : incoming;
|
|
1322
1374
|
let from = 0;
|
|
1323
1375
|
for (const cut of (0, chunking_js_1.cutPoints)(pending, profile)) {
|
|
1324
|
-
await
|
|
1376
|
+
await queue(Buffer.from(pending.subarray(from, cut)));
|
|
1325
1377
|
from = cut;
|
|
1326
1378
|
}
|
|
1327
1379
|
pending = Buffer.from(pending.subarray(from));
|
|
@@ -1333,8 +1385,8 @@ class Uploader {
|
|
|
1333
1385
|
Dropping it truncates every chunked file by exactly its last piece.
|
|
1334
1386
|
*/
|
|
1335
1387
|
if (pending.length)
|
|
1336
|
-
await
|
|
1337
|
-
await
|
|
1388
|
+
await queue(pending);
|
|
1389
|
+
await flush();
|
|
1338
1390
|
/*
|
|
1339
1391
|
Order is the file. Every position must be filled: a hole would mean a
|
|
1340
1392
|
chunk that never landed, and publishing around it would produce a version
|
|
@@ -1351,7 +1403,7 @@ class Uploader {
|
|
|
1351
1403
|
};
|
|
1352
1404
|
}
|
|
1353
1405
|
/** Send one chunk, reusing it if the account already holds those bytes. */
|
|
1354
|
-
async sendChunk(repositoryId, declaration, piece,
|
|
1406
|
+
async sendChunk(repositoryId, declaration, piece, digest,
|
|
1355
1407
|
/*
|
|
1356
1408
|
Written at a known position rather than appended. The chunks are sent
|
|
1357
1409
|
several at a time and finish in whatever order the network decides, but
|
|
@@ -1362,48 +1414,27 @@ class Uploader {
|
|
|
1362
1414
|
chunks, at, note) {
|
|
1363
1415
|
{
|
|
1364
1416
|
this.check();
|
|
1365
|
-
const
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
const
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1417
|
+
const encoded = await (0, compress_js_1.encodeForUpload)(new Uint8Array(piece), declaration.path, await this.gzipAllowed());
|
|
1418
|
+
const query = encoded.encoding === "gzip"
|
|
1419
|
+
? `?kind=chunk&role=chunk&encoding=gzip` +
|
|
1420
|
+
`&logicalSize=${piece.length}` +
|
|
1421
|
+
`&storedSha256=${encoded.storedSha256}`
|
|
1422
|
+
: `?kind=chunk&role=chunk`;
|
|
1423
|
+
const stored = await this.call(`/v1/repositories/${repositoryId}/objects/${digest}${query}`, {
|
|
1424
|
+
method: "PUT",
|
|
1425
|
+
contentType: "application/octet-stream",
|
|
1426
|
+
body: encoded.body,
|
|
1427
|
+
}).catch((error) => {
|
|
1428
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
1429
|
+
throw new Error(`${declaration.path}: ${reason}`);
|
|
1375
1430
|
});
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
}
|
|
1384
|
-
else {
|
|
1385
|
-
const encoded = await (0, compress_js_1.encodeForUpload)(new Uint8Array(piece), declaration.path, await this.gzipAllowed());
|
|
1386
|
-
const query = encoded.encoding === "gzip"
|
|
1387
|
-
? `?kind=chunk&role=chunk&encoding=gzip` +
|
|
1388
|
-
`&logicalSize=${piece.length}` +
|
|
1389
|
-
`&storedSha256=${encoded.storedSha256}`
|
|
1390
|
-
: `?kind=chunk&role=chunk`;
|
|
1391
|
-
const stored = await this.call(`/v1/repositories/${repositoryId}/objects/${digest}${query}`, {
|
|
1392
|
-
method: "PUT",
|
|
1393
|
-
contentType: "application/octet-stream",
|
|
1394
|
-
body: encoded.body,
|
|
1395
|
-
}).catch((error) => {
|
|
1396
|
-
const reason = error instanceof Error ? error.message : String(error);
|
|
1397
|
-
throw new Error(`${declaration.path}: ${reason}`);
|
|
1398
|
-
});
|
|
1399
|
-
note("sent", encoded.body.byteLength);
|
|
1400
|
-
chunks[at] = {
|
|
1401
|
-
objectId: stored.objectId,
|
|
1402
|
-
sourceSize: piece.length,
|
|
1403
|
-
/* What the service keeps, which is smaller when the piece gzipped. */
|
|
1404
|
-
storedSize: stored.storedSize ?? stored.size,
|
|
1405
|
-
};
|
|
1406
|
-
}
|
|
1431
|
+
note("sent", encoded.body.byteLength);
|
|
1432
|
+
chunks[at] = {
|
|
1433
|
+
objectId: stored.objectId,
|
|
1434
|
+
sourceSize: piece.length,
|
|
1435
|
+
/* What the service keeps, which is smaller when the piece gzipped. */
|
|
1436
|
+
storedSize: stored.storedSize ?? stored.size,
|
|
1437
|
+
};
|
|
1407
1438
|
}
|
|
1408
1439
|
}
|
|
1409
1440
|
async putDirect(repositoryId, declaration) {
|