@lodestar/utils 1.35.0-dev.f80d2d52da → 1.35.0-dev.fcf8d024ea
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/lib/assert.d.ts.map +1 -0
- package/lib/assert.js +1 -1
- package/lib/assert.js.map +1 -1
- package/lib/base64.d.ts.map +1 -0
- package/lib/bytes/browser.d.ts.map +1 -0
- package/lib/bytes/index.d.ts.map +1 -0
- package/lib/bytes/nodejs.d.ts.map +1 -0
- package/lib/bytes.d.ts.map +1 -0
- package/lib/command.d.ts.map +1 -0
- package/lib/diff.d.ts.map +1 -0
- package/lib/err.d.ts.map +1 -0
- package/lib/errors.d.ts.map +1 -0
- package/lib/errors.js +1 -0
- package/lib/errors.js.map +1 -1
- package/lib/ethConversion.d.ts.map +1 -0
- package/lib/fetch.d.ts.map +1 -0
- package/lib/fetch.js +3 -0
- package/lib/fetch.js.map +1 -1
- package/lib/format.d.ts.map +1 -0
- package/lib/index.d.ts +8 -8
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +6 -6
- package/lib/index.js.map +1 -1
- package/lib/iterator.d.ts.map +1 -0
- package/lib/logger.d.ts.map +1 -0
- package/lib/map.d.ts.map +1 -0
- package/lib/map.js +6 -7
- package/lib/map.js.map +1 -1
- package/lib/math.d.ts.map +1 -0
- package/lib/metrics.d.ts.map +1 -0
- package/lib/notNullish.d.ts.map +1 -0
- package/lib/objects.d.ts.map +1 -0
- package/lib/promise.d.ts.map +1 -0
- package/lib/retry.d.ts.map +1 -0
- package/lib/sleep.d.ts.map +1 -0
- package/lib/sort.d.ts.map +1 -0
- package/lib/timeout.d.ts.map +1 -0
- package/lib/types.d.ts +6 -4
- package/lib/types.d.ts.map +1 -0
- package/lib/types.js.map +1 -1
- package/lib/url.d.ts.map +1 -0
- package/lib/verifyMerkleBranch.d.ts.map +1 -0
- package/lib/waitFor.d.ts.map +1 -0
- package/lib/yaml/index.d.ts.map +1 -0
- package/lib/yaml/int.d.ts.map +1 -0
- package/lib/yaml/schema.d.ts.map +1 -0
- package/lib/yaml/schema.js.map +1 -1
- package/package.json +11 -8
- package/src/assert.ts +86 -0
- package/src/base64.ts +9 -0
- package/src/bytes/browser.ts +123 -0
- package/src/bytes/index.ts +29 -0
- package/src/bytes/nodejs.ts +63 -0
- package/src/bytes.ts +84 -0
- package/src/command.ts +74 -0
- package/src/diff.ts +234 -0
- package/src/err.ts +105 -0
- package/src/errors.ts +73 -0
- package/src/ethConversion.ts +12 -0
- package/src/fetch.ts +188 -0
- package/src/format.ts +119 -0
- package/src/index.ts +28 -0
- package/src/iterator.ts +10 -0
- package/src/logger.ts +20 -0
- package/src/map.ts +108 -0
- package/src/math.ts +55 -0
- package/src/metrics.ts +73 -0
- package/src/notNullish.ts +11 -0
- package/src/objects.ts +102 -0
- package/src/promise.ts +163 -0
- package/src/retry.ts +75 -0
- package/src/sleep.ts +32 -0
- package/src/sort.ts +9 -0
- package/src/timeout.ts +27 -0
- package/src/types.ts +48 -0
- package/src/url.ts +29 -0
- package/src/verifyMerkleBranch.ts +27 -0
- package/src/waitFor.ts +87 -0
- package/src/yaml/index.ts +12 -0
- package/src/yaml/int.ts +190 -0
- package/src/yaml/schema.ts +8 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// "0".charCodeAt(0) = 48
|
|
2
|
+
const CHAR_CODE_0 = 48;
|
|
3
|
+
// "x".charCodeAt(0) = 120
|
|
4
|
+
const CHAR_CODE_X = 120;
|
|
5
|
+
|
|
6
|
+
export function toHex(bytes: Uint8Array): string {
|
|
7
|
+
const charCodes = new Array<number>(bytes.length * 2 + 2);
|
|
8
|
+
charCodes[0] = CHAR_CODE_0;
|
|
9
|
+
charCodes[1] = CHAR_CODE_X;
|
|
10
|
+
|
|
11
|
+
bytesIntoCharCodes(bytes, charCodes);
|
|
12
|
+
return String.fromCharCode(...charCodes);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const rootCharCodes = new Array<number>(32 * 2 + 2);
|
|
16
|
+
rootCharCodes[0] = CHAR_CODE_0;
|
|
17
|
+
rootCharCodes[1] = CHAR_CODE_X;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Convert a Uint8Array, length 32, to 0x-prefixed hex string
|
|
21
|
+
*/
|
|
22
|
+
export function toRootHex(root: Uint8Array): string {
|
|
23
|
+
if (root.length !== 32) {
|
|
24
|
+
throw Error(`Expect root to be 32 bytes, got ${root.length}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
bytesIntoCharCodes(root, rootCharCodes);
|
|
28
|
+
return String.fromCharCode(...rootCharCodes);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const pubkeyCharCodes = new Array<number>(48 * 2 + 2);
|
|
32
|
+
pubkeyCharCodes[0] = CHAR_CODE_0;
|
|
33
|
+
pubkeyCharCodes[1] = CHAR_CODE_X;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Convert a Uint8Array, length 48, to 0x-prefixed hex string
|
|
37
|
+
*/
|
|
38
|
+
export function toPubkeyHex(pubkey: Uint8Array): string {
|
|
39
|
+
if (pubkey.length !== CHAR_CODE_0) {
|
|
40
|
+
throw Error(`Expect pubkey to be 48 bytes, got ${pubkey.length}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
bytesIntoCharCodes(pubkey, pubkeyCharCodes);
|
|
44
|
+
return String.fromCharCode(...pubkeyCharCodes);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function fromHex(hex: string): Uint8Array {
|
|
48
|
+
if (typeof hex !== "string") {
|
|
49
|
+
throw new Error(`hex argument type ${typeof hex} must be of type string`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (hex.startsWith("0x")) {
|
|
53
|
+
hex = hex.slice(2);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (hex.length % 2 !== 0) {
|
|
57
|
+
throw new Error(`hex string length ${hex.length} must be multiple of 2`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const byteLen = hex.length / 2;
|
|
61
|
+
const bytes = new Uint8Array(byteLen);
|
|
62
|
+
for (let i = 0; i < byteLen; i++) {
|
|
63
|
+
const byte2i = charCodeToByte(hex.charCodeAt(i * 2));
|
|
64
|
+
const byte2i1 = charCodeToByte(hex.charCodeAt(i * 2 + 1));
|
|
65
|
+
bytes[i] = (byte2i << 4) | byte2i1;
|
|
66
|
+
}
|
|
67
|
+
return bytes;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function fromHexInto(hex: string, buffer: Uint8Array): void {
|
|
71
|
+
if (hex.startsWith("0x")) {
|
|
72
|
+
hex = hex.slice(2);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (hex.length !== buffer.length * 2) {
|
|
76
|
+
throw new Error(`hex string length ${hex.length} must be exactly double the buffer length ${buffer.length}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
80
|
+
const byte2i = charCodeToByte(hex.charCodeAt(i * 2));
|
|
81
|
+
const byte2i1 = charCodeToByte(hex.charCodeAt(i * 2 + 1));
|
|
82
|
+
buffer[i] = (byte2i << 4) | byte2i1;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Populate charCodes from bytes. Note that charCodes index 0 and 1 ("0x") are not populated.
|
|
88
|
+
*/
|
|
89
|
+
function bytesIntoCharCodes(bytes: Uint8Array, charCodes: number[]): void {
|
|
90
|
+
if (bytes.length * 2 + 2 !== charCodes.length) {
|
|
91
|
+
throw Error(`Expect charCodes to be of length ${bytes.length * 2 + 2}, got ${charCodes.length}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
95
|
+
const byte = bytes[i];
|
|
96
|
+
const first = (byte & 0xf0) >> 4;
|
|
97
|
+
const second = byte & 0x0f;
|
|
98
|
+
|
|
99
|
+
// "0".charCodeAt(0) = 48
|
|
100
|
+
// "a".charCodeAt(0) = 97 => delta = 87
|
|
101
|
+
charCodes[2 + 2 * i] = first < 10 ? first + 48 : first + 87;
|
|
102
|
+
charCodes[2 + 2 * i + 1] = second < 10 ? second + 48 : second + 87;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function charCodeToByte(charCode: number): number {
|
|
107
|
+
// "a".charCodeAt(0) = 97, "f".charCodeAt(0) = 102 => delta = 87
|
|
108
|
+
if (charCode >= 97 && charCode <= 102) {
|
|
109
|
+
return charCode - 87;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// "A".charCodeAt(0) = 65, "F".charCodeAt(0) = 70 => delta = 55
|
|
113
|
+
if (charCode >= 65 && charCode <= 70) {
|
|
114
|
+
return charCode - 55;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// "0".charCodeAt(0) = 48, "9".charCodeAt(0) = 57 => delta = 48
|
|
118
|
+
if (charCode >= 48 && charCode <= 57) {
|
|
119
|
+
return charCode - 48;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
throw new Error(`Invalid hex character code: ${charCode}`);
|
|
123
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {
|
|
2
|
+
fromHex as browserFromHex,
|
|
3
|
+
fromHexInto as browserFromHexInto,
|
|
4
|
+
toHex as browserToHex,
|
|
5
|
+
toPubkeyHex as browserToPubkeyHex,
|
|
6
|
+
toRootHex as browserToRootHex,
|
|
7
|
+
} from "./browser.js";
|
|
8
|
+
import {
|
|
9
|
+
fromHex as nodeFromHex,
|
|
10
|
+
toHex as nodeToHex,
|
|
11
|
+
toPubkeyHex as nodeToPubkeyHex,
|
|
12
|
+
toRootHex as nodeToRootHex,
|
|
13
|
+
} from "./nodejs.js";
|
|
14
|
+
|
|
15
|
+
let toHex = browserToHex;
|
|
16
|
+
let toRootHex = browserToRootHex;
|
|
17
|
+
let toPubkeyHex = browserToPubkeyHex;
|
|
18
|
+
let fromHex = browserFromHex;
|
|
19
|
+
// there is no fromHexInto for NodeJs as the performance of browserFromHexInto is >100x faster
|
|
20
|
+
const fromHexInto = browserFromHexInto;
|
|
21
|
+
|
|
22
|
+
if (typeof Buffer !== "undefined") {
|
|
23
|
+
toHex = nodeToHex;
|
|
24
|
+
toRootHex = nodeToRootHex;
|
|
25
|
+
toPubkeyHex = nodeToPubkeyHex;
|
|
26
|
+
fromHex = nodeFromHex;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export {toHex, toRootHex, toPubkeyHex, fromHex, fromHexInto};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export function toHex(buffer: Uint8Array | Parameters<typeof Buffer.from>[0]): string {
|
|
2
|
+
if (Buffer.isBuffer(buffer)) {
|
|
3
|
+
return "0x" + buffer.toString("hex");
|
|
4
|
+
}
|
|
5
|
+
if (buffer instanceof Uint8Array) {
|
|
6
|
+
return "0x" + Buffer.from(buffer.buffer, buffer.byteOffset, buffer.length).toString("hex");
|
|
7
|
+
}
|
|
8
|
+
return "0x" + Buffer.from(buffer).toString("hex");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Shared buffer to convert root to hex
|
|
12
|
+
let rootBuf: Buffer | undefined;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Convert a Uint8Array, length 32, to 0x-prefixed hex string
|
|
16
|
+
*/
|
|
17
|
+
export function toRootHex(root: Uint8Array): string {
|
|
18
|
+
if (root.length !== 32) {
|
|
19
|
+
throw Error(`Expect root to be 32 bytes, got ${root.length}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (rootBuf === undefined) {
|
|
23
|
+
rootBuf = Buffer.alloc(32);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
rootBuf.set(root);
|
|
27
|
+
return `0x${rootBuf.toString("hex")}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Shared buffer to convert pubkey to hex
|
|
31
|
+
let pubkeyBuf: Buffer | undefined;
|
|
32
|
+
|
|
33
|
+
export function toPubkeyHex(pubkey: Uint8Array): string {
|
|
34
|
+
if (pubkey.length !== 48) {
|
|
35
|
+
throw Error(`Expect pubkey to be 48 bytes, got ${pubkey.length}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (pubkeyBuf === undefined) {
|
|
39
|
+
pubkeyBuf = Buffer.alloc(48);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
pubkeyBuf.set(pubkey);
|
|
43
|
+
return `0x${pubkeyBuf.toString("hex")}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function fromHex(hex: string): Uint8Array {
|
|
47
|
+
if (typeof hex !== "string") {
|
|
48
|
+
throw new Error(`hex argument type ${typeof hex} must be of type string`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (hex.startsWith("0x")) {
|
|
52
|
+
hex = hex.slice(2);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (hex.length % 2 !== 0) {
|
|
56
|
+
throw new Error(`hex string length ${hex.length} must be multiple of 2`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const b = Buffer.from(hex, "hex");
|
|
60
|
+
return new Uint8Array(b.buffer, b.byteOffset, b.length);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/// the performance of fromHexInto using a preallocated buffer is very bad compared to browser so I moved it to the benchmark
|
package/src/bytes.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import {toBigIntBE, toBigIntLE, toBufferBE, toBufferLE} from "bigint-buffer";
|
|
2
|
+
|
|
3
|
+
type Endianness = "le" | "be";
|
|
4
|
+
|
|
5
|
+
const hexByByte: string[] = [];
|
|
6
|
+
/**
|
|
7
|
+
* @deprecated Use toHex() instead.
|
|
8
|
+
*/
|
|
9
|
+
export function toHexString(bytes: Uint8Array): string {
|
|
10
|
+
let hex = "0x";
|
|
11
|
+
for (const byte of bytes) {
|
|
12
|
+
if (!hexByByte[byte]) {
|
|
13
|
+
hexByByte[byte] = byte < 16 ? "0" + byte.toString(16) : byte.toString(16);
|
|
14
|
+
}
|
|
15
|
+
hex += hexByByte[byte];
|
|
16
|
+
}
|
|
17
|
+
return hex;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Return a byte array from a number or BigInt
|
|
22
|
+
*/
|
|
23
|
+
export function intToBytes(value: bigint | number, length: number, endianness: Endianness = "le"): Buffer {
|
|
24
|
+
return bigIntToBytes(BigInt(value), length, endianness);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Convert byte array in LE to integer.
|
|
29
|
+
*/
|
|
30
|
+
export function bytesToInt(value: Uint8Array, endianness: Endianness = "le"): number {
|
|
31
|
+
return Number(bytesToBigInt(value, endianness));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function bigIntToBytes(value: bigint, length: number, endianness: Endianness = "le"): Buffer {
|
|
35
|
+
if (endianness === "le") {
|
|
36
|
+
return toBufferLE(value, length);
|
|
37
|
+
}
|
|
38
|
+
if (endianness === "be") {
|
|
39
|
+
return toBufferBE(value, length);
|
|
40
|
+
}
|
|
41
|
+
throw new Error("endianness must be either 'le' or 'be'");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function bytesToBigInt(value: Uint8Array, endianness: Endianness = "le"): bigint {
|
|
45
|
+
if (!(value instanceof Uint8Array)) {
|
|
46
|
+
throw new TypeError("expected a Uint8Array");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (endianness === "le") {
|
|
50
|
+
return toBigIntLE(value as Buffer);
|
|
51
|
+
}
|
|
52
|
+
if (endianness === "be") {
|
|
53
|
+
return toBigIntBE(value as Buffer);
|
|
54
|
+
}
|
|
55
|
+
throw new Error("endianness must be either 'le' or 'be'");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function formatBytes(bytes: number): string {
|
|
59
|
+
if (bytes < 0) {
|
|
60
|
+
throw new Error("bytes must be a positive number, got " + bytes);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (bytes === 0) {
|
|
64
|
+
return "0 Bytes";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// size of a kb
|
|
68
|
+
const k = 1024;
|
|
69
|
+
|
|
70
|
+
// only support up to GB
|
|
71
|
+
const units = ["Bytes", "KB", "MB", "GB"];
|
|
72
|
+
const i = Math.min(Math.floor(Math.log(bytes) / Math.log(k)), units.length - 1);
|
|
73
|
+
const formattedSize = (bytes / Math.pow(k, i)).toFixed(2);
|
|
74
|
+
|
|
75
|
+
return `${formattedSize} ${units[i]}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function xor(a: Uint8Array, b: Uint8Array): Uint8Array {
|
|
79
|
+
const length = Math.min(a.length, b.length);
|
|
80
|
+
for (let i = 0; i < length; i++) {
|
|
81
|
+
a[i] = a[i] ^ b[i];
|
|
82
|
+
}
|
|
83
|
+
return a;
|
|
84
|
+
}
|
package/src/command.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type {Argv, Options} from "yargs";
|
|
2
|
+
|
|
3
|
+
export interface CliExample {
|
|
4
|
+
command: string;
|
|
5
|
+
title?: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// biome-ignore lint/suspicious/noExplicitAny: We need to use `any` type here
|
|
10
|
+
export interface CliOptionDefinition<T = any> extends Options {
|
|
11
|
+
example?: Omit<CliExample, "title">;
|
|
12
|
+
// Ensure `type` property matches type of `T`
|
|
13
|
+
type: T extends string
|
|
14
|
+
? "string"
|
|
15
|
+
: T extends number
|
|
16
|
+
? "number"
|
|
17
|
+
: T extends boolean
|
|
18
|
+
? "boolean"
|
|
19
|
+
: T extends Array<unknown>
|
|
20
|
+
? "array"
|
|
21
|
+
: never;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type CliCommandOptions<OwnArgs> = Required<{
|
|
25
|
+
[K in keyof OwnArgs]: undefined extends OwnArgs[K]
|
|
26
|
+
? CliOptionDefinition<OwnArgs[K]>
|
|
27
|
+
: // If arg cannot be undefined it must specify a default value or be provided by the user
|
|
28
|
+
CliOptionDefinition<OwnArgs[K]> & (Required<Pick<Options, "default">> | {demandOption: true});
|
|
29
|
+
}>;
|
|
30
|
+
|
|
31
|
+
// biome-ignore lint/suspicious/noExplicitAny: We need to use `any` type here
|
|
32
|
+
export interface CliCommand<OwnArgs = Record<never, never>, ParentArgs = Record<never, never>, R = any> {
|
|
33
|
+
command: string;
|
|
34
|
+
describe: string;
|
|
35
|
+
/**
|
|
36
|
+
* The folder in docs/pages that the cli.md should be placed in. If not provided no
|
|
37
|
+
* cli flags page will be generated for the command
|
|
38
|
+
*/
|
|
39
|
+
docsFolder?: string;
|
|
40
|
+
examples?: CliExample[];
|
|
41
|
+
options?: CliCommandOptions<OwnArgs>;
|
|
42
|
+
// 1st arg: any = free own sub command options
|
|
43
|
+
// 2nd arg: subcommand parent options is = to this command options + parent options
|
|
44
|
+
// biome-ignore lint/suspicious/noExplicitAny: We need to use `any` type here
|
|
45
|
+
subcommands?: CliCommand<any, OwnArgs & ParentArgs>[];
|
|
46
|
+
handler?: (args: OwnArgs & ParentArgs) => Promise<R>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Register a CliCommand type to yargs. Recursively registers subcommands too.
|
|
51
|
+
* @param yargs
|
|
52
|
+
* @param cliCommand
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
// biome-ignore lint/suspicious/noExplicitAny: We need to use `any` type here
|
|
56
|
+
export function registerCommandToYargs(yargs: Argv, cliCommand: CliCommand<any, any>): void {
|
|
57
|
+
yargs.command({
|
|
58
|
+
command: cliCommand.command,
|
|
59
|
+
describe: cliCommand.describe,
|
|
60
|
+
builder: (yargsBuilder) => {
|
|
61
|
+
yargsBuilder.options(cliCommand.options ?? {});
|
|
62
|
+
for (const subcommand of cliCommand.subcommands ?? []) {
|
|
63
|
+
registerCommandToYargs(yargsBuilder, subcommand);
|
|
64
|
+
}
|
|
65
|
+
if (cliCommand.examples) {
|
|
66
|
+
for (const example of cliCommand.examples) {
|
|
67
|
+
yargsBuilder.example(`$0 ${example.command}`, example.description ?? "");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return yargs;
|
|
71
|
+
},
|
|
72
|
+
handler: cliCommand.handler ?? function emptyHandler(): void {},
|
|
73
|
+
});
|
|
74
|
+
}
|
package/src/diff.ts
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
|
|
3
|
+
const primitiveTypeof = ["number", "string", "bigint", "boolean"];
|
|
4
|
+
export type BufferType = Uint8Array | Uint32Array;
|
|
5
|
+
export type PrimitiveType = number | string | bigint | boolean | BufferType;
|
|
6
|
+
export type DiffableCollection = Record<string | number, PrimitiveType>;
|
|
7
|
+
export type Diffable = PrimitiveType | Array<PrimitiveType> | DiffableCollection;
|
|
8
|
+
|
|
9
|
+
export interface Diff {
|
|
10
|
+
objectPath: string;
|
|
11
|
+
errorMessage?: string;
|
|
12
|
+
val1: Diffable;
|
|
13
|
+
val2: Diffable;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function diffUint8Array(val1: Uint8Array, val2: PrimitiveType, objectPath: string): Diff[] {
|
|
17
|
+
if (!(val2 instanceof Uint8Array)) {
|
|
18
|
+
return [
|
|
19
|
+
{
|
|
20
|
+
objectPath,
|
|
21
|
+
errorMessage: `val1${objectPath} is a Uint8Array, but val2${objectPath} is not`,
|
|
22
|
+
val1,
|
|
23
|
+
val2,
|
|
24
|
+
},
|
|
25
|
+
];
|
|
26
|
+
}
|
|
27
|
+
const hex1 = Buffer.from(val1).toString("hex");
|
|
28
|
+
const hex2 = Buffer.from(val2).toString("hex");
|
|
29
|
+
if (hex1 !== hex2) {
|
|
30
|
+
return [
|
|
31
|
+
{
|
|
32
|
+
objectPath,
|
|
33
|
+
val1: `0x${hex1}`,
|
|
34
|
+
val2: `0x${hex2}`,
|
|
35
|
+
},
|
|
36
|
+
];
|
|
37
|
+
}
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function diffUint32Array(val1: Uint32Array, val2: PrimitiveType, objectPath: string): Diff[] {
|
|
42
|
+
if (!(val2 instanceof Uint32Array)) {
|
|
43
|
+
return [
|
|
44
|
+
{
|
|
45
|
+
objectPath,
|
|
46
|
+
errorMessage: `val1${objectPath} is a Uint32Array, but val2${objectPath} is not`,
|
|
47
|
+
val1,
|
|
48
|
+
val2,
|
|
49
|
+
},
|
|
50
|
+
];
|
|
51
|
+
}
|
|
52
|
+
const diffs: Diff[] = [];
|
|
53
|
+
val1.forEach((value, index) => {
|
|
54
|
+
const value2 = val2[index];
|
|
55
|
+
if (value !== value2) {
|
|
56
|
+
diffs.push({
|
|
57
|
+
objectPath: `${objectPath}[${index}]`,
|
|
58
|
+
val1: `0x${value.toString(16).padStart(8, "0")}`,
|
|
59
|
+
val2: value2 ? `0x${val2[index].toString(16).padStart(8, "0")}` : "undefined",
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
return diffs;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function diffPrimitiveValue(val1: PrimitiveType, val2: PrimitiveType, objectPath: string): Diff[] {
|
|
67
|
+
if (val1 instanceof Uint8Array) {
|
|
68
|
+
return diffUint8Array(val1, val2, objectPath);
|
|
69
|
+
}
|
|
70
|
+
if (val1 instanceof Uint32Array) {
|
|
71
|
+
return diffUint32Array(val1, val2, objectPath);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const diff = {objectPath, val1, val2} as Diff;
|
|
75
|
+
const type1 = typeof val1;
|
|
76
|
+
if (!primitiveTypeof.includes(type1)) {
|
|
77
|
+
diff.errorMessage = `val1${objectPath} is not a supported type`;
|
|
78
|
+
}
|
|
79
|
+
const type2 = typeof val2;
|
|
80
|
+
if (!primitiveTypeof.includes(type2)) {
|
|
81
|
+
diff.errorMessage = `val2${objectPath} is not a supported type`;
|
|
82
|
+
}
|
|
83
|
+
if (type1 !== type2) {
|
|
84
|
+
diff.errorMessage = `val1${objectPath} is not the same type as val2${objectPath}`;
|
|
85
|
+
}
|
|
86
|
+
if (val1 !== val2) {
|
|
87
|
+
return [diff];
|
|
88
|
+
}
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function isPrimitiveValue(val: unknown): val is PrimitiveType {
|
|
93
|
+
if (Array.isArray(val)) return false;
|
|
94
|
+
if (typeof val === "object") {
|
|
95
|
+
return val instanceof Uint8Array || val instanceof Uint32Array;
|
|
96
|
+
}
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function isDiffable(val: unknown): val is Diffable {
|
|
101
|
+
return !(typeof val === "function" || typeof val === "symbol" || typeof val === "undefined" || val === null);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function getDiffs(val1: Diffable, val2: Diffable, objectPath: string): Diff[] {
|
|
105
|
+
if (isPrimitiveValue(val1)) {
|
|
106
|
+
if (!isPrimitiveValue(val2)) {
|
|
107
|
+
return [
|
|
108
|
+
{
|
|
109
|
+
objectPath,
|
|
110
|
+
errorMessage: `val1${objectPath} is a primitive value and val2${objectPath} is not`,
|
|
111
|
+
val1,
|
|
112
|
+
val2,
|
|
113
|
+
},
|
|
114
|
+
];
|
|
115
|
+
}
|
|
116
|
+
return diffPrimitiveValue(val1, val2, objectPath);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const isArray = Array.isArray(val1);
|
|
120
|
+
let errorMessage: string | undefined;
|
|
121
|
+
if (isArray && !Array.isArray(val2)) {
|
|
122
|
+
errorMessage = `val1${objectPath} is an array and val2${objectPath} is not`;
|
|
123
|
+
} else if (typeof val1 === "object" && typeof val2 !== "object") {
|
|
124
|
+
errorMessage = `val1${objectPath} is a nested object and val2${objectPath} is not`;
|
|
125
|
+
}
|
|
126
|
+
if (errorMessage) {
|
|
127
|
+
return [
|
|
128
|
+
{
|
|
129
|
+
objectPath,
|
|
130
|
+
errorMessage,
|
|
131
|
+
val1,
|
|
132
|
+
val2,
|
|
133
|
+
},
|
|
134
|
+
];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const diffs: Diff[] = [];
|
|
138
|
+
for (const [index, value] of Object.entries(val1)) {
|
|
139
|
+
if (!isDiffable(value)) {
|
|
140
|
+
diffs.push({objectPath, val1, val2, errorMessage: `val1${objectPath} is not Diffable`});
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
const value2 = (val2 as DiffableCollection)[index];
|
|
144
|
+
if (!isDiffable(value2)) {
|
|
145
|
+
diffs.push({objectPath, val1, val2, errorMessage: `val2${objectPath} is not Diffable`});
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const innerPath = isArray ? `${objectPath}[${index}]` : `${objectPath}.${index}`;
|
|
149
|
+
diffs.push(...getDiffs(value, value2, innerPath));
|
|
150
|
+
}
|
|
151
|
+
return diffs;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Find the different values on complex, nested objects. Outputs the path through the object to
|
|
156
|
+
* each value that does not match from val1 and val2. Optionally can output the values that differ.
|
|
157
|
+
*
|
|
158
|
+
* For objects that differ greatly, can write to a file instead of the terminal for analysis
|
|
159
|
+
*
|
|
160
|
+
* ## Example
|
|
161
|
+
* ```ts
|
|
162
|
+
* const obj1 = {
|
|
163
|
+
* key1: {
|
|
164
|
+
* key2: [
|
|
165
|
+
* { key3: 1 },
|
|
166
|
+
* { key3: new Uint8Array([1, 2, 3]) }
|
|
167
|
+
* ]
|
|
168
|
+
* },
|
|
169
|
+
* key4: new Uint32Array([1, 2, 3]),
|
|
170
|
+
* key5: 362436
|
|
171
|
+
* };
|
|
172
|
+
*
|
|
173
|
+
* const obj2 = {
|
|
174
|
+
* key1: {
|
|
175
|
+
* key2: [
|
|
176
|
+
* { key3: 1 },
|
|
177
|
+
* { key3: new Uint8Array([1, 2, 4]) }
|
|
178
|
+
* ]
|
|
179
|
+
* },
|
|
180
|
+
* key4: new Uint32Array([1, 2, 4])
|
|
181
|
+
* key5: true
|
|
182
|
+
* };
|
|
183
|
+
*
|
|
184
|
+
* diffObjects(obj1, obj2, true);
|
|
185
|
+
*
|
|
186
|
+
*
|
|
187
|
+
* ```
|
|
188
|
+
*
|
|
189
|
+
* ## Output
|
|
190
|
+
* ```sh
|
|
191
|
+
* val.key1.key2[1].key3
|
|
192
|
+
* - 0x010203
|
|
193
|
+
* - 0x010204
|
|
194
|
+
* val.key4[2]
|
|
195
|
+
* - 0x00000003
|
|
196
|
+
* - 0x00000004
|
|
197
|
+
* val.key5
|
|
198
|
+
* val1.key5 is not the same type as val2.key5
|
|
199
|
+
* - 362436
|
|
200
|
+
* - true
|
|
201
|
+
* ```
|
|
202
|
+
*/
|
|
203
|
+
export function diff(val1: unknown, val2: unknown, outputValues = false, filename?: string): void {
|
|
204
|
+
if (!isDiffable(val1)) {
|
|
205
|
+
// biome-ignore lint/suspicious/noConsole: We need to log to the console
|
|
206
|
+
console.log("val1 is not Diffable");
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (!isDiffable(val2)) {
|
|
210
|
+
// biome-ignore lint/suspicious/noConsole: We need to log to the console
|
|
211
|
+
console.log("val2 is not Diffable");
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const diffs = getDiffs(val1, val2, "");
|
|
215
|
+
let output = "";
|
|
216
|
+
if (diffs.length) {
|
|
217
|
+
for (const diff of diffs) {
|
|
218
|
+
let diffOutput = `value${diff.objectPath}`;
|
|
219
|
+
if (diff.errorMessage) {
|
|
220
|
+
diffOutput += `\n ${diff.errorMessage}`;
|
|
221
|
+
}
|
|
222
|
+
if (outputValues) {
|
|
223
|
+
diffOutput += `\n - ${diff.val1.toString()}\n - ${diff.val2.toString()}\n`;
|
|
224
|
+
}
|
|
225
|
+
output += `${diffOutput}\n`;
|
|
226
|
+
}
|
|
227
|
+
if (filename) {
|
|
228
|
+
fs.writeFileSync(filename, output);
|
|
229
|
+
} else {
|
|
230
|
+
// biome-ignore lint/suspicious/noConsole: We need to log to the console
|
|
231
|
+
console.log(output);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|