@ytspar/sweetlink 1.18.0 → 1.19.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/dist/cli/sweetlink.js +163 -0
- package/dist/cli/sweetlink.js.map +1 -1
- package/dist/simulator/android.d.ts +35 -0
- package/dist/simulator/android.d.ts.map +1 -0
- package/dist/simulator/android.js +119 -0
- package/dist/simulator/android.js.map +1 -0
- package/dist/simulator/ios.d.ts +39 -0
- package/dist/simulator/ios.d.ts.map +1 -0
- package/dist/simulator/ios.js +115 -0
- package/dist/simulator/ios.js.map +1 -0
- package/dist/term/ansi.d.ts +37 -0
- package/dist/term/ansi.d.ts.map +1 -0
- package/dist/term/ansi.js +170 -0
- package/dist/term/ansi.js.map +1 -0
- package/dist/term/player.d.ts +25 -0
- package/dist/term/player.d.ts.map +1 -0
- package/dist/term/player.js +243 -0
- package/dist/term/player.js.map +1 -0
- package/dist/term/recorder.d.ts +33 -0
- package/dist/term/recorder.d.ts.map +1 -0
- package/dist/term/recorder.js +77 -0
- package/dist/term/recorder.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Android Emulator screen recording via `adb shell screenrecord`.
|
|
3
|
+
*
|
|
4
|
+
* `adb shell screenrecord` records to a file ON THE EMULATOR and stops on
|
|
5
|
+
* SIGINT. Maximum length is 180 seconds (an Android limitation, not ours)
|
|
6
|
+
* — we surface that as a clear error if the user requests longer.
|
|
7
|
+
*
|
|
8
|
+
* Workflow:
|
|
9
|
+
* 1. Pick a device (preference → first online emulator)
|
|
10
|
+
* 2. Start `adb shell screenrecord /sdcard/sl-<stamp>.mp4` as a child
|
|
11
|
+
* 3. Run the user's test command
|
|
12
|
+
* 4. SIGINT screenrecord, wait for it to flush
|
|
13
|
+
* 5. `adb pull` the .mp4 off the emulator to the user-requested output
|
|
14
|
+
*/
|
|
15
|
+
import { spawn, execFile } from 'child_process';
|
|
16
|
+
import { promises as fs } from 'fs';
|
|
17
|
+
import * as path from 'path';
|
|
18
|
+
function adb(args) {
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
execFile('adb', args, { encoding: 'utf-8' }, (err, stdout, stderr) => {
|
|
21
|
+
if (err) {
|
|
22
|
+
const e = err;
|
|
23
|
+
if (e.code === 'ENOENT') {
|
|
24
|
+
reject(new Error('adb is not on PATH. Install Android Platform Tools.'));
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
reject(new Error(`adb ${args.join(' ')} failed: ${stderr || err.message}`));
|
|
28
|
+
}
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
resolve(stdout);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
/** Returns the first online emulator/device serial, optionally filtered by a preference. */
|
|
36
|
+
export async function findAndroidDevice(preference) {
|
|
37
|
+
const out = await adb(['devices']);
|
|
38
|
+
// Lines: "<serial>\tdevice" (skip header)
|
|
39
|
+
const devices = out
|
|
40
|
+
.split('\n')
|
|
41
|
+
.slice(1)
|
|
42
|
+
.map((l) => l.trim())
|
|
43
|
+
.filter((l) => l.endsWith('\tdevice'))
|
|
44
|
+
.map((l) => l.split('\t')[0])
|
|
45
|
+
.filter(Boolean);
|
|
46
|
+
if (preference && devices.includes(preference))
|
|
47
|
+
return preference;
|
|
48
|
+
return devices[0] ?? null;
|
|
49
|
+
}
|
|
50
|
+
export async function recordAndroidEmulator(options) {
|
|
51
|
+
const device = await findAndroidDevice(options.device);
|
|
52
|
+
if (!device) {
|
|
53
|
+
throw new Error('No online Android device. Boot an emulator (`emulator -avd ...`) or connect a device.');
|
|
54
|
+
}
|
|
55
|
+
const timeLimit = Math.min(options.timeLimit ?? 180, 180);
|
|
56
|
+
await fs.mkdir(path.dirname(options.output), { recursive: true });
|
|
57
|
+
// Pick a unique remote path so concurrent recordings don't collide.
|
|
58
|
+
const remotePath = `/sdcard/sl-record-${Date.now()}.mp4`;
|
|
59
|
+
// Start screen recording on the device. screenrecord exits on its own
|
|
60
|
+
// at the time-limit OR on SIGINT.
|
|
61
|
+
const recProc = spawn('adb', ['-s', device, 'shell', 'screenrecord', '--time-limit', String(timeLimit), remotePath], { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
62
|
+
let recStderr = '';
|
|
63
|
+
recProc.stderr?.on('data', (d) => { recStderr += d.toString(); });
|
|
64
|
+
// Give screenrecord a moment to start and create the file.
|
|
65
|
+
await new Promise((r) => setTimeout(r, 800));
|
|
66
|
+
if (recProc.exitCode !== null) {
|
|
67
|
+
throw new Error(`screenrecord failed to start on ${device}: ${recStderr.trim() || 'unknown error'}`);
|
|
68
|
+
}
|
|
69
|
+
const startedAt = Date.now();
|
|
70
|
+
const cmdResult = await new Promise((resolve) => {
|
|
71
|
+
const child = spawn(options.shell ?? '/bin/sh', ['-c', options.command], {
|
|
72
|
+
cwd: options.cwd,
|
|
73
|
+
stdio: 'inherit',
|
|
74
|
+
});
|
|
75
|
+
child.on('close', (code) => resolve({ exitCode: code ?? 0 }));
|
|
76
|
+
});
|
|
77
|
+
const durationSec = (Date.now() - startedAt) / 1000;
|
|
78
|
+
// Stop and flush.
|
|
79
|
+
const closed = await new Promise((resolve) => {
|
|
80
|
+
let resolved = false;
|
|
81
|
+
const timer = setTimeout(() => {
|
|
82
|
+
if (!resolved) {
|
|
83
|
+
resolved = true;
|
|
84
|
+
try {
|
|
85
|
+
recProc.kill('SIGKILL');
|
|
86
|
+
}
|
|
87
|
+
catch { /* ignore */ }
|
|
88
|
+
resolve(false);
|
|
89
|
+
}
|
|
90
|
+
}, 5_000);
|
|
91
|
+
recProc.on('close', () => {
|
|
92
|
+
if (!resolved) {
|
|
93
|
+
resolved = true;
|
|
94
|
+
clearTimeout(timer);
|
|
95
|
+
resolve(true);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
try {
|
|
99
|
+
recProc.kill('SIGINT');
|
|
100
|
+
}
|
|
101
|
+
catch { /* ignore */ }
|
|
102
|
+
});
|
|
103
|
+
// Pull the file off the device. Wait a bit for screenrecord to fsync.
|
|
104
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
105
|
+
await adb(['-s', device, 'pull', remotePath, options.output]);
|
|
106
|
+
// Best-effort cleanup on the device.
|
|
107
|
+
try {
|
|
108
|
+
await adb(['-s', device, 'shell', 'rm', '-f', remotePath]);
|
|
109
|
+
}
|
|
110
|
+
catch { /* ignore */ }
|
|
111
|
+
return {
|
|
112
|
+
output: options.output,
|
|
113
|
+
device,
|
|
114
|
+
exitCode: cmdResult.exitCode,
|
|
115
|
+
durationSec,
|
|
116
|
+
recordingClosed: closed,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=android.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"android.js","sourceRoot":"","sources":["../../src/simulator/android.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* iOS Simulator screen recording.
|
|
3
|
+
*
|
|
4
|
+
* Wraps `xcrun simctl io <device> recordVideo` so callers can capture the
|
|
5
|
+
* Simulator screen while running an XCUITest / fastlane scan / any other
|
|
6
|
+
* command. The command runs synchronously; while it runs we keep
|
|
7
|
+
* recordVideo open as a child process and SIGINT it after the command
|
|
8
|
+
* exits, which is the documented way to flush the .mp4.
|
|
9
|
+
*/
|
|
10
|
+
export interface IosRecordOptions {
|
|
11
|
+
/** Shell command to run while recording. */
|
|
12
|
+
command: string;
|
|
13
|
+
/** Output path. Should end in .mp4 — simctl can also write .mov. */
|
|
14
|
+
output: string;
|
|
15
|
+
/** Simulator UDID or device name. Defaults to the first booted simulator. */
|
|
16
|
+
device?: string;
|
|
17
|
+
/** Optional cwd for the command. */
|
|
18
|
+
cwd?: string;
|
|
19
|
+
/** Override shell. */
|
|
20
|
+
shell?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface IosRecordResult {
|
|
23
|
+
output: string;
|
|
24
|
+
device: string;
|
|
25
|
+
exitCode: number;
|
|
26
|
+
durationSec: number;
|
|
27
|
+
/** True when the recordVideo process exited cleanly (mp4 fully flushed). */
|
|
28
|
+
recordingClosed: boolean;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Discover a usable simulator UDID.
|
|
32
|
+
* Preference order: explicit `device` arg → first booted device → none.
|
|
33
|
+
*/
|
|
34
|
+
export declare function findIosDevice(preference?: string): Promise<{
|
|
35
|
+
udid: string;
|
|
36
|
+
name: string;
|
|
37
|
+
} | null>;
|
|
38
|
+
export declare function recordIosSimulator(options: IosRecordOptions): Promise<IosRecordResult>;
|
|
39
|
+
//# sourceMappingURL=ios.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ios.d.ts","sourceRoot":"","sources":["../../src/simulator/ios.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,MAAM,WAAW,gBAAgB;IAC/B,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,MAAM,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oCAAoC;IACpC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,sBAAsB;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,4EAA4E;IAC5E,eAAe,EAAE,OAAO,CAAC;CAC1B;AAoBD;;;GAGG;AACH,wBAAsB,aAAa,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAgBvG;AAED,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC,CAwE5F"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* iOS Simulator screen recording.
|
|
3
|
+
*
|
|
4
|
+
* Wraps `xcrun simctl io <device> recordVideo` so callers can capture the
|
|
5
|
+
* Simulator screen while running an XCUITest / fastlane scan / any other
|
|
6
|
+
* command. The command runs synchronously; while it runs we keep
|
|
7
|
+
* recordVideo open as a child process and SIGINT it after the command
|
|
8
|
+
* exits, which is the documented way to flush the .mp4.
|
|
9
|
+
*/
|
|
10
|
+
import { spawn, execFile } from 'child_process';
|
|
11
|
+
import { promises as fs } from 'fs';
|
|
12
|
+
import * as path from 'path';
|
|
13
|
+
/** Run `xcrun simctl <args>` and return parsed JSON or text output. */
|
|
14
|
+
function simctl(args, json = false) {
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
execFile('xcrun', ['simctl', ...args], { encoding: 'utf-8' }, (err, stdout, stderr) => {
|
|
17
|
+
if (err) {
|
|
18
|
+
const e = err;
|
|
19
|
+
if (e.code === 'ENOENT') {
|
|
20
|
+
reject(new Error('xcrun is not on PATH. Install Xcode Command Line Tools.'));
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
reject(new Error(`simctl ${args.join(' ')} failed: ${stderr || err.message}`));
|
|
24
|
+
}
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
resolve(json ? stdout : stdout.trim());
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Discover a usable simulator UDID.
|
|
33
|
+
* Preference order: explicit `device` arg → first booted device → none.
|
|
34
|
+
*/
|
|
35
|
+
export async function findIosDevice(preference) {
|
|
36
|
+
const raw = await simctl(['list', 'devices', '--json'], true);
|
|
37
|
+
const data = JSON.parse(raw);
|
|
38
|
+
const all = Object.values(data.devices).flat().filter((d) => d.isAvailable);
|
|
39
|
+
if (preference) {
|
|
40
|
+
const match = all.find((d) => d.udid === preference || d.name.toLowerCase() === preference.toLowerCase());
|
|
41
|
+
if (match)
|
|
42
|
+
return { udid: match.udid, name: match.name };
|
|
43
|
+
}
|
|
44
|
+
const booted = all.find((d) => d.state === 'Booted');
|
|
45
|
+
if (booted)
|
|
46
|
+
return { udid: booted.udid, name: booted.name };
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
export async function recordIosSimulator(options) {
|
|
50
|
+
const device = await findIosDevice(options.device);
|
|
51
|
+
if (!device) {
|
|
52
|
+
throw new Error('No booted iOS Simulator. Open Simulator.app or specify --device "iPhone 15".');
|
|
53
|
+
}
|
|
54
|
+
await fs.mkdir(path.dirname(options.output), { recursive: true });
|
|
55
|
+
// Start screen recording as a long-running child. `recordVideo` writes
|
|
56
|
+
// the mp4 incrementally and flushes on SIGINT (cmd-C in interactive use).
|
|
57
|
+
// simctl picks the container from the file extension; codec defaults
|
|
58
|
+
// to hevc but h264 is more universally playable in browsers/HTML5.
|
|
59
|
+
// --force overwrites the file if it already exists.
|
|
60
|
+
const recProc = spawn('xcrun', ['simctl', 'io', device.udid, 'recordVideo', '--codec=h264', '--force', options.output], { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
61
|
+
// Capture stderr in case the recording fails to start (e.g. "device not booted").
|
|
62
|
+
let recStderr = '';
|
|
63
|
+
recProc.stderr?.on('data', (d) => { recStderr += d.toString(); });
|
|
64
|
+
// Give simctl ~600ms to start the recording (it needs to attach to the
|
|
65
|
+
// simulator's IOSurface). If it died early, surface the error now.
|
|
66
|
+
await new Promise((r) => setTimeout(r, 600));
|
|
67
|
+
if (recProc.exitCode !== null) {
|
|
68
|
+
throw new Error(`recordVideo failed to start on ${device.name}: ${recStderr.trim() || 'unknown error'}`);
|
|
69
|
+
}
|
|
70
|
+
// Run the user's command and capture its exit code.
|
|
71
|
+
const startedAt = Date.now();
|
|
72
|
+
const cmdResult = await new Promise((resolve) => {
|
|
73
|
+
const child = spawn(options.shell ?? '/bin/sh', ['-c', options.command], {
|
|
74
|
+
cwd: options.cwd,
|
|
75
|
+
stdio: 'inherit',
|
|
76
|
+
});
|
|
77
|
+
child.on('close', (code) => resolve({ exitCode: code ?? 0 }));
|
|
78
|
+
});
|
|
79
|
+
const durationSec = (Date.now() - startedAt) / 1000;
|
|
80
|
+
// Stop recording: SIGINT triggers simctl's flush-and-exit path. SIGTERM
|
|
81
|
+
// would also work but truncates the trailing buffer.
|
|
82
|
+
const closed = await new Promise((resolve) => {
|
|
83
|
+
let resolved = false;
|
|
84
|
+
const timer = setTimeout(() => {
|
|
85
|
+
if (!resolved) {
|
|
86
|
+
resolved = true;
|
|
87
|
+
// Recording didn't shut down within 5s — kill it forcefully.
|
|
88
|
+
try {
|
|
89
|
+
recProc.kill('SIGKILL');
|
|
90
|
+
}
|
|
91
|
+
catch { /* ignore */ }
|
|
92
|
+
resolve(false);
|
|
93
|
+
}
|
|
94
|
+
}, 5_000);
|
|
95
|
+
recProc.on('close', () => {
|
|
96
|
+
if (!resolved) {
|
|
97
|
+
resolved = true;
|
|
98
|
+
clearTimeout(timer);
|
|
99
|
+
resolve(true);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
try {
|
|
103
|
+
recProc.kill('SIGINT');
|
|
104
|
+
}
|
|
105
|
+
catch { /* ignore */ }
|
|
106
|
+
});
|
|
107
|
+
return {
|
|
108
|
+
output: options.output,
|
|
109
|
+
device: device.name,
|
|
110
|
+
exitCode: cmdResult.exitCode,
|
|
111
|
+
durationSec,
|
|
112
|
+
recordingClosed: closed,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=ios.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ios.js","sourceRoot":"","sources":["../../src/simulator/ios.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal ANSI escape parser for the terminal player.
|
|
3
|
+
*
|
|
4
|
+
* Handles the subset that test runners typically emit:
|
|
5
|
+
* - SGR (colour/style) codes: ESC[Nm ESC[N;Nm ESC[N;N;Nm
|
|
6
|
+
* - 256-color: ESC[38;5;Nm ESC[48;5;Nm
|
|
7
|
+
* - Truecolor: ESC[38;2;R;G;Bm ESC[48;2;R;G;Bm
|
|
8
|
+
* - Reset (plain ESC[m, ESC[0m)
|
|
9
|
+
* - Erase line (ESC[K) — just clear from cursor to EOL
|
|
10
|
+
*
|
|
11
|
+
* Cursor-positioning escapes (ESC[NA, ESC[H, etc.) are stripped — the
|
|
12
|
+
* player models the screen as a flat scrollback buffer, not a TUI grid.
|
|
13
|
+
*/
|
|
14
|
+
export interface AnsiSpan {
|
|
15
|
+
text: string;
|
|
16
|
+
/** Inline CSS — passed straight to `style="..."` so the player has no extra deps. */
|
|
17
|
+
style: string;
|
|
18
|
+
}
|
|
19
|
+
/** State carried across a stream of writes (so colour persists between chunks). */
|
|
20
|
+
export interface AnsiState {
|
|
21
|
+
fg: string | null;
|
|
22
|
+
bg: string | null;
|
|
23
|
+
bold: boolean;
|
|
24
|
+
italic: boolean;
|
|
25
|
+
underline: boolean;
|
|
26
|
+
dim: boolean;
|
|
27
|
+
}
|
|
28
|
+
export declare function freshState(): AnsiState;
|
|
29
|
+
/**
|
|
30
|
+
* Parse a chunk of bytes into HTML-ready spans, advancing `state` so
|
|
31
|
+
* subsequent chunks pick up where this one left off.
|
|
32
|
+
*
|
|
33
|
+
* The returned string is HTML — caller must escape any user data NOT
|
|
34
|
+
* coming from the recorded stream (it's intended to be embedded inline).
|
|
35
|
+
*/
|
|
36
|
+
export declare function ansiToHtml(input: string, state: AnsiState): string;
|
|
37
|
+
//# sourceMappingURL=ansi.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ansi.d.ts","sourceRoot":"","sources":["../../src/term/ansi.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,qFAAqF;IACrF,KAAK,EAAE,MAAM,CAAC;CACf;AAED,mFAAmF;AACnF,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,GAAG,EAAE,OAAO,CAAC;CACd;AAED,wBAAgB,UAAU,IAAI,SAAS,CAEtC;AA+ED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,MAAM,CA2ClE"}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal ANSI escape parser for the terminal player.
|
|
3
|
+
*
|
|
4
|
+
* Handles the subset that test runners typically emit:
|
|
5
|
+
* - SGR (colour/style) codes: ESC[Nm ESC[N;Nm ESC[N;N;Nm
|
|
6
|
+
* - 256-color: ESC[38;5;Nm ESC[48;5;Nm
|
|
7
|
+
* - Truecolor: ESC[38;2;R;G;Bm ESC[48;2;R;G;Bm
|
|
8
|
+
* - Reset (plain ESC[m, ESC[0m)
|
|
9
|
+
* - Erase line (ESC[K) — just clear from cursor to EOL
|
|
10
|
+
*
|
|
11
|
+
* Cursor-positioning escapes (ESC[NA, ESC[H, etc.) are stripped — the
|
|
12
|
+
* player models the screen as a flat scrollback buffer, not a TUI grid.
|
|
13
|
+
*/
|
|
14
|
+
export function freshState() {
|
|
15
|
+
return { fg: null, bg: null, bold: false, italic: false, underline: false, dim: false };
|
|
16
|
+
}
|
|
17
|
+
const BASIC_FG = {
|
|
18
|
+
30: '#000', 31: '#cd3131', 32: '#0dbc79', 33: '#e5e510',
|
|
19
|
+
34: '#2472c8', 35: '#bc3fbc', 36: '#11a8cd', 37: '#e5e5e5',
|
|
20
|
+
90: '#666', 91: '#f14c4c', 92: '#23d18b', 93: '#f5f543',
|
|
21
|
+
94: '#3b8eea', 95: '#d670d6', 96: '#29b8db', 97: '#fff',
|
|
22
|
+
};
|
|
23
|
+
const BASIC_BG = {
|
|
24
|
+
40: '#000', 41: '#cd3131', 42: '#0dbc79', 43: '#e5e510',
|
|
25
|
+
44: '#2472c8', 45: '#bc3fbc', 46: '#11a8cd', 47: '#e5e5e5',
|
|
26
|
+
100: '#666', 101: '#f14c4c', 102: '#23d18b', 103: '#f5f543',
|
|
27
|
+
104: '#3b8eea', 105: '#d670d6', 106: '#29b8db', 107: '#fff',
|
|
28
|
+
};
|
|
29
|
+
function color256(n) {
|
|
30
|
+
if (n < 16) {
|
|
31
|
+
const lut = [
|
|
32
|
+
'#000', '#cd3131', '#0dbc79', '#e5e510', '#2472c8', '#bc3fbc', '#11a8cd', '#e5e5e5',
|
|
33
|
+
'#666', '#f14c4c', '#23d18b', '#f5f543', '#3b8eea', '#d670d6', '#29b8db', '#fff',
|
|
34
|
+
];
|
|
35
|
+
return lut[n];
|
|
36
|
+
}
|
|
37
|
+
if (n < 232) {
|
|
38
|
+
const i = n - 16;
|
|
39
|
+
const r = Math.floor(i / 36) * 51;
|
|
40
|
+
const g = Math.floor((i % 36) / 6) * 51;
|
|
41
|
+
const b = (i % 6) * 51;
|
|
42
|
+
return `rgb(${r},${g},${b})`;
|
|
43
|
+
}
|
|
44
|
+
const v = (n - 232) * 10 + 8;
|
|
45
|
+
return `rgb(${v},${v},${v})`;
|
|
46
|
+
}
|
|
47
|
+
function applyParams(state, params) {
|
|
48
|
+
let i = 0;
|
|
49
|
+
while (i < params.length) {
|
|
50
|
+
const p = params[i];
|
|
51
|
+
if (p === 0) {
|
|
52
|
+
Object.assign(state, freshState());
|
|
53
|
+
}
|
|
54
|
+
else if (p === 1)
|
|
55
|
+
state.bold = true;
|
|
56
|
+
else if (p === 2)
|
|
57
|
+
state.dim = true;
|
|
58
|
+
else if (p === 3)
|
|
59
|
+
state.italic = true;
|
|
60
|
+
else if (p === 4)
|
|
61
|
+
state.underline = true;
|
|
62
|
+
else if (p === 22) {
|
|
63
|
+
state.bold = false;
|
|
64
|
+
state.dim = false;
|
|
65
|
+
}
|
|
66
|
+
else if (p === 23)
|
|
67
|
+
state.italic = false;
|
|
68
|
+
else if (p === 24)
|
|
69
|
+
state.underline = false;
|
|
70
|
+
else if (p === 39)
|
|
71
|
+
state.fg = null;
|
|
72
|
+
else if (p === 49)
|
|
73
|
+
state.bg = null;
|
|
74
|
+
else if (BASIC_FG[p])
|
|
75
|
+
state.fg = BASIC_FG[p];
|
|
76
|
+
else if (BASIC_BG[p])
|
|
77
|
+
state.bg = BASIC_BG[p];
|
|
78
|
+
else if (p === 38 || p === 48) {
|
|
79
|
+
const isFg = p === 38;
|
|
80
|
+
const mode = params[i + 1];
|
|
81
|
+
if (mode === 5 && params[i + 2] !== undefined) {
|
|
82
|
+
const c = color256(params[i + 2]);
|
|
83
|
+
if (isFg)
|
|
84
|
+
state.fg = c;
|
|
85
|
+
else
|
|
86
|
+
state.bg = c;
|
|
87
|
+
i += 2;
|
|
88
|
+
}
|
|
89
|
+
else if (mode === 2 && params[i + 4] !== undefined) {
|
|
90
|
+
const c = `rgb(${params[i + 2]},${params[i + 3]},${params[i + 4]})`;
|
|
91
|
+
if (isFg)
|
|
92
|
+
state.fg = c;
|
|
93
|
+
else
|
|
94
|
+
state.bg = c;
|
|
95
|
+
i += 4;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
i++;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function styleOf(state) {
|
|
102
|
+
const parts = [];
|
|
103
|
+
if (state.fg)
|
|
104
|
+
parts.push(`color:${state.fg}`);
|
|
105
|
+
if (state.bg)
|
|
106
|
+
parts.push(`background:${state.bg}`);
|
|
107
|
+
if (state.bold)
|
|
108
|
+
parts.push('font-weight:600');
|
|
109
|
+
if (state.italic)
|
|
110
|
+
parts.push('font-style:italic');
|
|
111
|
+
if (state.underline)
|
|
112
|
+
parts.push('text-decoration:underline');
|
|
113
|
+
if (state.dim)
|
|
114
|
+
parts.push('opacity:0.7');
|
|
115
|
+
return parts.join(';');
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Parse a chunk of bytes into HTML-ready spans, advancing `state` so
|
|
119
|
+
* subsequent chunks pick up where this one left off.
|
|
120
|
+
*
|
|
121
|
+
* The returned string is HTML — caller must escape any user data NOT
|
|
122
|
+
* coming from the recorded stream (it's intended to be embedded inline).
|
|
123
|
+
*/
|
|
124
|
+
export function ansiToHtml(input, state) {
|
|
125
|
+
const ESC = '';
|
|
126
|
+
let i = 0;
|
|
127
|
+
let out = '';
|
|
128
|
+
let buffer = '';
|
|
129
|
+
const flush = () => {
|
|
130
|
+
if (!buffer)
|
|
131
|
+
return;
|
|
132
|
+
const escaped = buffer
|
|
133
|
+
.replace(/&/g, '&')
|
|
134
|
+
.replace(/</g, '<')
|
|
135
|
+
.replace(/>/g, '>');
|
|
136
|
+
const style = styleOf(state);
|
|
137
|
+
if (style) {
|
|
138
|
+
out += `<span style="${style}">${escaped}</span>`;
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
out += escaped;
|
|
142
|
+
}
|
|
143
|
+
buffer = '';
|
|
144
|
+
};
|
|
145
|
+
while (i < input.length) {
|
|
146
|
+
const ch = input[i];
|
|
147
|
+
if (ch === ESC && input[i + 1] === '[') {
|
|
148
|
+
flush();
|
|
149
|
+
// Read up to the final byte (a letter)
|
|
150
|
+
let j = i + 2;
|
|
151
|
+
while (j < input.length && !/[A-Za-z]/.test(input[j]))
|
|
152
|
+
j++;
|
|
153
|
+
const final = input[j];
|
|
154
|
+
const body = input.slice(i + 2, j);
|
|
155
|
+
if (final === 'm') {
|
|
156
|
+
const params = body.split(';').map((s) => parseInt(s, 10) || 0);
|
|
157
|
+
applyParams(state, params.length === 0 ? [0] : params);
|
|
158
|
+
}
|
|
159
|
+
// K, A, B, C, D, H, J, etc. are ignored (not modeled by the flat scrollback).
|
|
160
|
+
i = j + 1;
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
buffer += ch;
|
|
164
|
+
i++;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
flush();
|
|
168
|
+
return out;
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=ansi.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ansi.js","sourceRoot":"","sources":["../../src/term/ansi.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Self-contained HTML player for asciicast v2 recordings.
|
|
3
|
+
*
|
|
4
|
+
* Inlines the cast events as JSON in the document — the player has no
|
|
5
|
+
* runtime dependencies, works offline, opens anywhere a browser does.
|
|
6
|
+
*
|
|
7
|
+
* UX: play/pause, 0.1×–4× speed control, seek bar, ANSI colour rendering.
|
|
8
|
+
* Carriage returns (\r) reposition the cursor to column 0 of the current
|
|
9
|
+
* line so progress bars overwrite cleanly. Form-feeds (\f) and bare
|
|
10
|
+
* cursor escapes are dropped — this is a flat scrollback, not a TUI grid.
|
|
11
|
+
*
|
|
12
|
+
* Rendering uses createElement + textContent (NOT innerHTML) — every
|
|
13
|
+
* character from the recorded stream lands as a text node, never as
|
|
14
|
+
* markup. Inline style attrs are built from a hardcoded colour palette
|
|
15
|
+
* with no user input.
|
|
16
|
+
*/
|
|
17
|
+
export interface PlayerOptions {
|
|
18
|
+
castPath: string;
|
|
19
|
+
/** Title shown in the player header; defaults to the filename. */
|
|
20
|
+
title?: string;
|
|
21
|
+
/** Output HTML path. Defaults to `<castPath without .cast>.html`. */
|
|
22
|
+
outputPath?: string;
|
|
23
|
+
}
|
|
24
|
+
export declare function generatePlayer(options: PlayerOptions): Promise<string>;
|
|
25
|
+
//# sourceMappingURL=player.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"player.d.ts","sourceRoot":"","sources":["../../src/term/player.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,kEAAkE;IAClE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAUD,wBAAsB,cAAc,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CA6N5E"}
|