@termwright/probe-ink 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -15
- package/dist/bun-preload.js +19 -2
- package/dist/bun-preload.js.map +1 -1
- package/dist/{chunk-LO7YF74P.js → chunk-67M2GX5S.js} +1 -1
- package/dist/{chunk-LO7YF74P.js.map → chunk-67M2GX5S.js.map} +1 -1
- package/dist/{chunk-IUFXTMZ7.js → chunk-CLY2SLYH.js} +12 -2
- package/dist/chunk-CLY2SLYH.js.map +1 -0
- package/dist/chunk-Q75BSILO.js +649 -0
- package/dist/chunk-Q75BSILO.js.map +1 -0
- package/dist/chunk-SLKX554P.js +152 -0
- package/dist/chunk-SLKX554P.js.map +1 -0
- package/dist/index.d.ts +37 -32
- package/dist/index.js +19 -12
- package/dist/index.js.map +1 -1
- package/dist/instrument.d.ts +12 -2
- package/dist/instrument.js +628 -67
- package/dist/instrument.js.map +1 -1
- package/dist/instrumentation.d.ts +26 -0
- package/dist/instrumentation.js +23 -0
- package/dist/instrumentation.js.map +1 -0
- package/dist/node-hook.js +31 -8
- package/dist/node-hook.js.map +1 -1
- package/dist/react-commit-bridge-BzePo8gM.d.ts +169 -0
- package/package.json +15 -11
- package/dist/chunk-IUFXTMZ7.js.map +0 -1
- package/dist/chunk-Y5WYMWRU.js +0 -358
- package/dist/chunk-Y5WYMWRU.js.map +0 -1
- package/dist/observe-C853n-dp.d.ts +0 -71
package/dist/instrument.js
CHANGED
|
@@ -1,115 +1,637 @@
|
|
|
1
1
|
import {
|
|
2
2
|
PACKAGE_VERSION,
|
|
3
|
-
|
|
3
|
+
acquireReactCommitBridge,
|
|
4
|
+
activateInkRendererObservation,
|
|
5
|
+
createInkMarkerWriter,
|
|
4
6
|
createInkSession,
|
|
5
7
|
onInkAnnotationChange,
|
|
6
8
|
probeInfo
|
|
7
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-Q75BSILO.js";
|
|
8
10
|
import {
|
|
9
11
|
isInstrumented
|
|
10
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-67M2GX5S.js";
|
|
13
|
+
import {
|
|
14
|
+
INK_FRAME_CONTEXT,
|
|
15
|
+
INK_RENDER_CAPTURE,
|
|
16
|
+
instrumentationSentinel
|
|
17
|
+
} from "./chunk-SLKX554P.js";
|
|
11
18
|
|
|
12
19
|
// src/instrument.ts
|
|
13
20
|
import { Stream } from "stream";
|
|
14
21
|
import { createElement, Fragment } from "react";
|
|
15
|
-
import { ENV_ENDPOINT,
|
|
22
|
+
import { ENV_ENDPOINT, ENV_TOKEN } from "@termwright/protocol";
|
|
16
23
|
import { connectProbe } from "@termwright/probe-runtime";
|
|
24
|
+
|
|
25
|
+
// src/frame-capture.ts
|
|
26
|
+
var latest = /* @__PURE__ */ new WeakMap();
|
|
27
|
+
function installInkCaptureHook() {
|
|
28
|
+
const globals = globalThis;
|
|
29
|
+
const previous = globals[INK_RENDER_CAPTURE];
|
|
30
|
+
const previousContext = globals[INK_FRAME_CONTEXT];
|
|
31
|
+
const hook = (root, rendered, screenReader) => {
|
|
32
|
+
if (screenReader) {
|
|
33
|
+
retainInkFrame({
|
|
34
|
+
root,
|
|
35
|
+
staticRoots: root.staticNode === void 0 ? [] : [root.staticNode],
|
|
36
|
+
staticChildren: snapshotStaticChildren(root.staticNode),
|
|
37
|
+
rendered,
|
|
38
|
+
screenReader,
|
|
39
|
+
geometry: /* @__PURE__ */ new Map(),
|
|
40
|
+
liveRows: visibleRows(rendered.output),
|
|
41
|
+
staticRows: visibleRows(rendered.staticOutput)
|
|
42
|
+
});
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
retainInkFrame(captureInkLayout(root, rendered));
|
|
46
|
+
};
|
|
47
|
+
globals[INK_RENDER_CAPTURE] = hook;
|
|
48
|
+
const contextHook = (root, context) => {
|
|
49
|
+
const frame = latest.get(root);
|
|
50
|
+
if (frame !== void 0) latest.set(root, { ...frame, context });
|
|
51
|
+
};
|
|
52
|
+
globals[INK_FRAME_CONTEXT] = contextHook;
|
|
53
|
+
return () => {
|
|
54
|
+
if (globals[INK_RENDER_CAPTURE] === hook) {
|
|
55
|
+
if (previous === void 0) delete globals[INK_RENDER_CAPTURE];
|
|
56
|
+
else globals[INK_RENDER_CAPTURE] = previous;
|
|
57
|
+
}
|
|
58
|
+
if (globals[INK_FRAME_CONTEXT] === contextHook) {
|
|
59
|
+
if (previousContext === void 0) delete globals[INK_FRAME_CONTEXT];
|
|
60
|
+
else globals[INK_FRAME_CONTEXT] = previousContext;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function captureInkLayout(root, rendered, context) {
|
|
65
|
+
const inkRoot = root;
|
|
66
|
+
const geometry = /* @__PURE__ */ new Map();
|
|
67
|
+
const width = integer(inkRoot.yogaNode?.getComputedWidth());
|
|
68
|
+
const height = integer(inkRoot.yogaNode?.getComputedHeight());
|
|
69
|
+
walk(inkRoot, "live", 0, 0, rect(0, 0, width, height), geometry, true);
|
|
70
|
+
if (inkRoot.staticNode !== void 0) {
|
|
71
|
+
const staticRoot = inkRoot.staticNode;
|
|
72
|
+
walk(
|
|
73
|
+
staticRoot,
|
|
74
|
+
"static",
|
|
75
|
+
0,
|
|
76
|
+
0,
|
|
77
|
+
rect(
|
|
78
|
+
0,
|
|
79
|
+
0,
|
|
80
|
+
integer(staticRoot.yogaNode?.getComputedWidth()),
|
|
81
|
+
integer(staticRoot.yogaNode?.getComputedHeight())
|
|
82
|
+
),
|
|
83
|
+
geometry,
|
|
84
|
+
false
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
root: inkRoot,
|
|
89
|
+
staticRoots: inkRoot.staticNode === void 0 ? [] : [inkRoot.staticNode],
|
|
90
|
+
staticChildren: snapshotStaticChildren(inkRoot.staticNode),
|
|
91
|
+
rendered,
|
|
92
|
+
screenReader: false,
|
|
93
|
+
geometry,
|
|
94
|
+
liveRows: visibleRows(rendered.output),
|
|
95
|
+
staticRows: visibleRows(rendered.staticOutput),
|
|
96
|
+
...context === void 0 ? {} : { context }
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function capturedInkFrame(root) {
|
|
100
|
+
return latest.get(root);
|
|
101
|
+
}
|
|
102
|
+
function retainInkFrame(capture) {
|
|
103
|
+
const previous = latest.get(capture.root);
|
|
104
|
+
if (previous === void 0 || capture.screenReader) {
|
|
105
|
+
latest.set(capture.root, capture);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const previousRoots = new Set(previous.staticRoots);
|
|
109
|
+
const addedRoots = capture.staticRoots.filter((root) => !previousRoots.has(root));
|
|
110
|
+
const newStaticNodes = new Set(
|
|
111
|
+
[...capture.staticChildren.keys()].filter((node) => !previous.staticChildren.has(node))
|
|
112
|
+
);
|
|
113
|
+
const hasNewStaticOutput = capture.staticRows > 0 && newStaticNodes.size > 0;
|
|
114
|
+
if (!hasNewStaticOutput) {
|
|
115
|
+
const retainedGeometry = new Map(capture.geometry);
|
|
116
|
+
for (const [node, geometry2] of previous.geometry) {
|
|
117
|
+
if (geometry2.region === "static") retainedGeometry.set(node, geometry2);
|
|
118
|
+
}
|
|
119
|
+
latest.set(capture.root, {
|
|
120
|
+
...capture,
|
|
121
|
+
staticRoots: previous.staticRoots,
|
|
122
|
+
staticChildren: previous.staticChildren,
|
|
123
|
+
staticRows: previous.staticRows,
|
|
124
|
+
geometry: retainedGeometry
|
|
125
|
+
});
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const geometry = new Map(capture.geometry);
|
|
129
|
+
for (const [node, retained] of previous.geometry) {
|
|
130
|
+
if (retained.region === "static") geometry.set(node, retained);
|
|
131
|
+
}
|
|
132
|
+
for (const [node, current] of capture.geometry) {
|
|
133
|
+
if (current.region === "static") {
|
|
134
|
+
if (newStaticNodes.has(node)) {
|
|
135
|
+
geometry.set(node, {
|
|
136
|
+
...current,
|
|
137
|
+
intended: shiftRows(current.intended, previous.staticRows),
|
|
138
|
+
visible: shiftRows(current.visible, previous.staticRows)
|
|
139
|
+
});
|
|
140
|
+
} else if (capture.staticRoots.includes(node)) {
|
|
141
|
+
const retained = previous.geometry.get(node);
|
|
142
|
+
if (retained !== void 0) {
|
|
143
|
+
geometry.set(node, {
|
|
144
|
+
...retained,
|
|
145
|
+
intended: { ...retained.intended, height: previous.staticRows + capture.staticRows },
|
|
146
|
+
visible: { ...retained.visible, height: previous.staticRows + capture.staticRows }
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const staticChildren = new Map(previous.staticChildren);
|
|
153
|
+
for (const [parent, currentChildren] of capture.staticChildren) {
|
|
154
|
+
const retained = staticChildren.get(parent) ?? [];
|
|
155
|
+
staticChildren.set(parent, [
|
|
156
|
+
...retained,
|
|
157
|
+
...currentChildren.filter((child) => !retained.includes(child))
|
|
158
|
+
]);
|
|
159
|
+
}
|
|
160
|
+
latest.set(capture.root, {
|
|
161
|
+
...capture,
|
|
162
|
+
staticRoots: [...previous.staticRoots, ...addedRoots],
|
|
163
|
+
staticChildren,
|
|
164
|
+
staticRows: previous.staticRows + capture.staticRows,
|
|
165
|
+
geometry
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
function snapshotStaticChildren(root) {
|
|
169
|
+
const result = /* @__PURE__ */ new Map();
|
|
170
|
+
if (root === void 0) return result;
|
|
171
|
+
const stack = [root];
|
|
172
|
+
while (stack.length > 0) {
|
|
173
|
+
const node = stack.pop();
|
|
174
|
+
const children = [...node.childNodes];
|
|
175
|
+
result.set(node, children);
|
|
176
|
+
for (const child of children) if (child.nodeName !== "#text") stack.push(child);
|
|
177
|
+
}
|
|
178
|
+
return result;
|
|
179
|
+
}
|
|
180
|
+
function shiftRows(value, rows) {
|
|
181
|
+
return { ...value, row: value.row + rows };
|
|
182
|
+
}
|
|
183
|
+
function walk(node, region, offsetX, offsetY, ancestorClip, output, skipStatic) {
|
|
184
|
+
if (skipStatic && node.internal_static === true) return;
|
|
185
|
+
const yoga = node.yogaNode;
|
|
186
|
+
if (yoga === void 0 || yoga.getDisplay() === 1) return;
|
|
187
|
+
const x = offsetX + integer(yoga.getComputedLeft());
|
|
188
|
+
const y = offsetY + integer(yoga.getComputedTop());
|
|
189
|
+
const intended = rect(x, y, integer(yoga.getComputedWidth()), integer(yoga.getComputedHeight()));
|
|
190
|
+
const visible = intersection(intended, ancestorClip);
|
|
191
|
+
output.set(node, { intended, visible, region });
|
|
192
|
+
let childClip = ancestorClip;
|
|
193
|
+
if (node.nodeName === "ink-box") {
|
|
194
|
+
const horizontal = node.style?.overflowX === "hidden" || node.style?.overflow === "hidden";
|
|
195
|
+
const vertical = node.style?.overflowY === "hidden" || node.style?.overflow === "hidden";
|
|
196
|
+
if (horizontal || vertical) {
|
|
197
|
+
const left = horizontal ? x + integer(yoga.getComputedBorder(0)) : ancestorClip.column;
|
|
198
|
+
const right = horizontal ? x + intended.width - integer(yoga.getComputedBorder(2)) : ancestorClip.column + ancestorClip.width;
|
|
199
|
+
const top = vertical ? y + integer(yoga.getComputedBorder(1)) : ancestorClip.row;
|
|
200
|
+
const bottom = vertical ? y + intended.height - integer(yoga.getComputedBorder(3)) : ancestorClip.row + ancestorClip.height;
|
|
201
|
+
childClip = intersection(ancestorClip, rect(left, top, right - left, bottom - top));
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
for (const child of node.childNodes) {
|
|
205
|
+
if (child.nodeName !== "#text") {
|
|
206
|
+
walk(child, region, x, y, childClip, output, skipStatic);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
function integer(value) {
|
|
211
|
+
return Number.isFinite(value) ? Math.trunc(value) : 0;
|
|
212
|
+
}
|
|
213
|
+
function rect(column, row, width, height) {
|
|
214
|
+
return {
|
|
215
|
+
row,
|
|
216
|
+
column,
|
|
217
|
+
width: Math.max(0, width),
|
|
218
|
+
height: Math.max(0, height)
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
function intersection(a, b) {
|
|
222
|
+
const column = Math.max(a.column, b.column);
|
|
223
|
+
const row = Math.max(a.row, b.row);
|
|
224
|
+
const right = Math.max(column, Math.min(a.column + a.width, b.column + b.width));
|
|
225
|
+
const bottom = Math.max(row, Math.min(a.row + a.height, b.row + b.height));
|
|
226
|
+
return rect(column, row, right - column, bottom - row);
|
|
227
|
+
}
|
|
228
|
+
function visibleRows(output) {
|
|
229
|
+
if (output === "") return 0;
|
|
230
|
+
const lines = output.split("\n");
|
|
231
|
+
return output.endsWith("\n") ? lines.length - 1 : lines.length;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// src/terminal-tracker.ts
|
|
235
|
+
import { createTerminal } from "@termwright/vt";
|
|
236
|
+
var ShadowWriteQueue = class {
|
|
237
|
+
#queue = Promise.resolve();
|
|
238
|
+
#failure;
|
|
239
|
+
enqueue(operation) {
|
|
240
|
+
this.#queue = this.#queue.then(async () => {
|
|
241
|
+
if (this.#failure !== void 0) return;
|
|
242
|
+
try {
|
|
243
|
+
await operation();
|
|
244
|
+
} catch (error) {
|
|
245
|
+
this.#failure = error instanceof Error ? error : new Error(String(error));
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
async drain() {
|
|
250
|
+
await this.#queue;
|
|
251
|
+
if (this.#failure !== void 0) throw this.#failure;
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
function trackTerminal(stdout, stderr) {
|
|
255
|
+
const built = createTerminal({
|
|
256
|
+
columns: positive(stdout.columns, 80),
|
|
257
|
+
rows: positive(stdout.rows, 24),
|
|
258
|
+
scrollback: 1e5
|
|
259
|
+
});
|
|
260
|
+
const terminal = built.terminal;
|
|
261
|
+
const queue = new ShadowWriteQueue();
|
|
262
|
+
let stopped = false;
|
|
263
|
+
const restorers = [];
|
|
264
|
+
const observe = (chunk, encoding) => {
|
|
265
|
+
if (stopped) return;
|
|
266
|
+
const bytes = Buffer.isBuffer(chunk) || chunk instanceof Uint8Array ? chunk : Buffer.from(
|
|
267
|
+
String(chunk),
|
|
268
|
+
typeof encoding === "string" ? encoding : "utf8"
|
|
269
|
+
);
|
|
270
|
+
const committed = stdout.isTTY ? withOnlcr(bytes) : bytes;
|
|
271
|
+
queue.enqueue(() => writeTerminal(terminal, committed));
|
|
272
|
+
};
|
|
273
|
+
for (const stream of /* @__PURE__ */ new Set([stdout, stderr])) restorers.push(intercept(stream, observe));
|
|
274
|
+
const onResize = () => terminal.resize(positive(stdout.columns, terminal.cols), positive(stdout.rows, terminal.rows));
|
|
275
|
+
stdout.on("resize", onResize);
|
|
276
|
+
restorers.push(() => stdout.off("resize", onResize));
|
|
277
|
+
return {
|
|
278
|
+
drain: () => queue.drain(),
|
|
279
|
+
position() {
|
|
280
|
+
const buffer = terminal.buffer.active;
|
|
281
|
+
return { row: buffer.cursorY, column: buffer.cursorX, buffer: buffer.type };
|
|
282
|
+
},
|
|
283
|
+
resize(columns, rows) {
|
|
284
|
+
terminal.resize(columns, rows);
|
|
285
|
+
},
|
|
286
|
+
stop() {
|
|
287
|
+
if (stopped) return;
|
|
288
|
+
stopped = true;
|
|
289
|
+
for (const restore of restorers.reverse()) restore();
|
|
290
|
+
terminal.dispose();
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
function withOnlcr(bytes) {
|
|
295
|
+
let newlines = 0;
|
|
296
|
+
for (const byte of bytes) if (byte === 10) newlines += 1;
|
|
297
|
+
if (newlines === 0) return bytes;
|
|
298
|
+
const output = new Uint8Array(bytes.length + newlines);
|
|
299
|
+
let index = 0;
|
|
300
|
+
for (const byte of bytes) {
|
|
301
|
+
if (byte === 10) output[index++] = 13;
|
|
302
|
+
output[index++] = byte;
|
|
303
|
+
}
|
|
304
|
+
return output;
|
|
305
|
+
}
|
|
306
|
+
function intercept(stream, observe) {
|
|
307
|
+
const target = stream;
|
|
308
|
+
const original = target.write;
|
|
309
|
+
const wrapped = function(...args) {
|
|
310
|
+
observe(args[0], args[1]);
|
|
311
|
+
return Reflect.apply(original, this, args);
|
|
312
|
+
};
|
|
313
|
+
try {
|
|
314
|
+
target.write = wrapped;
|
|
315
|
+
} catch (error) {
|
|
316
|
+
throw new Error("Ink terminal stream cannot be instrumented exactly", { cause: error });
|
|
317
|
+
}
|
|
318
|
+
return () => {
|
|
319
|
+
if (target.write === wrapped) target.write = original;
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
function writeTerminal(terminal, bytes) {
|
|
323
|
+
return new Promise((resolve) => terminal.write(bytes, resolve));
|
|
324
|
+
}
|
|
325
|
+
function positive(value, fallback) {
|
|
326
|
+
return Number.isSafeInteger(value) && value > 0 ? value : fallback;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// src/render-boundary.ts
|
|
330
|
+
var RenderBoundaryQueue = class {
|
|
331
|
+
#pending = [];
|
|
332
|
+
#preparing = /* @__PURE__ */ new Set();
|
|
333
|
+
#nextGeneration = 1;
|
|
334
|
+
#stopped = false;
|
|
335
|
+
take(committedGeneration) {
|
|
336
|
+
const boundary = this.#pending[0];
|
|
337
|
+
if (boundary === void 0 || boundary.generation !== committedGeneration) return void 0;
|
|
338
|
+
return this.#pending.shift();
|
|
339
|
+
}
|
|
340
|
+
afterCurrentRender(waitForCurrentRender, mutate) {
|
|
341
|
+
if (this.#stopped) return Promise.reject(stoppedError());
|
|
342
|
+
return new Promise((resolve, reject) => {
|
|
343
|
+
const boundary = { generation: this.#nextGeneration, resolve, reject };
|
|
344
|
+
this.#nextGeneration += 1;
|
|
345
|
+
this.#preparing.add(boundary);
|
|
346
|
+
let flush;
|
|
347
|
+
try {
|
|
348
|
+
flush = waitForCurrentRender();
|
|
349
|
+
} catch (error) {
|
|
350
|
+
this.#preparing.delete(boundary);
|
|
351
|
+
reject(asError(error));
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
void flush.then(
|
|
355
|
+
() => {
|
|
356
|
+
this.#preparing.delete(boundary);
|
|
357
|
+
if (this.#stopped) return;
|
|
358
|
+
this.#pending.push(boundary);
|
|
359
|
+
try {
|
|
360
|
+
mutate(boundary.generation);
|
|
361
|
+
} catch (error) {
|
|
362
|
+
const index = this.#pending.indexOf(boundary);
|
|
363
|
+
if (index !== -1) this.#pending.splice(index, 1);
|
|
364
|
+
reject(asError(error));
|
|
365
|
+
}
|
|
366
|
+
},
|
|
367
|
+
(error) => {
|
|
368
|
+
this.#preparing.delete(boundary);
|
|
369
|
+
reject(asError(error));
|
|
370
|
+
}
|
|
371
|
+
);
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
stop() {
|
|
375
|
+
if (this.#stopped) return;
|
|
376
|
+
this.#stopped = true;
|
|
377
|
+
for (const boundary of this.#preparing) boundary.reject(stoppedError());
|
|
378
|
+
for (const boundary of this.#pending.splice(0)) {
|
|
379
|
+
boundary.reject(stoppedError());
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
function stoppedError() {
|
|
384
|
+
return new Error("Ink probe stopped before the render boundary");
|
|
385
|
+
}
|
|
386
|
+
function asError(error) {
|
|
387
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// src/instrument.ts
|
|
391
|
+
var processTracker = trackTerminal(process.stdout, process.stderr);
|
|
17
392
|
var ADAPTER_NAME = "@termwright/probe-ink";
|
|
18
393
|
var ADAPTER_VERSION = PACKAGE_VERSION;
|
|
394
|
+
var INK_CAPABILITIES = [
|
|
395
|
+
"tree",
|
|
396
|
+
"intended-geometry",
|
|
397
|
+
"clipped-geometry",
|
|
398
|
+
"states",
|
|
399
|
+
"actions",
|
|
400
|
+
"render-revisions"
|
|
401
|
+
];
|
|
402
|
+
var INK_FLUSH_NEXT_RENDER = /* @__PURE__ */ Symbol.for("@termwright/probe-ink/flush-next-render");
|
|
403
|
+
var COMMIT_GENERATION_ATTRIBUTE = "__termwrightCommitGeneration";
|
|
19
404
|
function wrapInkRender(ink, options = {}) {
|
|
20
405
|
const env = options.env ?? process.env;
|
|
21
406
|
const wrapped = (node, suppliedOptions) => {
|
|
22
407
|
if (!isInstrumented(env)) return ink.render(node, suppliedOptions);
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
408
|
+
return instrumentedRender(
|
|
409
|
+
ink,
|
|
410
|
+
node,
|
|
411
|
+
suppliedOptions,
|
|
412
|
+
env,
|
|
413
|
+
options.certifiedHarness === true,
|
|
414
|
+
options.reconciler,
|
|
415
|
+
options.connect ?? connectProbe
|
|
416
|
+
);
|
|
28
417
|
};
|
|
29
418
|
Object.defineProperty(wrapped, "__termwright__", { value: true });
|
|
30
419
|
return wrapped;
|
|
31
420
|
}
|
|
32
|
-
function instrumentedRender(ink, node, suppliedOptions, env) {
|
|
33
|
-
const
|
|
421
|
+
function instrumentedRender(ink, node, suppliedOptions, env, certifiedHarness, reconciler, connector) {
|
|
422
|
+
const certifiedRuntime = instrumentationSentinel() !== void 0;
|
|
423
|
+
if (!certifiedRuntime && !certifiedHarness) return ink.render(node, suppliedOptions);
|
|
424
|
+
let options;
|
|
425
|
+
try {
|
|
426
|
+
options = normalizeOptions(suppliedOptions);
|
|
427
|
+
} catch (error) {
|
|
428
|
+
return renderAfterSetupFailure(ink, node, suppliedOptions, connector, env, error);
|
|
429
|
+
}
|
|
430
|
+
let currentNode = node;
|
|
431
|
+
let commitGeneration = 0;
|
|
34
432
|
const stdout = options.stdout ?? process.stdout;
|
|
433
|
+
const stderr = options.stderr ?? process.stderr;
|
|
434
|
+
const ownsTracker = stdout !== process.stdout || stderr !== process.stderr;
|
|
435
|
+
let tracker = processTracker;
|
|
35
436
|
const probeRef = { current: null };
|
|
36
437
|
const state = {
|
|
37
438
|
channel: null,
|
|
38
439
|
session: null
|
|
39
440
|
};
|
|
40
441
|
let disposed = false;
|
|
41
|
-
const
|
|
442
|
+
const renderBoundaries = new RenderBoundaryQueue();
|
|
443
|
+
let reactRoot = null;
|
|
444
|
+
let reactBridge;
|
|
445
|
+
let releaseCapture;
|
|
446
|
+
let releaseReactBridge;
|
|
447
|
+
let releaseReactBridgeHook;
|
|
448
|
+
let releaseAnnotations;
|
|
449
|
+
const stop = () => {
|
|
450
|
+
if (disposed) return;
|
|
451
|
+
disposed = true;
|
|
452
|
+
renderBoundaries.stop();
|
|
453
|
+
releaseCapture?.();
|
|
454
|
+
releaseReactBridge?.();
|
|
455
|
+
releaseReactBridgeHook?.();
|
|
456
|
+
releaseAnnotations?.();
|
|
457
|
+
if (ownsTracker) tracker.stop();
|
|
458
|
+
state.session?.stop();
|
|
459
|
+
state.channel?.close();
|
|
460
|
+
};
|
|
461
|
+
try {
|
|
462
|
+
releaseCapture = installInkCaptureHook();
|
|
463
|
+
tracker = ownsTracker ? trackTerminal(stdout, stderr) : processTracker;
|
|
464
|
+
if (reconciler !== void 0) {
|
|
465
|
+
const bridgeLease = acquireReactCommitBridge();
|
|
466
|
+
reactBridge = bridgeLease.bridge;
|
|
467
|
+
releaseReactBridgeHook = bridgeLease.release;
|
|
468
|
+
if (env["DEV"] !== "true") {
|
|
469
|
+
reactBridge = activateInkRendererObservation(reconciler);
|
|
470
|
+
}
|
|
471
|
+
releaseReactBridge = reactBridge.subscribe((event) => {
|
|
472
|
+
if (event.type === "commit" && probeRef.current?.parentNode === event.root) {
|
|
473
|
+
reactRoot = event.root;
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
releaseAnnotations = onInkAnnotationChange(() => {
|
|
478
|
+
setImmediate(() => {
|
|
479
|
+
if (!disposed) state.session?.notifyRender({ allowUnsettled: true });
|
|
480
|
+
});
|
|
481
|
+
});
|
|
482
|
+
} catch (error) {
|
|
483
|
+
stop();
|
|
484
|
+
return renderAfterSetupFailure(ink, node, suppliedOptions, connector, env, error);
|
|
485
|
+
}
|
|
42
486
|
const wrap = (child) => createElement(
|
|
43
487
|
Fragment,
|
|
44
488
|
null,
|
|
45
|
-
createElement(ink.Box, {
|
|
489
|
+
createElement(ink.Box, {
|
|
490
|
+
ref: probeRef,
|
|
491
|
+
display: "none",
|
|
492
|
+
[COMMIT_GENERATION_ATTRIBUTE]: commitGeneration
|
|
493
|
+
}),
|
|
46
494
|
child
|
|
47
495
|
);
|
|
48
496
|
const userOnRender = options.onRender;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
497
|
+
let instrumentedNode;
|
|
498
|
+
let instrumentedOptions;
|
|
499
|
+
try {
|
|
500
|
+
instrumentedNode = wrap(node);
|
|
501
|
+
instrumentedOptions = {
|
|
502
|
+
...options,
|
|
503
|
+
onRender(metrics) {
|
|
504
|
+
const generation = probeRef.current?.style?.[COMMIT_GENERATION_ATTRIBUTE];
|
|
505
|
+
const boundary = typeof generation === "number" ? renderBoundaries.take(generation) : void 0;
|
|
506
|
+
try {
|
|
507
|
+
if (!certifiedRuntime) {
|
|
508
|
+
const root = probeRef.current?.parentNode ?? null;
|
|
509
|
+
if (root !== null) {
|
|
510
|
+
const measured = ink.measureElement(root);
|
|
511
|
+
const staticNode = root.staticNode;
|
|
512
|
+
const staticRows = staticNode === void 0 ? 0 : ink.measureElement(staticNode).height;
|
|
513
|
+
retainInkFrame(
|
|
514
|
+
captureInkLayout(
|
|
515
|
+
root,
|
|
516
|
+
{
|
|
517
|
+
output: "",
|
|
518
|
+
outputHeight: measured.height,
|
|
519
|
+
staticOutput: "\n".repeat(staticRows)
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
interactive: options.interactive === true,
|
|
523
|
+
alternateScreen: options.alternateScreen === true,
|
|
524
|
+
debug: options.debug === true,
|
|
525
|
+
stdoutIsTTY: stdout.isTTY === true,
|
|
526
|
+
rows: stdout.rows ?? 24
|
|
527
|
+
}
|
|
528
|
+
)
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
const publication = state.session?.notifyRender({
|
|
533
|
+
awaitPublication: boundary !== void 0
|
|
534
|
+
});
|
|
535
|
+
if (boundary !== void 0) {
|
|
536
|
+
if (publication === void 0) {
|
|
537
|
+
boundary.reject(new Error("Ink semantic session is not attached"));
|
|
538
|
+
} else {
|
|
539
|
+
void publication.then(
|
|
540
|
+
(revision) => {
|
|
541
|
+
if (revision === null) boundary.reject(new Error("Ink render was not published"));
|
|
542
|
+
else boundary.resolve(revision);
|
|
543
|
+
},
|
|
544
|
+
(error) => boundary.reject(error instanceof Error ? error : new Error(String(error)))
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
} catch (error) {
|
|
549
|
+
boundary?.reject(error instanceof Error ? error : new Error(String(error)));
|
|
550
|
+
state.session?.stop();
|
|
551
|
+
}
|
|
552
|
+
userOnRender?.(metrics);
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
} catch (error) {
|
|
556
|
+
stop();
|
|
557
|
+
return renderAfterSetupFailure(ink, node, suppliedOptions, connector, env, error);
|
|
558
|
+
}
|
|
559
|
+
let instance;
|
|
560
|
+
try {
|
|
561
|
+
instance = ink.render(instrumentedNode, instrumentedOptions);
|
|
562
|
+
} catch (error) {
|
|
563
|
+
stop();
|
|
564
|
+
throw error;
|
|
565
|
+
}
|
|
566
|
+
if (reconciler !== void 0 && reactBridge?.hasInkRenderer() !== true) {
|
|
567
|
+
const error = new Error(
|
|
568
|
+
"Ink semantic probe unavailable: React renderer instrumentation did not register Ink."
|
|
569
|
+
);
|
|
570
|
+
stop();
|
|
571
|
+
reportSetupFailure(connector, env, error);
|
|
572
|
+
return instance;
|
|
573
|
+
}
|
|
574
|
+
let connection;
|
|
575
|
+
try {
|
|
576
|
+
connection = connector({
|
|
577
|
+
endpoint: env[ENV_ENDPOINT],
|
|
578
|
+
token: env[ENV_TOKEN],
|
|
579
|
+
probe: probeInfo(),
|
|
580
|
+
capabilities: INK_CAPABILITIES,
|
|
581
|
+
adapterName: ADAPTER_NAME,
|
|
582
|
+
adapterVersion: ADAPTER_VERSION
|
|
583
|
+
}).then(async (channel) => {
|
|
584
|
+
if (channel === null || disposed) {
|
|
585
|
+
channel?.close();
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
state.channel = channel;
|
|
589
|
+
state.session = createInkSession({
|
|
590
|
+
channel,
|
|
591
|
+
// The React bridge observes the same committed Ink host root through
|
|
592
|
+
// FiberRoot.containerInfo. Keep the hidden ref as the certified
|
|
593
|
+
// control while differential conformance is still in progress.
|
|
594
|
+
resolveRoot: () => reactRoot ?? probeRef.current?.parentNode ?? null,
|
|
595
|
+
resolveExcluded: () => probeRef.current,
|
|
596
|
+
resolveCapture: (root) => capturedInkFrame(root),
|
|
597
|
+
waitForRenderFlush: () => instance.waitUntilRenderFlush(),
|
|
598
|
+
stdout,
|
|
599
|
+
writeMarker: createInkMarkerWriter(stdout, { certifiedHarness }),
|
|
600
|
+
tracker,
|
|
601
|
+
onGuaranteeViolation: (error) => {
|
|
602
|
+
state.session?.stop();
|
|
603
|
+
channel.fail("adapter-guarantee-violation", error.message);
|
|
604
|
+
if (channel.isOpen) channel.close();
|
|
605
|
+
}
|
|
606
|
+
});
|
|
52
607
|
try {
|
|
53
|
-
|
|
608
|
+
await renderBoundaries.afterCurrentRender(
|
|
609
|
+
() => instance.waitUntilRenderFlush(),
|
|
610
|
+
(generation) => {
|
|
611
|
+
commitGeneration = generation;
|
|
612
|
+
instance.rerender(wrap(currentNode));
|
|
613
|
+
}
|
|
614
|
+
);
|
|
54
615
|
} catch {
|
|
55
|
-
state.session
|
|
616
|
+
state.session.stop();
|
|
617
|
+
return;
|
|
56
618
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
stdoutIsTTY: stdout.isTTY === true
|
|
64
|
-
});
|
|
65
|
-
const baseCapabilities = includeGeometry ? ["tree", "bounds", "absolute-bounds", "states", "actions", "render-revisions"] : ["tree", "states", "actions", "render-revisions"];
|
|
66
|
-
const qualified = env[ENV_PROTOCOL] === PROTOCOL_V2_ID;
|
|
67
|
-
const capabilities = qualified ? [...baseCapabilities, "qualified-observations"] : baseCapabilities;
|
|
68
|
-
const connection = connectProbe({
|
|
69
|
-
endpoint: env[ENV_ENDPOINT],
|
|
70
|
-
token: env[ENV_TOKEN],
|
|
71
|
-
probe: probeInfo(),
|
|
72
|
-
capabilities,
|
|
73
|
-
adapterName: ADAPTER_NAME,
|
|
74
|
-
adapterVersion: ADAPTER_VERSION,
|
|
75
|
-
...qualified ? { protocol: PROTOCOL_V2_ID } : {}
|
|
76
|
-
}).then(async (channel) => {
|
|
77
|
-
if (channel === null || disposed) {
|
|
78
|
-
channel?.close();
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
state.channel = channel;
|
|
82
|
-
state.session = createInkSession({
|
|
83
|
-
channel,
|
|
84
|
-
resolveRoot: () => probeRef.current?.parentNode ?? null,
|
|
85
|
-
resolveExcluded: () => probeRef.current,
|
|
86
|
-
measureElement: ink.measureElement,
|
|
87
|
-
stdout,
|
|
88
|
-
includeGeometry
|
|
89
|
-
});
|
|
90
|
-
try {
|
|
91
|
-
await instance.waitUntilRenderFlush();
|
|
92
|
-
} catch {
|
|
93
|
-
state.session.stop();
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
if (!disposed && state.session.frames === 0) state.session.notifyRender();
|
|
97
|
-
}).catch(() => void 0);
|
|
98
|
-
const stop = () => {
|
|
99
|
-
if (disposed) return;
|
|
100
|
-
disposed = true;
|
|
101
|
-
releaseAnnotations();
|
|
102
|
-
state.session?.stop();
|
|
103
|
-
state.channel?.close();
|
|
104
|
-
};
|
|
619
|
+
}).catch(() => void 0);
|
|
620
|
+
} catch (error) {
|
|
621
|
+
stop();
|
|
622
|
+
reportSetupFailure(connector, env, error);
|
|
623
|
+
return instance;
|
|
624
|
+
}
|
|
105
625
|
void instance.waitUntilExit().catch(() => void 0).then(async () => {
|
|
626
|
+
renderBoundaries.stop();
|
|
106
627
|
await connection;
|
|
107
628
|
await state.session?.flush();
|
|
108
629
|
stop();
|
|
109
630
|
}).catch(stop);
|
|
110
|
-
|
|
631
|
+
const wrappedInstance = {
|
|
111
632
|
...instance,
|
|
112
633
|
rerender(next) {
|
|
634
|
+
currentNode = next;
|
|
113
635
|
instance.rerender(wrap(next));
|
|
114
636
|
},
|
|
115
637
|
unmount(error) {
|
|
@@ -118,8 +640,18 @@ function instrumentedRender(ink, node, suppliedOptions, env) {
|
|
|
118
640
|
cleanup() {
|
|
119
641
|
stop();
|
|
120
642
|
instance.cleanup();
|
|
643
|
+
},
|
|
644
|
+
[INK_FLUSH_NEXT_RENDER](mutate) {
|
|
645
|
+
return renderBoundaries.afterCurrentRender(
|
|
646
|
+
() => instance.waitUntilRenderFlush(),
|
|
647
|
+
(generation) => {
|
|
648
|
+
commitGeneration = generation;
|
|
649
|
+
mutate();
|
|
650
|
+
}
|
|
651
|
+
);
|
|
121
652
|
}
|
|
122
653
|
};
|
|
654
|
+
return wrappedInstance;
|
|
123
655
|
}
|
|
124
656
|
function normalizeOptions(supplied) {
|
|
125
657
|
if (supplied === void 0) return {};
|
|
@@ -128,7 +660,36 @@ function normalizeOptions(supplied) {
|
|
|
128
660
|
}
|
|
129
661
|
return supplied;
|
|
130
662
|
}
|
|
663
|
+
function reportSetupFailure(connector, env, failure) {
|
|
664
|
+
const error = failure instanceof Error ? failure : new Error(String(failure));
|
|
665
|
+
try {
|
|
666
|
+
void connector({
|
|
667
|
+
endpoint: env[ENV_ENDPOINT],
|
|
668
|
+
token: env[ENV_TOKEN],
|
|
669
|
+
probe: probeInfo(),
|
|
670
|
+
capabilities: INK_CAPABILITIES,
|
|
671
|
+
adapterName: ADAPTER_NAME,
|
|
672
|
+
adapterVersion: ADAPTER_VERSION
|
|
673
|
+
}).then((channel) => {
|
|
674
|
+
if (channel === null) return;
|
|
675
|
+
channel.fail("adapter-guarantee-violation", error.message);
|
|
676
|
+
if (channel.isOpen) channel.close();
|
|
677
|
+
}).catch(() => void 0);
|
|
678
|
+
} catch {
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
function renderAfterSetupFailure(ink, node, suppliedOptions, connector, env, setupFailure) {
|
|
682
|
+
try {
|
|
683
|
+
const instance = ink.render(node, suppliedOptions);
|
|
684
|
+
reportSetupFailure(connector, env, setupFailure);
|
|
685
|
+
return instance;
|
|
686
|
+
} catch (applicationError) {
|
|
687
|
+
reportSetupFailure(connector, env, setupFailure);
|
|
688
|
+
throw applicationError;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
131
691
|
export {
|
|
692
|
+
INK_FLUSH_NEXT_RENDER,
|
|
132
693
|
wrapInkRender
|
|
133
694
|
};
|
|
134
695
|
//# sourceMappingURL=instrument.js.map
|