jsmdcui 0.10.1 → 0.11.2
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 +73 -0
- package/README.md +88 -29
- package/package.json +1 -1
- package/runmd.mjs +51 -7
- package/runtime/help/help.md +88 -29
- package/runtime/jsplugins/example/example.js +3 -1
- package/single-exe/README.md +82 -25
- package/single-exe/README.md-rpc.js +623 -0
- package/single-exe/README.md-server.js +127 -0
- package/single-exe/README.md.back.js +2 -0
- package/single-exe/README.md.front.js +27 -0
- package/single-exe/README.md.html +325 -0
- package/single-exe/compiled.js +59 -1
- package/src/build-args.js +40 -0
- package/src/index.js +237 -22
- package/src/plugins/js-bridge.js +5 -0
- package/tests/compiled-runtime.test.js +132 -1
- package/tests/demo.test.js +136 -1
- package/tests/js-plugin-prompts.test.js +30 -0
- package/tests/wui-responsive-images.test.js +21 -2
- package/tests/wui.test.js +31 -0
package/tests/demo.test.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { expect, test } from "bun:test";
|
|
2
|
-
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { readMarkdownInput } from "../runmd.mjs";
|
|
6
6
|
|
|
7
7
|
const tui = join(import.meta.dir, "..", "tui");
|
|
8
|
+
const indexEntry = join(import.meta.dir, "..", "src", "index.js");
|
|
9
|
+
const demosDirectory = join(import.meta.dir, "..", "demos");
|
|
8
10
|
const bunBin = Bun.which("bun") || process.argv0;
|
|
9
11
|
|
|
10
12
|
test("--help describes the non-overwriting demo behavior", () => {
|
|
@@ -40,6 +42,139 @@ test("--demo-list lists root and automatically discovered demos", () => {
|
|
|
40
42
|
expect(output).toContain("--demo-imgtool --demo-image-processor");
|
|
41
43
|
});
|
|
42
44
|
|
|
45
|
+
test("--version lists every MDCUI build define", () => {
|
|
46
|
+
const result = Bun.spawnSync([bunBin, tui, "--version"], {
|
|
47
|
+
stdout: "pipe",
|
|
48
|
+
stderr: "pipe",
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
expect(result.exitCode).toBe(0);
|
|
52
|
+
const output = result.stdout.toString();
|
|
53
|
+
expect(output).toContain("MDCUI_DEFAULT_EDIT:");
|
|
54
|
+
expect(output).toContain("MDCUI_DEFAULT_DEMO:");
|
|
55
|
+
expect(output).toContain("MDCUI_DEFAULT_DEMO_WUI:");
|
|
56
|
+
expect(output).toContain("MDCUI_OVERWRITE_DEMO:");
|
|
57
|
+
expect(output).toContain("global.MDCUI_MAIN:");
|
|
58
|
+
expect(output).toContain("global.MDCUI_MAIN_BASE:");
|
|
59
|
+
|
|
60
|
+
const presenceResult = Bun.spawnSync(
|
|
61
|
+
[
|
|
62
|
+
bunBin,
|
|
63
|
+
"--define",
|
|
64
|
+
"MDCUI_DEFAULT_DEMO_WUI=0",
|
|
65
|
+
indexEntry,
|
|
66
|
+
"--version",
|
|
67
|
+
],
|
|
68
|
+
{
|
|
69
|
+
stdout: "pipe",
|
|
70
|
+
stderr: "pipe",
|
|
71
|
+
},
|
|
72
|
+
);
|
|
73
|
+
expect(presenceResult.exitCode).toBe(0);
|
|
74
|
+
expect(presenceResult.stdout.toString()).toContain(
|
|
75
|
+
"MDCUI_DEFAULT_DEMO_WUI: enabled",
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("--demo names allow Chinese but reject whitespace", () => {
|
|
80
|
+
const unicodeResult = Bun.spawnSync(
|
|
81
|
+
[bunBin, tui, "--demo-不存在的程式", "-cat", "-encoding", "utf8"],
|
|
82
|
+
{
|
|
83
|
+
stdout: "pipe",
|
|
84
|
+
stderr: "pipe",
|
|
85
|
+
},
|
|
86
|
+
);
|
|
87
|
+
expect(unicodeResult.exitCode).toBe(2);
|
|
88
|
+
expect(unicodeResult.stderr.toString()).toContain(
|
|
89
|
+
"Unknown demo --demo-不存在的程式",
|
|
90
|
+
);
|
|
91
|
+
expect(unicodeResult.stderr.toString()).not.toContain("Invalid demo option");
|
|
92
|
+
|
|
93
|
+
const whitespaceResult = Bun.spawnSync(
|
|
94
|
+
[bunBin, tui, "--demo-中文 程式", "-cat", "-encoding", "utf8"],
|
|
95
|
+
{
|
|
96
|
+
stdout: "pipe",
|
|
97
|
+
stderr: "pipe",
|
|
98
|
+
},
|
|
99
|
+
);
|
|
100
|
+
expect(whitespaceResult.exitCode).toBe(2);
|
|
101
|
+
expect(whitespaceResult.stderr.toString()).toContain("Invalid demo option");
|
|
102
|
+
expect(whitespaceResult.stderr.toString()).toContain(
|
|
103
|
+
"whitespace is not allowed",
|
|
104
|
+
);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("Unicode MDCUI_MAIN overwrite starts the embedded WUI server", async () => {
|
|
108
|
+
const directory = await mkdtemp(join(tmpdir(), "jsmdcui-main-unicode-"));
|
|
109
|
+
const sourceDirectory = join(directory, "source");
|
|
110
|
+
const workDirectory = join(directory, "work");
|
|
111
|
+
const demoName = `中文工具-${crypto.randomUUID()}`;
|
|
112
|
+
const markdownName = `${demoName}.md`;
|
|
113
|
+
const sourcePath = join(sourceDirectory, markdownName);
|
|
114
|
+
const workPath = join(workDirectory, markdownName);
|
|
115
|
+
const bundledDemoPath = join(demosDirectory, markdownName);
|
|
116
|
+
const preloadPath = join(directory, "mock-serve.mjs");
|
|
117
|
+
const markdown = `# 中文工具
|
|
118
|
+
|
|
119
|
+
\`\`\`js front
|
|
120
|
+
export async function run() {
|
|
121
|
+
return await rpc.answer();
|
|
122
|
+
}
|
|
123
|
+
\`\`\`
|
|
124
|
+
|
|
125
|
+
\`\`\`js back
|
|
126
|
+
import { helper } from "./helper.js";
|
|
127
|
+
export function answer() {
|
|
128
|
+
return helper();
|
|
129
|
+
}
|
|
130
|
+
\`\`\`
|
|
131
|
+
`;
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
await mkdir(sourceDirectory);
|
|
135
|
+
await mkdir(workDirectory);
|
|
136
|
+
await writeFile(sourcePath, markdown);
|
|
137
|
+
await writeFile(
|
|
138
|
+
join(sourceDirectory, "helper.js"),
|
|
139
|
+
'export function helper() { return "ok"; }\n',
|
|
140
|
+
);
|
|
141
|
+
await writeFile(workPath, "# stale file with a different byte length\n");
|
|
142
|
+
await writeFile(
|
|
143
|
+
preloadPath,
|
|
144
|
+
`Bun.serve = () => ({ port: 34567, stop() {} });\n`,
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
const result = Bun.spawnSync(
|
|
148
|
+
[
|
|
149
|
+
bunBin,
|
|
150
|
+
"--preload",
|
|
151
|
+
preloadPath,
|
|
152
|
+
"--define",
|
|
153
|
+
`global.MDCUI_MAIN=${JSON.stringify(sourcePath)}`,
|
|
154
|
+
indexEntry,
|
|
155
|
+
"--wui",
|
|
156
|
+
`--demo-${demoName}`,
|
|
157
|
+
"--overwrite-demo",
|
|
158
|
+
],
|
|
159
|
+
{
|
|
160
|
+
cwd: workDirectory,
|
|
161
|
+
stdout: "pipe",
|
|
162
|
+
stderr: "pipe",
|
|
163
|
+
},
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
expect(result.exitCode).toBe(0);
|
|
167
|
+
expect(await readFile(workPath, "utf8")).toBe(markdown);
|
|
168
|
+
const stderr = Bun.stripANSI(result.stderr.toString());
|
|
169
|
+
expect(stderr).toContain("[mdcui] Starting embedded WUI server:");
|
|
170
|
+
expect(stderr).toContain(sourcePath);
|
|
171
|
+
expect(stderr).not.toContain("Cannot find module");
|
|
172
|
+
} finally {
|
|
173
|
+
await rm(bundledDemoPath, { force: true });
|
|
174
|
+
await rm(directory, { recursive: true, force: true });
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
43
178
|
test("--export-cdp-maze writes and overwrites the bundled solver", async () => {
|
|
44
179
|
const dir = await mkdtemp(join(tmpdir(), "jsmdcui-export-cdp-maze-"));
|
|
45
180
|
const outputPath = join(dir, "cdp-maze.js");
|
|
@@ -44,3 +44,33 @@ test("micro prompt APIs delegate and return synchronously", () => {
|
|
|
44
44
|
else globalThis.$ = previousSelector;
|
|
45
45
|
}
|
|
46
46
|
});
|
|
47
|
+
|
|
48
|
+
test("current buffer exposes its actual MDCUI module source", () => {
|
|
49
|
+
const previousMicro = globalThis.micro;
|
|
50
|
+
const previousSelector = globalThis.$;
|
|
51
|
+
const previousMain = global.MDCUI_MAIN;
|
|
52
|
+
const buffer = {
|
|
53
|
+
path: "/tmp/app.md",
|
|
54
|
+
_useBundledMdcuiModules: true,
|
|
55
|
+
};
|
|
56
|
+
const app = { buffer };
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
global.MDCUI_MAIN = "/build/app.md";
|
|
60
|
+
const micro = buildMicroGlobal({ _app: app, _ctx: null, on() {} });
|
|
61
|
+
|
|
62
|
+
expect(micro.CurPane().Buf.MdcuiModuleSource).toBe("embedded");
|
|
63
|
+
buffer._useBundledMdcuiModules = false;
|
|
64
|
+
expect(micro.CurPane().Buf.MdcuiModuleSource).toBe("external");
|
|
65
|
+
delete global.MDCUI_MAIN;
|
|
66
|
+
buffer._useBundledMdcuiModules = true;
|
|
67
|
+
expect(micro.CurPane().Buf.MdcuiModuleSource).toBe("external");
|
|
68
|
+
} finally {
|
|
69
|
+
if (previousMain === undefined) delete global.MDCUI_MAIN;
|
|
70
|
+
else global.MDCUI_MAIN = previousMain;
|
|
71
|
+
if (previousMicro === undefined) delete globalThis.micro;
|
|
72
|
+
else globalThis.micro = previousMicro;
|
|
73
|
+
if (previousSelector === undefined) delete globalThis.$;
|
|
74
|
+
else globalThis.$ = previousSelector;
|
|
75
|
+
}
|
|
76
|
+
});
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { afterEach, expect, test } from "bun:test";
|
|
2
|
-
import { mkdtempSync, rmSync } from "node:fs";
|
|
2
|
+
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
-
import { createWui } from "../runmd.mjs";
|
|
5
|
+
import { createWui, extractJs } from "../runmd.mjs";
|
|
6
6
|
|
|
7
7
|
const temporaryDirectories = [];
|
|
8
8
|
|
|
@@ -33,3 +33,22 @@ test("createWui injects the image rule into an existing document head", async ()
|
|
|
33
33
|
expect(html.indexOf("max-width: 100%")).toBeLessThan(html.indexOf("</head>"));
|
|
34
34
|
expect(html.match(/document\.md\.front\.js/g)).toHaveLength(1);
|
|
35
35
|
});
|
|
36
|
+
|
|
37
|
+
test("bundling mode creates a browser-only front entry", async () => {
|
|
38
|
+
const directory = mkdtempSync(join(tmpdir(), "jsmdcui-wui-bundling-"));
|
|
39
|
+
temporaryDirectories.push(directory);
|
|
40
|
+
const markdownPath = join(directory, "bundle.md");
|
|
41
|
+
const source = "```js front\nexport function pav() { return rpc }\n```";
|
|
42
|
+
const markdown = await extractJs(source, markdownPath, { bundling: true });
|
|
43
|
+
const html = await createWui(markdown, markdownPath, { bundling: true });
|
|
44
|
+
const frontSource = readFileSync(`${markdownPath}.tmpfs.js`, "utf8");
|
|
45
|
+
const installerSource = readFileSync(`${markdownPath}.tmpfi.js`, "utf8");
|
|
46
|
+
|
|
47
|
+
expect(frontSource).toContain("const rpc = wuiRpcClient");
|
|
48
|
+
expect(frontSource).not.toContain("globalThis.process");
|
|
49
|
+
expect(frontSource).not.toContain('import("./bundle.md.front.js")');
|
|
50
|
+
expect(installerSource).toContain('import * as frontMod from "./bundle.md.tmpfs.js"');
|
|
51
|
+
expect(installerSource).toContain("Object.assign(window, frontMod)");
|
|
52
|
+
expect(html).toContain('src="./bundle.md.tmpfi.js"');
|
|
53
|
+
expect(html).not.toContain("bundle.md.front.js");
|
|
54
|
+
});
|
package/tests/wui.test.js
CHANGED
|
@@ -4,6 +4,9 @@ import { tmpdir } from "node:os";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { readMarkdownInput, writeRuntimeFiles } from "../runmd.mjs";
|
|
6
6
|
|
|
7
|
+
const indexEntry = join(import.meta.dir, "..", "src", "index.js");
|
|
8
|
+
const bunBin = Bun.which("bun") || process.argv0;
|
|
9
|
+
|
|
7
10
|
test("WUI writes the bundled testapp.md when it is missing", async () => {
|
|
8
11
|
const dir = await mkdtemp(join(tmpdir(), "jsmdcui-wui-"));
|
|
9
12
|
const mdpath = join(dir, "testapp.md");
|
|
@@ -43,3 +46,31 @@ test("generated WUI server falls back to a system port when 3000 is occupied", a
|
|
|
43
46
|
await rm(dir, { recursive: true, force: true });
|
|
44
47
|
}
|
|
45
48
|
});
|
|
49
|
+
|
|
50
|
+
test("WUI reports an external server before starting it", async () => {
|
|
51
|
+
const dir = await mkdtemp(join(tmpdir(), "jsmdcui-wui-source-report-"));
|
|
52
|
+
const mdpath = join(dir, "external.md");
|
|
53
|
+
const preloadPath = join(dir, "mock-serve.mjs");
|
|
54
|
+
try {
|
|
55
|
+
await writeFile(mdpath, "# External WUI\n");
|
|
56
|
+
await writeFile(
|
|
57
|
+
preloadPath,
|
|
58
|
+
`Bun.serve = () => ({ port: 34567, stop() {} });\n`,
|
|
59
|
+
);
|
|
60
|
+
const result = Bun.spawnSync(
|
|
61
|
+
[bunBin, "--preload", preloadPath, indexEntry, "--wui", mdpath],
|
|
62
|
+
{
|
|
63
|
+
cwd: dir,
|
|
64
|
+
stdout: "pipe",
|
|
65
|
+
stderr: "pipe",
|
|
66
|
+
},
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
expect(result.exitCode).toBe(0);
|
|
70
|
+
const stderr = Bun.stripANSI(result.stderr.toString());
|
|
71
|
+
expect(stderr).toContain("[mdcui] Starting external WUI server:");
|
|
72
|
+
expect(stderr).toContain(`${mdpath}-server.js`);
|
|
73
|
+
} finally {
|
|
74
|
+
await rm(dir, { recursive: true, force: true });
|
|
75
|
+
}
|
|
76
|
+
});
|