@nite-framework/nite-zk-profiler 0.1.3 → 0.2.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.
- package/README.md +104 -6
- package/dist/budget.d.ts +36 -10
- package/dist/budget.js +94 -37
- package/dist/cache.d.ts +16 -0
- package/dist/cache.js +54 -0
- package/dist/cli.d.ts +10 -3
- package/dist/cli.js +211 -50
- package/dist/colors.d.ts +40 -0
- package/dist/colors.js +69 -0
- package/dist/compile.d.ts +1 -1
- package/dist/compile.js +17 -8
- package/dist/deep.d.ts +24 -0
- package/dist/deep.js +60 -0
- package/dist/diff.d.ts +30 -0
- package/dist/diff.js +64 -0
- package/dist/estimate.d.ts +38 -0
- package/dist/estimate.js +85 -0
- package/dist/measure.d.ts +14 -5
- package/dist/measure.js +72 -17
- package/dist/progress.d.ts +19 -0
- package/dist/progress.js +51 -0
- package/dist/report.d.ts +20 -6
- package/dist/report.js +163 -39
- package/dist/version.d.ts +9 -0
- package/dist/version.js +19 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -370,15 +370,113 @@ Anything outside this range is rejected with a clear error naming the version fo
|
|
|
370
370
|
## Command line surface
|
|
371
371
|
|
|
372
372
|
```text
|
|
373
|
-
nite-zk profile <source
|
|
374
|
-
nite-zk save <source
|
|
375
|
-
nite-zk check [<source
|
|
373
|
+
nite-zk profile <source...> Report rows, k and relative cost per circuit
|
|
374
|
+
nite-zk save <source...> Write zk-budget.json from current measurements
|
|
375
|
+
nite-zk check [<source...>] Compare against zk-budget.json
|
|
376
|
+
nite-zk diff <ref> [<source>] Compare a contract against a git ref
|
|
377
|
+
nite-zk calibrate --observed <ms> --at-k <k>
|
|
378
|
+
Anchor proving estimates to a real proof
|
|
379
|
+
|
|
380
|
+
--estimate Show modelled proving time per circuit
|
|
381
|
+
--deep Also generate real proving keys, and report
|
|
382
|
+
measured setup time and prover key size
|
|
383
|
+
--json Machine readable output
|
|
384
|
+
--out <dir> Compile into a specific directory
|
|
385
|
+
--budget <file> Use a different baseline path
|
|
386
|
+
--strict check: fail on circuits missing from the budget
|
|
387
|
+
--no-color Plain output (NO_COLOR is honoured too)
|
|
388
|
+
--no-cache Ignore cached measurements for this run
|
|
389
|
+
+VERSION Pin the Compact toolchain, e.g. +0.31.1
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### Several contracts at once
|
|
393
|
+
|
|
394
|
+
Pass more than one entry point, and one budget file covers them all:
|
|
395
|
+
|
|
396
|
+
```text
|
|
397
|
+
$ nite-zk save packages/pool/src/lending.compact packages/mint/src/mint.compact
|
|
398
|
+
Wrote zk-budget.json: 2 contracts, 16 circuits
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
`check` then reads every contract back out of the budget, so CI stays one step
|
|
402
|
+
whether the repository holds one contract or ten.
|
|
403
|
+
|
|
404
|
+
### Comparing against a branch
|
|
405
|
+
|
|
406
|
+
```text
|
|
407
|
+
$ nite-zk diff main
|
|
408
|
+
|
|
409
|
+
transferFunds k 15 -> 16 +1, about 2x more expensive
|
|
410
|
+
proveMembership k 16 -> 16 unchanged
|
|
411
|
+
newHelper k - -> 9 new circuit
|
|
412
|
+
|
|
413
|
+
1 circuit more expensive than main
|
|
414
|
+
```
|
|
376
415
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
416
|
+
The ref is exported with `git archive` into a temporary directory, so the
|
|
417
|
+
working tree and the index are never touched. Exits nonzero when a circuit that
|
|
418
|
+
already existed got more expensive, which makes it usable as a pull request
|
|
419
|
+
check without committing a budget file first.
|
|
420
|
+
|
|
421
|
+
### Estimated proving time
|
|
422
|
+
|
|
423
|
+
`--estimate` models proving time as `time = rate * 2^k`, since a Halo2 proof is
|
|
424
|
+
dominated by work over the full `2^k` domain.
|
|
425
|
+
|
|
426
|
+
The rate is machine specific, so the shipped default is only an order of
|
|
427
|
+
magnitude. Time one real proof and record it, and every estimate becomes
|
|
428
|
+
anchored to your own prover:
|
|
429
|
+
|
|
430
|
+
```text
|
|
431
|
+
$ nite-zk calibrate --observed 9000 --at-k 16
|
|
432
|
+
Calibrated: 0.1373 ms per domain row, from 9000ms at k=16.
|
|
380
433
|
```
|
|
381
434
|
|
|
435
|
+
Two limits worth stating plainly. Proving delegated to a remote proof server is
|
|
436
|
+
often dominated by network round trip and server load rather than by circuit
|
|
437
|
+
size, and no model of `k` can see that. And the same gate degree effect that
|
|
438
|
+
makes key size unpredictable, described below, puts a factor of about two around
|
|
439
|
+
any figure derived from `k` alone. Treat it as a ratio you can reason with, not
|
|
440
|
+
a stopwatch.
|
|
441
|
+
|
|
442
|
+
`save` records which contracts the budget describes, so `check` needs no
|
|
443
|
+
arguments afterwards. That is what makes it a one line CI step. It also records
|
|
444
|
+
rows, capacity and relative cost per circuit, so a reviewer reading the diff can
|
|
445
|
+
see what changed without running anything. Only `maxK` is enforced.
|
|
446
|
+
|
|
447
|
+
### Measuring the real thing
|
|
448
|
+
|
|
449
|
+
`--deep` stops mocking and generates actual proving keys. It answers two
|
|
450
|
+
questions the fast path cannot, and both are measured rather than modelled:
|
|
451
|
+
|
|
452
|
+
```text
|
|
453
|
+
circuit rows k capacity cost setup prover key
|
|
454
|
+
k05 24 5 32 1x 156ms 14 KB
|
|
455
|
+
k13 4189 13 8192 256x 2.3s 2.7 MB
|
|
456
|
+
k14 11965 14 16384 512x 4.6s 5.0 MB
|
|
457
|
+
k15 19657 15 32768 1024x 9.6s 9.5 MB
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
Setup time and prover key size both roughly double per step in `k`, which is
|
|
461
|
+
the same 2^k curve proving time follows. The prover key size is worth attention
|
|
462
|
+
on its own: it is what users download and hold in memory, and a k=15 circuit
|
|
463
|
+
carries a 9.5 MB key against 14 KB at k=5.
|
|
464
|
+
|
|
465
|
+
This mode is slow by design, since it does the work `--skip-zk` exists to avoid.
|
|
466
|
+
Use it when choosing between designs, not in an edit loop.
|
|
467
|
+
|
|
468
|
+
**Why key size is measured rather than predicted.** It looks predictable from
|
|
469
|
+
`k`: three structurally unrelated circuits at k=13 agreed within 0.11%, and a
|
|
470
|
+
synthetic k=15 circuit matched a production one to 0.08%. It does not hold. At
|
|
471
|
+
k=16 a production circuit produced 19,524,757 bytes while a synthetic one
|
|
472
|
+
produced 38,513,181, a factor of two apart, with identical verifier key sizes.
|
|
473
|
+
|
|
474
|
+
The likely cause is the extended evaluation domain, which Halo2 sizes by maximum
|
|
475
|
+
gate degree, and which `mock-compile` does not report. From `k` alone there is
|
|
476
|
+
no way to tell which case a circuit is in, and a figure that is exact at one `k`
|
|
477
|
+
and twice wrong at the next is worse than no figure. So key size comes only from
|
|
478
|
+
`--deep`, where it is measured.
|
|
479
|
+
|
|
382
480
|
Designed to work as a single CI step:
|
|
383
481
|
|
|
384
482
|
```yaml
|
package/dist/budget.d.ts
CHANGED
|
@@ -1,14 +1,33 @@
|
|
|
1
1
|
import type { CircuitCost } from "./analyze.ts";
|
|
2
2
|
export declare const DEFAULT_BUDGET_PATH = "zk-budget.json";
|
|
3
|
+
/**
|
|
4
|
+
* Per circuit entry.
|
|
5
|
+
*
|
|
6
|
+
* `maxK` is the only enforced field. The rest is a snapshot recorded at save
|
|
7
|
+
* time so a reviewer reading the diff can see what changed and by how much,
|
|
8
|
+
* without running the tool. Snapshot drift never fails a check.
|
|
9
|
+
*/
|
|
10
|
+
export interface CircuitBudget {
|
|
11
|
+
maxK: number;
|
|
12
|
+
rows?: number;
|
|
13
|
+
capacity?: number;
|
|
14
|
+
relativeCost?: number;
|
|
15
|
+
}
|
|
16
|
+
export interface ContractBudget {
|
|
17
|
+
circuits: Record<string, CircuitBudget>;
|
|
18
|
+
}
|
|
3
19
|
export interface Budget {
|
|
20
|
+
/** Which release wrote this, so an old file can be recognised. */
|
|
21
|
+
tool?: string;
|
|
4
22
|
/** Toolchain line the ceilings were established against. */
|
|
5
23
|
toolchain: string;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
24
|
+
generated?: string;
|
|
25
|
+
/** Keyed by source path, relative to the budget file. */
|
|
26
|
+
contracts: Record<string, ContractBudget>;
|
|
9
27
|
}
|
|
10
28
|
export type Status = "under" | "at" | "over" | "undeclared" | "stale";
|
|
11
29
|
export interface CheckRow {
|
|
30
|
+
contract: string;
|
|
12
31
|
circuit: string;
|
|
13
32
|
status: Status;
|
|
14
33
|
k?: number;
|
|
@@ -18,16 +37,23 @@ export interface CheckResult {
|
|
|
18
37
|
rows: CheckRow[];
|
|
19
38
|
failed: boolean;
|
|
20
39
|
}
|
|
21
|
-
/**
|
|
22
|
-
export
|
|
40
|
+
/** Measurements for one contract, as the CLI collects them. */
|
|
41
|
+
export interface ContractCosts {
|
|
42
|
+
source: string;
|
|
43
|
+
costs: CircuitCost[];
|
|
44
|
+
}
|
|
45
|
+
/** Build a budget granting every circuit exactly what it currently costs. */
|
|
46
|
+
export declare function budgetFrom(contracts: ContractCosts[], toolchainLine: string): Budget;
|
|
23
47
|
export declare function writeBudget(path: string, budget: Budget): void;
|
|
24
48
|
export declare function readBudget(path: string): Budget;
|
|
49
|
+
/** Every contract the budget describes. */
|
|
50
|
+
export declare function budgetSources(budget: Budget): string[];
|
|
25
51
|
/**
|
|
26
52
|
* Compare measurements against declared ceilings.
|
|
27
53
|
*
|
|
28
|
-
* The gate is a ceiling, not a diff against the last run. A circuit's `k`
|
|
29
|
-
* is often intentional, so only exceeding a ceiling the project has
|
|
30
|
-
* is a failure. Raising a ceiling deliberately is a one line
|
|
31
|
-
* sees, which is the point.
|
|
54
|
+
* The gate is a ceiling, not a diff against the last run. A circuit's `k`
|
|
55
|
+
* rising is often intentional, so only exceeding a ceiling the project has
|
|
56
|
+
* committed to is a failure. Raising a ceiling deliberately is a one line
|
|
57
|
+
* change a reviewer sees, which is the point.
|
|
32
58
|
*/
|
|
33
|
-
export declare function check(
|
|
59
|
+
export declare function check(measured: ContractCosts[], budget: Budget, strict: boolean): CheckResult;
|
package/dist/budget.js
CHANGED
|
@@ -1,17 +1,49 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { ProfilerError } from "./errors.js";
|
|
3
|
+
import { toolVersion } from "./version.js";
|
|
3
4
|
export const DEFAULT_BUDGET_PATH = "zk-budget.json";
|
|
4
|
-
/** Build a budget
|
|
5
|
-
export function budgetFrom(
|
|
6
|
-
const
|
|
7
|
-
for (const
|
|
8
|
-
circuits
|
|
5
|
+
/** Build a budget granting every circuit exactly what it currently costs. */
|
|
6
|
+
export function budgetFrom(contracts, toolchainLine) {
|
|
7
|
+
const out = {};
|
|
8
|
+
for (const { source, costs } of [...contracts].sort((a, b) => a.source.localeCompare(b.source))) {
|
|
9
|
+
const circuits = {};
|
|
10
|
+
for (const c of [...costs].sort((a, b) => a.circuit.localeCompare(b.circuit))) {
|
|
11
|
+
circuits[c.circuit] = {
|
|
12
|
+
maxK: c.k,
|
|
13
|
+
rows: c.rows,
|
|
14
|
+
capacity: c.capacity,
|
|
15
|
+
relativeCost: c.relativeCost,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
out[source] = { circuits };
|
|
9
19
|
}
|
|
10
|
-
return {
|
|
20
|
+
return {
|
|
21
|
+
tool: `nite-zk-profiler ${toolVersion()}`,
|
|
22
|
+
toolchain: toolchainLine,
|
|
23
|
+
generated: new Date().toISOString(),
|
|
24
|
+
contracts: out,
|
|
25
|
+
};
|
|
11
26
|
}
|
|
12
27
|
export function writeBudget(path, budget) {
|
|
13
28
|
writeFileSync(path, `${JSON.stringify(budget, null, 2)}\n`, "utf8");
|
|
14
29
|
}
|
|
30
|
+
/** Bring the pre multi contract shape forward, so old files keep working. */
|
|
31
|
+
function normalise(parsed, path) {
|
|
32
|
+
if ("contracts" in parsed && parsed.contracts)
|
|
33
|
+
return parsed;
|
|
34
|
+
const legacy = parsed;
|
|
35
|
+
if (!legacy.circuits) {
|
|
36
|
+
throw new ProfilerError(`Malformed budget file: ${path}`, 'Expected a "contracts" map. Rewrite it with `nite-zk save <source>`.');
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
toolchain: legacy.toolchain,
|
|
40
|
+
contracts: {
|
|
41
|
+
[legacy.source ?? ""]: {
|
|
42
|
+
circuits: Object.fromEntries(Object.entries(legacy.circuits).map(([n, e]) => [n, { maxK: e.maxK }])),
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
15
47
|
export function readBudget(path) {
|
|
16
48
|
let raw;
|
|
17
49
|
try {
|
|
@@ -27,50 +59,75 @@ export function readBudget(path) {
|
|
|
27
59
|
catch (e) {
|
|
28
60
|
throw new ProfilerError(`Could not parse ${path}`, String(e));
|
|
29
61
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
throw new ProfilerError(`Malformed budget file: ${path}`, 'Expected {"toolchain": "...", "circuits": {"name": {"maxK": N}}}.');
|
|
62
|
+
if (!parsed || typeof parsed !== "object") {
|
|
63
|
+
throw new ProfilerError(`Malformed budget file: ${path}`, "Expected a JSON object.");
|
|
33
64
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
65
|
+
const budget = normalise(parsed, path);
|
|
66
|
+
for (const [source, contract] of Object.entries(budget.contracts)) {
|
|
67
|
+
for (const [name, entry] of Object.entries(contract.circuits ?? {})) {
|
|
68
|
+
if (!entry || typeof entry.maxK !== "number" || !Number.isInteger(entry.maxK)) {
|
|
69
|
+
throw new ProfilerError(`Malformed budget entry for "${name}" in ${source || path}`, 'Each circuit needs an integer "maxK".');
|
|
70
|
+
}
|
|
37
71
|
}
|
|
38
72
|
}
|
|
39
73
|
return budget;
|
|
40
74
|
}
|
|
75
|
+
/** Every contract the budget describes. */
|
|
76
|
+
export function budgetSources(budget) {
|
|
77
|
+
return Object.keys(budget.contracts).filter((s) => s.length > 0);
|
|
78
|
+
}
|
|
41
79
|
/**
|
|
42
80
|
* Compare measurements against declared ceilings.
|
|
43
81
|
*
|
|
44
|
-
* The gate is a ceiling, not a diff against the last run. A circuit's `k`
|
|
45
|
-
* is often intentional, so only exceeding a ceiling the project has
|
|
46
|
-
* is a failure. Raising a ceiling deliberately is a one line
|
|
47
|
-
* sees, which is the point.
|
|
82
|
+
* The gate is a ceiling, not a diff against the last run. A circuit's `k`
|
|
83
|
+
* rising is often intentional, so only exceeding a ceiling the project has
|
|
84
|
+
* committed to is a failure. Raising a ceiling deliberately is a one line
|
|
85
|
+
* change a reviewer sees, which is the point.
|
|
48
86
|
*/
|
|
49
|
-
export function check(
|
|
87
|
+
export function check(measured, budget, strict) {
|
|
50
88
|
const rows = [];
|
|
51
89
|
let failed = false;
|
|
52
|
-
for (const
|
|
53
|
-
const declared = budget.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
90
|
+
for (const { source, costs } of measured) {
|
|
91
|
+
const declared = budget.contracts[source]?.circuits ??
|
|
92
|
+
// A legacy budget stores its single contract under "", so fall back to it
|
|
93
|
+
// when only one contract is declared and the path does not match.
|
|
94
|
+
(Object.keys(budget.contracts).length === 1
|
|
95
|
+
? Object.values(budget.contracts)[0].circuits
|
|
96
|
+
: {});
|
|
97
|
+
for (const cost of costs) {
|
|
98
|
+
const entry = declared[cost.circuit];
|
|
99
|
+
if (!entry) {
|
|
100
|
+
rows.push({
|
|
101
|
+
contract: source,
|
|
102
|
+
circuit: cost.circuit,
|
|
103
|
+
status: "undeclared",
|
|
104
|
+
k: cost.k,
|
|
105
|
+
});
|
|
106
|
+
if (strict)
|
|
107
|
+
failed = true;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
let status = "under";
|
|
111
|
+
if (cost.k > entry.maxK) {
|
|
112
|
+
status = "over";
|
|
57
113
|
failed = true;
|
|
58
|
-
|
|
114
|
+
}
|
|
115
|
+
else if (cost.k === entry.maxK) {
|
|
116
|
+
status = "at";
|
|
117
|
+
}
|
|
118
|
+
rows.push({
|
|
119
|
+
contract: source,
|
|
120
|
+
circuit: cost.circuit,
|
|
121
|
+
status,
|
|
122
|
+
k: cost.k,
|
|
123
|
+
maxK: entry.maxK,
|
|
124
|
+
});
|
|
59
125
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
else if (cost.k === declared.maxK) {
|
|
66
|
-
status = "at";
|
|
67
|
-
}
|
|
68
|
-
rows.push({ circuit: cost.circuit, status, k: cost.k, maxK: declared.maxK });
|
|
69
|
-
}
|
|
70
|
-
const measured = new Set(costs.map((c) => c.circuit));
|
|
71
|
-
for (const name of Object.keys(budget.circuits)) {
|
|
72
|
-
if (!measured.has(name)) {
|
|
73
|
-
rows.push({ circuit: name, status: "stale", maxK: budget.circuits[name].maxK });
|
|
126
|
+
const seen = new Set(costs.map((c) => c.circuit));
|
|
127
|
+
for (const [name, entry] of Object.entries(declared)) {
|
|
128
|
+
if (!seen.has(name)) {
|
|
129
|
+
rows.push({ contract: source, circuit: name, status: "stale", maxK: entry.maxK });
|
|
130
|
+
}
|
|
74
131
|
}
|
|
75
132
|
}
|
|
76
133
|
return { rows, failed };
|
package/dist/cache.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Measurement } from "./measure.ts";
|
|
2
|
+
import type { Toolchain } from "./toolchain.ts";
|
|
3
|
+
/**
|
|
4
|
+
* Measurement cache, keyed on the emitted IR rather than on the source.
|
|
5
|
+
*
|
|
6
|
+
* Compilation is the cheap half: on a nine circuit contract it took 1.1s
|
|
7
|
+
* against 17s to measure. So the compile always runs, and its output is used as
|
|
8
|
+
* the cache key. Identical IR provably yields identical constraint counts, so
|
|
9
|
+
* this cannot go stale in a way that produces a wrong answer, which a source
|
|
10
|
+
* timestamp or a partial import scan could.
|
|
11
|
+
*/
|
|
12
|
+
export declare function cacheDir(): string;
|
|
13
|
+
/** Hash every emitted IR file, plus the toolchain that produced and reads it. */
|
|
14
|
+
export declare function cacheKey(zkirDir: string, toolchain: Toolchain): string;
|
|
15
|
+
export declare function readCache(key: string): Measurement[] | undefined;
|
|
16
|
+
export declare function writeCache(key: string, measurements: Measurement[]): void;
|
package/dist/cache.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { toolVersion } from "./version.js";
|
|
6
|
+
/**
|
|
7
|
+
* Measurement cache, keyed on the emitted IR rather than on the source.
|
|
8
|
+
*
|
|
9
|
+
* Compilation is the cheap half: on a nine circuit contract it took 1.1s
|
|
10
|
+
* against 17s to measure. So the compile always runs, and its output is used as
|
|
11
|
+
* the cache key. Identical IR provably yields identical constraint counts, so
|
|
12
|
+
* this cannot go stale in a way that produces a wrong answer, which a source
|
|
13
|
+
* timestamp or a partial import scan could.
|
|
14
|
+
*/
|
|
15
|
+
export function cacheDir() {
|
|
16
|
+
return process.env.NITE_ZK_CACHE || join(tmpdir(), "nite-zk-profiler-cache");
|
|
17
|
+
}
|
|
18
|
+
/** Hash every emitted IR file, plus the toolchain that produced and reads it. */
|
|
19
|
+
export function cacheKey(zkirDir, toolchain) {
|
|
20
|
+
const hash = createHash("sha256");
|
|
21
|
+
// The profiler's own version is part of the key. Without it, upgrading the
|
|
22
|
+
// tool would keep serving results produced by the previous measurement code,
|
|
23
|
+
// which looks exactly like the upgrade having no effect.
|
|
24
|
+
hash.update(toolVersion());
|
|
25
|
+
hash.update(toolchain.version);
|
|
26
|
+
hash.update(toolchain.zkirVersion);
|
|
27
|
+
for (const file of readdirSync(zkirDir).filter((f) => f.endsWith(".zkir")).sort()) {
|
|
28
|
+
hash.update(file);
|
|
29
|
+
hash.update(readFileSync(join(zkirDir, file)));
|
|
30
|
+
}
|
|
31
|
+
return hash.digest("hex").slice(0, 32);
|
|
32
|
+
}
|
|
33
|
+
export function readCache(key) {
|
|
34
|
+
const file = join(cacheDir(), `${key}.json`);
|
|
35
|
+
if (!existsSync(file))
|
|
36
|
+
return undefined;
|
|
37
|
+
try {
|
|
38
|
+
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
39
|
+
return Array.isArray(parsed) ? parsed : undefined;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// A corrupt entry is not worth failing over; measure again instead.
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export function writeCache(key, measurements) {
|
|
47
|
+
try {
|
|
48
|
+
mkdirSync(cacheDir(), { recursive: true });
|
|
49
|
+
writeFileSync(join(cacheDir(), `${key}.json`), JSON.stringify(measurements), "utf8");
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// Caching is an optimisation. Failing to write one must never fail a run.
|
|
53
|
+
}
|
|
54
|
+
}
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
interface Options {
|
|
3
3
|
command?: string;
|
|
4
|
-
|
|
4
|
+
sources: string[];
|
|
5
|
+
ref?: string;
|
|
5
6
|
json: boolean;
|
|
6
7
|
strict: boolean;
|
|
8
|
+
deep: boolean;
|
|
9
|
+
estimate: boolean;
|
|
10
|
+
noColor: boolean;
|
|
11
|
+
noCache: boolean;
|
|
7
12
|
out?: string;
|
|
8
13
|
budget: string;
|
|
9
14
|
versionArg?: string;
|
|
15
|
+
observedMs?: number;
|
|
16
|
+
atK?: number;
|
|
10
17
|
help: boolean;
|
|
11
18
|
version: boolean;
|
|
12
19
|
}
|
|
13
20
|
export declare function parseArgs(argv: string[]): Options;
|
|
14
|
-
export declare function run(argv: string[]): number
|
|
15
|
-
export declare function main(argv: string[]): number
|
|
21
|
+
export declare function run(argv: string[]): Promise<number>;
|
|
22
|
+
export declare function main(argv: string[]): Promise<number>;
|
|
16
23
|
export {};
|