tty-assert 0.1.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/LICENSE +21 -0
- package/README.md +152 -0
- package/dist/index.d.ts +80 -0
- package/dist/index.js +840 -0
- package/dist/index.js.map +1 -0
- package/package.json +63 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 Odd-e
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# `tty-assert`
|
|
2
|
+
|
|
3
|
+
Node-only helpers for asserting on **PTY (pseudo-terminal) transcripts**: ANSI stripping, **xterm.js** replay, and **explicit search surfaces** so tests do not rely on “search the whole history and hope labels disambiguate.”
|
|
4
|
+
|
|
5
|
+
This package is **test-runner neutral** (no Cypress peer dependency). Integrators use it from Vitest, Playwright-sidecar tasks, or custom harnesses.
|
|
6
|
+
|
|
7
|
+
**Imports:** **`package.json` exposes only the package root**. Published installs resolve `tty-assert` to **`dist/`** (compiled ESM and types). In this repo, tests and implementation use **`src/`** via relative imports.
|
|
8
|
+
|
|
9
|
+
**Internal layout:** `ansi/` (strip escapes), `defaults/` (geometry), `diagnostics/` (failure formatting and dump payloads), `pty/` (buffered PTY), `xterm/` (headless replay, viewport PNG/GIF, live-terminal surface attempts), `surface/` (`waitForTextInSurface`, poll loop, cell expectations, strict errors), `managed/` (managed session).
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Strip vs replay vs locators
|
|
14
|
+
|
|
15
|
+
| Concept | What it is | Typical use |
|
|
16
|
+
|--------|------------|-------------|
|
|
17
|
+
| **Stripped transcript** | Cumulative PTY bytes with **ANSI/OSC escapes removed** (`stripAnsiCliPty`). One long string — **not** an emulator layout. | “Did this text ever appear in the session?” (plugin startup wait, many cumulative assertions). |
|
|
18
|
+
| **Viewport replay** | Feed raw bytes through **headless xterm**, then read the **visible viewport** as plain text: one row per screen line, rows joined with **`\n`** (`ptyTranscriptToViewportPlaintext` / `ptyTranscriptToVisiblePlaintextViaXterm`). | **Current screen** plain text; product-specific parsing (e.g. “current guidance”) stays in adapters. |
|
|
19
|
+
| **Locator surfaces** | After the same xterm replay, search a **named slice** of the buffer (`waitForTextInSurface`), or search the **stripped transcript** as a single haystack. | “Is this text visible in the **viewable** region?” vs “anywhere in **scrollback + viewport**?” vs “in the **stripped** log?” |
|
|
20
|
+
|
|
21
|
+
Stripped text and replayed viewport text **differ**: layout, wrapping, and scrollback mean a substring can appear in one model and not the other. Pick the surface that matches **what the user sees** for that assertion.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Flattening contract (locators)
|
|
26
|
+
|
|
27
|
+
**Viewport replay** (`ptyTranscriptToViewportPlaintext`): newline-**joined** rows (see implementation in `src/xterm/ptyTranscriptToVisiblePlaintextViaXterm.ts`).
|
|
28
|
+
|
|
29
|
+
**`waitForTextInSurface`** for xterm-backed surfaces (`viewableBuffer`, `fullBuffer`):
|
|
30
|
+
|
|
31
|
+
- **Search haystack:** each row is `cols` cells; empty cells become a **space**; rows are concatenated **with no `\n` between rows** (row-major flat block).
|
|
32
|
+
- **Failure snapshots:** **newline-separated** rows, each row `trimEnd`’d — readable and **not** identical to the flat search string.
|
|
33
|
+
|
|
34
|
+
For **`strippedTranscript`**, the haystack and snapshot are the **same**: the full ANSI-stripped string (no xterm geometry).
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Assertion failure messages
|
|
39
|
+
|
|
40
|
+
When `waitForTextInSurface` or `ManagedTtySession.assert` fails, the message body includes:
|
|
41
|
+
|
|
42
|
+
1. **Detail** and **`Search surface: "…"`** plus a short note (transcript vs row-major matching).
|
|
43
|
+
2. A **`---`** block with the snapshot as **numbered lines** (` 1 | …`, split on `\n`). The block is truncated after numbering (about **8000** characters).
|
|
44
|
+
3. **`ManagedTtySession.assert` only:** **`--- Final visible screen (viewport) ---`** plus the same **numbered-line** form of the xterm **viewport** (current visible rows), then a closing **`---`**. This is the plain-text “screenshot” of what is on screen at failure time, before the cumulative transcript dump.
|
|
45
|
+
4. A **raw PTY appendix** with ANSI stripped and safe-visible escaping, capped at about **12_000** visible characters.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Package root (`tty-assert`)
|
|
50
|
+
|
|
51
|
+
Exported today: **`startManagedTtySession`**, **`BufferedPtySession`**, and managed-session types (`ManagedTtySession`, **`ManagedTtyAssertInput`**, **`ManagedTtyAssertOptions`**, …). The return type of **`dumpDiagnostics()`** is internal to the package. Lower-level modules (`waitForTextInSurface`, `ptySession`, replay helpers, `stripAnsi`, …) are **not** separate entry points; they are composed internally and covered by this package’s unit tests.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Managed interactive session
|
|
56
|
+
|
|
57
|
+
Use this when one process owns **the same** PTY buffer, xterm headless instance, and assertion loop (retry + sync). That matches interactive CLI tests: start once, write many times, assert many times, dispose once.
|
|
58
|
+
|
|
59
|
+
**Lifecycle**
|
|
60
|
+
|
|
61
|
+
1. **`startManagedTtySession(opts)`** — spawns the PTY (`ptySession`), returns **`ManagedTtySession`**.
|
|
62
|
+
2. **`write` / `submit`** — send bytes or a line (`\r` appended for `submit`).
|
|
63
|
+
3. **`assert(opts)`** — polls until timeout: syncs new raw bytes into xterm, then runs the same surface logic as `waitForTextInSurface` (or stripped-transcript path without replay). No need to pass an updated `raw` string from outside; it always reads `session.buf.text`.
|
|
64
|
+
4. **`dumpDiagnostics()`** — async diagnostic snapshot (previews of viewport, stripped tail, etc.).
|
|
65
|
+
5. **`getViewportAnimationPngs()`** / **`buildViewportAnimationGif()`** — when the session was started with **`startManagedTtySession`**, each PTY `onData` schedules a debounced viewport sample (deduped by viewport plaintext, ring buffer capped at 56 PNGs). **`buildViewportAnimationGif`** flushes the pending sample, then encodes those PNGs to an animated GIF (requires at least two distinct frames). Sessions from **`attachManagedTtySession`** without the internal bridge omit recording (empty PNG list; GIF build throws).
|
|
66
|
+
6. **`dispose()`** — idempotent: tears down xterm, disposes the buffered PTY session. Safe if the child already exited.
|
|
67
|
+
|
|
68
|
+
**`ManagedTtyAssertInput`** is what **`assert(opts)`** accepts: same assertion knobs as `waitForTextInSurface` except **`raw`** is omitted, and `needle` / `startAfterAnchor` may use **`{ source, flags? }`** instead of `RegExp` (normalized inside **`assert`**). After normalization, the shape is **`ManagedTtyAssertOptions`** (`needle`: string or `RegExp` only).
|
|
69
|
+
|
|
70
|
+
**Integrators (e.g. Cypress):** map a task such as `cliAssert` to `managed.assert(payload)` — no separate conversion step. On failure, save artifacts from **`getViewportAnimationPngs`** / **`buildViewportAnimationGif()`** as needed. The task body must stay **JSON-serializable**: use `{ source, flags? }` instead of `RegExp` objects for needles and anchors.
|
|
71
|
+
|
|
72
|
+
**Node tests:** Prefer `startManagedTtySession` from `tty-assert`; do not round-trip PTY text through a browser.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## `waitForTextInSurface` (internal module)
|
|
77
|
+
|
|
78
|
+
Used by **`ManagedTtySession.assert`** and unit-tested under `tests/`. Direct use is via **relative imports** inside this package (see those tests), not a separate publish entry.
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
// e.g. from tests/waitForTextInSurface.test.ts
|
|
82
|
+
import { waitForTextInSurface } from '../src/surface/waitForTextInSurface'
|
|
83
|
+
|
|
84
|
+
await waitForTextInSurface({
|
|
85
|
+
raw: ptyBytes,
|
|
86
|
+
needle: 'Expected',
|
|
87
|
+
surface: 'viewableBuffer', // or 'fullBuffer' | 'strippedTranscript'
|
|
88
|
+
timeoutMs: 3000, // default 0 = single attempt (good for Vitest)
|
|
89
|
+
retryMs: 50,
|
|
90
|
+
strict: true, // default; throws if multiple non-overlapping matches
|
|
91
|
+
})
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
After the needle matches, optional **`cellExpectations`** assert on xterm cells (string needle, `viewableBuffer` / `fullBuffer` only). Example — bold on the **first** match and **palette 8** background on the **last**:
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
await waitForTextInSurface({
|
|
98
|
+
raw: ptyBytes,
|
|
99
|
+
needle: 'User paste',
|
|
100
|
+
surface: 'viewableBuffer',
|
|
101
|
+
strict: false,
|
|
102
|
+
cellExpectations: [
|
|
103
|
+
{ match: 'first', expectations: [{ kind: 'allBold' }] },
|
|
104
|
+
{ match: 'last', expectations: [{ kind: 'allBgPalette', index: 8 }] },
|
|
105
|
+
],
|
|
106
|
+
})
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
- **`viewableBuffer`:** lines from xterm `baseY` through `length - 1` (the scrollable view’s active region, not the full scrollback).
|
|
110
|
+
- **`fullBuffer`:** lines `0` through `length - 1` (scrollback + active).
|
|
111
|
+
- **`strippedTranscript`:** no replay; search `stripAnsiCliPty(raw)`.
|
|
112
|
+
- **`raw`:** string or a **getter** `() => string` so each poll can see updated buffer data.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Scripts
|
|
117
|
+
|
|
118
|
+
With [Nix](https://nixos.org/) (recommended — includes libraries needed to build **canvas** and **node-pty**):
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
nix develop -c pnpm install
|
|
122
|
+
nix develop -c pnpm test
|
|
123
|
+
nix develop -c pnpm lint
|
|
124
|
+
nix develop -c pnpm format
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Without Nix, install Node **22**, **pnpm**, and native deps for **canvas** (see CI workflow), then:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
pnpm install
|
|
131
|
+
pnpm test
|
|
132
|
+
pnpm lint
|
|
133
|
+
pnpm format
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Git history
|
|
139
|
+
|
|
140
|
+
This repository was bootstrapped from the [Doughnut](https://github.com/odde/doughnut) monorepo with history for `packages/tty-assert` and the earlier `e2e_test/config/tty-assert-staging/` tree preserved (paths rewritten to the repo root). To reproduce a fresh extract from an up-to-date clone of Doughnut:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
git clone <doughnut-url> tty-assert-work
|
|
144
|
+
cd tty-assert-work
|
|
145
|
+
git filter-repo \
|
|
146
|
+
--path packages/tty-assert/ \
|
|
147
|
+
--path e2e_test/config/tty-assert-staging/ \
|
|
148
|
+
--path-rename packages/tty-assert/: \
|
|
149
|
+
--path-rename e2e_test/config/tty-assert-staging/:
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Then add this repository as `origin` and push `main`.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { IPty } from '@lydell/node-pty';
|
|
2
|
+
|
|
3
|
+
type BufferedPtySession = {
|
|
4
|
+
pty: IPty;
|
|
5
|
+
buf: {
|
|
6
|
+
text: string;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
type StartBufferedPtySessionOptions = {
|
|
10
|
+
command: string;
|
|
11
|
+
args: string[];
|
|
12
|
+
cwd: string;
|
|
13
|
+
env: NodeJS.ProcessEnv;
|
|
14
|
+
cols?: number;
|
|
15
|
+
rows?: number;
|
|
16
|
+
onAfterPtyData?: () => void;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type TtyAssertDumpDiagnostics = {
|
|
20
|
+
rawByteLength: number;
|
|
21
|
+
ansiStrippedLength: number;
|
|
22
|
+
replayedScreenPlaintextHeadPreview: string;
|
|
23
|
+
replayedScreenPlaintextTailPreview: string;
|
|
24
|
+
strippedTranscriptTailPreview: string;
|
|
25
|
+
rawTailSanitizedPreview: string;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type CellExpectation = {
|
|
29
|
+
kind: 'allBold';
|
|
30
|
+
} | {
|
|
31
|
+
kind: 'allBgPalette';
|
|
32
|
+
index: number;
|
|
33
|
+
};
|
|
34
|
+
type CellExpectationBlock = {
|
|
35
|
+
match: 'first' | 'last';
|
|
36
|
+
expectations: CellExpectation[];
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
type TtySearchSurface = 'viewableBuffer' | 'fullBuffer' | 'strippedTranscript';
|
|
40
|
+
type WaitForTextInSurfaceOptions = {
|
|
41
|
+
raw: string | (() => string);
|
|
42
|
+
needle: string | RegExp;
|
|
43
|
+
surface: TtySearchSurface;
|
|
44
|
+
timeoutMs?: number;
|
|
45
|
+
retryMs?: number;
|
|
46
|
+
strict?: boolean;
|
|
47
|
+
cols?: number;
|
|
48
|
+
rows?: number;
|
|
49
|
+
startAfterAnchor?: RegExp[];
|
|
50
|
+
fallbackRowCount?: number;
|
|
51
|
+
messagePrefix?: string;
|
|
52
|
+
cellExpectations?: CellExpectationBlock[];
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
type ManagedTtyAssertOptions = Omit<WaitForTextInSurfaceOptions, 'raw'>;
|
|
56
|
+
type JsonRegexp = {
|
|
57
|
+
source: string;
|
|
58
|
+
flags?: string;
|
|
59
|
+
};
|
|
60
|
+
type ManagedTtyAssertInput = Omit<ManagedTtyAssertOptions, 'needle' | 'startAfterAnchor'> & {
|
|
61
|
+
needle: string | RegExp | JsonRegexp;
|
|
62
|
+
startAfterAnchor?: (RegExp | JsonRegexp)[];
|
|
63
|
+
};
|
|
64
|
+
type ManagedTtySession = {
|
|
65
|
+
readonly session: BufferedPtySession;
|
|
66
|
+
write(data: string): void;
|
|
67
|
+
submit(line: string): void;
|
|
68
|
+
assert(opts: ManagedTtyAssertInput): Promise<void>;
|
|
69
|
+
captureViewportPng(): Promise<Buffer>;
|
|
70
|
+
getViewportAnimationPngs(): Buffer[];
|
|
71
|
+
buildViewportAnimationGif(): Promise<Buffer>;
|
|
72
|
+
dumpDiagnostics(): Promise<TtyAssertDumpDiagnostics>;
|
|
73
|
+
dispose(): void;
|
|
74
|
+
};
|
|
75
|
+
declare function startManagedTtySession(opts: StartBufferedPtySessionOptions, geometry?: {
|
|
76
|
+
cols?: number;
|
|
77
|
+
rows?: number;
|
|
78
|
+
}): Promise<ManagedTtySession>;
|
|
79
|
+
|
|
80
|
+
export { type BufferedPtySession, type ManagedTtyAssertInput, type ManagedTtyAssertOptions, type ManagedTtySession, startManagedTtySession };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,840 @@
|
|
|
1
|
+
import { Terminal } from '@xterm/headless';
|
|
2
|
+
import { loadImage, createCanvas } from 'canvas';
|
|
3
|
+
import { createRequire } from 'module';
|
|
4
|
+
|
|
5
|
+
// src/managed/managedTtySession.ts
|
|
6
|
+
|
|
7
|
+
// src/ansi/stripAnsi.ts
|
|
8
|
+
function stripAnsiCliPty(text) {
|
|
9
|
+
const esc = `${String.fromCharCode(27)}${String.fromCharCode(155)}`;
|
|
10
|
+
const pattern = new RegExp(
|
|
11
|
+
`[${esc}][[\\]()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]`,
|
|
12
|
+
"g"
|
|
13
|
+
);
|
|
14
|
+
return text.replace(pattern, "");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// src/diagnostics/errorSnapshotFormatting.ts
|
|
18
|
+
var TERMINAL_ERROR_PREVIEW_LEN = 500;
|
|
19
|
+
var TERMINAL_ERROR_MAX_VISIBLE_SNAPSHOT_CHARS = 12e3;
|
|
20
|
+
var TERMINAL_ERROR_LOCATOR_SNAPSHOT_MAX_CHARS = 8e3;
|
|
21
|
+
function sanitizeVisibleTextForError(s) {
|
|
22
|
+
let out = s.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
23
|
+
out = out.split(String.fromCharCode(27)).join("<ESC>");
|
|
24
|
+
return [...out].map((ch) => {
|
|
25
|
+
const n = ch.charCodeAt(0);
|
|
26
|
+
if (n === 9 || n === 10) return ch;
|
|
27
|
+
if (n < 32 || n === 127) {
|
|
28
|
+
return `<0x${n.toString(16).padStart(2, "0")}>`;
|
|
29
|
+
}
|
|
30
|
+
return ch;
|
|
31
|
+
}).join("");
|
|
32
|
+
}
|
|
33
|
+
function formatRawTerminalSnapshotForError(raw) {
|
|
34
|
+
const stripped = stripAnsiCliPty(raw);
|
|
35
|
+
const visible = sanitizeVisibleTextForError(stripped);
|
|
36
|
+
const max = TERMINAL_ERROR_MAX_VISIBLE_SNAPSHOT_CHARS;
|
|
37
|
+
const truncated = visible.length > max ? `${visible.slice(0, max)}
|
|
38
|
+
|
|
39
|
+
\u2026 truncated (${visible.length} visible chars, showing first ${max})` : visible;
|
|
40
|
+
return `raw bytes: ${raw.length} | ANSI-stripped: ${stripped.length} chars
|
|
41
|
+
|
|
42
|
+
${truncated}`;
|
|
43
|
+
}
|
|
44
|
+
function headPreview(text) {
|
|
45
|
+
return text.length > TERMINAL_ERROR_PREVIEW_LEN ? `${text.slice(0, TERMINAL_ERROR_PREVIEW_LEN)}...` : text;
|
|
46
|
+
}
|
|
47
|
+
function tailPreview(text) {
|
|
48
|
+
return text.length > TERMINAL_ERROR_PREVIEW_LEN ? text.slice(-TERMINAL_ERROR_PREVIEW_LEN) : text;
|
|
49
|
+
}
|
|
50
|
+
function truncateLocatorFailureSnapshot(snapshot) {
|
|
51
|
+
const max = TERMINAL_ERROR_LOCATOR_SNAPSHOT_MAX_CHARS;
|
|
52
|
+
return snapshot.length > max ? `${snapshot.slice(0, max)}
|
|
53
|
+
\u2026 (truncated)` : snapshot;
|
|
54
|
+
}
|
|
55
|
+
function snapshotWithRowNumbers(snapshot) {
|
|
56
|
+
const lines = snapshot.split("\n");
|
|
57
|
+
const lastLineNo = lines.length;
|
|
58
|
+
const numWidth = Math.max(3, String(lastLineNo).length);
|
|
59
|
+
return lines.map((line, i) => `${String(i + 1).padStart(numWidth)} | ${line}`).join("\n");
|
|
60
|
+
}
|
|
61
|
+
function numberedSnapshotForError(snapshot) {
|
|
62
|
+
return truncateLocatorFailureSnapshot(snapshotWithRowNumbers(snapshot));
|
|
63
|
+
}
|
|
64
|
+
var TERMINAL_ERROR_FINAL_VIEWPORT_HEADING = "--- Final visible screen (viewport) ---";
|
|
65
|
+
function formatFinalViewportPlaintextForError(viewportPlain) {
|
|
66
|
+
const snap = numberedSnapshotForError(viewportPlain);
|
|
67
|
+
return `${TERMINAL_ERROR_FINAL_VIEWPORT_HEADING}
|
|
68
|
+
${snap}
|
|
69
|
+
---`;
|
|
70
|
+
}
|
|
71
|
+
function formatSearchSurfaceFailure(surface, detail, snapshot) {
|
|
72
|
+
const snap = numberedSnapshotForError(snapshot);
|
|
73
|
+
const note = surface === "strippedTranscript" ? "Search uses the ANSI-stripped cumulative transcript (same text as the snapshot below)." : "Snapshot: newline-separated trimmed rows. Matching uses a flat row-major block with no newlines between rows.";
|
|
74
|
+
return `${detail}
|
|
75
|
+
Search surface: "${surface}".
|
|
76
|
+
${note}
|
|
77
|
+
---
|
|
78
|
+
${snap}
|
|
79
|
+
---`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/defaults/geometry.ts
|
|
83
|
+
var CLI_INTERACTIVE_PTY_COLS = 80;
|
|
84
|
+
var CLI_INTERACTIVE_PTY_ROWS = 24;
|
|
85
|
+
|
|
86
|
+
// src/pty/ptySession.ts
|
|
87
|
+
async function startBufferedPtySession(opts) {
|
|
88
|
+
const { spawn } = await import('@lydell/node-pty');
|
|
89
|
+
const p = spawn(opts.command, opts.args, {
|
|
90
|
+
name: "xterm-256color",
|
|
91
|
+
cols: opts.cols ?? CLI_INTERACTIVE_PTY_COLS,
|
|
92
|
+
rows: opts.rows ?? CLI_INTERACTIVE_PTY_ROWS,
|
|
93
|
+
cwd: opts.cwd,
|
|
94
|
+
env: opts.env
|
|
95
|
+
});
|
|
96
|
+
const buf = { text: "" };
|
|
97
|
+
p.onData((data) => {
|
|
98
|
+
buf.text += data;
|
|
99
|
+
opts.onAfterPtyData?.();
|
|
100
|
+
});
|
|
101
|
+
return { pty: p, buf };
|
|
102
|
+
}
|
|
103
|
+
function disposeBufferedPtySession(session) {
|
|
104
|
+
if (!session) return;
|
|
105
|
+
try {
|
|
106
|
+
session.pty.kill();
|
|
107
|
+
} catch {
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// src/surface/cellExpectations.ts
|
|
112
|
+
function validateAndResolveCellExpectations(o) {
|
|
113
|
+
const blocks = o.cellExpectations?.length ? o.cellExpectations : [];
|
|
114
|
+
if (blocks.length === 0) return [];
|
|
115
|
+
if (o.surface === "strippedTranscript") {
|
|
116
|
+
throw new Error(
|
|
117
|
+
"tty-assert: cell expectations are only supported for viewableBuffer and fullBuffer."
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
if (typeof o.needle !== "string") {
|
|
121
|
+
throw new Error("tty-assert: cell expectations require a string needle.");
|
|
122
|
+
}
|
|
123
|
+
return blocks;
|
|
124
|
+
}
|
|
125
|
+
function viewportPlaintextFromHeadlessTerminal(term) {
|
|
126
|
+
const buffer = term.buffer.active;
|
|
127
|
+
const lines = [];
|
|
128
|
+
for (let i = 0; i < term.rows; i++) {
|
|
129
|
+
const line = buffer.getLine(buffer.viewportY + i);
|
|
130
|
+
lines.push(line?.translateToString(true) ?? "");
|
|
131
|
+
}
|
|
132
|
+
return lines.join("\n").replace(/\n+$/, "");
|
|
133
|
+
}
|
|
134
|
+
var require2 = createRequire(import.meta.url);
|
|
135
|
+
var GIFEncoder = require2("gifencoder");
|
|
136
|
+
var DEFAULT_FRAME_DELAY_MS = 250;
|
|
137
|
+
async function viewportPngBuffersToGif(pngBuffers, frameDelayMs = DEFAULT_FRAME_DELAY_MS) {
|
|
138
|
+
if (pngBuffers.length === 0) {
|
|
139
|
+
throw new Error("viewportPngBuffersToGif: no frames");
|
|
140
|
+
}
|
|
141
|
+
const first = await loadImage(pngBuffers[0]);
|
|
142
|
+
const w = first.width;
|
|
143
|
+
const h = first.height;
|
|
144
|
+
const encoder = new GIFEncoder(w, h);
|
|
145
|
+
const out = new Promise((resolve, reject) => {
|
|
146
|
+
const chunks = [];
|
|
147
|
+
encoder.createReadStream().on("data", (c) => {
|
|
148
|
+
chunks.push(c);
|
|
149
|
+
}).on("end", () => {
|
|
150
|
+
resolve(Buffer.concat(chunks));
|
|
151
|
+
}).on("error", reject);
|
|
152
|
+
});
|
|
153
|
+
encoder.start();
|
|
154
|
+
encoder.setRepeat(0);
|
|
155
|
+
encoder.setDelay(frameDelayMs);
|
|
156
|
+
encoder.setQuality(10);
|
|
157
|
+
const canvas = createCanvas(w, h);
|
|
158
|
+
const ctx = canvas.getContext("2d");
|
|
159
|
+
if (!ctx) {
|
|
160
|
+
throw new Error("viewportPngBuffersToGif: canvas 2d context unavailable");
|
|
161
|
+
}
|
|
162
|
+
for (const buf of pngBuffers) {
|
|
163
|
+
const img = await loadImage(buf);
|
|
164
|
+
if (img.width !== w || img.height !== h) {
|
|
165
|
+
throw new Error(
|
|
166
|
+
`viewportPngBuffersToGif: frame size ${img.width}x${img.height} != ${w}x${h}`
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
ctx.clearRect(0, 0, w, h);
|
|
170
|
+
ctx.drawImage(img, 0, 0);
|
|
171
|
+
encoder.addFrame(ctx);
|
|
172
|
+
}
|
|
173
|
+
encoder.finish();
|
|
174
|
+
return out;
|
|
175
|
+
}
|
|
176
|
+
var FONT_SIZE_PX = 14;
|
|
177
|
+
var VIEWPORT_PADDING_PX = 6;
|
|
178
|
+
var DEFAULT_BG = "#1e1e1e";
|
|
179
|
+
var ANSI16 = [
|
|
180
|
+
[0, 0, 0],
|
|
181
|
+
[205, 49, 49],
|
|
182
|
+
[13, 188, 121],
|
|
183
|
+
[229, 229, 16],
|
|
184
|
+
[36, 114, 200],
|
|
185
|
+
[188, 63, 188],
|
|
186
|
+
[17, 168, 205],
|
|
187
|
+
[229, 229, 229],
|
|
188
|
+
[102, 102, 102],
|
|
189
|
+
[241, 76, 76],
|
|
190
|
+
[35, 209, 139],
|
|
191
|
+
[245, 245, 67],
|
|
192
|
+
[59, 142, 234],
|
|
193
|
+
[214, 112, 214],
|
|
194
|
+
[41, 184, 219],
|
|
195
|
+
[255, 255, 255]
|
|
196
|
+
];
|
|
197
|
+
var CUBE_LEVELS = [0, 95, 135, 175, 215, 255];
|
|
198
|
+
function rgbCss(r, g, b) {
|
|
199
|
+
return `rgb(${r},${g},${b})`;
|
|
200
|
+
}
|
|
201
|
+
function ansi256ToRgb(idx) {
|
|
202
|
+
if (idx >= 0 && idx < 16) return ANSI16[idx];
|
|
203
|
+
if (idx >= 232) {
|
|
204
|
+
const v = 8 + (idx - 232) * 10;
|
|
205
|
+
return [v, v, v];
|
|
206
|
+
}
|
|
207
|
+
if (idx >= 16 && idx <= 231) {
|
|
208
|
+
const i = idx - 16;
|
|
209
|
+
const r = CUBE_LEVELS[Math.floor(i / 36)];
|
|
210
|
+
const g = CUBE_LEVELS[Math.floor(i % 36 / 6)];
|
|
211
|
+
const b = CUBE_LEVELS[i % 6];
|
|
212
|
+
return [r, g, b];
|
|
213
|
+
}
|
|
214
|
+
return [204, 204, 204];
|
|
215
|
+
}
|
|
216
|
+
function xtermRgbColor(n) {
|
|
217
|
+
const r = n >>> 16 & 255;
|
|
218
|
+
const g = n >>> 8 & 255;
|
|
219
|
+
const b = n & 255;
|
|
220
|
+
return [r, g, b];
|
|
221
|
+
}
|
|
222
|
+
function resolveSideRgb(cell, fg) {
|
|
223
|
+
if (fg) {
|
|
224
|
+
if (cell.isFgRGB()) return xtermRgbColor(cell.getFgColor());
|
|
225
|
+
if (cell.isFgPalette()) return ansi256ToRgb(cell.getFgColor() & 255);
|
|
226
|
+
if (cell.isFgDefault()) return [204, 204, 204];
|
|
227
|
+
return xtermRgbColor(cell.getFgColor());
|
|
228
|
+
}
|
|
229
|
+
if (cell.isBgRGB()) return xtermRgbColor(cell.getBgColor());
|
|
230
|
+
if (cell.isBgPalette()) return ansi256ToRgb(cell.getBgColor() & 255);
|
|
231
|
+
if (cell.isBgDefault()) return [30, 30, 30];
|
|
232
|
+
return xtermRgbColor(cell.getBgColor());
|
|
233
|
+
}
|
|
234
|
+
function effectiveRgb(cell) {
|
|
235
|
+
if (!cell) {
|
|
236
|
+
return {
|
|
237
|
+
fg: [204, 204, 204],
|
|
238
|
+
bg: [30, 30, 30]
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
let fg = resolveSideRgb(cell, true);
|
|
242
|
+
let bg = resolveSideRgb(cell, false);
|
|
243
|
+
if (cell.isInverse()) {
|
|
244
|
+
[fg, bg] = [bg, fg];
|
|
245
|
+
}
|
|
246
|
+
return { fg, bg };
|
|
247
|
+
}
|
|
248
|
+
function viewportPngFromHeadlessTerminal(term) {
|
|
249
|
+
const cols = term.cols;
|
|
250
|
+
const rows = term.rows;
|
|
251
|
+
const buffer = term.buffer.active;
|
|
252
|
+
const viewportY = buffer.viewportY;
|
|
253
|
+
const probe = createCanvas(16, 16);
|
|
254
|
+
const pctx = probe.getContext("2d");
|
|
255
|
+
pctx.font = `${FONT_SIZE_PX}px monospace`;
|
|
256
|
+
const cellW = Math.max(8, Math.ceil(pctx.measureText("M").width));
|
|
257
|
+
const cellH = Math.ceil(FONT_SIZE_PX * 1.25);
|
|
258
|
+
const w = cols * cellW + VIEWPORT_PADDING_PX * 2;
|
|
259
|
+
const h = rows * cellH + VIEWPORT_PADDING_PX * 2;
|
|
260
|
+
const canvas = createCanvas(w, h);
|
|
261
|
+
const ctx = canvas.getContext("2d");
|
|
262
|
+
ctx.fillStyle = DEFAULT_BG;
|
|
263
|
+
ctx.fillRect(0, 0, w, h);
|
|
264
|
+
ctx.textBaseline = "top";
|
|
265
|
+
for (let row = 0; row < rows; row++) {
|
|
266
|
+
const termLine = buffer.getLine(viewportY + row);
|
|
267
|
+
let prev = termLine?.getCell(0);
|
|
268
|
+
for (let col = 0; col < cols; col++) {
|
|
269
|
+
const cell = termLine?.getCell(col, prev);
|
|
270
|
+
prev = cell;
|
|
271
|
+
if (cell && cell.getWidth() === 0) continue;
|
|
272
|
+
const raw = cell?.getChars() ?? "";
|
|
273
|
+
const text = raw === "" ? " " : raw;
|
|
274
|
+
if (cell?.isInvisible()) {
|
|
275
|
+
const { bg: bg2 } = effectiveRgb(cell);
|
|
276
|
+
ctx.fillStyle = rgbCss(bg2[0], bg2[1], bg2[2]);
|
|
277
|
+
ctx.fillRect(
|
|
278
|
+
VIEWPORT_PADDING_PX + col * cellW,
|
|
279
|
+
VIEWPORT_PADDING_PX + row * cellH,
|
|
280
|
+
cellW,
|
|
281
|
+
cellH
|
|
282
|
+
);
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
const { fg, bg } = effectiveRgb(cell);
|
|
286
|
+
const x = VIEWPORT_PADDING_PX + col * cellW;
|
|
287
|
+
const y = VIEWPORT_PADDING_PX + row * cellH;
|
|
288
|
+
ctx.fillStyle = rgbCss(bg[0], bg[1], bg[2]);
|
|
289
|
+
ctx.fillRect(x, y, cellW, cellH);
|
|
290
|
+
const weight = cell?.isBold() ? "bold" : "normal";
|
|
291
|
+
ctx.font = `${weight} ${FONT_SIZE_PX}px monospace`;
|
|
292
|
+
ctx.fillStyle = rgbCss(fg[0], fg[1], fg[2]);
|
|
293
|
+
ctx.fillText(text, x, y);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return canvas.toBuffer("image/png");
|
|
297
|
+
}
|
|
298
|
+
var BRIGHT_BLACK_PALETTE_INDEX = 8;
|
|
299
|
+
function xtermBufferRows(term, startY, endY) {
|
|
300
|
+
const buffer = term.buffer.active;
|
|
301
|
+
const cols = term.cols;
|
|
302
|
+
const lines = [];
|
|
303
|
+
for (let y = startY; y < endY; y++) {
|
|
304
|
+
const termLine = buffer.getLine(y);
|
|
305
|
+
const line = [];
|
|
306
|
+
let cell = termLine?.getCell(0);
|
|
307
|
+
for (let x = 0; x < cols; x++) {
|
|
308
|
+
cell = termLine?.getCell(x, cell);
|
|
309
|
+
const rawChars = cell?.getChars() ?? "";
|
|
310
|
+
line.push(rawChars === "" ? " " : rawChars);
|
|
311
|
+
}
|
|
312
|
+
lines.push(line);
|
|
313
|
+
}
|
|
314
|
+
return lines;
|
|
315
|
+
}
|
|
316
|
+
function rowMajorSearchBlock(rows) {
|
|
317
|
+
return rows.map((r) => r.join("")).join("");
|
|
318
|
+
}
|
|
319
|
+
function rowMajorSnapshot(rows) {
|
|
320
|
+
return rows.map((r) => r.join("").trimEnd()).join("\n");
|
|
321
|
+
}
|
|
322
|
+
function findAnchorRowIndex(rows, anchors) {
|
|
323
|
+
for (const anchor of anchors) {
|
|
324
|
+
for (let i = rows.length - 1; i >= 0; i--) {
|
|
325
|
+
const row = rows[i];
|
|
326
|
+
if (row && anchor.test(row.join("").trimEnd())) return i;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return -1;
|
|
330
|
+
}
|
|
331
|
+
function sliceByAnchor(rows, anchors, fallbackRowCount) {
|
|
332
|
+
if (!anchors || anchors.length === 0) return { rows, rowOffset: 0 };
|
|
333
|
+
const idx = findAnchorRowIndex(rows, anchors);
|
|
334
|
+
if (idx >= 0) return { rows: rows.slice(idx + 1), rowOffset: idx + 1 };
|
|
335
|
+
if (fallbackRowCount != null) {
|
|
336
|
+
const start = Math.max(0, rows.length - fallbackRowCount);
|
|
337
|
+
return { rows: rows.slice(start), rowOffset: start };
|
|
338
|
+
}
|
|
339
|
+
return { rows, rowOffset: 0 };
|
|
340
|
+
}
|
|
341
|
+
function xtermSearchContext(term, surface, startAfterAnchor, fallbackRowCount) {
|
|
342
|
+
const buffer = term.buffer.active;
|
|
343
|
+
const startY = surface === "fullBuffer" ? 0 : buffer.baseY;
|
|
344
|
+
const endY = buffer.length;
|
|
345
|
+
const allRows = xtermBufferRows(term, startY, endY);
|
|
346
|
+
const { rows: searchRows, rowOffset } = sliceByAnchor(
|
|
347
|
+
allRows,
|
|
348
|
+
startAfterAnchor,
|
|
349
|
+
fallbackRowCount
|
|
350
|
+
);
|
|
351
|
+
return {
|
|
352
|
+
haystack: rowMajorSearchBlock(searchRows),
|
|
353
|
+
snapshot: rowMajorSnapshot(searchRows),
|
|
354
|
+
startY,
|
|
355
|
+
rowOffset
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
function buildHaystackAndSnapshotFromTerminal(term, surface, startAfterAnchor, fallbackRowCount) {
|
|
359
|
+
const ctx = xtermSearchContext(
|
|
360
|
+
term,
|
|
361
|
+
surface,
|
|
362
|
+
startAfterAnchor,
|
|
363
|
+
fallbackRowCount
|
|
364
|
+
);
|
|
365
|
+
return { haystack: ctx.haystack, snapshot: ctx.snapshot };
|
|
366
|
+
}
|
|
367
|
+
function countNonOverlapping(haystack, needle) {
|
|
368
|
+
let count = 0;
|
|
369
|
+
let from = 0;
|
|
370
|
+
for (; ; ) {
|
|
371
|
+
const index = haystack.indexOf(needle, from);
|
|
372
|
+
if (index < 0) break;
|
|
373
|
+
count++;
|
|
374
|
+
from = index + needle.length;
|
|
375
|
+
}
|
|
376
|
+
return count;
|
|
377
|
+
}
|
|
378
|
+
function regexForMatchAll(needle) {
|
|
379
|
+
if (needle.global) return needle;
|
|
380
|
+
const flags = needle.flags.includes("g") ? needle.flags : `${needle.flags}g`;
|
|
381
|
+
return new RegExp(needle.source, flags);
|
|
382
|
+
}
|
|
383
|
+
function matchCount(haystack, needle) {
|
|
384
|
+
if (typeof needle === "string") {
|
|
385
|
+
return countNonOverlapping(haystack, needle);
|
|
386
|
+
}
|
|
387
|
+
return Array.from(haystack.matchAll(regexForMatchAll(needle))).length;
|
|
388
|
+
}
|
|
389
|
+
function cellHasPaletteBackground(cell, index) {
|
|
390
|
+
return cell.isBgPalette() && cell.getBgColor() === index;
|
|
391
|
+
}
|
|
392
|
+
function blockNotFoundLabel(block) {
|
|
393
|
+
const kinds = new Set(block.expectations.map((e) => e.kind));
|
|
394
|
+
if (kinds.has("allBold") && kinds.size === 1) return "bold check";
|
|
395
|
+
if (kinds.has("allBgPalette")) {
|
|
396
|
+
return "palette background check";
|
|
397
|
+
}
|
|
398
|
+
return "cell expectation check";
|
|
399
|
+
}
|
|
400
|
+
function runCellExpectationBlocks(term, opts, blocks) {
|
|
401
|
+
const buffer = term.buffer.active;
|
|
402
|
+
const { haystack, snapshot, startY, rowOffset } = xtermSearchContext(
|
|
403
|
+
term,
|
|
404
|
+
opts.surface,
|
|
405
|
+
opts.startAfterAnchor,
|
|
406
|
+
opts.fallbackRowCount
|
|
407
|
+
);
|
|
408
|
+
const rowWidth = opts.cols;
|
|
409
|
+
for (const block of blocks) {
|
|
410
|
+
const index = block.match === "first" ? haystack.indexOf(opts.needle) : haystack.lastIndexOf(opts.needle);
|
|
411
|
+
if (index < 0) {
|
|
412
|
+
const label = blockNotFoundLabel(block);
|
|
413
|
+
return {
|
|
414
|
+
ok: false,
|
|
415
|
+
snapshot,
|
|
416
|
+
detail: `Substring ${JSON.stringify(opts.needle)} not found in surface "${opts.surface}" (${label}).`
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
for (const exp of block.expectations) {
|
|
420
|
+
if (exp.kind === "allBold") {
|
|
421
|
+
const which = block.match === "first" ? "first occurrence" : "last occurrence";
|
|
422
|
+
for (let i = 0; i < opts.needle.length; i++) {
|
|
423
|
+
const pos = index + i;
|
|
424
|
+
const localY = Math.floor(pos / rowWidth);
|
|
425
|
+
const x = pos % rowWidth;
|
|
426
|
+
const termLine = buffer.getLine(startY + rowOffset + localY);
|
|
427
|
+
const cell = termLine?.getCell(x);
|
|
428
|
+
if ((cell?.isBold() ?? 0) === 0) {
|
|
429
|
+
return {
|
|
430
|
+
ok: false,
|
|
431
|
+
snapshot,
|
|
432
|
+
detail: `Matched text ${JSON.stringify(opts.needle)} is present but not all cells at the ${which} are bold.`
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
if (exp.kind === "allBgPalette") {
|
|
439
|
+
const isBrightBlackBg = exp.index === BRIGHT_BLACK_PALETTE_INDEX;
|
|
440
|
+
for (let i = 0; i < opts.needle.length; i++) {
|
|
441
|
+
const pos = index + i;
|
|
442
|
+
const localY = Math.floor(pos / rowWidth);
|
|
443
|
+
const x = pos % rowWidth;
|
|
444
|
+
const termLine = buffer.getLine(startY + rowOffset + localY);
|
|
445
|
+
const cell = termLine?.getCell(x);
|
|
446
|
+
if (cell == null) {
|
|
447
|
+
return {
|
|
448
|
+
ok: false,
|
|
449
|
+
snapshot,
|
|
450
|
+
detail: `Palette background check: no cell at offset ${i} for last match of ${JSON.stringify(opts.needle)}.`
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
if (!cellHasPaletteBackground(cell, exp.index)) {
|
|
454
|
+
if (isBrightBlackBg) {
|
|
455
|
+
return {
|
|
456
|
+
ok: false,
|
|
457
|
+
snapshot,
|
|
458
|
+
detail: `Matched text ${JSON.stringify(opts.needle)} at the last occurrence: expected background palette index 8 (e.g. \\x1b[100m) on every cell; at least one cell does not have that background.`
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
return {
|
|
462
|
+
ok: false,
|
|
463
|
+
snapshot,
|
|
464
|
+
detail: `Matched text ${JSON.stringify(opts.needle)}: expected background palette index ${exp.index} on every cell in the span.`
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
return { ok: true };
|
|
472
|
+
}
|
|
473
|
+
function attemptOnceOnLiveTerminal(term, opts) {
|
|
474
|
+
if (typeof opts.needle === "string" && opts.needle.length === 0) {
|
|
475
|
+
return {
|
|
476
|
+
ok: "strict",
|
|
477
|
+
snapshot: "",
|
|
478
|
+
message: "tty-assert: empty string needle is not supported (ambiguous matches)."
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
const { haystack, snapshot } = buildHaystackAndSnapshotFromTerminal(
|
|
482
|
+
term,
|
|
483
|
+
opts.surface,
|
|
484
|
+
opts.startAfterAnchor,
|
|
485
|
+
opts.fallbackRowCount
|
|
486
|
+
);
|
|
487
|
+
const count = matchCount(haystack, opts.needle);
|
|
488
|
+
if (count > 1 && opts.strict) {
|
|
489
|
+
const label = typeof opts.needle === "string" ? JSON.stringify(opts.needle) : `${opts.needle}`;
|
|
490
|
+
return {
|
|
491
|
+
ok: "strict",
|
|
492
|
+
snapshot,
|
|
493
|
+
message: `Strict mode violation: ${label} matched ${count} non-overlapping occurrences in surface "${opts.surface}".`
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
if (count === 0) {
|
|
497
|
+
const label = typeof opts.needle === "string" ? JSON.stringify(opts.needle) : `${opts.needle}`;
|
|
498
|
+
return {
|
|
499
|
+
ok: false,
|
|
500
|
+
snapshot,
|
|
501
|
+
detail: `Substring/pattern ${label} not found in surface "${opts.surface}".`
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
const blocks = opts.cellExpectations ?? [];
|
|
505
|
+
if (blocks.length > 0) {
|
|
506
|
+
if (typeof opts.needle !== "string") {
|
|
507
|
+
throw new Error(
|
|
508
|
+
"tty-assert: cell expectations require a string needle (caller must validate)."
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
const cellResult = runCellExpectationBlocks(
|
|
512
|
+
term,
|
|
513
|
+
{
|
|
514
|
+
surface: opts.surface,
|
|
515
|
+
needle: opts.needle,
|
|
516
|
+
cols: opts.cols,
|
|
517
|
+
startAfterAnchor: opts.startAfterAnchor,
|
|
518
|
+
fallbackRowCount: opts.fallbackRowCount
|
|
519
|
+
},
|
|
520
|
+
blocks
|
|
521
|
+
);
|
|
522
|
+
if (!cellResult.ok) {
|
|
523
|
+
return {
|
|
524
|
+
ok: false,
|
|
525
|
+
snapshot: cellResult.snapshot,
|
|
526
|
+
detail: cellResult.detail
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
return { ok: true };
|
|
531
|
+
}
|
|
532
|
+
function attemptOnceStrippedTranscript(opts) {
|
|
533
|
+
if (typeof opts.needle === "string" && opts.needle.length === 0) {
|
|
534
|
+
return {
|
|
535
|
+
ok: "strict",
|
|
536
|
+
snapshot: "",
|
|
537
|
+
message: "tty-assert: empty string needle is not supported (ambiguous matches)."
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
const haystack = stripAnsiCliPty(opts.raw);
|
|
541
|
+
const snapshot = haystack;
|
|
542
|
+
const count = matchCount(haystack, opts.needle);
|
|
543
|
+
if (count > 1 && opts.strict) {
|
|
544
|
+
const label = typeof opts.needle === "string" ? JSON.stringify(opts.needle) : `${opts.needle}`;
|
|
545
|
+
return {
|
|
546
|
+
ok: "strict",
|
|
547
|
+
snapshot,
|
|
548
|
+
message: `Strict mode violation: ${label} matched ${count} non-overlapping occurrences in surface "${opts.surface}".`
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
if (count === 0) {
|
|
552
|
+
const label = typeof opts.needle === "string" ? JSON.stringify(opts.needle) : `${opts.needle}`;
|
|
553
|
+
return {
|
|
554
|
+
ok: false,
|
|
555
|
+
snapshot,
|
|
556
|
+
detail: `Substring/pattern ${label} not found in surface "${opts.surface}".`
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
return { ok: true };
|
|
560
|
+
}
|
|
561
|
+
function writeTranscriptToTerminal(term, data) {
|
|
562
|
+
return new Promise((resolve, reject) => {
|
|
563
|
+
try {
|
|
564
|
+
term.write(data, () => resolve());
|
|
565
|
+
} catch (e) {
|
|
566
|
+
reject(e);
|
|
567
|
+
}
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// src/surface/locatorRetryMs.ts
|
|
572
|
+
var TTY_ASSERT_LOCATOR_DEFAULT_RETRY_MS = 50;
|
|
573
|
+
|
|
574
|
+
// src/diagnostics/ttyAssertDumpDiagnostics.ts
|
|
575
|
+
function buildTtyAssertDumpDiagnostics(input) {
|
|
576
|
+
const stripped = stripAnsiCliPty(input.raw);
|
|
577
|
+
return {
|
|
578
|
+
rawByteLength: input.raw.length,
|
|
579
|
+
ansiStrippedLength: stripped.length,
|
|
580
|
+
replayedScreenPlaintextHeadPreview: headPreview(input.replayedPlain),
|
|
581
|
+
replayedScreenPlaintextTailPreview: tailPreview(input.replayedPlain),
|
|
582
|
+
strippedTranscriptTailPreview: tailPreview(stripped),
|
|
583
|
+
rawTailSanitizedPreview: sanitizeVisibleTextForError(
|
|
584
|
+
tailPreview(input.raw.slice(-800))
|
|
585
|
+
)
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// src/surface/ttyAssertStrictModeError.ts
|
|
590
|
+
var TtyAssertStrictModeViolationError = class extends Error {
|
|
591
|
+
name = "TtyAssertStrictModeViolationError";
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
// src/surface/pollSurfaceAssertLoop.ts
|
|
595
|
+
function sleep(ms) {
|
|
596
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
597
|
+
}
|
|
598
|
+
function withOptionalMessagePrefix(prefix, body) {
|
|
599
|
+
if (prefix == null || prefix === "") return body;
|
|
600
|
+
const p = prefix.replace(/\n+$/, "");
|
|
601
|
+
return `${p}
|
|
602
|
+
${body}`;
|
|
603
|
+
}
|
|
604
|
+
async function pollSurfaceAssertLoop(opts) {
|
|
605
|
+
const {
|
|
606
|
+
surface,
|
|
607
|
+
timeoutMs,
|
|
608
|
+
retryMs,
|
|
609
|
+
messagePrefix,
|
|
610
|
+
runAttempt,
|
|
611
|
+
appendFailure
|
|
612
|
+
} = opts;
|
|
613
|
+
const started = Date.now();
|
|
614
|
+
let lastFail;
|
|
615
|
+
for (; ; ) {
|
|
616
|
+
const { raw, result } = await runAttempt();
|
|
617
|
+
if (result.ok === true) return;
|
|
618
|
+
if (result.ok === "strict") {
|
|
619
|
+
const body = withOptionalMessagePrefix(
|
|
620
|
+
messagePrefix,
|
|
621
|
+
formatSearchSurfaceFailure(surface, result.message, result.snapshot)
|
|
622
|
+
);
|
|
623
|
+
throw new TtyAssertStrictModeViolationError(appendFailure(body, raw));
|
|
624
|
+
}
|
|
625
|
+
lastFail = { snapshot: result.snapshot, detail: result.detail };
|
|
626
|
+
if (Date.now() - started >= timeoutMs) {
|
|
627
|
+
const detail = timeoutMs === 0 ? lastFail.detail : `Timeout after ${timeoutMs}ms. ${lastFail.detail}`;
|
|
628
|
+
const body = withOptionalMessagePrefix(
|
|
629
|
+
messagePrefix,
|
|
630
|
+
formatSearchSurfaceFailure(surface, detail, lastFail.snapshot)
|
|
631
|
+
);
|
|
632
|
+
throw new Error(appendFailure(body, raw));
|
|
633
|
+
}
|
|
634
|
+
await sleep(retryMs);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// src/managed/managedTtySession.ts
|
|
639
|
+
function regExpFromJsonRegexp(r) {
|
|
640
|
+
return new RegExp(r.source, r.flags ?? "");
|
|
641
|
+
}
|
|
642
|
+
function normalizeManagedTtyAssertInput(input) {
|
|
643
|
+
const needle = typeof input.needle === "string" || input.needle instanceof RegExp ? input.needle : regExpFromJsonRegexp(input.needle);
|
|
644
|
+
const startAfterAnchor = input.startAfterAnchor?.map(
|
|
645
|
+
(a) => a instanceof RegExp ? a : regExpFromJsonRegexp(a)
|
|
646
|
+
);
|
|
647
|
+
return { ...input, needle, startAfterAnchor };
|
|
648
|
+
}
|
|
649
|
+
var VIEWPORT_ANIM_DEBOUNCE_MS = 40;
|
|
650
|
+
var VIEWPORT_ANIM_MAX_FRAMES = 56;
|
|
651
|
+
var CLI_TERMINAL_RAW_SNAPSHOT_HEADING = "--- CLI terminal snapshot (ANSI-stripped, safe text) ---";
|
|
652
|
+
function appendRawTerminalSnapshotForErrorMessage(body, raw) {
|
|
653
|
+
return `${body}
|
|
654
|
+
|
|
655
|
+
${CLI_TERMINAL_RAW_SNAPSHOT_HEADING}
|
|
656
|
+
${formatRawTerminalSnapshotForError(raw)}`;
|
|
657
|
+
}
|
|
658
|
+
function appendManagedAssertFailureDiagnostics(body, raw, term) {
|
|
659
|
+
const viewportPlain = viewportPlaintextFromHeadlessTerminal(term);
|
|
660
|
+
const withViewport = `${body}
|
|
661
|
+
|
|
662
|
+
${formatFinalViewportPlaintextForError(viewportPlain)}`;
|
|
663
|
+
return appendRawTerminalSnapshotForErrorMessage(withViewport, raw);
|
|
664
|
+
}
|
|
665
|
+
function attachManagedTtySession(session, geometry, ptyDataBridge) {
|
|
666
|
+
const cols = geometry?.cols ?? CLI_INTERACTIVE_PTY_COLS;
|
|
667
|
+
const rows = geometry?.rows ?? CLI_INTERACTIVE_PTY_ROWS;
|
|
668
|
+
const term = new Terminal({
|
|
669
|
+
cols,
|
|
670
|
+
rows,
|
|
671
|
+
allowProposedApi: true
|
|
672
|
+
});
|
|
673
|
+
let replayedByteCount = 0;
|
|
674
|
+
let disposed = false;
|
|
675
|
+
const animFrames = [];
|
|
676
|
+
let lastAnimViewportPlain;
|
|
677
|
+
let animDebounceTimer;
|
|
678
|
+
async function syncReplay() {
|
|
679
|
+
if (disposed) return;
|
|
680
|
+
const full = session.buf.text;
|
|
681
|
+
if (full.length < replayedByteCount) {
|
|
682
|
+
term.reset();
|
|
683
|
+
await writeTranscriptToTerminal(term, full);
|
|
684
|
+
replayedByteCount = full.length;
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
const delta = full.slice(replayedByteCount);
|
|
688
|
+
if (delta.length === 0) return;
|
|
689
|
+
const targetEnd = full.length;
|
|
690
|
+
await writeTranscriptToTerminal(term, delta);
|
|
691
|
+
replayedByteCount = targetEnd;
|
|
692
|
+
}
|
|
693
|
+
async function flushAnimationFrame() {
|
|
694
|
+
if (disposed || !ptyDataBridge) return;
|
|
695
|
+
await syncReplay();
|
|
696
|
+
const plain = viewportPlaintextFromHeadlessTerminal(term);
|
|
697
|
+
if (plain === lastAnimViewportPlain) return;
|
|
698
|
+
lastAnimViewportPlain = plain;
|
|
699
|
+
animFrames.push(viewportPngFromHeadlessTerminal(term));
|
|
700
|
+
while (animFrames.length > VIEWPORT_ANIM_MAX_FRAMES) {
|
|
701
|
+
animFrames.shift();
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
function scheduleAnimationSample() {
|
|
705
|
+
if (disposed || !ptyDataBridge) return;
|
|
706
|
+
clearTimeout(animDebounceTimer);
|
|
707
|
+
animDebounceTimer = setTimeout(() => {
|
|
708
|
+
flushAnimationFrame().catch(() => {
|
|
709
|
+
});
|
|
710
|
+
}, VIEWPORT_ANIM_DEBOUNCE_MS);
|
|
711
|
+
}
|
|
712
|
+
if (ptyDataBridge) {
|
|
713
|
+
ptyDataBridge.onPtyData = scheduleAnimationSample;
|
|
714
|
+
}
|
|
715
|
+
async function flushPendingAnimationSample() {
|
|
716
|
+
if (!ptyDataBridge) return;
|
|
717
|
+
clearTimeout(animDebounceTimer);
|
|
718
|
+
animDebounceTimer = void 0;
|
|
719
|
+
await flushAnimationFrame();
|
|
720
|
+
}
|
|
721
|
+
return {
|
|
722
|
+
session,
|
|
723
|
+
write(data) {
|
|
724
|
+
session.pty.write(data);
|
|
725
|
+
},
|
|
726
|
+
submit(line) {
|
|
727
|
+
session.pty.write(`${line}\r`);
|
|
728
|
+
},
|
|
729
|
+
async assert(opts) {
|
|
730
|
+
if (disposed) {
|
|
731
|
+
throw new Error("ManagedTtySession.assert after dispose");
|
|
732
|
+
}
|
|
733
|
+
const normalized = normalizeManagedTtyAssertInput(opts);
|
|
734
|
+
const cellExpectations = validateAndResolveCellExpectations({
|
|
735
|
+
surface: normalized.surface,
|
|
736
|
+
needle: normalized.needle,
|
|
737
|
+
cellExpectations: normalized.cellExpectations
|
|
738
|
+
});
|
|
739
|
+
const timeoutMs = normalized.timeoutMs ?? 3e3;
|
|
740
|
+
const retryMs = normalized.retryMs ?? TTY_ASSERT_LOCATOR_DEFAULT_RETRY_MS;
|
|
741
|
+
const strict = normalized.strict ?? true;
|
|
742
|
+
const messagePrefix = normalized.messagePrefix;
|
|
743
|
+
await pollSurfaceAssertLoop({
|
|
744
|
+
surface: normalized.surface,
|
|
745
|
+
timeoutMs,
|
|
746
|
+
retryMs,
|
|
747
|
+
messagePrefix,
|
|
748
|
+
runAttempt: async () => {
|
|
749
|
+
if (disposed) {
|
|
750
|
+
throw new Error("ManagedTtySession.assert after dispose");
|
|
751
|
+
}
|
|
752
|
+
await syncReplay();
|
|
753
|
+
const raw = session.buf.text;
|
|
754
|
+
const result = normalized.surface === "strippedTranscript" ? attemptOnceStrippedTranscript({
|
|
755
|
+
needle: normalized.needle,
|
|
756
|
+
surface: "strippedTranscript",
|
|
757
|
+
raw,
|
|
758
|
+
strict}) : attemptOnceOnLiveTerminal(term, {
|
|
759
|
+
needle: normalized.needle,
|
|
760
|
+
surface: normalized.surface,
|
|
761
|
+
strict,
|
|
762
|
+
cols,
|
|
763
|
+
startAfterAnchor: normalized.startAfterAnchor,
|
|
764
|
+
fallbackRowCount: normalized.fallbackRowCount,
|
|
765
|
+
cellExpectations
|
|
766
|
+
});
|
|
767
|
+
return { raw, result };
|
|
768
|
+
},
|
|
769
|
+
appendFailure: (body, raw) => appendManagedAssertFailureDiagnostics(body, raw, term)
|
|
770
|
+
});
|
|
771
|
+
},
|
|
772
|
+
async captureViewportPng() {
|
|
773
|
+
if (disposed) {
|
|
774
|
+
throw new Error("ManagedTtySession.captureViewportPng after dispose");
|
|
775
|
+
}
|
|
776
|
+
await syncReplay();
|
|
777
|
+
return viewportPngFromHeadlessTerminal(term);
|
|
778
|
+
},
|
|
779
|
+
getViewportAnimationPngs() {
|
|
780
|
+
return [...animFrames];
|
|
781
|
+
},
|
|
782
|
+
async buildViewportAnimationGif() {
|
|
783
|
+
if (!ptyDataBridge) {
|
|
784
|
+
throw new Error(
|
|
785
|
+
"ManagedTtySession.buildViewportAnimationGif: session was started without PTY animation bridge"
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
if (disposed) {
|
|
789
|
+
throw new Error(
|
|
790
|
+
"ManagedTtySession.buildViewportAnimationGif after dispose"
|
|
791
|
+
);
|
|
792
|
+
}
|
|
793
|
+
await flushPendingAnimationSample();
|
|
794
|
+
if (animFrames.length < 2) {
|
|
795
|
+
throw new Error(
|
|
796
|
+
`ManagedTtySession.buildViewportAnimationGif: need at least 2 distinct viewport frames, got ${animFrames.length}`
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
return viewportPngBuffersToGif([...animFrames]);
|
|
800
|
+
},
|
|
801
|
+
async dumpDiagnostics() {
|
|
802
|
+
if (disposed) {
|
|
803
|
+
throw new Error("ManagedTtySession.dumpDiagnostics after dispose");
|
|
804
|
+
}
|
|
805
|
+
await syncReplay();
|
|
806
|
+
const raw = session.buf.text;
|
|
807
|
+
const replayed = viewportPlaintextFromHeadlessTerminal(term);
|
|
808
|
+
return buildTtyAssertDumpDiagnostics({ raw, replayedPlain: replayed });
|
|
809
|
+
},
|
|
810
|
+
dispose() {
|
|
811
|
+
if (disposed) return;
|
|
812
|
+
disposed = true;
|
|
813
|
+
clearTimeout(animDebounceTimer);
|
|
814
|
+
animDebounceTimer = void 0;
|
|
815
|
+
try {
|
|
816
|
+
term.dispose();
|
|
817
|
+
} catch {
|
|
818
|
+
}
|
|
819
|
+
disposeBufferedPtySession(session);
|
|
820
|
+
}
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
async function startManagedTtySession(opts, geometry) {
|
|
824
|
+
const cols = opts.cols ?? geometry?.cols ?? CLI_INTERACTIVE_PTY_COLS;
|
|
825
|
+
const rows = opts.rows ?? geometry?.rows ?? CLI_INTERACTIVE_PTY_ROWS;
|
|
826
|
+
const bridge = {
|
|
827
|
+
onPtyData: () => void 0
|
|
828
|
+
};
|
|
829
|
+
const session = await startBufferedPtySession({
|
|
830
|
+
...opts,
|
|
831
|
+
cols,
|
|
832
|
+
rows,
|
|
833
|
+
onAfterPtyData: () => bridge.onPtyData()
|
|
834
|
+
});
|
|
835
|
+
return attachManagedTtySession(session, { cols, rows }, bridge);
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
export { startManagedTtySession };
|
|
839
|
+
//# sourceMappingURL=index.js.map
|
|
840
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ansi/stripAnsi.ts","../src/diagnostics/errorSnapshotFormatting.ts","../src/defaults/geometry.ts","../src/pty/ptySession.ts","../src/surface/cellExpectations.ts","../src/xterm/ptyTranscriptToVisiblePlaintextViaXterm.ts","../src/xterm/viewportPngSequenceToGif.ts","../src/xterm/viewportPngFromHeadlessTerminal.ts","../src/xterm/surfaceAttemptOnTerminal.ts","../src/surface/locatorRetryMs.ts","../src/diagnostics/ttyAssertDumpDiagnostics.ts","../src/surface/ttyAssertStrictModeError.ts","../src/surface/pollSurfaceAssertLoop.ts","../src/managed/managedTtySession.ts"],"names":["require","createCanvas","bg","Terminal"],"mappings":"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","file":"index.js","sourcesContent":["/** Shared ANSI strip for PTY transcripts (Node tasks + Cypress page objects). */\nexport function stripAnsiCliPty(text: string): string {\n const esc = `${String.fromCharCode(0x1b)}${String.fromCharCode(0x9b)}`\n const pattern = new RegExp(\n `[${esc}][[\\\\]()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]`,\n 'g'\n )\n return text.replace(pattern, '')\n}\n","/** Diagnostic-only: safe visible text and truncation for PTY assertion failures (internal to tty-assert). */\nimport { stripAnsiCliPty } from '../ansi/stripAnsi'\n\nconst TERMINAL_ERROR_PREVIEW_LEN = 500\nconst TERMINAL_ERROR_MAX_VISIBLE_SNAPSHOT_CHARS = 12_000\nconst TERMINAL_ERROR_LOCATOR_SNAPSHOT_MAX_CHARS = 8000\n\n/** Raw PTY tail length before `tailPreview` / sanitize in `dumpDiagnostics` output. */\nexport const TERMINAL_ERROR_RAW_TAIL_BYTES = 800\n\nexport function sanitizeVisibleTextForError(s: string): string {\n let out = s.replace(/\\r\\n/g, '\\n').replace(/\\r/g, '\\n')\n out = out.split(String.fromCharCode(0x1b)).join('<ESC>')\n return [...out]\n .map((ch) => {\n const n = ch.charCodeAt(0)\n if (n === 0x09 || n === 0x0a) return ch\n if (n < 0x20 || n === 0x7f) {\n return `<0x${n.toString(16).padStart(2, '0')}>`\n }\n return ch\n })\n .join('')\n}\n\nexport function formatRawTerminalSnapshotForError(raw: string): string {\n const stripped = stripAnsiCliPty(raw)\n const visible = sanitizeVisibleTextForError(stripped)\n const max = TERMINAL_ERROR_MAX_VISIBLE_SNAPSHOT_CHARS\n const truncated =\n visible.length > max\n ? `${visible.slice(0, max)}\\n\\n… truncated (${visible.length} visible chars, showing first ${max})`\n : visible\n return `raw bytes: ${raw.length} | ANSI-stripped: ${stripped.length} chars\\n\\n${truncated}`\n}\n\nexport function headPreview(text: string): string {\n return text.length > TERMINAL_ERROR_PREVIEW_LEN\n ? `${text.slice(0, TERMINAL_ERROR_PREVIEW_LEN)}...`\n : text\n}\n\nexport function tailPreview(text: string): string {\n return text.length > TERMINAL_ERROR_PREVIEW_LEN\n ? text.slice(-TERMINAL_ERROR_PREVIEW_LEN)\n : text\n}\n\nfunction truncateLocatorFailureSnapshot(snapshot: string): string {\n const max = TERMINAL_ERROR_LOCATOR_SNAPSHOT_MAX_CHARS\n return snapshot.length > max\n ? `${snapshot.slice(0, max)}\\n… (truncated)`\n : snapshot\n}\n\nfunction snapshotWithRowNumbers(snapshot: string): string {\n const lines = snapshot.split('\\n')\n const lastLineNo = lines.length\n const numWidth = Math.max(3, String(lastLineNo).length)\n return lines\n .map((line, i) => `${String(i + 1).padStart(numWidth)} | ${line}`)\n .join('\\n')\n}\n\nfunction numberedSnapshotForError(snapshot: string): string {\n return truncateLocatorFailureSnapshot(snapshotWithRowNumbers(snapshot))\n}\n\n/** Managed session assertion failures: xterm viewport as plain rows (before raw transcript appendix). */\nexport const TERMINAL_ERROR_FINAL_VIEWPORT_HEADING =\n '--- Final visible screen (viewport) ---'\n\nexport function formatFinalViewportPlaintextForError(\n viewportPlain: string\n): string {\n const snap = numberedSnapshotForError(viewportPlain)\n return `${TERMINAL_ERROR_FINAL_VIEWPORT_HEADING}\\n${snap}\\n---`\n}\n\n/**\n * Body for `waitForTextInSurface` / managed session assertion failures (before raw appendix).\n */\nexport function formatSearchSurfaceFailure(\n surface: 'viewableBuffer' | 'fullBuffer' | 'strippedTranscript',\n detail: string,\n snapshot: string\n): string {\n const snap = numberedSnapshotForError(snapshot)\n const note =\n surface === 'strippedTranscript'\n ? 'Search uses the ANSI-stripped cumulative transcript (same text as the snapshot below).'\n : 'Snapshot: newline-separated trimmed rows. Matching uses a flat row-major block with no newlines between rows.'\n return `${detail}\\nSearch surface: \"${surface}\".\\n${note}\\n---\\n${snap}\\n---`\n}\n","/** Default PTY size; node-pty spawn cols/rows must match for replay. */\nexport const CLI_INTERACTIVE_PTY_COLS = 80\nexport const CLI_INTERACTIVE_PTY_ROWS = 24\n","import type { IPty } from '@lydell/node-pty'\nimport { headPreview } from '../diagnostics/errorSnapshotFormatting'\nimport {\n CLI_INTERACTIVE_PTY_COLS,\n CLI_INTERACTIVE_PTY_ROWS,\n} from '../defaults/geometry'\nimport { stripAnsiCliPty } from '../ansi/stripAnsi'\n\nexport type BufferedPtySession = {\n pty: IPty\n buf: { text: string }\n}\n\nexport type StartBufferedPtySessionOptions = {\n command: string\n args: string[]\n cwd: string\n env: NodeJS.ProcessEnv\n cols?: number\n rows?: number\n /** Called after each PTY `onData` append to `buf.text` (managed session uses this for viewport animation samples). */\n onAfterPtyData?: () => void\n}\n\nexport async function startBufferedPtySession(\n opts: StartBufferedPtySessionOptions\n): Promise<BufferedPtySession> {\n const { spawn } = await import('@lydell/node-pty')\n const p = spawn(opts.command, opts.args, {\n name: 'xterm-256color',\n cols: opts.cols ?? CLI_INTERACTIVE_PTY_COLS,\n rows: opts.rows ?? CLI_INTERACTIVE_PTY_ROWS,\n cwd: opts.cwd,\n env: opts.env,\n })\n const buf = { text: '' }\n p.onData((data: string) => {\n buf.text += data\n opts.onAfterPtyData?.()\n })\n return { pty: p, buf }\n}\n\nexport function disposeBufferedPtySession(\n session: BufferedPtySession | null | undefined\n): void {\n if (!session) return\n try {\n session.pty.kill()\n } catch {\n /* already exited */\n }\n}\n\nexport function waitForVisiblePlaintextSubstring(\n getRawOutput: () => string,\n needle: string,\n timeoutMs: number,\n retryMs = 50\n): Promise<void> {\n const started = Date.now()\n return new Promise((resolve, reject) => {\n const tick = () => {\n const stripped = stripAnsiCliPty(getRawOutput())\n if (stripped.includes(needle)) {\n resolve()\n return\n }\n if (Date.now() - started >= timeoutMs) {\n const preview = headPreview(stripped)\n reject(\n new Error(\n `Timeout after ${timeoutMs}ms waiting for substring ${JSON.stringify(needle)} in interactive CLI PTY output. Preview (ANSI-stripped):\\n${preview}`\n )\n )\n return\n }\n setTimeout(tick, retryMs)\n }\n tick()\n })\n}\n","/** Serializable cell-span checks after a string needle matches (Cypress-task–friendly). */\n\nexport type CellExpectation =\n | { kind: 'allBold' }\n | { kind: 'allBgPalette'; index: number }\n\nexport type CellExpectationBlock = {\n match: 'first' | 'last'\n expectations: CellExpectation[]\n}\n\nexport type CellExpectationsValidationInput = {\n surface: 'viewableBuffer' | 'fullBuffer' | 'strippedTranscript'\n needle: string | RegExp\n cellExpectations?: CellExpectationBlock[]\n}\n\n/**\n * Validates style-related options and returns blocks to run on xterm (may be empty).\n * @throws On strippedTranscript / RegExp needle with cell checks.\n */\nexport function validateAndResolveCellExpectations(\n o: CellExpectationsValidationInput\n): CellExpectationBlock[] {\n const blocks = o.cellExpectations?.length ? o.cellExpectations : []\n if (blocks.length === 0) return []\n\n if (o.surface === 'strippedTranscript') {\n throw new Error(\n 'tty-assert: cell expectations are only supported for viewableBuffer and fullBuffer.'\n )\n }\n if (typeof o.needle !== 'string') {\n throw new Error('tty-assert: cell expectations require a string needle.')\n }\n\n return blocks\n}\n","import { Terminal } from '@xterm/headless'\nimport {\n CLI_INTERACTIVE_PTY_COLS,\n CLI_INTERACTIVE_PTY_ROWS,\n} from '../defaults/geometry'\n\n/** Viewport plaintext from a headless xterm after replay (not re-exported from the package root). */\n\nexport function viewportPlaintextFromHeadlessTerminal(term: Terminal): string {\n const buffer = term.buffer.active\n const lines: string[] = []\n for (let i = 0; i < term.rows; i++) {\n const line = buffer.getLine(buffer.viewportY + i)\n lines.push(line?.translateToString(true) ?? '')\n }\n return lines.join('\\n').replace(/\\n+$/, '')\n}\n\n/**\n * Replays a PTY transcript through xterm.js (headless) and returns visible plaintext\n * for the **viewport**: one row per screen line, trailing spaces stripped per row,\n * rows joined with `\\n`, no trailing blank lines — for downstream heuristics (e.g. Current guidance).\n *\n * `write` is asynchronous; the promise resolves after the parser has applied the transcript.\n *\n * Internal to `tty-assert` and its tests; integrators use the package root (`managed` session) or in-repo relative imports.\n */\nexport function ptyTranscriptToVisiblePlaintextViaXterm(\n raw: string,\n cols: number = CLI_INTERACTIVE_PTY_COLS,\n rows: number = CLI_INTERACTIVE_PTY_ROWS\n): Promise<string> {\n const term = new Terminal({\n cols,\n rows,\n allowProposedApi: true,\n })\n return new Promise((resolve, reject) => {\n try {\n term.write(raw, () => {\n try {\n resolve(viewportPlaintextFromHeadlessTerminal(term))\n } finally {\n term.dispose()\n }\n })\n } catch (e) {\n term.dispose()\n reject(e)\n }\n })\n}\n\n/** @see {@link ptyTranscriptToVisiblePlaintextViaXterm} — canonical name for new imports. */\nexport const ptyTranscriptToViewportPlaintext =\n ptyTranscriptToVisiblePlaintextViaXterm\n","import { createCanvas, loadImage, type CanvasRenderingContext2D } from 'canvas'\nimport { createRequire } from 'node:module'\n\nconst require = createRequire(import.meta.url)\n\ntype GifEncoderInstance = {\n createReadStream(): NodeJS.ReadableStream\n start(): void\n setRepeat(n: number): void\n setDelay(ms: number): void\n setQuality(n: number): void\n addFrame(ctx: CanvasRenderingContext2D): void\n finish(): void\n}\n\nconst GIFEncoder = require('gifencoder') as new (\n width: number,\n height: number\n) => GifEncoderInstance\n\nconst DEFAULT_FRAME_DELAY_MS = 250\n\n/**\n * Encodes same-size viewport PNG buffers (from {@link viewportPngFromHeadlessTerminal})\n * into an animated GIF. {@link GIFEncoder#setDelay} uses milliseconds per frame.\n */\nexport async function viewportPngBuffersToGif(\n pngBuffers: Buffer[],\n frameDelayMs = DEFAULT_FRAME_DELAY_MS\n): Promise<Buffer> {\n if (pngBuffers.length === 0) {\n throw new Error('viewportPngBuffersToGif: no frames')\n }\n const first = await loadImage(pngBuffers[0]!)\n const w = first.width\n const h = first.height\n const encoder = new GIFEncoder(w, h)\n const out = new Promise<Buffer>((resolve, reject) => {\n const chunks: Buffer[] = []\n encoder\n .createReadStream()\n .on('data', (c: Buffer) => {\n chunks.push(c)\n })\n .on('end', () => {\n resolve(Buffer.concat(chunks))\n })\n .on('error', reject)\n })\n\n encoder.start()\n encoder.setRepeat(0)\n encoder.setDelay(frameDelayMs)\n encoder.setQuality(10)\n\n const canvas = createCanvas(w, h)\n const ctx = canvas.getContext('2d')\n if (!ctx) {\n throw new Error('viewportPngBuffersToGif: canvas 2d context unavailable')\n }\n\n for (const buf of pngBuffers) {\n const img = await loadImage(buf)\n if (img.width !== w || img.height !== h) {\n throw new Error(\n `viewportPngBuffersToGif: frame size ${img.width}x${img.height} != ${w}x${h}`\n )\n }\n ctx.clearRect(0, 0, w, h)\n ctx.drawImage(img, 0, 0)\n encoder.addFrame(ctx)\n }\n encoder.finish()\n return out\n}\n","import { createCanvas } from 'canvas'\nimport type { Terminal } from '@xterm/headless'\n\nconst FONT_SIZE_PX = 14\nconst VIEWPORT_PADDING_PX = 6\nconst DEFAULT_BG = '#1e1e1e'\n\n/** VGA-style ANSI 0–15 (foreground); index 0 is black, 7 light gray, 15 white. */\nconst ANSI16: [number, number, number][] = [\n [0, 0, 0],\n [205, 49, 49],\n [13, 188, 121],\n [229, 229, 16],\n [36, 114, 200],\n [188, 63, 188],\n [17, 168, 205],\n [229, 229, 229],\n [102, 102, 102],\n [241, 76, 76],\n [35, 209, 139],\n [245, 245, 67],\n [59, 142, 234],\n [214, 112, 214],\n [41, 184, 219],\n [255, 255, 255],\n]\n\nconst CUBE_LEVELS = [0, 95, 135, 175, 215, 255]\n\nfunction rgbCss(r: number, g: number, b: number): string {\n return `rgb(${r},${g},${b})`\n}\n\nfunction ansi256ToRgb(idx: number): [number, number, number] {\n if (idx >= 0 && idx < 16) return ANSI16[idx]!\n if (idx >= 232) {\n const v = 8 + (idx - 232) * 10\n return [v, v, v]\n }\n if (idx >= 16 && idx <= 231) {\n const i = idx - 16\n const r = CUBE_LEVELS[Math.floor(i / 36)]!\n const g = CUBE_LEVELS[Math.floor((i % 36) / 6)]!\n const b = CUBE_LEVELS[i % 6]!\n return [r, g, b]\n }\n return [204, 204, 204]\n}\n\ntype CellLike = {\n getWidth(): number\n getChars(): string\n isInverse(): boolean\n isInvisible(): boolean\n isFgRGB(): boolean\n isBgRGB(): boolean\n isFgPalette(): boolean\n isBgPalette(): boolean\n isFgDefault(): boolean\n isBgDefault(): boolean\n getFgColor(): number\n getBgColor(): number\n isBold(): boolean\n}\n\nfunction xtermRgbColor(n: number): [number, number, number] {\n const r = (n >>> 16) & 255\n const g = (n >>> 8) & 255\n const b = n & 255\n return [r, g, b]\n}\n\nfunction resolveSideRgb(cell: CellLike, fg: boolean): [number, number, number] {\n if (fg) {\n if (cell.isFgRGB()) return xtermRgbColor(cell.getFgColor())\n if (cell.isFgPalette()) return ansi256ToRgb(cell.getFgColor() & 255)\n if (cell.isFgDefault()) return [204, 204, 204]\n return xtermRgbColor(cell.getFgColor())\n }\n if (cell.isBgRGB()) return xtermRgbColor(cell.getBgColor())\n if (cell.isBgPalette()) return ansi256ToRgb(cell.getBgColor() & 255)\n if (cell.isBgDefault()) return [30, 30, 30]\n return xtermRgbColor(cell.getBgColor())\n}\n\nfunction effectiveRgb(cell: CellLike | undefined): {\n fg: [number, number, number]\n bg: [number, number, number]\n} {\n if (!cell) {\n return {\n fg: [204, 204, 204],\n bg: [30, 30, 30],\n }\n }\n let fg = resolveSideRgb(cell, true)\n let bg = resolveSideRgb(cell, false)\n if (cell.isInverse()) {\n ;[fg, bg] = [bg, fg]\n }\n return { fg, bg }\n}\n\n/**\n * Rasterize the xterm **viewport** (visible rows) to a PNG buffer.\n * Uses node-canvas; intended for failure artifacts (CLI E2E), not pixel-perfect theme parity.\n */\nexport function viewportPngFromHeadlessTerminal(term: Terminal): Buffer {\n const cols = term.cols\n const rows = term.rows\n const buffer = term.buffer.active\n const viewportY = buffer.viewportY\n\n const probe = createCanvas(16, 16)\n const pctx = probe.getContext('2d')\n pctx.font = `${FONT_SIZE_PX}px monospace`\n const cellW = Math.max(8, Math.ceil(pctx.measureText('M').width))\n const cellH = Math.ceil(FONT_SIZE_PX * 1.25)\n\n const w = cols * cellW + VIEWPORT_PADDING_PX * 2\n const h = rows * cellH + VIEWPORT_PADDING_PX * 2\n const canvas = createCanvas(w, h)\n const ctx = canvas.getContext('2d')\n ctx.fillStyle = DEFAULT_BG\n ctx.fillRect(0, 0, w, h)\n ctx.textBaseline = 'top'\n\n for (let row = 0; row < rows; row++) {\n const termLine = buffer.getLine(viewportY + row)\n let prev = termLine?.getCell(0) as CellLike | undefined\n for (let col = 0; col < cols; col++) {\n const cell = termLine?.getCell(col, prev) as CellLike | undefined\n prev = cell\n if (cell && cell.getWidth() === 0) continue\n\n const raw = cell?.getChars() ?? ''\n const text = raw === '' ? ' ' : raw\n if (cell?.isInvisible()) {\n const { bg } = effectiveRgb(cell)\n ctx.fillStyle = rgbCss(bg[0], bg[1], bg[2])\n ctx.fillRect(\n VIEWPORT_PADDING_PX + col * cellW,\n VIEWPORT_PADDING_PX + row * cellH,\n cellW,\n cellH\n )\n continue\n }\n\n const { fg, bg } = effectiveRgb(cell)\n const x = VIEWPORT_PADDING_PX + col * cellW\n const y = VIEWPORT_PADDING_PX + row * cellH\n ctx.fillStyle = rgbCss(bg[0], bg[1], bg[2])\n ctx.fillRect(x, y, cellW, cellH)\n const weight = cell?.isBold() ? 'bold' : 'normal'\n ctx.font = `${weight} ${FONT_SIZE_PX}px monospace`\n ctx.fillStyle = rgbCss(fg[0], fg[1], fg[2])\n ctx.fillText(text, x, y)\n }\n }\n\n return canvas.toBuffer('image/png')\n}\n","/**\n * xterm Terminal–scoped haystack building and assertion attempts.\n * Used by `waitForTextInSurface` (disposable full replay) and `ManagedTtySession` (incremental replay).\n */\n\nimport { Terminal } from '@xterm/headless'\nimport type { CellExpectationBlock } from '../surface/cellExpectations'\nimport { stripAnsiCliPty } from '../ansi/stripAnsi'\n\n/** ANSI bright black: `\\x1b[90m` (fg) / `\\x1b[100m` (bg); xterm palette index 8. */\nconst BRIGHT_BLACK_PALETTE_INDEX = 8\n\nexport type SurfaceAttemptResult =\n | { ok: true }\n | { ok: false; snapshot: string; detail: string }\n | { ok: 'strict'; message: string; snapshot: string }\n\nfunction xtermBufferRows(\n term: Terminal,\n startY: number,\n endY: number\n): string[][] {\n const buffer = term.buffer.active\n const cols = term.cols\n const lines: string[][] = []\n for (let y = startY; y < endY; y++) {\n const termLine = buffer.getLine(y)\n const line: string[] = []\n let cell = termLine?.getCell(0)\n for (let x = 0; x < cols; x++) {\n cell = termLine?.getCell(x, cell)\n const rawChars = cell?.getChars() ?? ''\n line.push(rawChars === '' ? ' ' : rawChars)\n }\n lines.push(line)\n }\n return lines\n}\n\nfunction rowMajorSearchBlock(rows: string[][]): string {\n return rows.map((r) => r.join('')).join('')\n}\n\nfunction rowMajorSnapshot(rows: string[][]): string {\n return rows.map((r) => r.join('').trimEnd()).join('\\n')\n}\n\nfunction findAnchorRowIndex(rows: string[][], anchors: RegExp[]): number {\n for (const anchor of anchors) {\n for (let i = rows.length - 1; i >= 0; i--) {\n const row = rows[i]\n if (row && anchor.test(row.join('').trimEnd())) return i\n }\n }\n return -1\n}\n\nfunction sliceByAnchor(\n rows: string[][],\n anchors: RegExp[] | undefined,\n fallbackRowCount: number | undefined\n): { rows: string[][]; rowOffset: number } {\n if (!anchors || anchors.length === 0) return { rows, rowOffset: 0 }\n const idx = findAnchorRowIndex(rows, anchors)\n if (idx >= 0) return { rows: rows.slice(idx + 1), rowOffset: idx + 1 }\n if (fallbackRowCount != null) {\n const start = Math.max(0, rows.length - fallbackRowCount)\n return { rows: rows.slice(start), rowOffset: start }\n }\n return { rows, rowOffset: 0 }\n}\n\nfunction xtermSearchContext(\n term: Terminal,\n surface: 'viewableBuffer' | 'fullBuffer',\n startAfterAnchor: RegExp[] | undefined,\n fallbackRowCount: number | undefined\n): {\n haystack: string\n snapshot: string\n startY: number\n rowOffset: number\n} {\n const buffer = term.buffer.active\n const startY = surface === 'fullBuffer' ? 0 : buffer.baseY\n const endY = buffer.length\n const allRows = xtermBufferRows(term, startY, endY)\n const { rows: searchRows, rowOffset } = sliceByAnchor(\n allRows,\n startAfterAnchor,\n fallbackRowCount\n )\n return {\n haystack: rowMajorSearchBlock(searchRows),\n snapshot: rowMajorSnapshot(searchRows),\n startY,\n rowOffset,\n }\n}\n\nexport function buildHaystackAndSnapshotFromTerminal(\n term: Terminal,\n surface: 'viewableBuffer' | 'fullBuffer',\n startAfterAnchor: RegExp[] | undefined,\n fallbackRowCount: number | undefined\n): { haystack: string; snapshot: string } {\n const ctx = xtermSearchContext(\n term,\n surface,\n startAfterAnchor,\n fallbackRowCount\n )\n return { haystack: ctx.haystack, snapshot: ctx.snapshot }\n}\n\nfunction countNonOverlapping(haystack: string, needle: string): number {\n let count = 0\n let from = 0\n for (;;) {\n const index = haystack.indexOf(needle, from)\n if (index < 0) break\n count++\n from = index + needle.length\n }\n return count\n}\n\nfunction regexForMatchAll(needle: RegExp): RegExp {\n if (needle.global) return needle\n const flags = needle.flags.includes('g') ? needle.flags : `${needle.flags}g`\n return new RegExp(needle.source, flags)\n}\n\nfunction matchCount(haystack: string, needle: string | RegExp): number {\n if (typeof needle === 'string') {\n return countNonOverlapping(haystack, needle)\n }\n return Array.from(haystack.matchAll(regexForMatchAll(needle))).length\n}\n\nfunction cellHasPaletteBackground(\n cell: { isBgPalette(): boolean; getBgColor(): number },\n index: number\n): boolean {\n return cell.isBgPalette() && cell.getBgColor() === index\n}\n\nfunction blockNotFoundLabel(block: CellExpectationBlock): string {\n const kinds = new Set(block.expectations.map((e) => e.kind))\n if (kinds.has('allBold') && kinds.size === 1) return 'bold check'\n if (kinds.has('allBgPalette')) {\n return 'palette background check'\n }\n return 'cell expectation check'\n}\n\nfunction runCellExpectationBlocks(\n term: Terminal,\n opts: {\n surface: 'viewableBuffer' | 'fullBuffer'\n needle: string\n cols: number\n startAfterAnchor?: RegExp[]\n fallbackRowCount?: number\n },\n blocks: CellExpectationBlock[]\n): { ok: true } | { ok: false; snapshot: string; detail: string } {\n const buffer = term.buffer.active\n const { haystack, snapshot, startY, rowOffset } = xtermSearchContext(\n term,\n opts.surface,\n opts.startAfterAnchor,\n opts.fallbackRowCount\n )\n const rowWidth = opts.cols\n\n for (const block of blocks) {\n const index =\n block.match === 'first'\n ? haystack.indexOf(opts.needle)\n : haystack.lastIndexOf(opts.needle)\n if (index < 0) {\n const label = blockNotFoundLabel(block)\n return {\n ok: false,\n snapshot,\n detail: `Substring ${JSON.stringify(opts.needle)} not found in surface \"${opts.surface}\" (${label}).`,\n }\n }\n\n for (const exp of block.expectations) {\n if (exp.kind === 'allBold') {\n const which =\n block.match === 'first' ? 'first occurrence' : 'last occurrence'\n for (let i = 0; i < opts.needle.length; i++) {\n const pos = index + i\n const localY = Math.floor(pos / rowWidth)\n const x = pos % rowWidth\n const termLine = buffer.getLine(startY + rowOffset + localY)\n const cell = termLine?.getCell(x)\n if ((cell?.isBold() ?? 0) === 0) {\n return {\n ok: false,\n snapshot,\n detail: `Matched text ${JSON.stringify(opts.needle)} is present but not all cells at the ${which} are bold.`,\n }\n }\n }\n continue\n }\n\n if (exp.kind === 'allBgPalette') {\n const isBrightBlackBg = exp.index === BRIGHT_BLACK_PALETTE_INDEX\n for (let i = 0; i < opts.needle.length; i++) {\n const pos = index + i\n const localY = Math.floor(pos / rowWidth)\n const x = pos % rowWidth\n const termLine = buffer.getLine(startY + rowOffset + localY)\n const cell = termLine?.getCell(x)\n if (cell == null) {\n return {\n ok: false,\n snapshot,\n detail: `Palette background check: no cell at offset ${i} for last match of ${JSON.stringify(opts.needle)}.`,\n }\n }\n if (!cellHasPaletteBackground(cell, exp.index)) {\n if (isBrightBlackBg) {\n return {\n ok: false,\n snapshot,\n detail: `Matched text ${JSON.stringify(opts.needle)} at the last occurrence: expected background palette index 8 (e.g. \\\\x1b[100m) on every cell; at least one cell does not have that background.`,\n }\n }\n return {\n ok: false,\n snapshot,\n detail: `Matched text ${JSON.stringify(opts.needle)}: expected background palette index ${exp.index} on every cell in the span.`,\n }\n }\n }\n }\n }\n }\n\n return { ok: true }\n}\n\nexport type SurfaceAttemptOpts = {\n needle: string | RegExp\n surface: TtySearchSurfaceForAttempt\n raw: string\n strict: boolean\n cols: number\n rows: number\n startAfterAnchor?: RegExp[]\n fallbackRowCount?: number\n /** Resolved style checks; empty means none. Caller must validate surface and needle. */\n cellExpectations?: CellExpectationBlock[]\n}\n\ntype TtySearchSurfaceForAttempt =\n | 'viewableBuffer'\n | 'fullBuffer'\n | 'strippedTranscript'\n\n/**\n * Single assertion attempt using an existing terminal that already reflects `raw` transcript.\n */\nexport function attemptOnceOnLiveTerminal(\n term: Terminal,\n opts: SurfaceAttemptOpts & {\n surface: 'viewableBuffer' | 'fullBuffer'\n }\n): SurfaceAttemptResult {\n if (typeof opts.needle === 'string' && opts.needle.length === 0) {\n return {\n ok: 'strict',\n snapshot: '',\n message:\n 'tty-assert: empty string needle is not supported (ambiguous matches).',\n }\n }\n\n const { haystack, snapshot } = buildHaystackAndSnapshotFromTerminal(\n term,\n opts.surface,\n opts.startAfterAnchor,\n opts.fallbackRowCount\n )\n\n const count = matchCount(haystack, opts.needle)\n if (count > 1 && opts.strict) {\n const label =\n typeof opts.needle === 'string'\n ? JSON.stringify(opts.needle)\n : `${opts.needle}`\n return {\n ok: 'strict',\n snapshot,\n message: `Strict mode violation: ${label} matched ${count} non-overlapping occurrences in surface \"${opts.surface}\".`,\n }\n }\n if (count === 0) {\n const label =\n typeof opts.needle === 'string'\n ? JSON.stringify(opts.needle)\n : `${opts.needle}`\n return {\n ok: false,\n snapshot,\n detail: `Substring/pattern ${label} not found in surface \"${opts.surface}\".`,\n }\n }\n\n const blocks = opts.cellExpectations ?? []\n if (blocks.length > 0) {\n if (typeof opts.needle !== 'string') {\n throw new Error(\n 'tty-assert: cell expectations require a string needle (caller must validate).'\n )\n }\n const cellResult = runCellExpectationBlocks(\n term,\n {\n surface: opts.surface,\n needle: opts.needle,\n cols: opts.cols,\n startAfterAnchor: opts.startAfterAnchor,\n fallbackRowCount: opts.fallbackRowCount,\n },\n blocks\n )\n if (!cellResult.ok) {\n return {\n ok: false,\n snapshot: cellResult.snapshot,\n detail: cellResult.detail,\n }\n }\n }\n\n return { ok: true }\n}\n\nexport function attemptOnceStrippedTranscript(\n opts: SurfaceAttemptOpts & { surface: 'strippedTranscript' }\n): SurfaceAttemptResult {\n if (typeof opts.needle === 'string' && opts.needle.length === 0) {\n return {\n ok: 'strict',\n snapshot: '',\n message:\n 'tty-assert: empty string needle is not supported (ambiguous matches).',\n }\n }\n\n const haystack = stripAnsiCliPty(opts.raw)\n const snapshot = haystack\n\n const count = matchCount(haystack, opts.needle)\n if (count > 1 && opts.strict) {\n const label =\n typeof opts.needle === 'string'\n ? JSON.stringify(opts.needle)\n : `${opts.needle}`\n return {\n ok: 'strict',\n snapshot,\n message: `Strict mode violation: ${label} matched ${count} non-overlapping occurrences in surface \"${opts.surface}\".`,\n }\n }\n if (count === 0) {\n const label =\n typeof opts.needle === 'string'\n ? JSON.stringify(opts.needle)\n : `${opts.needle}`\n return {\n ok: false,\n snapshot,\n detail: `Substring/pattern ${label} not found in surface \"${opts.surface}\".`,\n }\n }\n\n return { ok: true }\n}\n\nexport function writeTranscriptToTerminal(\n term: Terminal,\n data: string\n): Promise<void> {\n return new Promise((resolve, reject) => {\n try {\n term.write(data, () => resolve())\n } catch (e) {\n reject(e)\n }\n })\n}\n\nexport function withReplayedXtermTerminal<T>(\n raw: string,\n cols: number,\n rows: number,\n fn: (term: Terminal) => T\n): Promise<T> {\n const term = new Terminal({\n cols,\n rows,\n allowProposedApi: true,\n })\n return new Promise((resolve, reject) => {\n try {\n term.write(raw, () => {\n try {\n resolve(fn(term))\n } finally {\n term.dispose()\n }\n })\n } catch (e) {\n term.dispose()\n reject(e)\n }\n })\n}\n\nexport async function attemptOnce(\n opts: SurfaceAttemptOpts\n): Promise<SurfaceAttemptResult> {\n if (opts.surface === 'strippedTranscript') {\n return attemptOnceStrippedTranscript(\n opts as SurfaceAttemptOpts & { surface: 'strippedTranscript' }\n )\n }\n return withReplayedXtermTerminal(opts.raw, opts.cols, opts.rows, (term) =>\n attemptOnceOnLiveTerminal(\n term,\n opts as SurfaceAttemptOpts & { surface: 'viewableBuffer' | 'fullBuffer' }\n )\n )\n}\n","/**\n * Default delay between poll attempts when `timeoutMs` is positive and `retryMs` is omitted.\n */\nexport const TTY_ASSERT_LOCATOR_DEFAULT_RETRY_MS = 50\n","import {\n TERMINAL_ERROR_RAW_TAIL_BYTES,\n headPreview,\n sanitizeVisibleTextForError,\n tailPreview,\n} from './errorSnapshotFormatting'\nimport { stripAnsiCliPty } from '../ansi/stripAnsi'\n\nexport type TtyAssertDumpDiagnostics = {\n rawByteLength: number\n ansiStrippedLength: number\n replayedScreenPlaintextHeadPreview: string\n replayedScreenPlaintextTailPreview: string\n strippedTranscriptTailPreview: string\n rawTailSanitizedPreview: string\n}\n\nexport function buildTtyAssertDumpDiagnostics(input: {\n raw: string\n replayedPlain: string\n}): TtyAssertDumpDiagnostics {\n const stripped = stripAnsiCliPty(input.raw)\n return {\n rawByteLength: input.raw.length,\n ansiStrippedLength: stripped.length,\n replayedScreenPlaintextHeadPreview: headPreview(input.replayedPlain),\n replayedScreenPlaintextTailPreview: tailPreview(input.replayedPlain),\n strippedTranscriptTailPreview: tailPreview(stripped),\n rawTailSanitizedPreview: sanitizeVisibleTextForError(\n tailPreview(input.raw.slice(-TERMINAL_ERROR_RAW_TAIL_BYTES))\n ),\n }\n}\n","export class TtyAssertStrictModeViolationError extends Error {\n readonly name = 'TtyAssertStrictModeViolationError'\n}\n","import { formatSearchSurfaceFailure } from '../diagnostics/errorSnapshotFormatting'\nimport type { SurfaceAttemptResult } from '../xterm/surfaceAttemptOnTerminal'\nimport { TtyAssertStrictModeViolationError } from './ttyAssertStrictModeError'\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n\nfunction withOptionalMessagePrefix(\n prefix: string | undefined,\n body: string\n): string {\n if (prefix == null || prefix === '') return body\n const p = prefix.replace(/\\n+$/, '')\n return `${p}\\n${body}`\n}\n\ntype TtySurface = 'viewableBuffer' | 'fullBuffer' | 'strippedTranscript'\n\n/**\n * Shared poll / timeout / strict handling for surface text asserts.\n * `runAttempt` supplies fresh `raw` and the result of one {@link attemptOnce}-style check.\n */\nexport async function pollSurfaceAssertLoop(opts: {\n surface: TtySurface\n timeoutMs: number\n retryMs: number\n messagePrefix?: string\n runAttempt: () => Promise<{ raw: string; result: SurfaceAttemptResult }>\n appendFailure: (body: string, raw: string) => string\n}): Promise<void> {\n const {\n surface,\n timeoutMs,\n retryMs,\n messagePrefix,\n runAttempt,\n appendFailure,\n } = opts\n const started = Date.now()\n let lastFail: { snapshot: string; detail: string } | undefined\n\n for (;;) {\n const { raw, result } = await runAttempt()\n\n if (result.ok === true) return\n\n if (result.ok === 'strict') {\n const body = withOptionalMessagePrefix(\n messagePrefix,\n formatSearchSurfaceFailure(surface, result.message, result.snapshot)\n )\n throw new TtyAssertStrictModeViolationError(appendFailure(body, raw))\n }\n\n lastFail = { snapshot: result.snapshot, detail: result.detail }\n if (Date.now() - started >= timeoutMs) {\n const detail =\n timeoutMs === 0\n ? lastFail.detail\n : `Timeout after ${timeoutMs}ms. ${lastFail.detail}`\n const body = withOptionalMessagePrefix(\n messagePrefix,\n formatSearchSurfaceFailure(surface, detail, lastFail.snapshot)\n )\n throw new Error(appendFailure(body, raw))\n }\n await sleep(retryMs)\n }\n}\n","import { Terminal } from '@xterm/headless'\nimport {\n formatFinalViewportPlaintextForError,\n formatRawTerminalSnapshotForError,\n} from '../diagnostics/errorSnapshotFormatting'\nimport {\n CLI_INTERACTIVE_PTY_COLS,\n CLI_INTERACTIVE_PTY_ROWS,\n} from '../defaults/geometry'\nimport {\n disposeBufferedPtySession,\n startBufferedPtySession,\n type BufferedPtySession,\n type StartBufferedPtySessionOptions,\n} from '../pty/ptySession'\nimport { validateAndResolveCellExpectations } from '../surface/cellExpectations'\nimport { viewportPlaintextFromHeadlessTerminal } from '../xterm/ptyTranscriptToVisiblePlaintextViaXterm'\nimport { viewportPngBuffersToGif } from '../xterm/viewportPngSequenceToGif'\nimport { viewportPngFromHeadlessTerminal } from '../xterm/viewportPngFromHeadlessTerminal'\nimport {\n attemptOnceOnLiveTerminal,\n attemptOnceStrippedTranscript,\n writeTranscriptToTerminal,\n} from '../xterm/surfaceAttemptOnTerminal'\nimport { TTY_ASSERT_LOCATOR_DEFAULT_RETRY_MS } from '../surface/locatorRetryMs'\nimport {\n buildTtyAssertDumpDiagnostics,\n type TtyAssertDumpDiagnostics,\n} from '../diagnostics/ttyAssertDumpDiagnostics'\nimport { pollSurfaceAssertLoop } from '../surface/pollSurfaceAssertLoop'\nimport type { WaitForTextInSurfaceOptions } from '../surface/waitForTextInSurface'\n\nexport type ManagedTtyAssertOptions = Omit<WaitForTextInSurfaceOptions, 'raw'>\n\n/** Plain object form of `RegExp` for JSON-serializable assert payloads (e.g. `cy.task`). */\nexport type JsonRegexp = { source: string; flags?: string }\n\nexport type ManagedTtyAssertInput = Omit<\n ManagedTtyAssertOptions,\n 'needle' | 'startAfterAnchor'\n> & {\n needle: string | RegExp | JsonRegexp\n startAfterAnchor?: (RegExp | JsonRegexp)[]\n}\n\nfunction regExpFromJsonRegexp(r: JsonRegexp): RegExp {\n return new RegExp(r.source, r.flags ?? '')\n}\n\nexport function normalizeManagedTtyAssertInput(\n input: ManagedTtyAssertInput\n): ManagedTtyAssertOptions {\n const needle =\n typeof input.needle === 'string' || input.needle instanceof RegExp\n ? input.needle\n : regExpFromJsonRegexp(input.needle)\n const startAfterAnchor = input.startAfterAnchor?.map((a) =>\n a instanceof RegExp ? a : regExpFromJsonRegexp(a)\n )\n return { ...input, needle, startAfterAnchor }\n}\n\n/** Wired by {@link startManagedTtySession} so PTY `onData` can schedule viewport animation samples. */\nexport type ManagedTtySessionPtyDataBridge = {\n onPtyData: () => void\n}\n\nconst VIEWPORT_ANIM_DEBOUNCE_MS = 40\nconst VIEWPORT_ANIM_MAX_FRAMES = 56\n\nconst CLI_TERMINAL_RAW_SNAPSHOT_HEADING =\n '--- CLI terminal snapshot (ANSI-stripped, safe text) ---'\n\nfunction appendRawTerminalSnapshotForErrorMessage(\n body: string,\n raw: string\n): string {\n return `${body}\\n\\n${CLI_TERMINAL_RAW_SNAPSHOT_HEADING}\\n${formatRawTerminalSnapshotForError(raw)}`\n}\n\nfunction appendManagedAssertFailureDiagnostics(\n body: string,\n raw: string,\n term: Terminal\n): string {\n const viewportPlain = viewportPlaintextFromHeadlessTerminal(term)\n const withViewport = `${body}\\n\\n${formatFinalViewportPlaintextForError(viewportPlain)}`\n return appendRawTerminalSnapshotForErrorMessage(withViewport, raw)\n}\n\nexport type ManagedTtySession = {\n readonly session: BufferedPtySession\n write(data: string): void\n submit(line: string): void\n assert(opts: ManagedTtyAssertInput): Promise<void>\n captureViewportPng(): Promise<Buffer>\n /** Recorded viewport PNGs when a {@link ManagedTtySessionPtyDataBridge} was used; else empty. */\n getViewportAnimationPngs(): Buffer[]\n /** Flushes pending animation sample, then builds a GIF from recorded frames (needs ≥2 frames). */\n buildViewportAnimationGif(): Promise<Buffer>\n dumpDiagnostics(): Promise<TtyAssertDumpDiagnostics>\n dispose(): void\n}\n\nexport function attachManagedTtySession(\n session: BufferedPtySession,\n geometry?: { cols?: number; rows?: number },\n ptyDataBridge?: ManagedTtySessionPtyDataBridge\n): ManagedTtySession {\n const cols = geometry?.cols ?? CLI_INTERACTIVE_PTY_COLS\n const rows = geometry?.rows ?? CLI_INTERACTIVE_PTY_ROWS\n const term = new Terminal({\n cols,\n rows,\n allowProposedApi: true,\n })\n let replayedByteCount = 0\n let disposed = false\n\n const animFrames: Buffer[] = []\n let lastAnimViewportPlain: string | undefined\n let animDebounceTimer: ReturnType<typeof setTimeout> | undefined\n\n async function syncReplay(): Promise<void> {\n if (disposed) return\n const full = session.buf.text\n if (full.length < replayedByteCount) {\n term.reset()\n await writeTranscriptToTerminal(term, full)\n replayedByteCount = full.length\n return\n }\n const delta = full.slice(replayedByteCount)\n if (delta.length === 0) return\n const targetEnd = full.length\n await writeTranscriptToTerminal(term, delta)\n replayedByteCount = targetEnd\n }\n\n async function flushAnimationFrame(): Promise<void> {\n if (disposed || !ptyDataBridge) return\n await syncReplay()\n const plain = viewportPlaintextFromHeadlessTerminal(term)\n if (plain === lastAnimViewportPlain) return\n lastAnimViewportPlain = plain\n animFrames.push(viewportPngFromHeadlessTerminal(term))\n while (animFrames.length > VIEWPORT_ANIM_MAX_FRAMES) {\n animFrames.shift()\n }\n }\n\n function scheduleAnimationSample(): void {\n if (disposed || !ptyDataBridge) return\n clearTimeout(animDebounceTimer)\n animDebounceTimer = setTimeout(() => {\n flushAnimationFrame().catch(() => {\n /* best-effort viewport sample; ignore PTY/xterm errors */\n })\n }, VIEWPORT_ANIM_DEBOUNCE_MS)\n }\n\n if (ptyDataBridge) {\n ptyDataBridge.onPtyData = scheduleAnimationSample\n }\n\n async function flushPendingAnimationSample(): Promise<void> {\n if (!ptyDataBridge) return\n clearTimeout(animDebounceTimer)\n animDebounceTimer = undefined\n await flushAnimationFrame()\n }\n\n return {\n session,\n write(data: string) {\n session.pty.write(data)\n },\n submit(line: string) {\n session.pty.write(`${line}\\r`)\n },\n async assert(opts: ManagedTtyAssertInput): Promise<void> {\n if (disposed) {\n throw new Error('ManagedTtySession.assert after dispose')\n }\n\n const normalized = normalizeManagedTtyAssertInput(opts)\n\n const cellExpectations = validateAndResolveCellExpectations({\n surface: normalized.surface,\n needle: normalized.needle,\n cellExpectations: normalized.cellExpectations,\n })\n\n const timeoutMs = normalized.timeoutMs ?? 3000\n const retryMs = normalized.retryMs ?? TTY_ASSERT_LOCATOR_DEFAULT_RETRY_MS\n const strict = normalized.strict ?? true\n const messagePrefix = normalized.messagePrefix\n\n await pollSurfaceAssertLoop({\n surface: normalized.surface,\n timeoutMs,\n retryMs,\n messagePrefix,\n runAttempt: async () => {\n if (disposed) {\n throw new Error('ManagedTtySession.assert after dispose')\n }\n await syncReplay()\n const raw = session.buf.text\n const result =\n normalized.surface === 'strippedTranscript'\n ? attemptOnceStrippedTranscript({\n needle: normalized.needle,\n surface: 'strippedTranscript',\n raw,\n strict,\n cols,\n rows,\n })\n : attemptOnceOnLiveTerminal(term, {\n needle: normalized.needle,\n surface: normalized.surface,\n raw,\n strict,\n cols,\n rows,\n startAfterAnchor: normalized.startAfterAnchor,\n fallbackRowCount: normalized.fallbackRowCount,\n cellExpectations,\n })\n return { raw, result }\n },\n appendFailure: (body, raw) =>\n appendManagedAssertFailureDiagnostics(body, raw, term),\n })\n },\n async captureViewportPng(): Promise<Buffer> {\n if (disposed) {\n throw new Error('ManagedTtySession.captureViewportPng after dispose')\n }\n await syncReplay()\n return viewportPngFromHeadlessTerminal(term)\n },\n getViewportAnimationPngs(): Buffer[] {\n return [...animFrames]\n },\n async buildViewportAnimationGif(): Promise<Buffer> {\n if (!ptyDataBridge) {\n throw new Error(\n 'ManagedTtySession.buildViewportAnimationGif: session was started without PTY animation bridge'\n )\n }\n if (disposed) {\n throw new Error(\n 'ManagedTtySession.buildViewportAnimationGif after dispose'\n )\n }\n await flushPendingAnimationSample()\n if (animFrames.length < 2) {\n throw new Error(\n `ManagedTtySession.buildViewportAnimationGif: need at least 2 distinct viewport frames, got ${animFrames.length}`\n )\n }\n return viewportPngBuffersToGif([...animFrames])\n },\n async dumpDiagnostics(): Promise<TtyAssertDumpDiagnostics> {\n if (disposed) {\n throw new Error('ManagedTtySession.dumpDiagnostics after dispose')\n }\n await syncReplay()\n const raw = session.buf.text\n const replayed = viewportPlaintextFromHeadlessTerminal(term)\n return buildTtyAssertDumpDiagnostics({ raw, replayedPlain: replayed })\n },\n dispose() {\n if (disposed) return\n disposed = true\n clearTimeout(animDebounceTimer)\n animDebounceTimer = undefined\n try {\n term.dispose()\n } catch {\n /* ignore */\n }\n disposeBufferedPtySession(session)\n },\n }\n}\n\nexport async function startManagedTtySession(\n opts: StartBufferedPtySessionOptions,\n geometry?: { cols?: number; rows?: number }\n): Promise<ManagedTtySession> {\n const cols = opts.cols ?? geometry?.cols ?? CLI_INTERACTIVE_PTY_COLS\n const rows = opts.rows ?? geometry?.rows ?? CLI_INTERACTIVE_PTY_ROWS\n const bridge: ManagedTtySessionPtyDataBridge = {\n onPtyData: () => undefined,\n }\n const session = await startBufferedPtySession({\n ...opts,\n cols,\n rows,\n onAfterPtyData: () => bridge.onPtyData(),\n })\n return attachManagedTtySession(session, { cols, rows }, bridge)\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tty-assert",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "PTY transcript assertions: ANSI strip, xterm replay, and explicit search surfaces for tests",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"pty",
|
|
7
|
+
"terminal",
|
|
8
|
+
"xterm",
|
|
9
|
+
"testing",
|
|
10
|
+
"ansi",
|
|
11
|
+
"assert"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/terryyin/tty-assert.git"
|
|
17
|
+
},
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/terryyin/tty-assert/issues"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://github.com/terryyin/tty-assert#readme",
|
|
22
|
+
"type": "module",
|
|
23
|
+
"packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319",
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsup",
|
|
26
|
+
"prepublishOnly": "pnpm run build",
|
|
27
|
+
"format": "biome check --write .",
|
|
28
|
+
"lint": "biome check .",
|
|
29
|
+
"test": "vitest run",
|
|
30
|
+
"test:watch": "vitest"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@lydell/node-pty": "1.1.0",
|
|
34
|
+
"@xterm/headless": "6.0.0",
|
|
35
|
+
"canvas": "^3.2.3",
|
|
36
|
+
"gifencoder": "^2.0.1"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@biomejs/biome": "2.4.11",
|
|
40
|
+
"@types/node": "25.6.0",
|
|
41
|
+
"tsup": "8.5.1",
|
|
42
|
+
"typescript": "6.0.2",
|
|
43
|
+
"vitest": "4.1.4"
|
|
44
|
+
},
|
|
45
|
+
"types": "./dist/index.d.ts",
|
|
46
|
+
"exports": {
|
|
47
|
+
".": {
|
|
48
|
+
"types": "./dist/index.d.ts",
|
|
49
|
+
"import": "./dist/index.js"
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"files": [
|
|
53
|
+
"dist",
|
|
54
|
+
"README.md",
|
|
55
|
+
"LICENSE"
|
|
56
|
+
],
|
|
57
|
+
"pnpm": {
|
|
58
|
+
"onlyBuiltDependencies": ["canvas"],
|
|
59
|
+
"overrides": {
|
|
60
|
+
"canvas": "3.2.3"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|