@coderook/cli 0.46.2 → 0.46.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/.claude-plugin/plugin.json +1 -1
- package/dist/cbx/src/core/upload.js +29 -5
- package/dist/cbx/src/local/history.js +27 -4
- package/dist/cbx/src/local/repository.js +90 -11
- package/dist/cbx/src/local/snapshot.js +110 -9
- package/dist/cbx/src/local/store.js +365 -9
- package/dist/cbx/src/local/store_v2.js +269 -0
- package/dist/cbx/src/shared/delta.js +12 -7
- package/dist/cbx/src/shared/native_engine.js +23 -0
- package/dist/cli/src/bin.js +21 -0
- package/dist/cli/src/local_status.js +18 -33
- package/dist/cli/src/paint.js +16 -0
- package/dist/cli/src/status_lines.js +26 -0
- package/engine-bin/darwin-arm64/cbx-engine +0 -0
- package/engine-bin/darwin-x64/cbx-engine +0 -0
- package/engine-bin/linux-arm64/cbx-engine +0 -0
- package/engine-bin/linux-x64/cbx-engine +0 -0
- package/engine-bin/win32-x64/cbx-engine.exe +0 -0
- package/package.json +1 -1
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.V2IndexFile = exports.V2_PACK_BYTES = exports.MAX_CHAIN = exports.LARGE_WINDOW_LOG = exports.LARGE_BLOCK_BYTES = exports.SOLID_BLOCK_BYTES = exports.SOLID_OBJECT_LIMIT = exports.KIND_DELTA_ZSTD = exports.KIND_DELTA = exports.KIND_SOLID = exports.KIND_WHOLE_ZSTD = exports.KIND_WHOLE = void 0;
|
|
4
|
+
exports.zstdAt = zstdAt;
|
|
5
|
+
exports.compresses = compresses;
|
|
6
|
+
exports.unzstd = unzstd;
|
|
7
|
+
exports.encodeIndex = encodeIndex;
|
|
8
|
+
exports.decodeIndex = decodeIndex;
|
|
9
|
+
exports.solidKey = solidKey;
|
|
10
|
+
exports.sha256Hex = sha256Hex;
|
|
11
|
+
/**
|
|
12
|
+
* How a cbx-local 2 history keeps objects on disk (engine/V2-STORE.md).
|
|
13
|
+
*
|
|
14
|
+
* A v2 history holds exactly the objects a v1 history holds, under the same
|
|
15
|
+
* names; only the way each is kept changes. A v2 pack is a run of records,
|
|
16
|
+
* each one of:
|
|
17
|
+
*
|
|
18
|
+
* whole the object's bytes, as they are or zstd-compressed
|
|
19
|
+
* solid many small objects joined and compressed as one block, so what
|
|
20
|
+
* files share (licence headers, imports, near-copies) is stored once
|
|
21
|
+
* delta an exact COPY/INSERT patch (CRP2, the format the service applies)
|
|
22
|
+
* against another object, as it is or zstd-compressed
|
|
23
|
+
*
|
|
24
|
+
* Every object read is rebuilt and checked against its name before it is
|
|
25
|
+
* used, delta bases included, so damage anywhere is refused rather than
|
|
26
|
+
* passed on.
|
|
27
|
+
*
|
|
28
|
+
* .cbx/objects/v2/<id>.pack records, written whole then renamed
|
|
29
|
+
* .cbx/objects/v2/<id>.idx binary index, sorted by name with a fan-out
|
|
30
|
+
*/
|
|
31
|
+
const node_crypto_1 = require("node:crypto");
|
|
32
|
+
const node_zlib_1 = require("node:zlib");
|
|
33
|
+
exports.KIND_WHOLE = 1;
|
|
34
|
+
exports.KIND_WHOLE_ZSTD = 2;
|
|
35
|
+
exports.KIND_SOLID = 3;
|
|
36
|
+
exports.KIND_DELTA = 4;
|
|
37
|
+
exports.KIND_DELTA_ZSTD = 5;
|
|
38
|
+
/** Objects at most this big wait to be joined into a solid block. */
|
|
39
|
+
exports.SOLID_OBJECT_LIMIT = 256 * 1024;
|
|
40
|
+
/** A solid block of small objects holds this much before it is compressed. */
|
|
41
|
+
exports.SOLID_BLOCK_BYTES = 16 * 1024 * 1024;
|
|
42
|
+
/*
|
|
43
|
+
Larger objects that compress are joined too, in bigger blocks with a window
|
|
44
|
+
as big as the block, so one program can be kept as what it shares with
|
|
45
|
+
another: CMake's fourteen largest files took 31.96 MB compressed one by one
|
|
46
|
+
and 23.89 MB together. Git finds the same thing with its deltas after gc.
|
|
47
|
+
*/
|
|
48
|
+
exports.LARGE_BLOCK_BYTES = 64 * 1024 * 1024;
|
|
49
|
+
exports.LARGE_WINDOW_LOG = 26;
|
|
50
|
+
/** A delta may stand on at most this many others; past that the object is kept whole. */
|
|
51
|
+
exports.MAX_CHAIN = 12;
|
|
52
|
+
/** A pack is written once the objects waiting for it reach this much. */
|
|
53
|
+
exports.V2_PACK_BYTES = 128 * 1024 * 1024;
|
|
54
|
+
function zstdAt(raw, level, long = false, windowLog, checksum = false) {
|
|
55
|
+
return (0, node_zlib_1.zstdCompressSync)(raw, {
|
|
56
|
+
params: {
|
|
57
|
+
[node_zlib_1.constants.ZSTD_c_compressionLevel]: level,
|
|
58
|
+
...(long ? { [node_zlib_1.constants.ZSTD_c_enableLongDistanceMatching]: 1 } : {}),
|
|
59
|
+
...(windowLog ? { [node_zlib_1.constants.ZSTD_c_windowLog]: windowLog } : {}),
|
|
60
|
+
/*
|
|
61
|
+
Blocks carry zstd's own checksum: four bytes, and damage to a block is
|
|
62
|
+
refused as damage even where it happens not to change the bytes. The
|
|
63
|
+
digest of every object read is checked either way.
|
|
64
|
+
*/
|
|
65
|
+
...(checksum ? { [node_zlib_1.constants.ZSTD_c_checksumFlag]: 1 } : {}),
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/** Whether bytes are worth compressing, judged from a 64 KiB sample of their middle. */
|
|
70
|
+
function compresses(raw) {
|
|
71
|
+
const sample = raw.length > 131072 ? raw.subarray((raw.length >> 1) - 32768, (raw.length >> 1) + 32768) : raw;
|
|
72
|
+
return zstdAt(sample, 1).length < sample.length * 0.92;
|
|
73
|
+
}
|
|
74
|
+
function unzstd(stored) {
|
|
75
|
+
return (0, node_zlib_1.zstdDecompressSync)(stored);
|
|
76
|
+
}
|
|
77
|
+
const INDEX_MAGIC = Buffer.from("CBXI", "latin1");
|
|
78
|
+
const INDEX_VERSION = 2;
|
|
79
|
+
const ENTRY_BYTES = 32 + 1 + 1 + 4 + 4 + 4 + 4;
|
|
80
|
+
const FANOUT = 256;
|
|
81
|
+
/* Below this many entries the fan-out table costs more than it saves, and is left out. */
|
|
82
|
+
const FANOUT_FROM = 1024;
|
|
83
|
+
const FLAG_FANOUT = 1;
|
|
84
|
+
/**
|
|
85
|
+
* The index of one v2 pack: a 16-byte header, a 256-entry fan-out by first
|
|
86
|
+
* byte (as Git's pack index has) when there are enough entries to need one,
|
|
87
|
+
* fixed 50-byte entries sorted by name, and the names delta bases refer to,
|
|
88
|
+
* once each. A delta's `inner` field is the position of its base in that
|
|
89
|
+
* table.
|
|
90
|
+
*/
|
|
91
|
+
function encodeIndex(entries) {
|
|
92
|
+
const names = [...entries.keys()].sort();
|
|
93
|
+
const bases = [];
|
|
94
|
+
const baseAt = new Map();
|
|
95
|
+
for (const name of names) {
|
|
96
|
+
const base = entries.get(name).base;
|
|
97
|
+
if (base && !baseAt.has(base)) {
|
|
98
|
+
baseAt.set(base, bases.length);
|
|
99
|
+
bases.push(base);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const fanout = names.length >= FANOUT_FROM;
|
|
103
|
+
const entriesAt = 16 + (fanout ? FANOUT * 4 : 0);
|
|
104
|
+
const out = Buffer.alloc(entriesAt + names.length * ENTRY_BYTES + bases.length * 32);
|
|
105
|
+
INDEX_MAGIC.copy(out, 0);
|
|
106
|
+
out.writeUInt8(INDEX_VERSION, 4);
|
|
107
|
+
out.writeUInt8(fanout ? FLAG_FANOUT : 0, 5);
|
|
108
|
+
out.writeUInt32LE(names.length, 8);
|
|
109
|
+
out.writeUInt32LE(bases.length, 12);
|
|
110
|
+
if (fanout) {
|
|
111
|
+
const counts = new Array(FANOUT).fill(0);
|
|
112
|
+
for (const name of names)
|
|
113
|
+
counts[Number.parseInt(name.slice(0, 2), 16)] += 1;
|
|
114
|
+
let running = 0;
|
|
115
|
+
for (let byte = 0; byte < FANOUT; byte += 1) {
|
|
116
|
+
running += counts[byte];
|
|
117
|
+
out.writeUInt32LE(running, 16 + byte * 4);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
let at = entriesAt;
|
|
121
|
+
for (const name of names) {
|
|
122
|
+
const entry = entries.get(name);
|
|
123
|
+
Buffer.from(name, "hex").copy(out, at);
|
|
124
|
+
out.writeUInt8(entry.kind, at + 32);
|
|
125
|
+
out.writeUInt8(entry.chain, at + 33);
|
|
126
|
+
out.writeUInt32LE(entry.offset, at + 34);
|
|
127
|
+
out.writeUInt32LE(entry.length, at + 38);
|
|
128
|
+
out.writeUInt32LE(entry.base ? baseAt.get(entry.base) : entry.inner, at + 42);
|
|
129
|
+
out.writeUInt32LE(entry.raw, at + 46);
|
|
130
|
+
at += ENTRY_BYTES;
|
|
131
|
+
}
|
|
132
|
+
for (const base of bases) {
|
|
133
|
+
Buffer.from(base, "hex").copy(out, at);
|
|
134
|
+
at += 32;
|
|
135
|
+
}
|
|
136
|
+
return out;
|
|
137
|
+
}
|
|
138
|
+
function decodeIndex(bytes) {
|
|
139
|
+
if (bytes.length < 16 || !bytes.subarray(0, 4).equals(INDEX_MAGIC)) {
|
|
140
|
+
throw new Error("A v2 pack index is damaged");
|
|
141
|
+
}
|
|
142
|
+
if (bytes.readUInt8(4) !== INDEX_VERSION)
|
|
143
|
+
throw new Error("A v2 pack index is of a version this cbx does not read");
|
|
144
|
+
const count = bytes.readUInt32LE(8);
|
|
145
|
+
const baseCount = bytes.readUInt32LE(12);
|
|
146
|
+
const entriesAt = 16 + ((bytes.readUInt8(5) & FLAG_FANOUT) !== 0 ? FANOUT * 4 : 0);
|
|
147
|
+
const basesAt = entriesAt + count * ENTRY_BYTES;
|
|
148
|
+
if (bytes.length !== basesAt + baseCount * 32)
|
|
149
|
+
throw new Error("A v2 pack index is damaged");
|
|
150
|
+
const bases = [];
|
|
151
|
+
for (let index = 0; index < baseCount; index += 1) {
|
|
152
|
+
bases.push(bytes.subarray(basesAt + index * 32, basesAt + index * 32 + 32).toString("hex"));
|
|
153
|
+
}
|
|
154
|
+
const entries = new Map();
|
|
155
|
+
for (let index = 0; index < count; index += 1) {
|
|
156
|
+
const at = entriesAt + index * ENTRY_BYTES;
|
|
157
|
+
const kind = bytes.readUInt8(at + 32);
|
|
158
|
+
const inner = bytes.readUInt32LE(at + 42);
|
|
159
|
+
const delta = kind === exports.KIND_DELTA || kind === exports.KIND_DELTA_ZSTD;
|
|
160
|
+
if (delta && inner >= baseCount)
|
|
161
|
+
throw new Error("A v2 pack index is damaged");
|
|
162
|
+
entries.set(bytes.subarray(at, at + 32).toString("hex"), {
|
|
163
|
+
kind,
|
|
164
|
+
chain: bytes.readUInt8(at + 33),
|
|
165
|
+
offset: bytes.readUInt32LE(at + 34),
|
|
166
|
+
length: bytes.readUInt32LE(at + 38),
|
|
167
|
+
inner: delta ? 0 : inner,
|
|
168
|
+
raw: bytes.readUInt32LE(at + 46),
|
|
169
|
+
...(delta ? { base: bases[inner] } : {}),
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
return entries;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* The order small objects are joined in: by extension, then name, so files
|
|
176
|
+
* of one kind sit together where the compressor can see what they share.
|
|
177
|
+
*/
|
|
178
|
+
function solidKey(filePath) {
|
|
179
|
+
if (!filePath)
|
|
180
|
+
return "\u{10ffff}";
|
|
181
|
+
const name = filePath.slice(filePath.lastIndexOf("/") + 1);
|
|
182
|
+
const dot = name.lastIndexOf(".");
|
|
183
|
+
const extension = dot > 0 ? name.slice(dot + 1).toLowerCase() : "";
|
|
184
|
+
return `${extension}\u0000${name.toLowerCase()}\u0000${filePath}`;
|
|
185
|
+
}
|
|
186
|
+
function sha256Hex(bytes) {
|
|
187
|
+
return (0, node_crypto_1.createHash)("sha256").update(bytes).digest("hex");
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* One v2 index, searched where it lies rather than decoded whole: its
|
|
191
|
+
* entries are sorted by name, so finding one is a binary search over the
|
|
192
|
+
* bytes. A status needs a handful of objects, and decoding every entry of
|
|
193
|
+
* every index into a map of strings was its largest single cost in a v2
|
|
194
|
+
* history (15 ms of 270 for 8,500 files).
|
|
195
|
+
*/
|
|
196
|
+
class V2IndexFile {
|
|
197
|
+
pack;
|
|
198
|
+
count;
|
|
199
|
+
bytes;
|
|
200
|
+
entriesAt;
|
|
201
|
+
basesAt;
|
|
202
|
+
fanout;
|
|
203
|
+
baseCount;
|
|
204
|
+
constructor(pack, bytes) {
|
|
205
|
+
if (bytes.length < 16 || !bytes.subarray(0, 4).equals(INDEX_MAGIC)) {
|
|
206
|
+
throw new Error("A v2 pack index is damaged");
|
|
207
|
+
}
|
|
208
|
+
if (bytes.readUInt8(4) !== INDEX_VERSION)
|
|
209
|
+
throw new Error("A v2 pack index is of a version this cbx does not read");
|
|
210
|
+
this.pack = pack;
|
|
211
|
+
this.bytes = bytes;
|
|
212
|
+
this.count = bytes.readUInt32LE(8);
|
|
213
|
+
this.baseCount = bytes.readUInt32LE(12);
|
|
214
|
+
this.fanout = (bytes.readUInt8(5) & FLAG_FANOUT) !== 0;
|
|
215
|
+
this.entriesAt = 16 + (this.fanout ? FANOUT * 4 : 0);
|
|
216
|
+
this.basesAt = this.entriesAt + this.count * ENTRY_BYTES;
|
|
217
|
+
if (bytes.length !== this.basesAt + this.baseCount * 32)
|
|
218
|
+
throw new Error("A v2 pack index is damaged");
|
|
219
|
+
}
|
|
220
|
+
find(digest) {
|
|
221
|
+
const key = Buffer.from(digest, "hex");
|
|
222
|
+
if (key.length !== 32)
|
|
223
|
+
return undefined;
|
|
224
|
+
let low = 0;
|
|
225
|
+
let high = this.count;
|
|
226
|
+
if (this.fanout) {
|
|
227
|
+
const first = key[0];
|
|
228
|
+
low = first === 0 ? 0 : this.bytes.readUInt32LE(16 + (first - 1) * 4);
|
|
229
|
+
high = this.bytes.readUInt32LE(16 + first * 4);
|
|
230
|
+
}
|
|
231
|
+
while (low < high) {
|
|
232
|
+
const middle = (low + high) >> 1;
|
|
233
|
+
const at = this.entriesAt + middle * ENTRY_BYTES;
|
|
234
|
+
const order = key.compare(this.bytes, at, at + 32);
|
|
235
|
+
if (order === 0)
|
|
236
|
+
return this.entryAt(at);
|
|
237
|
+
if (order < 0)
|
|
238
|
+
high = middle;
|
|
239
|
+
else
|
|
240
|
+
low = middle + 1;
|
|
241
|
+
}
|
|
242
|
+
return undefined;
|
|
243
|
+
}
|
|
244
|
+
entryAt(at) {
|
|
245
|
+
const kind = this.bytes.readUInt8(at + 32);
|
|
246
|
+
const inner = this.bytes.readUInt32LE(at + 42);
|
|
247
|
+
const delta = kind === exports.KIND_DELTA || kind === exports.KIND_DELTA_ZSTD;
|
|
248
|
+
if (delta && inner >= this.baseCount)
|
|
249
|
+
throw new Error("A v2 pack index is damaged");
|
|
250
|
+
return {
|
|
251
|
+
kind,
|
|
252
|
+
chain: this.bytes.readUInt8(at + 33),
|
|
253
|
+
offset: this.bytes.readUInt32LE(at + 34),
|
|
254
|
+
length: this.bytes.readUInt32LE(at + 38),
|
|
255
|
+
inner: delta ? 0 : inner,
|
|
256
|
+
raw: this.bytes.readUInt32LE(at + 46),
|
|
257
|
+
...(delta
|
|
258
|
+
? { base: this.bytes.subarray(this.basesAt + inner * 32, this.basesAt + inner * 32 + 32).toString("hex") }
|
|
259
|
+
: {}),
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
*names() {
|
|
263
|
+
for (let index = 0; index < this.count; index += 1) {
|
|
264
|
+
const at = this.entriesAt + index * ENTRY_BYTES;
|
|
265
|
+
yield this.bytes.subarray(at, at + 32).toString("hex");
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
exports.V2IndexFile = V2IndexFile;
|
|
@@ -333,8 +333,10 @@ function windowKey(bytes, at) {
|
|
|
333
333
|
* The result is the same CRP2 format the receiver-signature path produces:
|
|
334
334
|
* any service that applies those applies this, and verifies the digest after.
|
|
335
335
|
*/
|
|
336
|
-
function createExactDeltaPatch(base, target
|
|
337
|
-
|
|
336
|
+
function createExactDeltaPatch(base, target,
|
|
337
|
+
/* The transport's limit unless said; a local history keeps larger pieces as deltas too. */
|
|
338
|
+
maxBytes = exports.DELTA_MAX_FILE_BYTES) {
|
|
339
|
+
if (target.byteLength > maxBytes || base.byteLength > maxBytes) {
|
|
338
340
|
throw new Error("Delta target exceeds the bounded transport limit");
|
|
339
341
|
}
|
|
340
342
|
const operations = [];
|
|
@@ -362,7 +364,8 @@ function createExactDeltaPatch(base, target) {
|
|
|
362
364
|
delta indexes, so an 8 MiB base is half a million entries, not eight
|
|
363
365
|
million. The target is still scanned at every byte, so moved text is found.
|
|
364
366
|
*/
|
|
365
|
-
const
|
|
367
|
+
const middle = baseEnd - prefix;
|
|
368
|
+
const stride = middle > 8 * 1024 * 1024 ? EXACT_WINDOW * 4 : middle > 256 * 1024 ? EXACT_WINDOW : 1;
|
|
366
369
|
for (let at = prefix; at + EXACT_WINDOW <= baseEnd; at += stride) {
|
|
367
370
|
const key = windowKey(base, at);
|
|
368
371
|
const list = index.get(key);
|
|
@@ -494,9 +497,11 @@ function deltaPatchTargetSize(patch) {
|
|
|
494
497
|
}
|
|
495
498
|
return size;
|
|
496
499
|
}
|
|
497
|
-
function applyDeltaPatch(base, patch
|
|
500
|
+
function applyDeltaPatch(base, patch,
|
|
501
|
+
/* The transport's limit unless said; see createExactDeltaPatch. */
|
|
502
|
+
maxBytes = exports.DELTA_MAX_FILE_BYTES) {
|
|
498
503
|
if (sameMagic(patch, PATCH_MAGIC_V2)) {
|
|
499
|
-
return applyDeltaPatchV2(base, patch);
|
|
504
|
+
return applyDeltaPatchV2(base, patch, maxBytes);
|
|
500
505
|
}
|
|
501
506
|
if (patch.byteLength < PATCH_HEADER_V1_BYTES ||
|
|
502
507
|
!sameMagic(patch, PATCH_MAGIC_V1)) {
|
|
@@ -554,12 +559,12 @@ function applyDeltaPatch(base, patch) {
|
|
|
554
559
|
}
|
|
555
560
|
return out;
|
|
556
561
|
}
|
|
557
|
-
function applyDeltaPatchV2(base, patch) {
|
|
562
|
+
function applyDeltaPatchV2(base, patch, maxBytes = exports.DELTA_MAX_FILE_BYTES) {
|
|
558
563
|
const targetField = readVarUint(patch, 4);
|
|
559
564
|
const targetSize = targetField.value;
|
|
560
565
|
const countField = readVarUint(patch, targetField.next);
|
|
561
566
|
const operationCount = countField.value;
|
|
562
|
-
if (targetSize >
|
|
567
|
+
if (targetSize > maxBytes || operationCount > 1_000_000) {
|
|
563
568
|
throw new Error("Delta patch dimensions are invalid");
|
|
564
569
|
}
|
|
565
570
|
const out = new Uint8Array(targetSize);
|
|
@@ -11,6 +11,7 @@ exports.engineStore = engineStore;
|
|
|
11
11
|
exports.engineMeasure = engineMeasure;
|
|
12
12
|
exports.engineScan = engineScan;
|
|
13
13
|
exports.resetEngineForTests = resetEngineForTests;
|
|
14
|
+
exports.engineQuickStatus = engineQuickStatus;
|
|
14
15
|
/**
|
|
15
16
|
* The native byte engine (cbx/engine, Rust), when there is one.
|
|
16
17
|
*
|
|
@@ -287,3 +288,25 @@ function resetEngineForTests() {
|
|
|
287
288
|
located = undefined;
|
|
288
289
|
broken = null;
|
|
289
290
|
}
|
|
291
|
+
/**
|
|
292
|
+
* A quiet status answered by the engine alone (engine/src/quick.rs): "clean"
|
|
293
|
+
* only when a cbx-local 2 history's folder is exactly the line's newest save,
|
|
294
|
+
* judged from the stamp status leaves in the index and a walk of the folder.
|
|
295
|
+
* Anything else, and an engine too old to know the command, is null or
|
|
296
|
+
* "unknown", and the full status answers. Never marks the engine broken: an
|
|
297
|
+
* older engine still scans for that full status.
|
|
298
|
+
*/
|
|
299
|
+
async function engineQuickStatus(from) {
|
|
300
|
+
if (!enginePath() || broken || process.env.CBX_ENGINE_QUICK === "off" || !usable())
|
|
301
|
+
return null;
|
|
302
|
+
const answer = await run(["quick-status", from], "");
|
|
303
|
+
if (answer.code !== 0)
|
|
304
|
+
return null;
|
|
305
|
+
try {
|
|
306
|
+
const parsed = JSON.parse(answer.stdout);
|
|
307
|
+
return parsed.protocol === PROTOCOL ? parsed : null;
|
|
308
|
+
}
|
|
309
|
+
catch {
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
}
|
package/dist/cli/src/bin.js
CHANGED
|
@@ -57,6 +57,27 @@ async function quickStatus() {
|
|
|
57
57
|
return null;
|
|
58
58
|
if (await (0, reminder_gate_js_1.reminderDue)("status"))
|
|
59
59
|
return null;
|
|
60
|
+
/*
|
|
61
|
+
A folder that is exactly its last save is answered by the engine alone,
|
|
62
|
+
from the stamp the last status left (engine/src/quick.rs): one walk and
|
|
63
|
+
four small reads, and none of the history code loaded here. Anything it
|
|
64
|
+
cannot vouch for is answered below as before.
|
|
65
|
+
*/
|
|
66
|
+
const { engineQuickStatus } = await Promise.resolve().then(() => __importStar(require("../../cbx/src/shared/native_engine.js")));
|
|
67
|
+
const quick = await engineQuickStatus(process.cwd());
|
|
68
|
+
if (quick?.answer === "clean" || quick?.answer === "changed") {
|
|
69
|
+
const { accent, bold, dim, short } = await Promise.resolve().then(() => __importStar(require("./paint.js")));
|
|
70
|
+
console.log(`${bold(quick.project)} on ${accent(quick.line)}` +
|
|
71
|
+
dim(` last saved ${short(quick.head)}: ${quick.message.split("\n")[0]}`));
|
|
72
|
+
if (quick.answer === "clean") {
|
|
73
|
+
console.log("Nothing has changed since the last save.");
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
const { printChangedSummary } = await Promise.resolve().then(() => __importStar(require("./status_lines.js")));
|
|
77
|
+
printChangedSummary({ added: quick.added, modified: quick.modified, deleted: quick.deleted });
|
|
78
|
+
}
|
|
79
|
+
return 0;
|
|
80
|
+
}
|
|
60
81
|
const { Repository } = await Promise.resolve().then(() => __importStar(require("../../cbx/src/local/repository.js")));
|
|
61
82
|
const repository = await Repository.find(process.cwd());
|
|
62
83
|
if (!repository)
|
|
@@ -3,8 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
7
|
-
exports.printChanges = printChanges;
|
|
6
|
+
exports.short = exports.red = exports.printChanges = exports.plural = exports.green = exports.dim = exports.bold = exports.accent = void 0;
|
|
8
7
|
exports.printLocalStatus = printLocalStatus;
|
|
9
8
|
/**
|
|
10
9
|
* What `cbx status` prints in a folder with a local history, kept apart from
|
|
@@ -17,37 +16,25 @@ exports.printLocalStatus = printLocalStatus;
|
|
|
17
16
|
*/
|
|
18
17
|
const node_path_1 = __importDefault(require("node:path"));
|
|
19
18
|
const history_js_1 = require("../../cbx/src/local/history.js");
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
exports
|
|
23
|
-
exports
|
|
24
|
-
exports
|
|
25
|
-
exports
|
|
26
|
-
exports
|
|
27
|
-
const
|
|
28
|
-
exports.
|
|
29
|
-
|
|
30
|
-
exports.plural = plural;
|
|
31
|
-
function printChanges(changes, limit = 50) {
|
|
32
|
-
const rows = [
|
|
33
|
-
...changes.added.map((file) => [(0, exports.green)("added "), file]),
|
|
34
|
-
...changes.modified.map((file) => [(0, exports.accent)("changed "), file]),
|
|
35
|
-
...changes.deleted.map((file) => [(0, exports.red)("deleted "), file]),
|
|
36
|
-
].sort((left, right) => (left[1] < right[1] ? -1 : left[1] > right[1] ? 1 : 0));
|
|
37
|
-
for (const [mark, file] of rows.slice(0, limit))
|
|
38
|
-
console.log(` ${mark}${file}`);
|
|
39
|
-
if (rows.length > limit)
|
|
40
|
-
console.log((0, exports.dim)(` … and ${rows.length - limit} more`));
|
|
41
|
-
}
|
|
19
|
+
const paint_js_1 = require("./paint.js");
|
|
20
|
+
Object.defineProperty(exports, "accent", { enumerable: true, get: function () { return paint_js_1.accent; } });
|
|
21
|
+
Object.defineProperty(exports, "bold", { enumerable: true, get: function () { return paint_js_1.bold; } });
|
|
22
|
+
Object.defineProperty(exports, "dim", { enumerable: true, get: function () { return paint_js_1.dim; } });
|
|
23
|
+
Object.defineProperty(exports, "green", { enumerable: true, get: function () { return paint_js_1.green; } });
|
|
24
|
+
Object.defineProperty(exports, "red", { enumerable: true, get: function () { return paint_js_1.red; } });
|
|
25
|
+
Object.defineProperty(exports, "short", { enumerable: true, get: function () { return paint_js_1.short; } });
|
|
26
|
+
const status_lines_js_1 = require("./status_lines.js");
|
|
27
|
+
Object.defineProperty(exports, "plural", { enumerable: true, get: function () { return status_lines_js_1.plural; } });
|
|
28
|
+
Object.defineProperty(exports, "printChanges", { enumerable: true, get: function () { return status_lines_js_1.printChanges; } });
|
|
42
29
|
/** The status of a local history, printed. */
|
|
43
30
|
async function printLocalStatus(repository) {
|
|
44
31
|
const now = await (0, history_js_1.status)(repository);
|
|
45
|
-
console.log(`${(0,
|
|
46
|
-
(now.head ? (0,
|
|
32
|
+
console.log(`${(0, paint_js_1.bold)(node_path_1.default.basename(repository.root))} on ${(0, paint_js_1.accent)(now.line)}` +
|
|
33
|
+
(now.head ? (0, paint_js_1.dim)(` last saved ${(0, paint_js_1.short)(now.head.id)}: ${now.head.message.split("\n")[0]}`) : (0, paint_js_1.dim)(" nothing saved yet")));
|
|
47
34
|
if (now.merging) {
|
|
48
35
|
const left = now.merging.unresolved.length;
|
|
49
|
-
console.log(`Merging ${(0,
|
|
50
|
-
(left ? (0,
|
|
36
|
+
console.log(`Merging ${(0, paint_js_1.accent)((0, paint_js_1.short)(now.merging.theirs))}: ` +
|
|
37
|
+
(left ? (0, paint_js_1.red)(`${(0, status_lines_js_1.plural)(left, "file")} still ${left === 1 ? "has" : "have"} conflict markers`) : (0, paint_js_1.green)("no conflict markers left")));
|
|
51
38
|
for (const conflict of now.merging.conflicts) {
|
|
52
39
|
const how = conflict.kind === "text"
|
|
53
40
|
? now.merging.unresolved.includes(conflict.path) ? "markers in it" : "edited"
|
|
@@ -56,17 +43,15 @@ async function printLocalStatus(repository) {
|
|
|
56
43
|
: conflict.kind === "deleted-here"
|
|
57
44
|
? "deleted here, changed there; their copy is in the folder"
|
|
58
45
|
: "changed here, deleted there; this copy is in the folder";
|
|
59
|
-
console.log(` ${(0,
|
|
46
|
+
console.log(` ${(0, paint_js_1.red)("conflict")} ${conflict.path} ${(0, paint_js_1.dim)(how)}`);
|
|
60
47
|
}
|
|
61
|
-
console.log((0,
|
|
48
|
+
console.log((0, paint_js_1.dim)(`Finish with cbx save, take a side with cbx merge --mine or --theirs, or cbx merge --cancel.`));
|
|
62
49
|
}
|
|
63
50
|
const count = (0, history_js_1.changeCount)(now.changes);
|
|
64
51
|
if (!count) {
|
|
65
52
|
console.log("Nothing has changed since the last save.");
|
|
66
53
|
return 0;
|
|
67
54
|
}
|
|
68
|
-
|
|
69
|
-
printChanges(now.changes);
|
|
70
|
-
console.log((0, exports.dim)(`cbx diff shows the lines; cbx save -m "…" records them.`));
|
|
55
|
+
(0, status_lines_js_1.printChangedSummary)(now.changes);
|
|
71
56
|
return 0;
|
|
72
57
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.short = exports.accent = exports.green = exports.red = exports.bold = exports.dim = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* The colours cbx prints in, on their own so the quickest paths (bin.ts)
|
|
6
|
+
* can print exactly what the full commands print without loading them.
|
|
7
|
+
*/
|
|
8
|
+
const ESC = String.fromCharCode(27);
|
|
9
|
+
const paint = (code) => (value) => `${ESC}[${code}m${value}${ESC}[0m`;
|
|
10
|
+
exports.dim = paint("2");
|
|
11
|
+
exports.bold = paint("1");
|
|
12
|
+
exports.red = paint("31");
|
|
13
|
+
exports.green = paint("32");
|
|
14
|
+
exports.accent = paint("33");
|
|
15
|
+
const short = (id) => id.slice(0, 10);
|
|
16
|
+
exports.short = short;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.plural = void 0;
|
|
4
|
+
exports.printChanges = printChanges;
|
|
5
|
+
exports.printChangedSummary = printChangedSummary;
|
|
6
|
+
const paint_js_1 = require("./paint.js");
|
|
7
|
+
const plural = (count, one, many = `${one}s`) => `${count.toLocaleString()} ${count === 1 ? one : many}`;
|
|
8
|
+
exports.plural = plural;
|
|
9
|
+
function printChanges(changes, limit = 50) {
|
|
10
|
+
const rows = [
|
|
11
|
+
...changes.added.map((file) => [(0, paint_js_1.green)("added "), file]),
|
|
12
|
+
...changes.modified.map((file) => [(0, paint_js_1.accent)("changed "), file]),
|
|
13
|
+
...changes.deleted.map((file) => [(0, paint_js_1.red)("deleted "), file]),
|
|
14
|
+
].sort((left, right) => (left[1] < right[1] ? -1 : left[1] > right[1] ? 1 : 0));
|
|
15
|
+
for (const [mark, file] of rows.slice(0, limit))
|
|
16
|
+
console.log(` ${mark}${file}`);
|
|
17
|
+
if (rows.length > limit)
|
|
18
|
+
console.log((0, paint_js_1.dim)(` … and ${rows.length - limit} more`));
|
|
19
|
+
}
|
|
20
|
+
/** Everything after the first line when something changed. */
|
|
21
|
+
function printChangedSummary(changes) {
|
|
22
|
+
const count = changes.added.length + changes.modified.length + changes.deleted.length;
|
|
23
|
+
console.log(`${(0, exports.plural)(count, "file")} changed since the last save:`);
|
|
24
|
+
printChanges(changes);
|
|
25
|
+
console.log((0, paint_js_1.dim)(`cbx diff shows the lines; cbx save -m "…" records them.`));
|
|
26
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coderook/cli",
|
|
3
|
-
"version": "0.46.
|
|
3
|
+
"version": "0.46.3",
|
|
4
4
|
"description": "The command line for CodeRook (coderook.com): version control and project hosting for projects of any size. Every save is a compressed snapshot, private or public, never used to train AI.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://coderook.com",
|