@jsenv/terminal-recorder 1.1.11 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/js/terminal_recorder.js +787 -5
- package/package.json +4 -2
- package/src/client/fixtures/hello_world_2_lines.txt +2 -0
- package/src/client/terminal_recorder.js +40 -5
- package/src/client/xterm_dev.html +65 -23
- package/src/command_recording.js +107 -0
- package/src/main.js +1 -0
- package/src/terminal_recording.js +91 -96
- /package/src/client/{source.txt → fixtures/source.txt} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/terminal-recorder",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Record terminal output as .svg, .gif, .webm, .mp4",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -32,16 +32,18 @@
|
|
|
32
32
|
"prepublishOnly": "npm run build"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
+
"@jsenv/humanize": "1.2.3",
|
|
35
36
|
"@jsenv/server": "15.2.12",
|
|
36
37
|
"@playwright/browser-webkit": "1.45.1",
|
|
37
38
|
"ansi-regex": "6.0.1",
|
|
38
39
|
"he": "1.2.0",
|
|
39
40
|
"playwright": "1.45.1",
|
|
40
|
-
"strip-ansi": "7.1.0",
|
|
41
41
|
"string-width": "5.1.2",
|
|
42
|
+
"strip-ansi": "7.1.0",
|
|
42
43
|
"webm-to-mp4": "1.0.0"
|
|
43
44
|
},
|
|
44
45
|
"devDependencies": {
|
|
46
|
+
"@xterm/addon-serialize": "0.13.0",
|
|
45
47
|
"xterm": "5.3.0",
|
|
46
48
|
"xterm-addon-webgl": "0.16.0"
|
|
47
49
|
}
|
|
@@ -14,10 +14,12 @@ https://github.com/xtermjs/xterm.js/blob/master/typings/xterm.d.ts
|
|
|
14
14
|
|
|
15
15
|
import "xterm";
|
|
16
16
|
import "xterm-addon-webgl";
|
|
17
|
+
import "@xterm/addon-serialize?as_js_module";
|
|
17
18
|
import { createGifEncoder } from "./gif_encoder.js";
|
|
18
19
|
|
|
19
20
|
const { Terminal } = window;
|
|
20
21
|
const { WebglAddon } = window.WebglAddon;
|
|
22
|
+
const { SerializeAddon } = window.SerializeAddon;
|
|
21
23
|
|
|
22
24
|
export const initTerminal = ({
|
|
23
25
|
cols = 80,
|
|
@@ -27,14 +29,16 @@ export const initTerminal = ({
|
|
|
27
29
|
fontFamily = "SauceCodePro Nerd Font, Source Code Pro, Courier",
|
|
28
30
|
fontSize = 12,
|
|
29
31
|
convertEol = true,
|
|
32
|
+
textInViewport,
|
|
30
33
|
gif,
|
|
31
34
|
video,
|
|
32
35
|
logs,
|
|
33
36
|
}) => {
|
|
34
|
-
if (
|
|
37
|
+
if (textInViewport === true) textInViewport = {};
|
|
35
38
|
if (gif === true) gif = {};
|
|
36
|
-
if (
|
|
37
|
-
|
|
39
|
+
if (video === true) video = {};
|
|
40
|
+
if (!textInViewport && !video && !gif) {
|
|
41
|
+
throw new Error("ansi, video or gif must be enabled");
|
|
38
42
|
}
|
|
39
43
|
|
|
40
44
|
const log = (...args) => {
|
|
@@ -79,6 +83,9 @@ export const initTerminal = ({
|
|
|
79
83
|
true,
|
|
80
84
|
),
|
|
81
85
|
);
|
|
86
|
+
const serializeAddon = new SerializeAddon();
|
|
87
|
+
term.loadAddon(serializeAddon);
|
|
88
|
+
|
|
82
89
|
const terminalElement = document.getElementById("terminal");
|
|
83
90
|
term.open(terminalElement);
|
|
84
91
|
|
|
@@ -133,10 +140,36 @@ export const initTerminal = ({
|
|
|
133
140
|
}
|
|
134
141
|
|
|
135
142
|
return {
|
|
143
|
+
term,
|
|
136
144
|
startRecording: async () => {
|
|
137
145
|
const records = {};
|
|
146
|
+
let writePromise = Promise.resolve();
|
|
138
147
|
const frameCallbackSet = new Set();
|
|
139
148
|
const stopCallbackSet = new Set();
|
|
149
|
+
if (textInViewport) {
|
|
150
|
+
log("start recording text in viewport");
|
|
151
|
+
// https://github.com/xtermjs/xterm.js/issues/3681
|
|
152
|
+
// https://github.com/xtermjs/xterm.js/issues/3681
|
|
153
|
+
// get the first line
|
|
154
|
+
// term.buffer.active.getLine(term.buffer.active.viewportY).translateToString()
|
|
155
|
+
// get the last line
|
|
156
|
+
// term.buffer.active.getLine(term.buffer.active.length -1).translateToString()
|
|
157
|
+
// https://github.com/xtermjs/xterm.js/blob/a7952ff36c60ee6dce9141744b1355a5d582ee39/addons/addon-serialize/src/SerializeAddon.ts
|
|
158
|
+
stopCallbackSet.add(() => {
|
|
159
|
+
const startY = term.buffer.active.viewportY;
|
|
160
|
+
const endY = term.buffer.active.length - 1;
|
|
161
|
+
const range = {
|
|
162
|
+
start: startY,
|
|
163
|
+
end: endY,
|
|
164
|
+
};
|
|
165
|
+
log(`lines in viewport: ${startY}:${endY}`);
|
|
166
|
+
const output = serializeAddon.serialize({
|
|
167
|
+
range,
|
|
168
|
+
});
|
|
169
|
+
log(`text in viewport: ${output}`);
|
|
170
|
+
records.textInViewport = output;
|
|
171
|
+
});
|
|
172
|
+
}
|
|
140
173
|
if (video) {
|
|
141
174
|
log("start recording video");
|
|
142
175
|
const { mimeType = "video/webm;codecs=h264", msAddedAtTheEnd } = video;
|
|
@@ -277,8 +310,8 @@ export const initTerminal = ({
|
|
|
277
310
|
|
|
278
311
|
return {
|
|
279
312
|
writeIntoTerminal: async (data) => {
|
|
280
|
-
|
|
281
|
-
log(`write data
|
|
313
|
+
writePromise = new Promise((resolve) => {
|
|
314
|
+
log(`write data:`, data);
|
|
282
315
|
term.write(data, () => {
|
|
283
316
|
term._core._renderService._renderDebouncer._innerRefresh();
|
|
284
317
|
replicateXterm();
|
|
@@ -286,8 +319,10 @@ export const initTerminal = ({
|
|
|
286
319
|
resolve();
|
|
287
320
|
});
|
|
288
321
|
});
|
|
322
|
+
await writePromise;
|
|
289
323
|
},
|
|
290
324
|
stopRecording: async () => {
|
|
325
|
+
await writePromise;
|
|
291
326
|
const promises = [];
|
|
292
327
|
for (const stopCallback of stopCallbackSet) {
|
|
293
328
|
promises.push(stopCallback());
|
|
@@ -14,40 +14,82 @@
|
|
|
14
14
|
<script type="module">
|
|
15
15
|
import { initTerminal } from "./terminal_recorder.js";
|
|
16
16
|
|
|
17
|
+
const delayBetweenLines = 200;
|
|
17
18
|
const { startRecording } = initTerminal({
|
|
19
|
+
textInViewport: true,
|
|
18
20
|
gif: {
|
|
19
21
|
repeat: true,
|
|
20
22
|
msAddedAtTheEnd: 3_000,
|
|
21
23
|
},
|
|
22
|
-
logs: true,
|
|
24
|
+
// logs: true,
|
|
23
25
|
// video: true,
|
|
24
26
|
});
|
|
25
27
|
const { writeIntoTerminal, stopRecording } = await startRecording();
|
|
26
|
-
const sourceResponse = await fetch("./source.txt");
|
|
27
|
-
const source = await sourceResponse.text();
|
|
28
|
-
const lines = source.split("\n");
|
|
29
|
-
for (const line of lines) {
|
|
30
|
-
writeIntoTerminal(`${line}\n`);
|
|
31
|
-
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
32
|
-
}
|
|
33
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
34
|
-
const terminalRecords = await stopRecording();
|
|
35
|
-
const video = terminalRecords.video;
|
|
36
|
-
const gif = terminalRecords.gif;
|
|
37
28
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
29
|
+
window.renderFromSource = async (sourceUrl) => {
|
|
30
|
+
document.querySelector("#text_in_viewport").innerHTML = "...";
|
|
31
|
+
document.querySelector("#video").src = "";
|
|
32
|
+
document.querySelector("#gif").src = "";
|
|
42
33
|
|
|
43
|
-
|
|
44
|
-
|
|
34
|
+
const sourceResponse = await fetch(sourceUrl);
|
|
35
|
+
const source = await sourceResponse.text();
|
|
36
|
+
let lines = source.split("\n");
|
|
37
|
+
// lines = lines.slice(0, 12);
|
|
38
|
+
for (const line of lines) {
|
|
39
|
+
writeIntoTerminal(`${line}\n`);
|
|
40
|
+
await new Promise((resolve) =>
|
|
41
|
+
setTimeout(resolve, delayBetweenLines),
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
45
|
+
const terminalRecords = await stopRecording();
|
|
46
|
+
const textInViewport = terminalRecords.textInViewport;
|
|
47
|
+
const video = terminalRecords.video;
|
|
48
|
+
const gif = terminalRecords.gif;
|
|
49
|
+
|
|
50
|
+
const webmDataUrl = `data:video/webm;codecs=h264;base64,${window.btoa(
|
|
51
|
+
video,
|
|
52
|
+
)}`;
|
|
53
|
+
const gifDataUrl = `data:image/gif;base64,${window.btoa(gif)}`;
|
|
54
|
+
|
|
55
|
+
document.querySelector("#text_in_viewport").innerHTML = textInViewport;
|
|
56
|
+
document.querySelector("#video").src = webmDataUrl;
|
|
57
|
+
document.querySelector("#gif").src = gifDataUrl;
|
|
58
|
+
};
|
|
59
|
+
</script>
|
|
60
|
+
<form>
|
|
61
|
+
<select name="source">
|
|
62
|
+
<option value="./fixtures/hello_world_2_lines.txt">
|
|
63
|
+
./fixtures/hello_world_2_lines.txt
|
|
64
|
+
</option>
|
|
65
|
+
<option value="./fixtures/source.txt">./fixtures/source.txt</option>
|
|
66
|
+
</select>
|
|
67
|
+
<button name="submit" type="submit">Go</button>
|
|
68
|
+
</form>
|
|
69
|
+
<script>
|
|
70
|
+
const form = document.querySelector("form");
|
|
71
|
+
form.onsubmit = async (e) => {
|
|
72
|
+
e.preventDefault();
|
|
73
|
+
const source = form.elements.source.value;
|
|
74
|
+
const submit = form.elements.submit;
|
|
75
|
+
submit.disabled = true;
|
|
76
|
+
await window.renderFromSource(source);
|
|
77
|
+
submit.disabled = false;
|
|
78
|
+
};
|
|
45
79
|
</script>
|
|
46
|
-
<div id="records">
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
80
|
+
<div id="records" style="display: flex">
|
|
81
|
+
<div style="flex-grow: 1">
|
|
82
|
+
<h3>Text in viewport</h3>
|
|
83
|
+
<pre id="text_in_viewport"></pre>
|
|
84
|
+
</div>
|
|
85
|
+
<div style="flex-grow: 1">
|
|
86
|
+
<h3>GIF</h3>
|
|
87
|
+
<img id="gif" src="data:," />
|
|
88
|
+
</div>
|
|
89
|
+
<div style="flex-grow: 1">
|
|
90
|
+
<h3>Video</h3>
|
|
91
|
+
<video id="video" src="data:," controls></video>
|
|
92
|
+
</div>
|
|
51
93
|
</div>
|
|
52
94
|
</body>
|
|
53
95
|
</html>
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { UNICODE, ANSI } from "@jsenv/humanize";
|
|
3
|
+
|
|
4
|
+
import { startTerminalRecording } from "./terminal_recording.js";
|
|
5
|
+
|
|
6
|
+
export const recordCommandToSvg = async (
|
|
7
|
+
command,
|
|
8
|
+
options,
|
|
9
|
+
svgOptions,
|
|
10
|
+
terminalOptions = {},
|
|
11
|
+
) => {
|
|
12
|
+
const terminalRecorder = await startTerminalRecording({
|
|
13
|
+
svg: {
|
|
14
|
+
title: "Terminal",
|
|
15
|
+
...svgOptions,
|
|
16
|
+
},
|
|
17
|
+
...terminalOptions,
|
|
18
|
+
});
|
|
19
|
+
await executeCommand(command, {
|
|
20
|
+
...options,
|
|
21
|
+
onStdout: (data) => {
|
|
22
|
+
terminalRecorder.write(String(data));
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
const terminalRecords = await terminalRecorder.stop();
|
|
26
|
+
const terminalSvg = await terminalRecords.svg();
|
|
27
|
+
return terminalSvg;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const executeCommand = (
|
|
31
|
+
command,
|
|
32
|
+
{
|
|
33
|
+
signal = new AbortController().signal,
|
|
34
|
+
onStdout = () => {},
|
|
35
|
+
onStderr = () => {},
|
|
36
|
+
cwd,
|
|
37
|
+
env,
|
|
38
|
+
timeout,
|
|
39
|
+
} = {},
|
|
40
|
+
) => {
|
|
41
|
+
return new Promise((resolve, reject) => {
|
|
42
|
+
let args = [];
|
|
43
|
+
let commandWithoutArgs = "";
|
|
44
|
+
const firstSpaceIndex = command.indexOf(" ");
|
|
45
|
+
if (firstSpaceIndex === -1) {
|
|
46
|
+
commandWithoutArgs = command;
|
|
47
|
+
} else {
|
|
48
|
+
commandWithoutArgs = command.slice(0, firstSpaceIndex);
|
|
49
|
+
const argsRaw = command.slice(firstSpaceIndex + 1).trim();
|
|
50
|
+
args = argsRaw.split(" ");
|
|
51
|
+
}
|
|
52
|
+
const commandProcess = spawn(commandWithoutArgs, args, {
|
|
53
|
+
signal,
|
|
54
|
+
cwd:
|
|
55
|
+
cwd && typeof cwd === "string" && cwd.startsWith("file:")
|
|
56
|
+
? new URL(cwd)
|
|
57
|
+
: cwd,
|
|
58
|
+
env: {
|
|
59
|
+
...process.env,
|
|
60
|
+
...(ANSI.supported ? { FORCE_COLOR: "1" } : {}),
|
|
61
|
+
...(UNICODE.supported ? { FORCE_UNICODE: "1" } : {}),
|
|
62
|
+
...env,
|
|
63
|
+
},
|
|
64
|
+
timeout,
|
|
65
|
+
// silent: true,
|
|
66
|
+
});
|
|
67
|
+
commandProcess.on("error", (error) => {
|
|
68
|
+
if (error && error.code === "ETIMEDOUT") {
|
|
69
|
+
console.error(`timeout after ${timeout} ms`);
|
|
70
|
+
reject(error);
|
|
71
|
+
} else {
|
|
72
|
+
reject(error);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
const stdoutDatas = [];
|
|
76
|
+
commandProcess.stdout.on("data", (data) => {
|
|
77
|
+
stdoutDatas.push(data);
|
|
78
|
+
onStdout(data);
|
|
79
|
+
});
|
|
80
|
+
let stderrDatas = [];
|
|
81
|
+
commandProcess.stderr.on("data", (data) => {
|
|
82
|
+
stderrDatas.push(data);
|
|
83
|
+
onStderr(data);
|
|
84
|
+
});
|
|
85
|
+
if (commandProcess.stdin) {
|
|
86
|
+
commandProcess.stdin.on("error", (error) => {
|
|
87
|
+
reject(error);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
commandProcess.on("exit", (exitCode, signal) => {
|
|
91
|
+
if (signal) {
|
|
92
|
+
reject(new Error(`killed with ${signal}`));
|
|
93
|
+
}
|
|
94
|
+
if (exitCode) {
|
|
95
|
+
reject(
|
|
96
|
+
new Error(
|
|
97
|
+
`failed with exit code ${exitCode}
|
|
98
|
+
--- command stderr ---
|
|
99
|
+
${stderrDatas.join("")}`,
|
|
100
|
+
),
|
|
101
|
+
);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
resolve({ exitCode, signal });
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
};
|
package/src/main.js
CHANGED
|
@@ -29,7 +29,7 @@ const startLocalServer = async () => {
|
|
|
29
29
|
return devServer;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
const serverDirectoryUrl = new URL("
|
|
32
|
+
const serverDirectoryUrl = new URL("../dist/", import.meta.url);
|
|
33
33
|
const { startServer, fetchFileSystem } = await import("@jsenv/server");
|
|
34
34
|
const server = await startServer({
|
|
35
35
|
logLevel: "warn",
|
|
@@ -75,116 +75,111 @@ export const startTerminalRecording = async ({
|
|
|
75
75
|
throw new Error("video not recorded");
|
|
76
76
|
},
|
|
77
77
|
};
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
let ansi = "";
|
|
81
|
-
writeCallbackSet.add((data) => {
|
|
82
|
-
ansi += data;
|
|
83
|
-
});
|
|
84
|
-
stopCallbackSet.add(() => {
|
|
85
|
-
terminalRecords.svg = async () => {
|
|
86
|
-
const terminalSvg = await renderTerminalSvg(ansi, svg);
|
|
87
|
-
ansi = "";
|
|
88
|
-
return terminalSvg;
|
|
89
|
-
};
|
|
90
|
-
});
|
|
78
|
+
if (!svg && !gif && !video) {
|
|
79
|
+
throw new Error("svg, video or gif must be enabled ");
|
|
91
80
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const __term__ = await window.initTerminal({
|
|
116
|
-
cols,
|
|
117
|
-
rows,
|
|
118
|
-
convertEol,
|
|
119
|
-
gif,
|
|
120
|
-
video,
|
|
121
|
-
logs,
|
|
122
|
-
});
|
|
123
|
-
window.__term__ = __term__;
|
|
124
|
-
},
|
|
125
|
-
{
|
|
81
|
+
const { webkit } = await import("playwright");
|
|
82
|
+
const server = await startLocalServer();
|
|
83
|
+
const browser = await webkit.launch({
|
|
84
|
+
// channel: "chrome", // https://github.com/microsoft/playwright/issues/7716#issuecomment-882634893
|
|
85
|
+
headless: !debug,
|
|
86
|
+
// needed because https-localhost fails to trust cert on chrome + linux (ubuntu 20.04)
|
|
87
|
+
args: ["--ignore-certificate-errors"],
|
|
88
|
+
});
|
|
89
|
+
const page = await browser.newPage({
|
|
90
|
+
ignoreHTTPSErrors: true,
|
|
91
|
+
});
|
|
92
|
+
page.on("pageerror", (error) => {
|
|
93
|
+
throw error;
|
|
94
|
+
});
|
|
95
|
+
page.on("console", (consoleMessage) => {
|
|
96
|
+
console.log(`browser> ${consoleMessage.text()}`);
|
|
97
|
+
});
|
|
98
|
+
await page.goto(`${server.origin}/xterm.html`);
|
|
99
|
+
await page.evaluate(
|
|
100
|
+
/* eslint-env browser */
|
|
101
|
+
async ({ cols, rows, convertEol, textInViewport, gif, video, logs }) => {
|
|
102
|
+
await window.xtreamReadyPromise;
|
|
103
|
+
const __term__ = await window.initTerminal({
|
|
126
104
|
cols,
|
|
127
105
|
rows,
|
|
128
|
-
convertEol
|
|
106
|
+
convertEol,
|
|
107
|
+
textInViewport,
|
|
129
108
|
gif,
|
|
130
109
|
video,
|
|
131
110
|
logs,
|
|
111
|
+
});
|
|
112
|
+
window.__term__ = __term__;
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
cols,
|
|
116
|
+
rows,
|
|
117
|
+
convertEol: process.platform !== "win32",
|
|
118
|
+
textInViewport: Boolean(svg),
|
|
119
|
+
gif,
|
|
120
|
+
video,
|
|
121
|
+
logs,
|
|
122
|
+
},
|
|
123
|
+
/* eslint-env node */
|
|
124
|
+
);
|
|
125
|
+
await page.evaluate(
|
|
126
|
+
/* eslint-env browser */
|
|
127
|
+
async () => {
|
|
128
|
+
const { writeIntoTerminal, stopRecording } =
|
|
129
|
+
await window.__term__.startRecording();
|
|
130
|
+
window.terminalRecording = { writeIntoTerminal, stopRecording };
|
|
131
|
+
},
|
|
132
|
+
/* eslint-env node */
|
|
133
|
+
);
|
|
134
|
+
writeCallbackSet.add(async (data) => {
|
|
135
|
+
await page.evaluate(
|
|
136
|
+
/* eslint-env browser */
|
|
137
|
+
async (data) => {
|
|
138
|
+
await window.terminalRecording.writeIntoTerminal(data);
|
|
132
139
|
},
|
|
140
|
+
data,
|
|
133
141
|
/* eslint-env node */
|
|
134
142
|
);
|
|
135
|
-
|
|
143
|
+
});
|
|
144
|
+
stopCallbackSet.add(async () => {
|
|
145
|
+
const recordedFormats = await page.evaluate(
|
|
136
146
|
/* eslint-env browser */
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
await window.__term__.startRecording();
|
|
140
|
-
window.terminalRecording = { writeIntoTerminal, stopRecording };
|
|
147
|
+
() => {
|
|
148
|
+
return window.terminalRecording.stopRecording();
|
|
141
149
|
},
|
|
142
150
|
/* eslint-env node */
|
|
143
151
|
);
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
);
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const terminalGifBuffer = Buffer.from(recordedFormats.gif, "binary");
|
|
171
|
-
return terminalGifBuffer;
|
|
172
|
-
};
|
|
173
|
-
terminalRecords.webm = () => {
|
|
174
|
-
const terminalWebmBuffer = Buffer.from(recordedFormats.video, "binary");
|
|
175
|
-
return terminalWebmBuffer;
|
|
176
|
-
};
|
|
177
|
-
terminalRecords.mp4 = async () => {
|
|
178
|
-
const terminalWebmBuffer = Buffer.from(terminalRecords.video, "binary");
|
|
179
|
-
const webmToMp4Namespace = await import("webm-to-mp4");
|
|
180
|
-
const webmToMp4 = webmToMp4Namespace.default;
|
|
181
|
-
const terminalMp4Buffer = Buffer.from(webmToMp4(terminalWebmBuffer));
|
|
182
|
-
return terminalMp4Buffer;
|
|
183
|
-
};
|
|
184
|
-
});
|
|
185
|
-
}
|
|
152
|
+
if (!debug) {
|
|
153
|
+
server.stop();
|
|
154
|
+
browser.close();
|
|
155
|
+
}
|
|
156
|
+
terminalRecords.svg = async () => {
|
|
157
|
+
const ansi = recordedFormats.textInViewport;
|
|
158
|
+
const terminalSvg = await renderTerminalSvg(ansi, svg);
|
|
159
|
+
return terminalSvg;
|
|
160
|
+
};
|
|
161
|
+
terminalRecords.gif = () => {
|
|
162
|
+
const terminalGifBuffer = Buffer.from(recordedFormats.gif, "binary");
|
|
163
|
+
return terminalGifBuffer;
|
|
164
|
+
};
|
|
165
|
+
terminalRecords.webm = () => {
|
|
166
|
+
const terminalWebmBuffer = Buffer.from(recordedFormats.video, "binary");
|
|
167
|
+
return terminalWebmBuffer;
|
|
168
|
+
};
|
|
169
|
+
terminalRecords.mp4 = async () => {
|
|
170
|
+
const terminalWebmBuffer = Buffer.from(terminalRecords.video, "binary");
|
|
171
|
+
const webmToMp4Namespace = await import("webm-to-mp4");
|
|
172
|
+
const webmToMp4 = webmToMp4Namespace.default;
|
|
173
|
+
const terminalMp4Buffer = Buffer.from(webmToMp4(terminalWebmBuffer));
|
|
174
|
+
return terminalMp4Buffer;
|
|
175
|
+
};
|
|
176
|
+
});
|
|
177
|
+
let stopped = false;
|
|
186
178
|
return {
|
|
187
179
|
write: async (data) => {
|
|
180
|
+
if (stopped) {
|
|
181
|
+
throw new Error("write after stop()");
|
|
182
|
+
}
|
|
188
183
|
const promises = [];
|
|
189
184
|
for (const writeCallback of writeCallbackSet) {
|
|
190
185
|
promises.push(writeCallback(data));
|
|
File without changes
|