@coderook/cli 0.29.0 → 0.29.1
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.29.
|
|
5
|
+
"version": "0.29.1",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "ACCA Gaming Productions",
|
|
8
8
|
"url": "https://coderook.com"
|
package/dist/cli/src/cli.js
CHANGED
|
@@ -661,6 +661,16 @@ Pass ${accent("--allow-secrets")} if these are not real keys.`);
|
|
|
661
661
|
localPath: folder,
|
|
662
662
|
changed: files.filter((file) => !file.deleted).map((file) => file.path),
|
|
663
663
|
deleted: files.filter((file) => file.deleted).map((file) => file.path),
|
|
664
|
+
/*
|
|
665
|
+
Nothing is left out, and `changed` above is not the whole of it.
|
|
666
|
+
|
|
667
|
+
`submit` sends whatever the rules allow — there is no per-file choosing
|
|
668
|
+
here — but it was passing the listed rows as the selection, and that list
|
|
669
|
+
stops at twenty thousand. A folder of twenty-one thousand files saved
|
|
670
|
+
twenty thousand of them and said "Saved v1" without mentioning the rest.
|
|
671
|
+
An empty exclusion list is how "all of it" is said.
|
|
672
|
+
*/
|
|
673
|
+
excluded: [],
|
|
664
674
|
message,
|
|
665
675
|
/*
|
|
666
676
|
A folder's name is the right default and the wrong answer for import,
|
package/dist/cli/src/publish.js
CHANGED
|
@@ -8,6 +8,7 @@ function uploadRequestFor(input) {
|
|
|
8
8
|
localPath: input.localPath,
|
|
9
9
|
// Both lists. See the note at the top of this file.
|
|
10
10
|
include: [...input.changed, ...input.deleted],
|
|
11
|
+
...(input.excluded === undefined ? {} : { excluded: input.excluded }),
|
|
11
12
|
deletions: input.deleted,
|
|
12
13
|
message: input.message,
|
|
13
14
|
projectName: input.projectName,
|
|
@@ -424,60 +424,98 @@ class Uploader {
|
|
|
424
424
|
// 1. Measure and hash. This is what makes an unchanged file free.
|
|
425
425
|
const declarations = [];
|
|
426
426
|
let totalBytes = 0;
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
427
|
+
/*
|
|
428
|
+
Hashed several at a time, and reported by how many are done.
|
|
429
|
+
|
|
430
|
+
Each file here is an open, a read and a close, and on Windows every open
|
|
431
|
+
is also an antivirus scan — so this stage is waiting, not computing.
|
|
432
|
+
Taken strictly one after another, a folder of twenty thousand small
|
|
433
|
+
files spent a quarter of an hour in a step that moves no data at all,
|
|
434
|
+
and the bar sat on 0% for the first thousand of them because it only
|
|
435
|
+
ever spanned a fifth of itself across the whole stage.
|
|
436
|
+
|
|
437
|
+
Overlapping the waits collapses them into one wait. The results are
|
|
438
|
+
written into their own slots rather than pushed, because which disk read
|
|
439
|
+
finishes first must not decide the order of a version's file list.
|
|
440
|
+
*/
|
|
441
|
+
const HASH_LANES = 8;
|
|
442
|
+
const measured = new Array(sending.length).fill(null);
|
|
443
|
+
let hashed = 0;
|
|
444
|
+
let cursor = 0;
|
|
445
|
+
/** Throttled: twenty thousand sends is its own cost, and nobody reads them. */
|
|
446
|
+
let told = 0;
|
|
447
|
+
const lane = async () => {
|
|
448
|
+
for (;;) {
|
|
449
|
+
const index = cursor;
|
|
450
|
+
cursor += 1;
|
|
451
|
+
if (index >= sending.length)
|
|
452
|
+
return;
|
|
453
|
+
const file = sending[index];
|
|
454
|
+
this.check();
|
|
455
|
+
const full = node_path_1.default.join(request.localPath, file.path);
|
|
456
|
+
let size;
|
|
457
|
+
try {
|
|
458
|
+
size = (await (0, promises_1.stat)(full)).size;
|
|
459
|
+
}
|
|
460
|
+
catch (error) {
|
|
461
|
+
// Unreadable now, though it was listed a moment ago. Skipping it
|
|
462
|
+
// would publish a version quietly missing a file the person chose.
|
|
463
|
+
vanished.push(file.path);
|
|
464
|
+
readFailures.set(file.path, error instanceof Error ? error.message : String(error));
|
|
465
|
+
hashed += 1;
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
/*
|
|
469
|
+
Hashing is the first thing that opens the file, and opening is what
|
|
470
|
+
fails on a file another program holds — a database in use, a browser
|
|
471
|
+
profile's LOCK, an antivirus mid-scan. On Windows those still answer
|
|
472
|
+
stat() perfectly well, so the check above lets them through and the
|
|
473
|
+
read below is the one that actually breaks.
|
|
474
|
+
|
|
475
|
+
It was unguarded, so the person got a raw "EBUSY: resource busy or
|
|
476
|
+
locked" naming a path they never chose to think about, instead of
|
|
477
|
+
the sentence this code already writes for exactly this situation.
|
|
478
|
+
*/
|
|
479
|
+
let digest;
|
|
480
|
+
try {
|
|
481
|
+
digest = await (0, profile_js_1.timed)("hash files", () => digestOf(full));
|
|
482
|
+
}
|
|
483
|
+
catch (error) {
|
|
484
|
+
vanished.push(file.path);
|
|
485
|
+
readFailures.set(file.path, error instanceof Error ? error.message : String(error));
|
|
486
|
+
hashed += 1;
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
measured[index] = {
|
|
490
|
+
path: file.path,
|
|
491
|
+
full,
|
|
492
|
+
size,
|
|
493
|
+
sha256: digest,
|
|
494
|
+
mediaType: mediaTypeOf(file.path),
|
|
495
|
+
};
|
|
496
|
+
hashed += 1;
|
|
497
|
+
const now = Date.now();
|
|
498
|
+
if (now - told > 80 || hashed === sending.length) {
|
|
499
|
+
told = now;
|
|
500
|
+
report({
|
|
501
|
+
stage: "hash",
|
|
502
|
+
files: hashed,
|
|
503
|
+
totalFiles: sending.length,
|
|
504
|
+
bytes: 0,
|
|
505
|
+
totalBytes: 0,
|
|
506
|
+
path: file.path,
|
|
507
|
+
percent: Math.round((hashed / sending.length) * 20),
|
|
508
|
+
bytesPerSecond: 0,
|
|
509
|
+
});
|
|
510
|
+
}
|
|
467
511
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
512
|
+
};
|
|
513
|
+
await Promise.all(Array.from({ length: Math.min(HASH_LANES, sending.length) }, () => lane()));
|
|
514
|
+
for (const one of measured) {
|
|
515
|
+
if (!one)
|
|
471
516
|
continue;
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
path: file.path,
|
|
475
|
-
full,
|
|
476
|
-
size,
|
|
477
|
-
sha256: digest,
|
|
478
|
-
mediaType: mediaTypeOf(file.path),
|
|
479
|
-
});
|
|
480
|
-
totalBytes += size;
|
|
517
|
+
declarations.push(one);
|
|
518
|
+
totalBytes += one.size;
|
|
481
519
|
}
|
|
482
520
|
/*
|
|
483
521
|
A deletion-only save has no file to hash or upload, but it is still a
|
|
@@ -1060,7 +1098,16 @@ class Uploader {
|
|
|
1060
1098
|
totalFiles: declarations.length,
|
|
1061
1099
|
bytes: sentBytes,
|
|
1062
1100
|
totalBytes,
|
|
1063
|
-
|
|
1101
|
+
/*
|
|
1102
|
+
A save, not a version.
|
|
1103
|
+
|
|
1104
|
+
Sending produces a commit; it becomes a version when somebody names
|
|
1105
|
+
it, which happens later and on purpose. This line said "Recording the
|
|
1106
|
+
version" while doing neither — it was the last wording left over from
|
|
1107
|
+
before the two were separate, and it sat on the one screen a person
|
|
1108
|
+
watches to the end.
|
|
1109
|
+
*/
|
|
1110
|
+
path: "Writing down what was sent",
|
|
1064
1111
|
percent: 96,
|
|
1065
1112
|
bytesPerSecond: rate(),
|
|
1066
1113
|
});
|