buninu 0.4.3 → 0.4.6
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/CHANGELOG.md +27 -0
- package/README.md +2 -1
- package/apps/bunmsh/CHANGELOG.md +34 -0
- package/apps/bunmsh/index.js +7 -0
- package/apps/bunmsh/package.json +3 -3
- package/apps/bunmsh/test/curl.test.js +1 -1
- package/apps/bunmsh/test/reference.test.js +26 -12
- package/apps/bunmsh/test/shell1.test.js +1065 -0
- package/apps/bunmsh/test/shell2.test.js +457 -0
- package/apps/bunmsh/test/shell3.test.js +741 -0
- package/apps/jsgotty/gotty.js +26 -6
- package/apps/jsgotty/package-lock.json +2 -2
- package/apps/jsgotty/package.json +2 -1
- package/apps/jsgotty/tests/websocket-origin.test.js +54 -0
- package/apps/jsmdcui/README.md +14 -0
- package/apps/jsmdcui/package.json +1 -1
- package/apps/jsmdcui/runtime/help/cdp.md +6 -0
- package/apps/jsmdcui/runtime/help/help.md +14 -0
- package/apps/jsmdcui/runtime/jsplugins/cdp/cdp-server.js +112 -34
- package/apps/jsmdcui/runtime/jsplugins/cdp/cdp.js +3 -1
- package/apps/jsmdcui/src/index.js +10 -0
- package/apps/jsmdcui/tests/cdp-security.test.js +32 -0
- package/apps/jsmdcui/tests/cdp-websocket-test.html +46 -0
- package/apps/jsmdcui/tests/demo.test.js +1 -0
- package/bin/init.js +4 -4
- package/bin/multicall.sh +7 -5
- package/package.json +3 -2
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { chmodSync, mkdirSync, mkdtempSync, readlinkSync, rmSync, statSync, symlinkSync, utimesSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import {
|
|
6
|
+
bunShellFallbackArgv,
|
|
7
|
+
builtinNames,
|
|
8
|
+
createState,
|
|
9
|
+
decode,
|
|
10
|
+
execute,
|
|
11
|
+
colorProcessCommand,
|
|
12
|
+
colorProcessTable,
|
|
13
|
+
highlightShellCommand,
|
|
14
|
+
executeArgv,
|
|
15
|
+
formatProcessTable,
|
|
16
|
+
isJavaScriptMode,
|
|
17
|
+
needsMoreInput,
|
|
18
|
+
parse,
|
|
19
|
+
parsePosixProcessList,
|
|
20
|
+
parseWindowsProcessList,
|
|
21
|
+
runUnameFallback,
|
|
22
|
+
SH_COLORS,
|
|
23
|
+
taskkillFailure,
|
|
24
|
+
tokenize,
|
|
25
|
+
windowsKillCommand,
|
|
26
|
+
} from "../src/shell.js";
|
|
27
|
+
import {
|
|
28
|
+
CommandIndex,
|
|
29
|
+
FileIndex,
|
|
30
|
+
VariableIndex,
|
|
31
|
+
completionContext,
|
|
32
|
+
fitGhost,
|
|
33
|
+
firstPrefixMatch,
|
|
34
|
+
historyGhost,
|
|
35
|
+
javascriptContext,
|
|
36
|
+
nextGhostChunk,
|
|
37
|
+
prefixMatches,
|
|
38
|
+
variableCompletion,
|
|
39
|
+
variableContext,
|
|
40
|
+
} from "../src/completion.js";
|
|
41
|
+
import {
|
|
42
|
+
bunmshHistoryPath,
|
|
43
|
+
importedHistory,
|
|
44
|
+
parseBashHistory,
|
|
45
|
+
parseFishHistory,
|
|
46
|
+
readlineHistory,
|
|
47
|
+
saveBunmshHistory,
|
|
48
|
+
safeHistoryEntry,
|
|
49
|
+
} from "../src/history.js";
|
|
50
|
+
import { isLinkerPath } from "../single-exe/compiled.js";
|
|
51
|
+
import { readAssetText } from "../single-exe/assetsHelper.js";
|
|
52
|
+
import { fancyLs } from "../src/fancy-ls.js";
|
|
53
|
+
import { MOUSE_OFF, MOUSE_ON, mouseInput } from "../src/mouse.js";
|
|
54
|
+
import { canonicalEnvironment, environmentValue, homeRelativePath } from "../src/environment.js";
|
|
55
|
+
import { findIsRegularBuiltin } from "../src/find.js";
|
|
56
|
+
|
|
57
|
+
async function run(source, options = {}) {
|
|
58
|
+
const state = createState({
|
|
59
|
+
env: { HOME: "/tmp", ...options.env },
|
|
60
|
+
cwd: options.cwd ?? process.cwd(),
|
|
61
|
+
args: options.args ?? ["bunmsh"],
|
|
62
|
+
history: options.history ?? [],
|
|
63
|
+
mouseTracking: options.mouseTracking,
|
|
64
|
+
pathSearch: options.pathSearch,
|
|
65
|
+
});
|
|
66
|
+
const output = await execute(source, state, { capture: true });
|
|
67
|
+
return {
|
|
68
|
+
...output,
|
|
69
|
+
stdout: decode(output.stdout),
|
|
70
|
+
stderr: decode(output.stderr),
|
|
71
|
+
state,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
describe("CLI", () => {
|
|
75
|
+
test("detects highest-priority Bun. lines inside script files", async () => {
|
|
76
|
+
const cwd = new URL("..", import.meta.url).pathname;
|
|
77
|
+
const proc = Bun.spawn([process.execPath, "src/main.js", "test/t.sh"], {
|
|
78
|
+
cwd,
|
|
79
|
+
stdout: "pipe",
|
|
80
|
+
stderr: "pipe",
|
|
81
|
+
});
|
|
82
|
+
const [status, stdout, stderr] = await Promise.all([
|
|
83
|
+
proc.exited,
|
|
84
|
+
new Response(proc.stdout).text(),
|
|
85
|
+
new Response(proc.stderr).text(),
|
|
86
|
+
]);
|
|
87
|
+
expect(status).toBe(0);
|
|
88
|
+
expect(stdout).toBe(
|
|
89
|
+
`shell-before\n${Bun.version}\n${Math.cos(1)}\n` +
|
|
90
|
+
`{ message: 'shared', count: 1 }\n2\n` +
|
|
91
|
+
`{ message: 'shared', count: 2 }\n` +
|
|
92
|
+
`from command substitution: shared\nshell-after\n`,
|
|
93
|
+
);
|
|
94
|
+
expect(stderr).toBe("");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("--readme renders the bundled README and exits", async () => {
|
|
98
|
+
const proc = Bun.spawn([process.execPath, "src/main.js", "--readme"], {
|
|
99
|
+
cwd: new URL("..", import.meta.url).pathname,
|
|
100
|
+
stdout: "pipe",
|
|
101
|
+
stderr: "pipe",
|
|
102
|
+
});
|
|
103
|
+
const [status, stdout, stderr] = await Promise.all([
|
|
104
|
+
proc.exited,
|
|
105
|
+
new Response(proc.stdout).text(),
|
|
106
|
+
new Response(proc.stderr).text(),
|
|
107
|
+
]);
|
|
108
|
+
expect(status).toBe(0);
|
|
109
|
+
expect(stdout).toContain("bunmsh");
|
|
110
|
+
expect(stdout).toContain("Built-in documentation");
|
|
111
|
+
expect(stderr).toBe("");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("--changelog renders the bundled changelog and exits", async () => {
|
|
115
|
+
const proc = Bun.spawn([process.execPath, "src/main.js", "--changelog"], {
|
|
116
|
+
cwd: new URL("..", import.meta.url).pathname,
|
|
117
|
+
stdout: "pipe",
|
|
118
|
+
stderr: "pipe",
|
|
119
|
+
});
|
|
120
|
+
const [status, stdout, stderr] = await Promise.all([
|
|
121
|
+
proc.exited,
|
|
122
|
+
new Response(proc.stdout).text(),
|
|
123
|
+
new Response(proc.stderr).text(),
|
|
124
|
+
]);
|
|
125
|
+
expect(status).toBe(0);
|
|
126
|
+
expect(stdout).toContain("Changelog");
|
|
127
|
+
expect(stdout).toContain("0.1.8");
|
|
128
|
+
expect(stdout).toContain("serve");
|
|
129
|
+
expect(stderr).toBe("");
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("expands \\w in the interactive prompt", async () => {
|
|
133
|
+
const cwd = new URL("..", import.meta.url).pathname.replace(/\/$/, "");
|
|
134
|
+
const home = cwd.slice(0, cwd.lastIndexOf("/"));
|
|
135
|
+
const proc = Bun.spawn([process.execPath, "src/main.js", "-i"], {
|
|
136
|
+
cwd,
|
|
137
|
+
env: { ...process.env, HOME: home, PS1: "[\\w] " },
|
|
138
|
+
stdin: "pipe",
|
|
139
|
+
stdout: "pipe",
|
|
140
|
+
stderr: "pipe",
|
|
141
|
+
});
|
|
142
|
+
proc.stdin.write("exit\n");
|
|
143
|
+
proc.stdin.end();
|
|
144
|
+
const [status, stdout, stderr] = await Promise.all([
|
|
145
|
+
proc.exited,
|
|
146
|
+
new Response(proc.stdout).text(),
|
|
147
|
+
new Response(proc.stderr).text(),
|
|
148
|
+
]);
|
|
149
|
+
expect(status).toBe(0);
|
|
150
|
+
expect(stdout).toBe(`[~${cwd.slice(home.length)}] `);
|
|
151
|
+
expect(stderr).toBe("");
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("colors only the prompt dollar red after an error and ? reports it", async () => {
|
|
155
|
+
let transcript = "";
|
|
156
|
+
const terminal = new Bun.Terminal({
|
|
157
|
+
cols: 80,
|
|
158
|
+
rows: 24,
|
|
159
|
+
data(_terminal, data) { transcript += data.toString(); },
|
|
160
|
+
});
|
|
161
|
+
const proc = Bun.spawn({
|
|
162
|
+
cmd: [process.execPath, "src/main.js", "-i"],
|
|
163
|
+
cwd: new URL("..", import.meta.url).pathname,
|
|
164
|
+
env: { ...process.env, PS1: "$ " },
|
|
165
|
+
terminal,
|
|
166
|
+
});
|
|
167
|
+
try {
|
|
168
|
+
await Bun.sleep(120);
|
|
169
|
+
terminal.write("false\r");
|
|
170
|
+
await Bun.sleep(80);
|
|
171
|
+
terminal.write("?\r");
|
|
172
|
+
await Bun.sleep(80);
|
|
173
|
+
terminal.write("exit\r");
|
|
174
|
+
expect(await proc.exited).toBe(0);
|
|
175
|
+
} finally { terminal.close(); }
|
|
176
|
+
expect(transcript).toContain("\x1b[31m$\x1b[0m ");
|
|
177
|
+
expect(transcript).toContain("1\r\n");
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("shows a PS2 continuation prompt across a here-document, like mksh", async () => {
|
|
181
|
+
let transcript = "";
|
|
182
|
+
const terminal = new Bun.Terminal({
|
|
183
|
+
cols: 80,
|
|
184
|
+
rows: 24,
|
|
185
|
+
data(_terminal, data) { transcript += data.toString(); },
|
|
186
|
+
});
|
|
187
|
+
const proc = Bun.spawn({
|
|
188
|
+
cmd: [process.execPath, "src/main.js", "-i"],
|
|
189
|
+
cwd: new URL("..", import.meta.url).pathname,
|
|
190
|
+
env: { ...process.env, PS1: "$ " },
|
|
191
|
+
terminal,
|
|
192
|
+
});
|
|
193
|
+
try {
|
|
194
|
+
await Bun.sleep(120);
|
|
195
|
+
terminal.write("cat <<EOF\r");
|
|
196
|
+
await Bun.sleep(80);
|
|
197
|
+
terminal.write("hello world\r");
|
|
198
|
+
await Bun.sleep(80);
|
|
199
|
+
terminal.write("EOF\r");
|
|
200
|
+
await Bun.sleep(120);
|
|
201
|
+
terminal.write("echo after:$?\r");
|
|
202
|
+
await Bun.sleep(80);
|
|
203
|
+
terminal.write("exit\r");
|
|
204
|
+
expect(await proc.exited).toBe(0);
|
|
205
|
+
} finally { terminal.close(); }
|
|
206
|
+
// Bun.Terminal emits different repaint sequences through PRoot and native
|
|
207
|
+
// Android. Compare the visible terminal text rather than those sequences.
|
|
208
|
+
const visible = Bun.stripANSI(transcript)
|
|
209
|
+
.replaceAll("\r\r\n", "\n")
|
|
210
|
+
.replaceAll("\r\n", "\n");
|
|
211
|
+
expect(visible.match(/^> /gm) ?? []).toHaveLength(2);
|
|
212
|
+
expect(visible).toContain("hello world\n");
|
|
213
|
+
expect(visible).toContain("after:0\n");
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test("Ctrl-C during a here-document continuation returns to the primary prompt", async () => {
|
|
217
|
+
let transcript = "";
|
|
218
|
+
const terminal = new Bun.Terminal({
|
|
219
|
+
cols: 80,
|
|
220
|
+
rows: 24,
|
|
221
|
+
data(_terminal, data) { transcript += data.toString(); },
|
|
222
|
+
});
|
|
223
|
+
const proc = Bun.spawn({
|
|
224
|
+
cmd: [process.execPath, "src/main.js", "-i"],
|
|
225
|
+
cwd: new URL("..", import.meta.url).pathname,
|
|
226
|
+
env: { ...process.env, PS1: "$ " },
|
|
227
|
+
terminal,
|
|
228
|
+
});
|
|
229
|
+
try {
|
|
230
|
+
await Bun.sleep(120);
|
|
231
|
+
terminal.write("cat <<EOF\r");
|
|
232
|
+
await Bun.sleep(80);
|
|
233
|
+
terminal.write("partial\r");
|
|
234
|
+
await Bun.sleep(80);
|
|
235
|
+
terminal.write("\x03");
|
|
236
|
+
await Bun.sleep(120);
|
|
237
|
+
terminal.write("echo back:$?\r");
|
|
238
|
+
await Bun.sleep(80);
|
|
239
|
+
terminal.write("exit\r");
|
|
240
|
+
expect(await proc.exited).toBe(0);
|
|
241
|
+
} finally { terminal.close(); }
|
|
242
|
+
// The interrupt aborted the pending heredoc before `cat` ever ran, so
|
|
243
|
+
// $? reflects the Ctrl-C signal (130), not a successful `cat` (0).
|
|
244
|
+
expect(transcript).toContain("back:130\r\n");
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test("Ctrl-D during a here-document continuation ends the body, not the shell", async () => {
|
|
248
|
+
let transcript = "";
|
|
249
|
+
const terminal = new Bun.Terminal({
|
|
250
|
+
cols: 80,
|
|
251
|
+
rows: 24,
|
|
252
|
+
data(_terminal, data) { transcript += data.toString(); },
|
|
253
|
+
});
|
|
254
|
+
const proc = Bun.spawn({
|
|
255
|
+
cmd: [process.execPath, "src/main.js", "-i"],
|
|
256
|
+
cwd: new URL("..", import.meta.url).pathname,
|
|
257
|
+
env: { ...process.env, PS1: "$ " },
|
|
258
|
+
terminal,
|
|
259
|
+
});
|
|
260
|
+
try {
|
|
261
|
+
await Bun.sleep(120);
|
|
262
|
+
terminal.write("cat <<EOF\r");
|
|
263
|
+
await Bun.sleep(80);
|
|
264
|
+
terminal.write("partial body no terminator\r");
|
|
265
|
+
await Bun.sleep(80);
|
|
266
|
+
// Ctrl-D on the empty PS2 line: Node's readline closes itself here,
|
|
267
|
+
// like it would on an empty primary-prompt line, but that must only
|
|
268
|
+
// end the here-document (leniently, like a script hitting real EOF),
|
|
269
|
+
// not exit the whole shell.
|
|
270
|
+
terminal.write("\x04");
|
|
271
|
+
await Bun.sleep(150);
|
|
272
|
+
terminal.write("echo still-alive:$?\r");
|
|
273
|
+
await Bun.sleep(80);
|
|
274
|
+
terminal.write("exit\r");
|
|
275
|
+
expect(await proc.exited).toBe(0);
|
|
276
|
+
} finally { terminal.close(); }
|
|
277
|
+
expect(transcript).toContain("partial body no terminator");
|
|
278
|
+
expect(transcript).toContain("still-alive:0\r\n");
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
test("Ctrl-D at the primary prompt still exits the shell", async () => {
|
|
282
|
+
let transcript = "";
|
|
283
|
+
const terminal = new Bun.Terminal({
|
|
284
|
+
cols: 80,
|
|
285
|
+
rows: 24,
|
|
286
|
+
data(_terminal, data) { transcript += data.toString(); },
|
|
287
|
+
});
|
|
288
|
+
const proc = Bun.spawn({
|
|
289
|
+
cmd: [process.execPath, "src/main.js", "-i"],
|
|
290
|
+
cwd: new URL("..", import.meta.url).pathname,
|
|
291
|
+
env: { ...process.env, PS1: "$ " },
|
|
292
|
+
terminal,
|
|
293
|
+
});
|
|
294
|
+
try {
|
|
295
|
+
await Bun.sleep(120);
|
|
296
|
+
terminal.write("echo hi\r");
|
|
297
|
+
await Bun.sleep(100);
|
|
298
|
+
terminal.write("\x04");
|
|
299
|
+
expect(await proc.exited).toBe(0);
|
|
300
|
+
} finally { terminal.close(); }
|
|
301
|
+
expect(transcript).toContain("hi\r\n");
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test("exiting flushes history without waiting for the periodic autosave", async () => {
|
|
305
|
+
const home = mkdtempSync(join(tmpdir(), "bunmsh-history-exit-flush-"));
|
|
306
|
+
try {
|
|
307
|
+
const terminal = new Bun.Terminal({
|
|
308
|
+
cols: 80,
|
|
309
|
+
rows: 24,
|
|
310
|
+
data() {},
|
|
311
|
+
});
|
|
312
|
+
const proc = Bun.spawn({
|
|
313
|
+
cmd: [process.execPath, "src/main.js", "-i"],
|
|
314
|
+
cwd: new URL("..", import.meta.url).pathname,
|
|
315
|
+
env: { ...process.env, PS1: "$ ", HOME: home, BUNMSH_IMPORT_HISTORY: "off" },
|
|
316
|
+
terminal,
|
|
317
|
+
});
|
|
318
|
+
try {
|
|
319
|
+
await Bun.sleep(120);
|
|
320
|
+
terminal.write("echo flush-me\r");
|
|
321
|
+
await Bun.sleep(100);
|
|
322
|
+
terminal.write("exit\r");
|
|
323
|
+
expect(await proc.exited).toBe(0);
|
|
324
|
+
} finally { terminal.close(); }
|
|
325
|
+
// The 60s periodic autosave never had a chance to fire here; only the
|
|
326
|
+
// on-exit flush could have written this.
|
|
327
|
+
const saved = await importedHistory({ HOME: home, BUNMSH_IMPORT_HISTORY: "off" });
|
|
328
|
+
expect(saved).toContain("echo flush-me");
|
|
329
|
+
expect(saved).toContain("exit");
|
|
330
|
+
} finally { rmSync(home, { recursive: true, force: true }); }
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
test.each(["SIGTERM", "SIGHUP"])(
|
|
334
|
+
"%s while waiting at the prompt flushes history and exits with a signal-derived status",
|
|
335
|
+
async (signal) => {
|
|
336
|
+
const home = mkdtempSync(join(tmpdir(), `bunmsh-history-${signal}-`));
|
|
337
|
+
try {
|
|
338
|
+
const terminal = new Bun.Terminal({ cols: 80, rows: 24, data() {} });
|
|
339
|
+
const proc = Bun.spawn({
|
|
340
|
+
cmd: [process.execPath, "src/main.js", "-i"],
|
|
341
|
+
cwd: new URL("..", import.meta.url).pathname,
|
|
342
|
+
env: { ...process.env, PS1: "$ ", HOME: home, BUNMSH_IMPORT_HISTORY: "off" },
|
|
343
|
+
terminal,
|
|
344
|
+
});
|
|
345
|
+
try {
|
|
346
|
+
await Bun.sleep(120);
|
|
347
|
+
terminal.write(`echo ${signal.toLowerCase()}-flush\r`);
|
|
348
|
+
await Bun.sleep(100);
|
|
349
|
+
proc.kill(signal);
|
|
350
|
+
const expectedStatus = signal === "SIGHUP" ? 129 : 143;
|
|
351
|
+
expect(await proc.exited).toBe(expectedStatus);
|
|
352
|
+
} finally { terminal.close(); }
|
|
353
|
+
const saved = await importedHistory({ HOME: home, BUNMSH_IMPORT_HISTORY: "off" });
|
|
354
|
+
expect(saved).toContain(`echo ${signal.toLowerCase()}-flush`);
|
|
355
|
+
} finally { rmSync(home, { recursive: true, force: true }); }
|
|
356
|
+
},
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
test("preserves command output without a trailing newline before repainting", async () => {
|
|
360
|
+
const cwd = new URL("..", import.meta.url).pathname;
|
|
361
|
+
let transcript = "";
|
|
362
|
+
let terminal;
|
|
363
|
+
terminal = new Bun.Terminal({
|
|
364
|
+
cols: 80,
|
|
365
|
+
rows: 24,
|
|
366
|
+
data(_terminal, data) {
|
|
367
|
+
const text = data.toString();
|
|
368
|
+
transcript += text;
|
|
369
|
+
if (text.includes("\x1b[6n")) terminal.write("\x1b[4;4R");
|
|
370
|
+
},
|
|
371
|
+
});
|
|
372
|
+
const entry = join(new URL("..", import.meta.url).pathname, "src/main.js");
|
|
373
|
+
const proc = Bun.spawn({
|
|
374
|
+
cmd: [Bun.which("bun") || process.argv0, entry, "-i"],
|
|
375
|
+
cwd,
|
|
376
|
+
env: { ...process.env, PS1: "> " },
|
|
377
|
+
terminal,
|
|
378
|
+
});
|
|
379
|
+
try {
|
|
380
|
+
await Bun.sleep(150);
|
|
381
|
+
terminal.write("printf hello\r");
|
|
382
|
+
await Bun.sleep(150);
|
|
383
|
+
terminal.write("exit\r");
|
|
384
|
+
expect(await proc.exited).toBe(0);
|
|
385
|
+
} finally { terminal.close(); }
|
|
386
|
+
// The "↩️" marker must appear right after the unterminated output and its
|
|
387
|
+
// cursor-position query, before the next prompt is repainted. The exact
|
|
388
|
+
// bytes readline uses to repaint that prompt (padding cursor moves and
|
|
389
|
+
// erase-to-end-of-line codes around the "> " text) are an internal
|
|
390
|
+
// implementation detail of the readline version in use, not something
|
|
391
|
+
// this shell controls, so check the marker's position and that a fresh
|
|
392
|
+
// prompt follows it, rather than pinning readline's own redraw bytes.
|
|
393
|
+
const markerIndex = transcript.indexOf("hello\x1b[6n↩️\r\n");
|
|
394
|
+
expect(markerIndex).toBeGreaterThan(-1);
|
|
395
|
+
const afterMarker = transcript.slice(markerIndex);
|
|
396
|
+
expect(afterMarker.indexOf("> ")).toBeGreaterThan(-1);
|
|
397
|
+
expect(afterMarker.indexOf("> ")).toBeLessThan(afterMarker.indexOf("exit"));
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
test("Ctrl-C returns to the prompt while Ctrl-D exits, including during serve", async () => {
|
|
401
|
+
const cwd = mkdtempSync(join(tmpdir(), "bunmsh-sigint-"));
|
|
402
|
+
let transcript = "";
|
|
403
|
+
let terminal;
|
|
404
|
+
terminal = new Bun.Terminal({
|
|
405
|
+
cols: 80,
|
|
406
|
+
rows: 24,
|
|
407
|
+
data(_terminal, data) {
|
|
408
|
+
const text = data.toString();
|
|
409
|
+
transcript += text;
|
|
410
|
+
if (text.includes("\x1b[6n")) terminal.write("\x1b[4;1R");
|
|
411
|
+
},
|
|
412
|
+
});
|
|
413
|
+
const entry = join(new URL("..", import.meta.url).pathname, "src/main.js");
|
|
414
|
+
const proc = Bun.spawn({
|
|
415
|
+
cmd: [Bun.which("bun") || process.argv0, entry, "-i"],
|
|
416
|
+
cwd,
|
|
417
|
+
env: { ...process.env, PORT: "0", PS1: "> " },
|
|
418
|
+
terminal,
|
|
419
|
+
});
|
|
420
|
+
const waitFor = async (needle) => {
|
|
421
|
+
for (let attempt = 0; attempt < 100 && !transcript.includes(needle); attempt++)
|
|
422
|
+
await Bun.sleep(10);
|
|
423
|
+
expect(transcript).toContain(needle);
|
|
424
|
+
};
|
|
425
|
+
try {
|
|
426
|
+
await waitFor("> ");
|
|
427
|
+
terminal.write("\x03");
|
|
428
|
+
await Bun.sleep(30);
|
|
429
|
+
terminal.write("echo prompt-survived\r");
|
|
430
|
+
await waitFor("prompt-survived\r\n");
|
|
431
|
+
terminal.write("builtin serve\r");
|
|
432
|
+
await waitFor("http://localhost:");
|
|
433
|
+
const beforeQuit = transcript.length;
|
|
434
|
+
terminal.write("q\r");
|
|
435
|
+
for (let attempt = 0; attempt < 100 && !transcript.slice(beforeQuit).includes("> "); attempt++)
|
|
436
|
+
await Bun.sleep(10);
|
|
437
|
+
expect(transcript.slice(beforeQuit)).toContain("> ");
|
|
438
|
+
terminal.write("builtin serve\r");
|
|
439
|
+
for (let attempt = 0; attempt < 100 &&
|
|
440
|
+
!transcript.slice(beforeQuit).includes("http://localhost:"); attempt++)
|
|
441
|
+
await Bun.sleep(10);
|
|
442
|
+
const promptsWhileServing = transcript.split("> ").length - 1;
|
|
443
|
+
terminal.resize(79, 24);
|
|
444
|
+
await Bun.sleep(30);
|
|
445
|
+
expect(transcript.split("> ").length - 1).toBe(promptsWhileServing);
|
|
446
|
+
proc.kill("SIGINT");
|
|
447
|
+
await Bun.sleep(30);
|
|
448
|
+
terminal.write("echo server-survived\r");
|
|
449
|
+
await waitFor("server-survived\r\n");
|
|
450
|
+
terminal.write("\x04");
|
|
451
|
+
expect(await proc.exited).toBe(0);
|
|
452
|
+
} finally { terminal.close(); }
|
|
453
|
+
expect(transcript).toContain("prompt-survived\r\n");
|
|
454
|
+
expect(transcript).toContain("Serving ");
|
|
455
|
+
expect(transcript).toContain("server-survived\r\n");
|
|
456
|
+
});
|
|
457
|
+
});
|