@vltpkg/output 1.6.0 → 1.6.2
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 +31 -0
- package/dist/index.js +9 -1
- package/dist/trace.d.ts +115 -0
- package/dist/trace.js +233 -0
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -3,3 +3,34 @@
|
|
|
3
3
|
# @vltpkg/output
|
|
4
4
|
|
|
5
5
|
## TODO
|
|
6
|
+
|
|
7
|
+
## Tracing
|
|
8
|
+
|
|
9
|
+
`@vltpkg/output/trace` is a low overhead tracer for the install
|
|
10
|
+
pipeline. It is off unless one of these is set, and when off every
|
|
11
|
+
call returns after one boolean check.
|
|
12
|
+
|
|
13
|
+
- `VLT_TRACE=<file>` appends one JSON line per event to `<file>`.
|
|
14
|
+
Every thread writes to the same file with the same wall clock, so
|
|
15
|
+
worker events and main thread events sort onto one timeline by `t`.
|
|
16
|
+
- `VLT_PHASES=1` prints one `PHASES ...` line to stderr at exit: the
|
|
17
|
+
milliseconds between consecutive phase marks, the event loop delay
|
|
18
|
+
percentiles (how long the main thread was busy with synchronous work
|
|
19
|
+
while IO completions waited), and peak memory.
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
VLT_TRACE=trace.jsonl VLT_PHASES=1 vlt install
|
|
23
|
+
./scripts/trace-report.ts trace.jsonl --bucket=250
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The report prints the phase timeline, what each span kind cost
|
|
27
|
+
(`manifest`, `request`, `extract`, `tarball:fetch`, `tarball:unpack`,
|
|
28
|
+
`inflate`, `write`), how many of each were in flight per time bucket,
|
|
29
|
+
and for every phase how long each lane sat idle. An idle lane next to
|
|
30
|
+
a busy one is a head-of-line stall.
|
|
31
|
+
|
|
32
|
+
Event kinds: `phase` (from `mark()`), `<kind>:start`/`<kind>:end`
|
|
33
|
+
(from `span()`, paired by `url`, `spec` or `name`), `request` (the
|
|
34
|
+
registry client's request events), `lane` (scheduler occupancy), `rss`
|
|
35
|
+
(memory samples), `build:level*` (one BFS level of the ideal graph
|
|
36
|
+
build), `open` and `exit`.
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import EventEmitter from 'node:events';
|
|
2
|
+
import { tracer } from "./trace.js";
|
|
2
3
|
export class OutputEmitter {
|
|
3
4
|
emitter = new EventEmitter();
|
|
4
5
|
emit(eventName, payload) {
|
|
@@ -16,8 +17,15 @@ export class OutputEmitter {
|
|
|
16
17
|
export const emitter = new OutputEmitter();
|
|
17
18
|
export const logRequest = (url, state, details = {}) => {
|
|
18
19
|
emitter.emit('request', { url, state, ...details });
|
|
20
|
+
if (tracer.file) {
|
|
21
|
+
tracer.trace('request', { ...details, url: String(url), state });
|
|
22
|
+
}
|
|
19
23
|
};
|
|
20
24
|
export const graphStep = (step) => {
|
|
21
25
|
emitter.emit('graphStep', { step, state: 'start' });
|
|
22
|
-
|
|
26
|
+
tracer.mark(step);
|
|
27
|
+
return () => {
|
|
28
|
+
emitter.emit('graphStep', { step, state: 'stop' });
|
|
29
|
+
tracer.mark(`${step}:done`);
|
|
30
|
+
};
|
|
23
31
|
};
|
package/dist/trace.d.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extra fields recorded with a trace event. Keep them flat and small:
|
|
3
|
+
* every event is one JSON line.
|
|
4
|
+
*/
|
|
5
|
+
export type TraceFields = Record<string, string | number | boolean | undefined>;
|
|
6
|
+
/**
|
|
7
|
+
* One trace event. `t` is an absolute wall clock in milliseconds
|
|
8
|
+
* (`performance.timeOrigin + performance.now()`), the same clock on every
|
|
9
|
+
* thread, so events from workers and the main thread can be laid on one
|
|
10
|
+
* timeline. `tid` is the worker thread id, 0 for the main thread.
|
|
11
|
+
*/
|
|
12
|
+
export type TraceEvent = {
|
|
13
|
+
t: number;
|
|
14
|
+
tid: number;
|
|
15
|
+
ev: string;
|
|
16
|
+
} & TraceFields;
|
|
17
|
+
export type TracerOptions = {
|
|
18
|
+
/** Path of the JSON-lines file to append events to (`VLT_TRACE`). */
|
|
19
|
+
file?: string;
|
|
20
|
+
/** Print a one-line phase summary to stderr at exit (`VLT_PHASES`). */
|
|
21
|
+
phases?: boolean;
|
|
22
|
+
/** The thread id to stamp events with. Defaults to the real one. */
|
|
23
|
+
threadId?: number;
|
|
24
|
+
/** Absolute clock in ms. Defaults to the shared performance clock. */
|
|
25
|
+
now?: () => number;
|
|
26
|
+
/** Appends a chunk of lines to the trace file. */
|
|
27
|
+
append?: (file: string, data: string) => void;
|
|
28
|
+
/** Writes the phase summary line. Defaults to stderr. */
|
|
29
|
+
write?: (line: string) => void;
|
|
30
|
+
/** Flush after this many buffered lines. */
|
|
31
|
+
flushLines?: number;
|
|
32
|
+
/** Flush this many ms after the first buffered line. */
|
|
33
|
+
flushMs?: number;
|
|
34
|
+
/** Sample `rss` this often, in ms. 0 disables sampling. */
|
|
35
|
+
sampleMs?: number;
|
|
36
|
+
/** Whether this is the main thread, which owns the event loop monitor. */
|
|
37
|
+
main?: boolean;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* A low overhead tracer for the install pipeline.
|
|
41
|
+
*
|
|
42
|
+
* It is off unless `VLT_TRACE=<file>` or `VLT_PHASES=1` is set, and when
|
|
43
|
+
* off every method returns after one boolean check, so call sites can
|
|
44
|
+
* stay in the hot path.
|
|
45
|
+
*
|
|
46
|
+
* With `VLT_TRACE` every event is appended to the file as one JSON line.
|
|
47
|
+
* Lines are buffered and flushed every `flushLines` lines or `flushMs`
|
|
48
|
+
* ms, and at exit. Worker threads
|
|
49
|
+
* get their own instance from the same env and append to the same file,
|
|
50
|
+
* so the file interleaves threads; sort by `t` to read it.
|
|
51
|
+
*
|
|
52
|
+
* With `VLT_PHASES` the main thread prints one line at exit: the ms
|
|
53
|
+
* between consecutive {@link Tracer.mark} calls, the event loop delay
|
|
54
|
+
* percentiles (the direct measure of how long the main thread was busy
|
|
55
|
+
* with synchronous work while IO completions waited), and peak memory.
|
|
56
|
+
*/
|
|
57
|
+
export declare class Tracer {
|
|
58
|
+
#private;
|
|
59
|
+
readonly enabled: boolean;
|
|
60
|
+
readonly file?: string;
|
|
61
|
+
readonly phases: boolean;
|
|
62
|
+
constructor(options?: TracerOptions);
|
|
63
|
+
/**
|
|
64
|
+
* Build a tracer from the environment: `VLT_TRACE` names the file,
|
|
65
|
+
* `VLT_PHASES` (any value but `0` or empty) asks for the summary.
|
|
66
|
+
*/
|
|
67
|
+
static fromEnv(env?: Record<string, string | undefined>, options?: Omit<TracerOptions, 'file' | 'phases'>): Tracer;
|
|
68
|
+
/** Record one event. */
|
|
69
|
+
trace(ev: string, fields?: TraceFields): void;
|
|
70
|
+
/**
|
|
71
|
+
* Record a `<ev>:start` event now and return a function that records
|
|
72
|
+
* `<ev>:end` with the elapsed `ms` (plus any fields it is given). The
|
|
73
|
+
* same `fields` are stamped on both ends so the two can be paired.
|
|
74
|
+
*/
|
|
75
|
+
span(ev: string, fields?: TraceFields): (end?: TraceFields) => void;
|
|
76
|
+
/**
|
|
77
|
+
* Mark the start of a phase. The summary reports the ms between each
|
|
78
|
+
* mark and the next one, and the trace records a `phase` event.
|
|
79
|
+
*/
|
|
80
|
+
mark(name: string, fields?: TraceFields): void;
|
|
81
|
+
/**
|
|
82
|
+
* Record how busy a resource lane is: how many jobs it is running and
|
|
83
|
+
* how many are waiting for a slot. The report derives idle time per
|
|
84
|
+
* lane from these.
|
|
85
|
+
*/
|
|
86
|
+
lane(name: string, busy: number, queued: number): void;
|
|
87
|
+
/** Record a memory sample. */
|
|
88
|
+
sample(): void;
|
|
89
|
+
/** Write buffered lines to the file. */
|
|
90
|
+
flush(): void;
|
|
91
|
+
/**
|
|
92
|
+
* The one-line phase summary, or undefined when phases are off. Each
|
|
93
|
+
* phase is reported as the ms from its mark to the next mark (or to
|
|
94
|
+
* now, for the last one).
|
|
95
|
+
*/
|
|
96
|
+
summary(): string | undefined;
|
|
97
|
+
/**
|
|
98
|
+
* Stop sampling, flush, and print the summary. Runs at process exit
|
|
99
|
+
* on the main thread; safe to call more than once.
|
|
100
|
+
*/
|
|
101
|
+
close(): void;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* The process-wide tracer, built from the environment. Worker threads
|
|
105
|
+
* inherit the environment, so they trace to the same file.
|
|
106
|
+
*/
|
|
107
|
+
export declare const tracer: Tracer;
|
|
108
|
+
/** Record one event on the process-wide tracer. */
|
|
109
|
+
export declare const trace: (ev: string, fields?: TraceFields) => void;
|
|
110
|
+
/** Open a span on the process-wide tracer. See {@link Tracer.span}. */
|
|
111
|
+
export declare const span: (ev: string, fields?: TraceFields) => ((end?: TraceFields) => void);
|
|
112
|
+
/** Mark a phase on the process-wide tracer. See {@link Tracer.mark}. */
|
|
113
|
+
export declare const mark: (name: string, fields?: TraceFields) => void;
|
|
114
|
+
/** Record lane occupancy on the process-wide tracer. */
|
|
115
|
+
export declare const lane: (name: string, busy: number, queued: number) => void;
|
package/dist/trace.js
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { appendFileSync } from 'node:fs';
|
|
2
|
+
import { monitorEventLoopDelay, performance } from 'node:perf_hooks';
|
|
3
|
+
import { threadId as ownThreadId } from 'node:worker_threads';
|
|
4
|
+
const sharedNow = () => performance.timeOrigin + performance.now();
|
|
5
|
+
// Owner-only: traces record full request and tarball URLs, which can
|
|
6
|
+
// carry tokens or signed query parameters. The mode applies when the file
|
|
7
|
+
// is created; an existing file keeps its own.
|
|
8
|
+
const defaultAppend = (file, data) => appendFileSync(file, data, { mode: 0o600 });
|
|
9
|
+
const defaultWrite = (line) => process.stderr.write(line + '\n');
|
|
10
|
+
/**
|
|
11
|
+
* A low overhead tracer for the install pipeline.
|
|
12
|
+
*
|
|
13
|
+
* It is off unless `VLT_TRACE=<file>` or `VLT_PHASES=1` is set, and when
|
|
14
|
+
* off every method returns after one boolean check, so call sites can
|
|
15
|
+
* stay in the hot path.
|
|
16
|
+
*
|
|
17
|
+
* With `VLT_TRACE` every event is appended to the file as one JSON line.
|
|
18
|
+
* Lines are buffered and flushed every `flushLines` lines or `flushMs`
|
|
19
|
+
* ms, and at exit. Worker threads
|
|
20
|
+
* get their own instance from the same env and append to the same file,
|
|
21
|
+
* so the file interleaves threads; sort by `t` to read it.
|
|
22
|
+
*
|
|
23
|
+
* With `VLT_PHASES` the main thread prints one line at exit: the ms
|
|
24
|
+
* between consecutive {@link Tracer.mark} calls, the event loop delay
|
|
25
|
+
* percentiles (the direct measure of how long the main thread was busy
|
|
26
|
+
* with synchronous work while IO completions waited), and peak memory.
|
|
27
|
+
*/
|
|
28
|
+
export class Tracer {
|
|
29
|
+
enabled;
|
|
30
|
+
file;
|
|
31
|
+
phases;
|
|
32
|
+
#tid;
|
|
33
|
+
#now;
|
|
34
|
+
#append;
|
|
35
|
+
#write;
|
|
36
|
+
#flushLines;
|
|
37
|
+
#flushMs;
|
|
38
|
+
#buffer = [];
|
|
39
|
+
#timer;
|
|
40
|
+
#sampler;
|
|
41
|
+
#marks = [];
|
|
42
|
+
#start;
|
|
43
|
+
#loop;
|
|
44
|
+
#closed = false;
|
|
45
|
+
constructor(options = {}) {
|
|
46
|
+
this.file = options.file || undefined;
|
|
47
|
+
this.phases = !!options.phases;
|
|
48
|
+
this.enabled = !!this.file || this.phases;
|
|
49
|
+
this.#tid = options.threadId ?? ownThreadId;
|
|
50
|
+
this.#now = options.now ?? sharedNow;
|
|
51
|
+
this.#append = options.append ?? defaultAppend;
|
|
52
|
+
this.#write = options.write ?? defaultWrite;
|
|
53
|
+
this.#flushLines = options.flushLines ?? 200;
|
|
54
|
+
this.#flushMs = options.flushMs ?? 50;
|
|
55
|
+
this.#start = this.#now();
|
|
56
|
+
if (!this.enabled)
|
|
57
|
+
return;
|
|
58
|
+
const main = options.main ?? this.#tid === 0;
|
|
59
|
+
if (main) {
|
|
60
|
+
this.#loop = monitorEventLoopDelay({ resolution: 20 });
|
|
61
|
+
this.#loop.enable();
|
|
62
|
+
const sampleMs = options.sampleMs ?? 20;
|
|
63
|
+
if (this.file && sampleMs > 0) {
|
|
64
|
+
this.#sampler = setInterval(() => this.sample(), sampleMs);
|
|
65
|
+
this.#sampler.unref();
|
|
66
|
+
}
|
|
67
|
+
process.once('exit', () => this.close());
|
|
68
|
+
}
|
|
69
|
+
this.trace('open', { pid: process.pid });
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Build a tracer from the environment: `VLT_TRACE` names the file,
|
|
73
|
+
* `VLT_PHASES` (any value but `0` or empty) asks for the summary.
|
|
74
|
+
*/
|
|
75
|
+
static fromEnv(env = process.env, options = {}) {
|
|
76
|
+
const phases = env.VLT_PHASES;
|
|
77
|
+
return new Tracer({
|
|
78
|
+
...options,
|
|
79
|
+
file: env.VLT_TRACE,
|
|
80
|
+
phases: !!phases && phases !== '0',
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/** Record one event. */
|
|
84
|
+
trace(ev, fields) {
|
|
85
|
+
if (!this.file || this.#closed)
|
|
86
|
+
return;
|
|
87
|
+
const event = { t: this.#now(), tid: this.#tid, ev };
|
|
88
|
+
if (fields) {
|
|
89
|
+
for (const [k, v] of Object.entries(fields)) {
|
|
90
|
+
if (v !== undefined)
|
|
91
|
+
event[k] = v;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
this.#buffer.push(JSON.stringify(event));
|
|
95
|
+
if (this.#buffer.length >= this.#flushLines)
|
|
96
|
+
this.flush();
|
|
97
|
+
else if (!this.#timer) {
|
|
98
|
+
this.#timer = setTimeout(() => this.flush(), this.#flushMs);
|
|
99
|
+
this.#timer.unref();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Record a `<ev>:start` event now and return a function that records
|
|
104
|
+
* `<ev>:end` with the elapsed `ms` (plus any fields it is given). The
|
|
105
|
+
* same `fields` are stamped on both ends so the two can be paired.
|
|
106
|
+
*/
|
|
107
|
+
span(ev, fields) {
|
|
108
|
+
if (!this.file)
|
|
109
|
+
return noop;
|
|
110
|
+
const t0 = this.#now();
|
|
111
|
+
this.trace(`${ev}:start`, fields);
|
|
112
|
+
return end => this.trace(`${ev}:end`, {
|
|
113
|
+
...fields,
|
|
114
|
+
...end,
|
|
115
|
+
ms: Math.round((this.#now() - t0) * 1000) / 1000,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Mark the start of a phase. The summary reports the ms between each
|
|
120
|
+
* mark and the next one, and the trace records a `phase` event.
|
|
121
|
+
*/
|
|
122
|
+
mark(name, fields) {
|
|
123
|
+
if (!this.enabled)
|
|
124
|
+
return;
|
|
125
|
+
this.#marks.push({ name, t: this.#now() });
|
|
126
|
+
this.trace('phase', { ...fields, name });
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Record how busy a resource lane is: how many jobs it is running and
|
|
130
|
+
* how many are waiting for a slot. The report derives idle time per
|
|
131
|
+
* lane from these.
|
|
132
|
+
*/
|
|
133
|
+
lane(name, busy, queued) {
|
|
134
|
+
if (!this.file)
|
|
135
|
+
return;
|
|
136
|
+
this.trace('lane', { name, busy, queued });
|
|
137
|
+
}
|
|
138
|
+
/** Record a memory sample. */
|
|
139
|
+
sample() {
|
|
140
|
+
if (!this.file)
|
|
141
|
+
return;
|
|
142
|
+
const m = process.memoryUsage();
|
|
143
|
+
this.trace('rss', {
|
|
144
|
+
rss: m.rss,
|
|
145
|
+
heap: m.heapUsed,
|
|
146
|
+
ext: m.external,
|
|
147
|
+
ab: m.arrayBuffers,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
/** Write buffered lines to the file. */
|
|
151
|
+
flush() {
|
|
152
|
+
if (this.#timer) {
|
|
153
|
+
clearTimeout(this.#timer);
|
|
154
|
+
this.#timer = undefined;
|
|
155
|
+
}
|
|
156
|
+
if (!this.file || !this.#buffer.length)
|
|
157
|
+
return;
|
|
158
|
+
const data = this.#buffer.join('\n') + '\n';
|
|
159
|
+
this.#buffer = [];
|
|
160
|
+
try {
|
|
161
|
+
this.#append(this.file, data);
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
// a trace that cannot be written must never fail the install
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* The one-line phase summary, or undefined when phases are off. Each
|
|
169
|
+
* phase is reported as the ms from its mark to the next mark (or to
|
|
170
|
+
* now, for the last one).
|
|
171
|
+
*/
|
|
172
|
+
summary() {
|
|
173
|
+
if (!this.phases)
|
|
174
|
+
return undefined;
|
|
175
|
+
const now = this.#now();
|
|
176
|
+
const parts = [];
|
|
177
|
+
for (const [i, m] of this.#marks.entries()) {
|
|
178
|
+
const next = this.#marks[i + 1]?.t ?? now;
|
|
179
|
+
parts.push(`${m.name}=${Math.round(next - m.t)}`);
|
|
180
|
+
}
|
|
181
|
+
parts.push(`total=${Math.round(now - this.#start)}`);
|
|
182
|
+
if (this.#loop) {
|
|
183
|
+
// the histogram is in nanoseconds
|
|
184
|
+
const ms = (n) => Math.round(n / 1e6);
|
|
185
|
+
parts.push(`loop p50=${ms(this.#loop.percentile(50))}` +
|
|
186
|
+
` p99=${ms(this.#loop.percentile(99))}` +
|
|
187
|
+
` max=${ms(this.#loop.max)}`);
|
|
188
|
+
}
|
|
189
|
+
const { maxRSS } = process.resourceUsage();
|
|
190
|
+
parts.push(`maxrss=${Math.round(maxRSS / 1024)}MB`);
|
|
191
|
+
return `PHASES ${parts.join(' ')}`;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Stop sampling, flush, and print the summary. Runs at process exit
|
|
195
|
+
* on the main thread; safe to call more than once.
|
|
196
|
+
*/
|
|
197
|
+
close() {
|
|
198
|
+
if (this.#closed)
|
|
199
|
+
return;
|
|
200
|
+
if (this.#sampler)
|
|
201
|
+
clearInterval(this.#sampler);
|
|
202
|
+
if (this.enabled) {
|
|
203
|
+
if (this.#loop) {
|
|
204
|
+
this.#loop.disable();
|
|
205
|
+
this.trace('exit', {
|
|
206
|
+
loopP50: Math.round(this.#loop.percentile(50) / 1e6),
|
|
207
|
+
loopP99: Math.round(this.#loop.percentile(99) / 1e6),
|
|
208
|
+
loopMax: Math.round(this.#loop.max / 1e6),
|
|
209
|
+
maxrss: process.resourceUsage().maxRSS,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
const line = this.summary();
|
|
213
|
+
this.flush();
|
|
214
|
+
if (line)
|
|
215
|
+
this.#write(line);
|
|
216
|
+
}
|
|
217
|
+
this.#closed = true;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
const noop = () => { };
|
|
221
|
+
/**
|
|
222
|
+
* The process-wide tracer, built from the environment. Worker threads
|
|
223
|
+
* inherit the environment, so they trace to the same file.
|
|
224
|
+
*/
|
|
225
|
+
export const tracer = Tracer.fromEnv();
|
|
226
|
+
/** Record one event on the process-wide tracer. */
|
|
227
|
+
export const trace = (ev, fields) => tracer.trace(ev, fields);
|
|
228
|
+
/** Open a span on the process-wide tracer. See {@link Tracer.span}. */
|
|
229
|
+
export const span = (ev, fields) => tracer.span(ev, fields);
|
|
230
|
+
/** Mark a phase on the process-wide tracer. See {@link Tracer.mark}. */
|
|
231
|
+
export const mark = (name, fields) => tracer.mark(name, fields);
|
|
232
|
+
/** Record lane occupancy on the process-wide tracer. */
|
|
233
|
+
export const lane = (name, busy, queued) => tracer.lane(name, busy, queued);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vltpkg/output",
|
|
3
3
|
"description": "Generate output from the vlt CLI",
|
|
4
|
-
"version": "1.6.
|
|
4
|
+
"version": "1.6.2",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/vltpkg/vltpkg.git",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"url": "http://vlt.sh"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@vltpkg/types": "1.6.
|
|
16
|
+
"@vltpkg/types": "1.6.2"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@eslint/js": "^9.39.1",
|
|
@@ -57,6 +57,11 @@
|
|
|
57
57
|
"import": {
|
|
58
58
|
"default": "./dist/error.js"
|
|
59
59
|
}
|
|
60
|
+
},
|
|
61
|
+
"./trace": {
|
|
62
|
+
"import": {
|
|
63
|
+
"default": "./dist/trace.js"
|
|
64
|
+
}
|
|
60
65
|
}
|
|
61
66
|
},
|
|
62
67
|
"files": [
|