jsmdcui 0.7.0 → 0.9.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/CHANGELOG.md +127 -0
- package/README.md +189 -19
- package/cdp-maze.js +141 -0
- package/demos/maze.md +149 -0
- package/demos/todo-zh.md +98 -0
- package/demos/todo.md +98 -0
- package/edit +9 -0
- package/llm-maze.txt +94 -0
- package/package.json +1 -1
- package/runmd.mjs +44 -2
- package/runtime/help/cdp.md +5 -0
- package/runtime/help/help.md +189 -19
- package/runtime/jsplugins/cdp/cdp-server.js +26 -2
- package/runtime/jsplugins/cdp/cdp.js +60 -51
- package/runtime/jsplugins/chapter/chapter.js +6 -3
- package/runtime/jsplugins/example/example.js +12 -7
- package/runtime/syntax/markdown.yaml +1 -0
- package/single-exe/README.md +223 -45
- package/single-exe/compiled.js +6 -3
- package/single-exe/entry.mjs +4 -3
- package/single-exe/packAssets.sh +1 -1
- package/src/cui/fence-events.mjs +106 -0
- package/src/cui/id-collision.mjs +10 -28
- package/src/cui/kitty-debug.mjs +1 -1
- package/src/cui/kitty-images.mjs +11 -1
- package/src/cui/rpc.mjs +6 -3
- package/src/cui/server.mjs +13 -2
- package/src/index.js +425 -72
- package/src/lua/engine.js +1 -4
- package/src/platform/terminal.js +5 -0
- package/src/plugins/js-bridge.js +157 -8
- package/tests/compiled-runtime.test.js +8 -0
- package/tests/demo.test.js +40 -0
- package/tests/fence-events.test.js +405 -0
- package/tests/id-collision.test.js +11 -0
- package/tests/js-plugin-prompts.test.js +46 -0
- package/tests/key-event.md +10 -0
- package/tests/kitty-demo.md +1 -1
- package/tests/kitty-images.test.js +22 -0
- package/tests/platform-terminal.test.js +8 -0
- package/tests/textarea.md +8 -0
- package/tests/wui.test.js +16 -1
- package/demo.resized.jpg +0 -0
- package/src/runtime/compiled.js +0 -25
package/src/lua/engine.js
CHANGED
|
@@ -2,7 +2,6 @@ import { existsSync } from "node:fs";
|
|
|
2
2
|
import { readInternalAssetBytes } from "../runtime/assets.js";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
|
-
import { isCompiledBinary, resolveCompiledBaseDir } from "../runtime/compiled.js";
|
|
6
5
|
import { REPO_ROOT } from "../../single-exe/compiled.js";
|
|
7
6
|
|
|
8
7
|
export async function createLuaEngine() {
|
|
@@ -44,9 +43,7 @@ async function resolveLuaWasmLocation() {
|
|
|
44
43
|
}
|
|
45
44
|
}
|
|
46
45
|
|
|
47
|
-
const fallbackPath =
|
|
48
|
-
? join(resolveCompiledBaseDir({ argv: process.argv }), wasmAssetPath)
|
|
49
|
-
: join(REPO_ROOT, wasmAssetPath);
|
|
46
|
+
const fallbackPath = join(REPO_ROOT, wasmAssetPath);
|
|
50
47
|
|
|
51
48
|
return existsSync(fallbackPath) ? fallbackPath : undefined;
|
|
52
49
|
}
|
package/src/plugins/js-bridge.js
CHANGED
|
@@ -4,6 +4,7 @@ import { dirname, join, basename, extname } from "node:path";
|
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
import { assetPath, hasInternalAssets, listInternalAssetDirs, listInternalAssetPaths, readInternalAssetBytes } from "../runtime/assets.js";
|
|
7
|
+
import { isMdcuiEncoding } from "../runtime/encodings.js";
|
|
7
8
|
import { newMessage, newMessageAtLine, MTError, MTWarning, MTInfo } from "../buffer/message.js";
|
|
8
9
|
import { Loc } from "../buffer/loc.js";
|
|
9
10
|
|
|
@@ -752,6 +753,56 @@ function _findBlock(lines, selector) {
|
|
|
752
753
|
return null;
|
|
753
754
|
}
|
|
754
755
|
|
|
756
|
+
export function findTuiBlockAtLine(lines, lineIndex) {
|
|
757
|
+
return findTuiBlockInIndex(buildTuiBlockIndex(lines), lineIndex);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
export function buildTuiBlockIndex(lines, declarations = null) {
|
|
761
|
+
const blocks = [];
|
|
762
|
+
for (let start = 0; start < lines.length; start++) {
|
|
763
|
+
const header = _blockHeader(lines[start]);
|
|
764
|
+
if (!header) continue;
|
|
765
|
+
let end = lines.length;
|
|
766
|
+
for (let y = start + 1; y < lines.length; y++) {
|
|
767
|
+
const line = String(lines[y] ?? "");
|
|
768
|
+
const rest = line.startsWith(header.indent)
|
|
769
|
+
? line.slice(header.indent.length)
|
|
770
|
+
: line;
|
|
771
|
+
if (header.kind === "fenced") {
|
|
772
|
+
const closing = rest.match(/^(`{3,})\s*$/);
|
|
773
|
+
if (!closing || closing[1].length < header.fenceLength) continue;
|
|
774
|
+
} else if (!/^(?:└─|╰─|\+-)\s*$/.test(rest)) {
|
|
775
|
+
continue;
|
|
776
|
+
}
|
|
777
|
+
end = y;
|
|
778
|
+
break;
|
|
779
|
+
}
|
|
780
|
+
const declaration = header.id ? declarations?.get(header.id) : null;
|
|
781
|
+
if (
|
|
782
|
+
declarations == null
|
|
783
|
+
|| (
|
|
784
|
+
declaration?.tag === header.tag
|
|
785
|
+
&& declaration.events?.size > 0
|
|
786
|
+
)
|
|
787
|
+
) blocks.push({ start, end, header });
|
|
788
|
+
start = Math.max(start, end);
|
|
789
|
+
}
|
|
790
|
+
return blocks;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
export function findTuiBlockInIndex(blocks, lineIndex) {
|
|
794
|
+
let low = 0;
|
|
795
|
+
let high = blocks.length - 1;
|
|
796
|
+
while (low <= high) {
|
|
797
|
+
const middle = (low + high) >> 1;
|
|
798
|
+
const block = blocks[middle];
|
|
799
|
+
if (lineIndex <= block.start) high = middle - 1;
|
|
800
|
+
else if (lineIndex >= block.end) low = middle + 1;
|
|
801
|
+
else return block;
|
|
802
|
+
}
|
|
803
|
+
return null;
|
|
804
|
+
}
|
|
805
|
+
|
|
755
806
|
function _blockValue(lines, selector) {
|
|
756
807
|
const block = _findBlock(lines, selector);
|
|
757
808
|
if (!block) return undefined;
|
|
@@ -983,7 +1034,7 @@ function _mutateTuiHeadingList(buffer, heading, method, args) {
|
|
|
983
1034
|
buffer._mdcuiHeadingTaskListAnchors = new Map();
|
|
984
1035
|
buffer._mdcuiHeadingTaskListAnchors.set(heading.ordinal, { index: start, indent: list.indent });
|
|
985
1036
|
}
|
|
986
|
-
|
|
1037
|
+
spliceTuiBufferLines(buffer, start, end - start, replacement.lines, {
|
|
987
1038
|
styles: replacement.styles,
|
|
988
1039
|
ansi: replacement.ansi,
|
|
989
1040
|
});
|
|
@@ -1003,14 +1054,14 @@ function _mutateTuiHeadingList(buffer, heading, method, args) {
|
|
|
1003
1054
|
indent: list.indent,
|
|
1004
1055
|
});
|
|
1005
1056
|
}
|
|
1006
|
-
|
|
1057
|
+
spliceTuiBufferLines(buffer, item.start, item.end - item.start, []);
|
|
1007
1058
|
return item.value;
|
|
1008
1059
|
}
|
|
1009
1060
|
|
|
1010
1061
|
const replacement = _tuiTaskItemReplacement(list.indent, args);
|
|
1011
1062
|
if (replacement.lines.length === 0) return list.items.length;
|
|
1012
1063
|
const start = method === "push" ? list.end : list.first;
|
|
1013
|
-
|
|
1064
|
+
spliceTuiBufferLines(buffer, start, 0, replacement.lines, {
|
|
1014
1065
|
styles: replacement.styles,
|
|
1015
1066
|
ansi: replacement.ansi,
|
|
1016
1067
|
});
|
|
@@ -1018,8 +1069,10 @@ function _mutateTuiHeadingList(buffer, heading, method, args) {
|
|
|
1018
1069
|
return list.items.length + replacement.lines.length;
|
|
1019
1070
|
}
|
|
1020
1071
|
|
|
1021
|
-
function
|
|
1072
|
+
export function spliceTuiBufferLines(buffer, start, deleteCount, replacement, replacementMeta = {}) {
|
|
1022
1073
|
const oldCursor = buffer.cursor ? { ...buffer.cursor } : null;
|
|
1074
|
+
buffer._mdcuiFenceBlockIndex = null;
|
|
1075
|
+
buffer._mdcuiControlBlockIndex = null;
|
|
1023
1076
|
buffer.lines.splice(start, deleteCount, ...replacement);
|
|
1024
1077
|
|
|
1025
1078
|
if (Array.isArray(buffer._ansiStyleLines)) {
|
|
@@ -1065,24 +1118,70 @@ function _spliceBufferLines(buffer, start, deleteCount, replacement, replacement
|
|
|
1065
1118
|
buffer.ensureCursor?.();
|
|
1066
1119
|
}
|
|
1067
1120
|
|
|
1121
|
+
export function insertTuiTextareaNewline(buffer, block) {
|
|
1122
|
+
if (!buffer || block?.header?.tag !== "textarea") return false;
|
|
1123
|
+
const prefix = block.header.indent + block.header.bodyMarker + " ";
|
|
1124
|
+
const line = String(buffer.lines?.[buffer.cursor.y] ?? "");
|
|
1125
|
+
const left = line.slice(0, buffer.cursor.x);
|
|
1126
|
+
const right = line.slice(buffer.cursor.x);
|
|
1127
|
+
buffer.lines[buffer.cursor.y] = left;
|
|
1128
|
+
spliceTuiBufferLines(buffer, buffer.cursor.y + 1, 0, [prefix + right]);
|
|
1129
|
+
buffer.cursor.y++;
|
|
1130
|
+
buffer.cursor.x = prefix.length;
|
|
1131
|
+
buffer.ensureCursor?.();
|
|
1132
|
+
return true;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
export function mergeTuiTextareaBackward(buffer, block) {
|
|
1136
|
+
if (!buffer || block?.header?.tag !== "textarea") return false;
|
|
1137
|
+
const prefix = block.header.indent + block.header.bodyMarker + " ";
|
|
1138
|
+
if (buffer.cursor.x > prefix.length || buffer.cursor.y <= block.start + 1) return false;
|
|
1139
|
+
const previousY = buffer.cursor.y - 1;
|
|
1140
|
+
const previousLength = String(buffer.lines?.[previousY] ?? "").length;
|
|
1141
|
+
const line = String(buffer.lines?.[buffer.cursor.y] ?? "");
|
|
1142
|
+
const body = line.startsWith(prefix) ? line.slice(prefix.length) : line;
|
|
1143
|
+
buffer.lines[previousY] += body;
|
|
1144
|
+
spliceTuiBufferLines(buffer, buffer.cursor.y, 1, []);
|
|
1145
|
+
buffer.cursor.y = previousY;
|
|
1146
|
+
buffer.cursor.x = previousLength;
|
|
1147
|
+
buffer.ensureCursor?.();
|
|
1148
|
+
return true;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
export function mergeTuiTextareaForward(buffer, block) {
|
|
1152
|
+
if (!buffer || block?.header?.tag !== "textarea") return false;
|
|
1153
|
+
const line = String(buffer.lines?.[buffer.cursor.y] ?? "");
|
|
1154
|
+
if (buffer.cursor.x < line.length || buffer.cursor.y >= block.end - 1) return false;
|
|
1155
|
+
const prefix = block.header.indent + block.header.bodyMarker + " ";
|
|
1156
|
+
const nextLine = String(buffer.lines?.[buffer.cursor.y + 1] ?? "");
|
|
1157
|
+
const body = nextLine.startsWith(prefix) ? nextLine.slice(prefix.length) : nextLine;
|
|
1158
|
+
buffer.lines[buffer.cursor.y] += body;
|
|
1159
|
+
spliceTuiBufferLines(buffer, buffer.cursor.y + 1, 1, []);
|
|
1160
|
+
buffer.ensureCursor?.();
|
|
1161
|
+
return true;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1068
1164
|
function _setBlockValue(buffer, selector, value) {
|
|
1069
1165
|
const lines = buffer.lines;
|
|
1070
1166
|
const block = _findBlock(lines, selector);
|
|
1071
1167
|
if (!block) return false;
|
|
1072
1168
|
|
|
1073
|
-
const
|
|
1169
|
+
const normalizedValue = String(value ?? "").replace(/\r\n?/g, "\n");
|
|
1170
|
+
if ((_blockValue(lines, selector) ?? "") === normalizedValue) return true;
|
|
1171
|
+
buffer.pushUndo?.(true);
|
|
1172
|
+
const values = normalizedValue.split("\n");
|
|
1074
1173
|
const contentStart = block.start + 1;
|
|
1075
1174
|
const capacity = Math.max(0, block.end - contentStart);
|
|
1076
1175
|
|
|
1077
1176
|
if (block.header.kind === "fenced") {
|
|
1078
1177
|
const replacement = values.map((line) => block.header.indent + line);
|
|
1079
|
-
|
|
1178
|
+
spliceTuiBufferLines(buffer, contentStart, capacity, replacement);
|
|
1080
1179
|
return true;
|
|
1081
1180
|
}
|
|
1082
1181
|
|
|
1083
1182
|
const rowPrefix = block.header.indent + block.header.bodyMarker + " ";
|
|
1084
1183
|
const replacement = values.map((line) => rowPrefix + line);
|
|
1085
|
-
|
|
1184
|
+
spliceTuiBufferLines(buffer, contentStart, capacity, replacement);
|
|
1086
1185
|
return true;
|
|
1087
1186
|
}
|
|
1088
1187
|
|
|
@@ -1229,7 +1328,12 @@ export function buildMicroGlobal(jsManager) {
|
|
|
1229
1328
|
// ── Messaging ─────────────────────────────────────────────────
|
|
1230
1329
|
Log: (...args) => console.log(...args),
|
|
1231
1330
|
TermMessage: (msg) => { const app = getApp(); if (app) { app.message = String(msg); if (app._started) app.render?.(); } },
|
|
1232
|
-
alert:
|
|
1331
|
+
alert: (msg) => { const app = getApp(); return app ? app.protectedAlert(msg) : console.log(String(msg)); },
|
|
1332
|
+
confirm: (msg) => { const app = getApp(); return app ? app.protectedConfirm(msg) : false; },
|
|
1333
|
+
prompt: (msg, defaultValue = "") => {
|
|
1334
|
+
const app = getApp();
|
|
1335
|
+
return app ? app.protectedPrompt(msg, defaultValue) : defaultValue;
|
|
1336
|
+
},
|
|
1233
1337
|
|
|
1234
1338
|
// ── Buffer line access (1-based line numbers; omit → cursor line) ─
|
|
1235
1339
|
|
|
@@ -1250,6 +1354,7 @@ export function buildMicroGlobal(jsManager) {
|
|
|
1250
1354
|
const y = lineNumber != null ? Number(lineNumber) - 1 : buf.cursor.y;
|
|
1251
1355
|
if (y < 0 || y >= buf.lines.length) return;
|
|
1252
1356
|
buf.pushUndo?.();
|
|
1357
|
+
buf._mdcuiFenceBlockIndex = null;
|
|
1253
1358
|
const parts = String(text).replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
|
|
1254
1359
|
buf.lines.splice(y, 1, ...parts);
|
|
1255
1360
|
buf.invalidateHighlightFrom?.(y, { force: parts.length > 1 });
|
|
@@ -1266,6 +1371,7 @@ export function buildMicroGlobal(jsManager) {
|
|
|
1266
1371
|
const y = lineNumber != null ? Number(lineNumber) - 1 : buf.cursor.y;
|
|
1267
1372
|
if (y < 0 || y >= buf.lines.length) return;
|
|
1268
1373
|
buf.pushUndo?.();
|
|
1374
|
+
buf._mdcuiFenceBlockIndex = null;
|
|
1269
1375
|
if (buf.lines.length === 1) {
|
|
1270
1376
|
buf.lines[0] = "";
|
|
1271
1377
|
} else {
|
|
@@ -1300,12 +1406,55 @@ export function buildMicroGlobal(jsManager) {
|
|
|
1300
1406
|
return app?.buffer?.lines.join("\n") ?? "";
|
|
1301
1407
|
},
|
|
1302
1408
|
|
|
1409
|
+
// Returns the rendered ANSI document when available, otherwise plain text.
|
|
1410
|
+
getAllAnsiText() {
|
|
1411
|
+
const buffer = getApp()?.buffer;
|
|
1412
|
+
return typeof buffer?._mdcuiAnsiText === "string"
|
|
1413
|
+
? buffer._mdcuiAnsiText
|
|
1414
|
+
: buffer?.lines.join("\n") ?? "";
|
|
1415
|
+
},
|
|
1416
|
+
|
|
1417
|
+
// Activates a 1-based rendered-buffer cell in MDCUI; otherwise falls back to goto.
|
|
1418
|
+
async clickBufferCell(column = 1, line = 1) {
|
|
1419
|
+
const app = getApp();
|
|
1420
|
+
const buffer = app?.buffer;
|
|
1421
|
+
if (!app || !buffer) return false;
|
|
1422
|
+
|
|
1423
|
+
const targetLine = Math.max(1, Math.trunc(Number(line)) || 1);
|
|
1424
|
+
const targetColumn = Math.max(1, Math.trunc(Number(column)) || 1);
|
|
1425
|
+
if (!isMdcuiEncoding(buffer.encoding ?? buffer.Settings?.encoding)) {
|
|
1426
|
+
await app.handleCommand(`goto ${targetLine}:${targetColumn}`);
|
|
1427
|
+
app.render?.();
|
|
1428
|
+
return true;
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
const y = Math.min(targetLine - 1, Math.max(0, buffer.lines.length - 1));
|
|
1432
|
+
const x = Math.min(targetColumn - 1, String(buffer.lines[y] ?? "").length);
|
|
1433
|
+
buffer.cursor = { x, y };
|
|
1434
|
+
const handled = await app.handleMdcuiCellCallback(buffer, y, x, "mouse");
|
|
1435
|
+
app.render?.();
|
|
1436
|
+
return handled;
|
|
1437
|
+
},
|
|
1438
|
+
|
|
1439
|
+
// Sends terminal input through the App's normal parser and event pipeline.
|
|
1440
|
+
async _dispatchRawInput(raw) {
|
|
1441
|
+
const app = getApp();
|
|
1442
|
+
if (!app) return false;
|
|
1443
|
+
const bytes = typeof raw === "string"
|
|
1444
|
+
? new TextEncoder().encode(raw)
|
|
1445
|
+
: raw;
|
|
1446
|
+
await app._dispatchInput(bytes);
|
|
1447
|
+
app.render?.();
|
|
1448
|
+
return true;
|
|
1449
|
+
},
|
|
1450
|
+
|
|
1303
1451
|
// Replaces the entire buffer content with text (may contain newlines).
|
|
1304
1452
|
putAllText(text) {
|
|
1305
1453
|
const app = getApp();
|
|
1306
1454
|
if (!app?.buffer) return;
|
|
1307
1455
|
const buf = app.buffer;
|
|
1308
1456
|
buf.pushUndo?.();
|
|
1457
|
+
buf._mdcuiFenceBlockIndex = null;
|
|
1309
1458
|
buf.lines = String(text).replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
|
|
1310
1459
|
if (buf.lines.length === 0) buf.lines = [""];
|
|
1311
1460
|
buf.invalidateHighlightFrom?.(0, { force: true });
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import { isCompiledBinary as isSingleExeCompiled } from "../single-exe/compiled.js";
|
|
3
|
+
|
|
4
|
+
test("single-exe recognizes Bun compiled virtual paths", () => {
|
|
5
|
+
expect(isSingleExeCompiled(["bun", "/$bunfs/root/app.js"])).toBe(true);
|
|
6
|
+
expect(isSingleExeCompiled(["bun", "B:/~BUN/root/app.js"])).toBe(true);
|
|
7
|
+
expect(isSingleExeCompiled(["bun", "/project/src/index.js"])).toBe(false);
|
|
8
|
+
});
|
package/tests/demo.test.js
CHANGED
|
@@ -21,6 +21,7 @@ test("--help describes the non-overwriting demo behavior", () => {
|
|
|
21
21
|
expect(output.match(/Open it in the TUI and write 5 generated files beside it/g)?.length).toBe(2);
|
|
22
22
|
expect(output).toContain("--demo-imgtool");
|
|
23
23
|
expect(output).toContain("--demo-imgtool-zh");
|
|
24
|
+
expect(output).toContain("--cdp-maze");
|
|
24
25
|
});
|
|
25
26
|
|
|
26
27
|
test("--demo-list lists root and automatically discovered demos", () => {
|
|
@@ -38,6 +39,27 @@ test("--demo-list lists root and automatically discovered demos", () => {
|
|
|
38
39
|
expect(output).toContain("--demo-imgtool --demo-image-processor");
|
|
39
40
|
});
|
|
40
41
|
|
|
42
|
+
test("--export-cdp-maze writes and overwrites the bundled solver", async () => {
|
|
43
|
+
const dir = await mkdtemp(join(tmpdir(), "jsmdcui-export-cdp-maze-"));
|
|
44
|
+
const outputPath = join(dir, "cdp-maze.js");
|
|
45
|
+
try {
|
|
46
|
+
await writeFile(outputPath, "old solver\n");
|
|
47
|
+
const result = Bun.spawnSync([bunBin, tui, "--export-cdp-maze"], {
|
|
48
|
+
cwd: dir,
|
|
49
|
+
stdout: "pipe",
|
|
50
|
+
stderr: "pipe",
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
expect(result.exitCode).toBe(0);
|
|
54
|
+
expect(result.stdout.toString()).toContain(`Wrote ${outputPath}`);
|
|
55
|
+
const exported = await readFile(outputPath, "utf8");
|
|
56
|
+
expect(exported).toContain("export async function runCdpMaze");
|
|
57
|
+
expect(exported).not.toContain("old solver");
|
|
58
|
+
} finally {
|
|
59
|
+
await rm(dir, { recursive: true, force: true });
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
41
63
|
test("--demo writes bundled testapp.md to cwd before opening it", async () => {
|
|
42
64
|
const dir = await mkdtemp(join(tmpdir(), "jsmdcui-demo-"));
|
|
43
65
|
try {
|
|
@@ -94,6 +116,24 @@ test("--demo-<filename> automatically loads a matching demos file", async () =>
|
|
|
94
116
|
}
|
|
95
117
|
});
|
|
96
118
|
|
|
119
|
+
test("--cdp-maze loads the maze demo", async () => {
|
|
120
|
+
const dir = await mkdtemp(join(tmpdir(), "jsmdcui-cdp-maze-"));
|
|
121
|
+
try {
|
|
122
|
+
const result = Bun.spawnSync([bunBin, tui, "--cdp-maze", "-cat", "-encoding", "utf8"], {
|
|
123
|
+
cwd: dir,
|
|
124
|
+
stdout: "pipe",
|
|
125
|
+
stderr: "pipe",
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
expect(result.exitCode).toBe(0);
|
|
129
|
+
const written = await readFile(join(dir, "maze.md"), "utf8");
|
|
130
|
+
expect(written).toContain("Put the cursor here");
|
|
131
|
+
expect(Bun.stripANSI(result.stdout.toString())).toContain("Put the cursor here");
|
|
132
|
+
} finally {
|
|
133
|
+
await rm(dir, { recursive: true, force: true });
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
97
137
|
test("--demo-<filename> preserves an existing local copy", async () => {
|
|
98
138
|
const dir = await mkdtemp(join(tmpdir(), "jsmdcui-demo-generic-existing-"));
|
|
99
139
|
const existing = "# Keep my selector demo\n";
|