jsmdcui 0.5.0 → 0.7.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.
@@ -0,0 +1,147 @@
1
+ import { expect, test } from "bun:test";
2
+ import { mkdtemp, rm, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { createTuiSelector } from "../src/plugins/js-bridge.js";
6
+
7
+ const tui = join(import.meta.dir, "..", "tui");
8
+ const bunBin = Bun.which("bun") || process.argv0;
9
+
10
+ async function runCheck(markdown) {
11
+ const dir = await mkdtemp(join(tmpdir(), "jsmdcui-check-"));
12
+ const file = join(dir, "app.md");
13
+ await writeFile(file, markdown);
14
+ const result = Bun.spawnSync([bunBin, tui, "--check", file], { stdout: "pipe", stderr: "pipe" });
15
+ await rm(dir, { recursive: true, force: true });
16
+ return result;
17
+ }
18
+
19
+ test("--check exits 0 and reports unique IDs", async () => {
20
+ const result = await runCheck("## Input Path\n\n```text#output-path\nvalue\n```\n");
21
+ expect(result.exitCode).toBe(0);
22
+ const raw = result.stdout.toString();
23
+ const output = Bun.stripANSI(raw);
24
+ expect(output).toContain("No ID collisions found");
25
+ expect(output).toContain("PASSED");
26
+ expect(raw).toContain(`${Bun.color("#00d75f", "ansi-16m")}\x1b[1mPASSED\x1b[0m`);
27
+ });
28
+
29
+ test("--check reports heading/fenced-block collisions with line details", async () => {
30
+ const result = await runCheck("## Write Status\n\n```text#write-status\nwaiting\n```\n");
31
+ const output = Bun.stripANSI(result.stdout.toString());
32
+ expect(result.exitCode).toBe(1);
33
+ expect(output).toContain("FAIL — Found 1 colliding ID(s)");
34
+ expect(output).toContain("ID #write-status");
35
+ expect(output).toContain("Declarations: 2");
36
+ expect(output).toContain("Line 1");
37
+ expect(output).toContain("Type: heading");
38
+ expect(output).toContain("Source: ## Write Status");
39
+ expect(output).toContain("Line 3");
40
+ expect(output).toContain("Type: text fenced block");
41
+ expect(output).toContain("Source: ```text#write-status");
42
+ expect(result.stdout.toString()).toContain(`${Bun.color("#ff3030", "ansi-16m")}\x1b[1mFAILED\x1b[0m`);
43
+ expect(output.lastIndexOf("FAILED")).toBeGreaterThan(output.lastIndexOf("Suggested fix"));
44
+ });
45
+
46
+ test("--check reports duplicate fenced-block IDs", async () => {
47
+ const result = await runCheck("```text#myid\na\n```\n\n```textarea#myid\nb\n```\n");
48
+ expect(result.exitCode).toBe(1);
49
+ const output = Bun.stripANSI(result.stdout.toString());
50
+ expect(output).toContain("ID #myid");
51
+ expect(output).toContain("Declarations: 2");
52
+ expect(output).toContain("FAILED");
53
+ });
54
+
55
+ test("--check includes IDs on arbitrary fenced-block tags", async () => {
56
+ const result = await runCheck("```hello#myid\nyou\n```\n\n# myid\n");
57
+ const output = Bun.stripANSI(result.stdout.toString());
58
+ expect(result.exitCode).toBe(1);
59
+ expect(output).toContain("ID #myid");
60
+ expect(output).toContain("Declarations: 2");
61
+ expect(output).toContain("Type: hello fenced block");
62
+ expect(output).toContain("Fenced blocks: 1");
63
+ expect(output).toContain("FAILED");
64
+ });
65
+
66
+ test("--check rejects duplicate Markdown heading IDs before Bun adds suffixes", async () => {
67
+ const result = await runCheck("# myid\n\n# myid\n");
68
+ const output = Bun.stripANSI(result.stdout.toString());
69
+ expect(result.exitCode).toBe(1);
70
+ expect(output).toContain("ID #myid");
71
+ expect(output).toContain("Declarations: 2");
72
+ expect(output).toContain("Line 1");
73
+ expect(output).toContain("Line 3");
74
+ expect(output).toContain("FAILED");
75
+ });
76
+
77
+ const identityMatrix = [
78
+ {
79
+ name: "tag without class",
80
+ info: "text#myid",
81
+ selectable: true,
82
+ },
83
+ {
84
+ name: "tag with class",
85
+ info: "text#myid.field",
86
+ selectable: true,
87
+ },
88
+ {
89
+ name: "no tag and no class",
90
+ info: "#myid",
91
+ selectable: false,
92
+ },
93
+ {
94
+ name: "no tag with class",
95
+ info: "#myid.field",
96
+ selectable: false,
97
+ },
98
+ ];
99
+
100
+ for (const scenario of identityMatrix) {
101
+ test(`--check identity matrix: ${scenario.name}`, async () => {
102
+ const result = await runCheck(`\`\`\`${scenario.info}\nvalue\n\`\`\`\n\n# myid\n`);
103
+ const output = Bun.stripANSI(result.stdout.toString());
104
+ expect(result.exitCode).toBe(scenario.selectable ? 1 : 0);
105
+ expect(output).toContain(`Fenced blocks: ${scenario.selectable ? 1 : 0}`);
106
+ if (scenario.selectable) {
107
+ expect(output).toContain("ID #myid");
108
+ expect(output).toContain("Declarations: 2");
109
+ expect(output).toContain("FAILED");
110
+ } else {
111
+ expect(output).toContain("Selectable IDs: 1");
112
+ expect(output).toContain("PASSED");
113
+ }
114
+ });
115
+ }
116
+
117
+ test("--check ignores tag and class differences when fenced-block IDs collide", async () => {
118
+ const result = await runCheck(
119
+ "```text#myid.left.primary\na\n```\n\n```json#myid.right.secondary\nb\n```\n",
120
+ );
121
+ const output = Bun.stripANSI(result.stdout.toString());
122
+ expect(result.exitCode).toBe(1);
123
+ expect(output).toContain("ID #myid");
124
+ expect(output).toContain("Declarations: 2");
125
+ expect(output).toContain("Type: text fenced block");
126
+ expect(output).toContain("Type: json fenced block");
127
+ expect(output).toContain("FAILED");
128
+ });
129
+
130
+ test("TUI $ selector finds one ID across every tag/class query combination", () => {
131
+ const markdown = "```text#myid.field.primary\nvalue\n```\n";
132
+ const buffer = { lines: markdown.trimEnd().split("\n") };
133
+ const $ = createTuiSelector(() => buffer);
134
+ const selectors = [
135
+ "text#myid.field",
136
+ "text#myid",
137
+ "#myid.field",
138
+ "#myid",
139
+ ];
140
+ for (const selector of selectors) expect($(selector).val()).toBe("value");
141
+ });
142
+
143
+ test("--check requires exactly one file", () => {
144
+ const result = Bun.spawnSync([bunBin, tui, "--check"], { stdout: "pipe", stderr: "pipe" });
145
+ expect(result.exitCode).toBe(2);
146
+ expect(result.stderr.toString()).toContain("Usage: jsmdcui --check FILE.md");
147
+ });
@@ -0,0 +1,184 @@
1
+ # Kitty scrolling test
2
+
3
+ Prelude filler line 01
4
+ Prelude filler line 02
5
+ Prelude filler line 03
6
+ Prelude filler line 04
7
+ Prelude filler line 05
8
+ Prelude filler line 06
9
+ Prelude filler line 07
10
+ Prelude filler line 08
11
+ Prelude filler line 09
12
+ Prelude filler line 10
13
+ Prelude filler line 11
14
+ Prelude filler line 12
15
+ Prelude filler line 13
16
+ Prelude filler line 14
17
+ Prelude filler line 15
18
+ Prelude filler line 16
19
+ Prelude filler line 17
20
+ Prelude filler line 18
21
+ Prelude filler line 19
22
+ Prelude filler line 20
23
+ Prelude filler line 21
24
+ Prelude filler line 22
25
+ Prelude filler line 23
26
+ Prelude filler line 24
27
+ Prelude filler line 25
28
+ Prelude filler line 26
29
+ Prelude filler line 27
30
+ Prelude filler line 28
31
+ Prelude filler line 29
32
+ Prelude filler line 30
33
+ Prelude filler line 31
34
+ Prelude filler line 32
35
+ Prelude filler line 33
36
+ Prelude filler line 34
37
+ Prelude filler line 35
38
+ Prelude filler line 36
39
+ Prelude filler line 37
40
+ Prelude filler line 38
41
+ Prelude filler line 39
42
+ Prelude filler line 40
43
+ Prelude filler line 41
44
+ Prelude filler line 42
45
+ Prelude filler line 43
46
+ Prelude filler line 44
47
+ Prelude filler line 45
48
+ Prelude filler line 46
49
+ Prelude filler line 47
50
+ Prelude filler line 48
51
+ Prelude filler line 49
52
+ Prelude filler line 50
53
+
54
+ ## Before image 01
55
+ Content before the image, line 01.
56
+
57
+ ## Before image 02
58
+ Content before the image, line 02.
59
+
60
+ ## Before image 03
61
+ Content before the image, line 03.
62
+
63
+ ## Before image 04
64
+ Content before the image, line 04.
65
+
66
+ ## Before image 05
67
+ Content before the image, line 05.
68
+
69
+ ## Before image 06
70
+ Content before the image, line 06.
71
+
72
+ ## Before image 07
73
+ Content before the image, line 07.
74
+
75
+ ## Before image 08
76
+ Content before the image, line 08.
77
+
78
+ ## Before image 09
79
+ Content before the image, line 09.
80
+
81
+ ## Before image 10
82
+ Content before the image, line 10.
83
+
84
+ ## Image anchor
85
+
86
+ ![img](../demo.resized.jpg)
87
+
88
+ ## After image 01
89
+ Content after the image, line 01.
90
+
91
+ ## After image 02
92
+ Content after the image, line 02.
93
+
94
+ ## After image 03
95
+ Content after the image, line 03.
96
+
97
+ ## After image 04
98
+ Content after the image, line 04.
99
+
100
+ ## After image 05
101
+ Content after the image, line 05.
102
+
103
+ ## After image 06
104
+ Content after the image, line 06.
105
+
106
+ ## After image 07
107
+ Content after the image, line 07.
108
+
109
+ ## After image 08
110
+ Content after the image, line 08.
111
+
112
+ ## After image 09
113
+ Content after the image, line 09.
114
+
115
+ ## After image 10
116
+ Content after the image, line 10.
117
+
118
+ ## After image 11
119
+ Content after the image, line 11.
120
+
121
+ ## After image 12
122
+ Content after the image, line 12.
123
+
124
+ ## After image 13
125
+ Content after the image, line 13.
126
+
127
+ ## After image 14
128
+ Content after the image, line 14.
129
+
130
+ ## After image 15
131
+ Content after the image, line 15.
132
+
133
+ ![img](../demo.jpg)
134
+
135
+ Epilogue filler line 01
136
+ Epilogue filler line 02
137
+ Epilogue filler line 03
138
+ Epilogue filler line 04
139
+ Epilogue filler line 05
140
+ Epilogue filler line 06
141
+ Epilogue filler line 07
142
+ Epilogue filler line 08
143
+ Epilogue filler line 09
144
+ Epilogue filler line 10
145
+ Epilogue filler line 11
146
+ Epilogue filler line 12
147
+ Epilogue filler line 13
148
+ Epilogue filler line 14
149
+ Epilogue filler line 15
150
+ Epilogue filler line 16
151
+ Epilogue filler line 17
152
+ Epilogue filler line 18
153
+ Epilogue filler line 19
154
+ Epilogue filler line 20
155
+ Epilogue filler line 21
156
+ Epilogue filler line 22
157
+ Epilogue filler line 23
158
+ Epilogue filler line 24
159
+ Epilogue filler line 25
160
+ Epilogue filler line 26
161
+ Epilogue filler line 27
162
+ Epilogue filler line 28
163
+ Epilogue filler line 29
164
+ Epilogue filler line 30
165
+ Epilogue filler line 31
166
+ Epilogue filler line 32
167
+ Epilogue filler line 33
168
+ Epilogue filler line 34
169
+ Epilogue filler line 35
170
+ Epilogue filler line 36
171
+ Epilogue filler line 37
172
+ Epilogue filler line 38
173
+ Epilogue filler line 39
174
+ Epilogue filler line 40
175
+ Epilogue filler line 41
176
+ Epilogue filler line 42
177
+ Epilogue filler line 43
178
+ Epilogue filler line 44
179
+ Epilogue filler line 45
180
+ Epilogue filler line 46
181
+ Epilogue filler line 47
182
+ Epilogue filler line 48
183
+ Epilogue filler line 49
184
+ Epilogue filler line 50
@@ -0,0 +1,147 @@
1
+ import { expect, test } from "bun:test";
2
+ import { mkdtemp, writeFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { tmpdir } from "node:os";
5
+ import { fitKittyImageToWidth, prepareKittyImages } from "../src/cui/kitty-images.mjs";
6
+ import { Screen } from "../src/screen/screen.js";
7
+
8
+ const ONE_PIXEL_PNG = Buffer.from(
9
+ "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=",
10
+ "base64",
11
+ );
12
+
13
+ test("Bun-rendered Markdown image links reserve rows and retain Kitty data", async () => {
14
+ const dir = await mkdtemp(join(tmpdir(), "jsmdcui-kitty-"));
15
+ const markdownPath = join(dir, "app.md");
16
+ await writeFile(join(dir, "pixel.png"), ONE_PIXEL_PNG);
17
+ const ansi = Bun.markdown.ansi("before\n\n![pixel](pixel.png)\n\nafter\n", {
18
+ hyperlinks: true,
19
+ columns: 40,
20
+ });
21
+ const result = await prepareKittyImages(ansi, markdownPath, 40);
22
+
23
+ expect(result.images).toHaveLength(1);
24
+ expect(result.images[0]).toMatchObject({ mime: "image/png", cols: 1, rows: 1 });
25
+ expect(result.images[0].data.equals(ONE_PIXEL_PNG)).toBe(true);
26
+ expect(Bun.stripANSI(result.rendered)).toContain("📷 pixel");
27
+ });
28
+
29
+ test("remote Kitty images require allowUrl and use the configured HTTP byte fetcher", async () => {
30
+ const imageUrl = "https://example.test/assets/pixel.png";
31
+ const ansi = Bun.markdown.ansi(`![pixel](${imageUrl})`, {
32
+ hyperlinks: true,
33
+ columns: 40,
34
+ });
35
+ const requests = [];
36
+ const fetchHttpBytes = async (url) => {
37
+ requests.push(url);
38
+ return ONE_PIXEL_PNG;
39
+ };
40
+
41
+ const blocked = await prepareKittyImages(ansi, "/tmp/app.md", 40, { fetchHttpBytes });
42
+ expect(blocked.images).toHaveLength(0);
43
+ expect(requests).toHaveLength(0);
44
+
45
+ const allowed = await prepareKittyImages(ansi, "/tmp/app.md", 40, {
46
+ allowUrl: true,
47
+ fetchHttpBytes,
48
+ });
49
+ expect(requests).toEqual([imageUrl]);
50
+ expect(allowed.images).toHaveLength(1);
51
+ expect(allowed.images[0].path).toBe(imageUrl);
52
+ });
53
+
54
+ test("remote Markdown resolves relative Kitty image URLs against its source URL", async () => {
55
+ const ansi = Bun.markdown.ansi("![pixel](../images/pixel.png?size=1)", {
56
+ hyperlinks: true,
57
+ columns: 40,
58
+ });
59
+ const requests = [];
60
+ const result = await prepareKittyImages(
61
+ ansi,
62
+ "https://example.test/docs/guide/app.md",
63
+ 40,
64
+ {
65
+ allowUrl: true,
66
+ fetchHttpBytes: async (url) => {
67
+ requests.push(url);
68
+ return ONE_PIXEL_PNG;
69
+ },
70
+ },
71
+ );
72
+
73
+ expect(requests).toEqual(["https://example.test/docs/images/pixel.png?size=1"]);
74
+ expect(result.images).toHaveLength(1);
75
+ });
76
+
77
+ test("Kitty image sizing subtracts the gutter width without depending on remaining rows", () => {
78
+ // A 100-column pane with a 5-column gutter has 95 usable image columns.
79
+ expect(fitKittyImageToWidth({ cols: 100, rows: 40 }, 95)).toEqual({ cols: 95, rows: 38 });
80
+ expect(fitKittyImageToWidth({ cols: 62, rows: 21 }, 95)).toEqual({ cols: 62, rows: 21 });
81
+ expect(fitKittyImageToWidth({ cols: 62, rows: 21 }, 40)).toEqual({ cols: 40, rows: 14 });
82
+ });
83
+
84
+ test("Screen emits chunked Kitty placement at the requested cell and deletes only its IDs", () => {
85
+ const screen = new Screen({ mouse: false, kittyMode: "extended" });
86
+ const writes = [];
87
+ screen.write = (value) => writes.push(String(value));
88
+ screen.setKittyImages([{
89
+ id: 77,
90
+ x: 3,
91
+ y: 4,
92
+ cols: 5,
93
+ rows: 2,
94
+ sourceX: 0,
95
+ sourceY: 0,
96
+ sourceWidth: 1,
97
+ sourceHeight: 1,
98
+ mime: "image/png",
99
+ data: ONE_PIXEL_PNG,
100
+ }]);
101
+ screen.Show();
102
+ const first = writes.join("");
103
+ expect(first).toContain("\x1b[5;4H");
104
+ expect(first).toContain("\x1b_Ga=T,f=100,t=d,i=77,p=77,q=2,x=0,y=0,w=1,h=1,c=5,r=2,C=1,U=image/png,m=0;");
105
+
106
+ writes.length = 0;
107
+ screen.setKittyImages([]);
108
+ screen.Show();
109
+ expect(writes.join("")).toContain("\x1b_Ga=d,d=i,i=77,q=2;");
110
+ expect(writes.join("")).not.toContain("d=a");
111
+
112
+ writes.length = 0;
113
+ screen.setKittyImages([{
114
+ id: 77, x: 3, y: 2, cols: 5, rows: 2,
115
+ sourceX: 0, sourceY: 0, sourceWidth: 1, sourceHeight: 1,
116
+ mime: "image/png", data: ONE_PIXEL_PNG,
117
+ }]);
118
+ screen.Show();
119
+ const replaced = writes.join("");
120
+ expect(replaced).toContain("\x1b_Ga=p,i=77,p=77,q=2,x=0,y=0,w=1,h=1,c=5,r=2,C=1,U=image/png;");
121
+ expect(replaced).not.toContain("a=T");
122
+ expect(replaced).not.toContain(ONE_PIXEL_PNG.toString("base64"));
123
+ });
124
+
125
+ test("Screen gates Kitty output by mode and compat mode omits the MIME U extension", () => {
126
+ const image = {
127
+ id: 88, x: 0, y: 0, cols: 1, rows: 1,
128
+ sourceX: 0, sourceY: 0, sourceWidth: 1, sourceHeight: 1,
129
+ mime: "image/jpeg", data: ONE_PIXEL_PNG,
130
+ };
131
+
132
+ const off = new Screen({ mouse: false });
133
+ const offWrites = [];
134
+ off.write = (value) => offWrites.push(String(value));
135
+ off.setKittyImages([image]);
136
+ off.Show();
137
+ expect(offWrites.join("")).not.toContain("\x1b_G");
138
+
139
+ const compat = new Screen({ mouse: false, kittyMode: "compat" });
140
+ const compatWrites = [];
141
+ compat.write = (value) => compatWrites.push(String(value));
142
+ compat.setKittyImages([image]);
143
+ compat.Show();
144
+ const output = compatWrites.join("");
145
+ expect(output).toContain("\x1b_Ga=T");
146
+ expect(output).not.toContain("U=image/jpeg");
147
+ });
@@ -0,0 +1,33 @@
1
+ import { expect, test } from "bun:test";
2
+ import {
3
+ toggleTaskCheckboxBeforeColumn,
4
+ updateAnsiTaskCheckbox,
5
+ } from "../src/cui/task-checkbox.mjs";
6
+
7
+ test("task checkbox toggle reports its position and new checked state", () => {
8
+ expect(toggleTaskCheckboxBeforeColumn(" ☐ todo", 4)).toEqual({
9
+ line: " ☒ todo",
10
+ toggled: true,
11
+ checkboxAt: 2,
12
+ checked: true,
13
+ });
14
+ expect(toggleTaskCheckboxBeforeColumn(" ☒ todo", 4)).toEqual({
15
+ line: " ☐ todo",
16
+ toggled: true,
17
+ checkboxAt: 2,
18
+ checked: false,
19
+ });
20
+ });
21
+
22
+ test("task checkbox toggle updates Bun Markdown ANSI color with the glyph", () => {
23
+ const unchecked = " \x1b[2m☐ \x1b[0mtodo";
24
+ const checked = " \x1b[32m☒ \x1b[0mtodo";
25
+ expect(updateAnsiTaskCheckbox(unchecked, 2, true)).toBe(checked);
26
+ expect(updateAnsiTaskCheckbox(checked, 2, false)).toBe(unchecked);
27
+ });
28
+
29
+ test("ANSI checkbox lookup ignores OSC 8 hyperlink metadata", () => {
30
+ const prefix = "\x1b]8;;https://example.test\x1b\\link\x1b]8;;\x1b\\ ";
31
+ expect(updateAnsiTaskCheckbox(`${prefix}\x1b[2m☐ \x1b[0mtodo`, 5, true))
32
+ .toBe(`${prefix}\x1b[32m☒ \x1b[0mtodo`);
33
+ });
@@ -0,0 +1,35 @@
1
+ import { afterEach, expect, test } from "bun:test";
2
+ import { mkdtempSync, rmSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { createWui } from "../runmd.mjs";
6
+
7
+ const temporaryDirectories = [];
8
+
9
+ afterEach(() => {
10
+ for (const directory of temporaryDirectories.splice(0)) {
11
+ rmSync(directory, { recursive: true, force: true });
12
+ }
13
+ });
14
+
15
+ test("createWui constrains images to their content width", async () => {
16
+ const directory = mkdtempSync(join(tmpdir(), "jsmdcui-wui-image-"));
17
+ temporaryDirectories.push(directory);
18
+ const markdownPath = join(directory, "image.md");
19
+ const html = await createWui("![wide](wide.png)", markdownPath);
20
+
21
+ expect(html).toContain("img {\n max-width: 100%;\n height: auto;\n}");
22
+ expect(html.match(/max-width: 100%/g)).toHaveLength(1);
23
+ expect(html.match(/image\.md\.front\.js/g)).toHaveLength(1);
24
+ });
25
+
26
+ test("createWui injects the image rule into an existing document head", async () => {
27
+ const directory = mkdtempSync(join(tmpdir(), "jsmdcui-wui-document-"));
28
+ temporaryDirectories.push(directory);
29
+ const markdownPath = join(directory, "document.md");
30
+ const source = "<!doctype html><html><head><title>x</title></head><body><img src=\"wide.png\"></body></html>";
31
+ const html = await createWui(source, markdownPath);
32
+
33
+ expect(html.indexOf("max-width: 100%")).toBeLessThan(html.indexOf("</head>"));
34
+ expect(html.match(/document\.md\.front\.js/g)).toHaveLength(1);
35
+ });
@@ -0,0 +1,30 @@
1
+ import { expect, test } from "bun:test";
2
+ import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { readMarkdownInput } from "../runmd.mjs";
6
+
7
+ test("WUI writes the bundled testapp.md when it is missing", async () => {
8
+ const dir = await mkdtemp(join(tmpdir(), "jsmdcui-wui-"));
9
+ const mdpath = join(dir, "testapp.md");
10
+ try {
11
+ const source = await readMarkdownInput(mdpath);
12
+ expect(source).toContain("# jsmdcui");
13
+ expect(await readFile(mdpath, "utf8")).toBe(source);
14
+ } finally {
15
+ await rm(dir, { recursive: true, force: true });
16
+ }
17
+ });
18
+
19
+ test("WUI preserves an existing testapp.md", async () => {
20
+ const dir = await mkdtemp(join(tmpdir(), "jsmdcui-wui-existing-"));
21
+ const mdpath = join(dir, "testapp.md");
22
+ const existing = "# Keep my WUI demo\n";
23
+ try {
24
+ await writeFile(mdpath, existing);
25
+ expect(await readMarkdownInput(mdpath)).toBe(existing);
26
+ expect(await readFile(mdpath, "utf8")).toBe(existing);
27
+ } finally {
28
+ await rm(dir, { recursive: true, force: true });
29
+ }
30
+ });
package/tui CHANGED
@@ -1,5 +1,7 @@
1
1
  #!/bin/sh
2
2
 
3
- sd=$(dirname "$(realpath "$0")")
3
+ ':' //; sd=$(dirname "$(realpath "$0")")
4
4
 
5
- exec bun "$sd"/src/index.js "$@"
5
+ ':' //; exec bun "$sd"/src/index.js "$@"
6
+
7
+ await import('./src/index.js')
package/wui CHANGED
@@ -1,5 +1,9 @@
1
1
  #!/bin/sh
2
2
 
3
- sd=$(dirname "$(realpath "$0")")
3
+ ':' //; sd=$(dirname "$(realpath "$0")")
4
4
 
5
- exec bun "$sd"/src/index.js --wui "$@"
5
+ ':' //; exec bun "$sd"/src/index.js --wui "$@"
6
+
7
+ process.argv.splice(2,0,'--wui')
8
+
9
+ await import('./src/index.js')