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/src/index.js CHANGED
@@ -128,7 +128,8 @@ import { isHex3Encoding, isMdcuiEncoding } from "./runtime/encodings.js";
128
128
  import { createInterface } from "node:readline/promises";
129
129
 
130
130
  import pkg from "../package.json" with { type: "json" };
131
- import { REPO_ROOT,IS_COMPILED, buildExecutable,buildEarlyExit } from "../single-exe/compiled.js";
131
+ import { REPO_ROOT,IS_COMPILED, buildExecutable,buildEarlyExit,stringifyNonPrimitiveDefineValues } from "../single-exe/compiled.js";
132
+ import { expandBuildMdAliases } from "./build-args.js";
132
133
 
133
134
 
134
135
  const __filename = fileURLToPath(import.meta.url);
@@ -186,6 +187,7 @@ const MDCUI_DEFAULT_DEMO_ENABLED = typeof MDCUI_DEFAULT_DEMO !== "undefined";
186
187
  const MDCUI_DEFAULT_DEMO_WUI_ENABLED = typeof MDCUI_DEFAULT_DEMO_WUI !== "undefined";
187
188
  const MDCUI_OVERWRITE_DEMO_ENABLED = typeof MDCUI_OVERWRITE_DEMO !== "undefined";
188
189
  let defaultEdit = false;
190
+ let noRuntimeArgs = false;
189
191
  const decoder = new TextDecoder();
190
192
  let _activeTtyStream = null; // set in App.start() for use by the global error handler
191
193
 
@@ -1173,9 +1175,15 @@ function parseArgs(argv) {
1173
1175
  }
1174
1176
  else if (arg.startsWith("--demo-")) {
1175
1177
  const name = arg.slice("--demo-".length);
1176
- flags.demo = /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(name)
1178
+ flags.demo = isValidDemoName(name)
1177
1179
  ? { option: arg, filename: `${name}.md`, asset: `demos/${name}.md` }
1178
- : { option: arg, error: "demo filename must contain only letters, numbers, dots, underscores, and hyphens" };
1180
+ : {
1181
+ option: arg,
1182
+ error:
1183
+ "demo filename must start with a Unicode letter or number "
1184
+ + "and contain only Unicode letters, numbers, dots, underscores, "
1185
+ + "or hyphens; whitespace is not allowed",
1186
+ };
1179
1187
  }
1180
1188
  else if (arg === "--allow-url") flags.allowUrl = true;
1181
1189
  else if (arg === "--kitty") flags.kittyMode = "extended";
@@ -1206,6 +1214,17 @@ function parseArgs(argv) {
1206
1214
  return { flags, files };
1207
1215
  }
1208
1216
 
1217
+ function isValidDemoName(name) {
1218
+ return /^[\p{L}\p{N}][\p{L}\p{N}\p{M}._-]*$/u.test(String(name));
1219
+ }
1220
+
1221
+ function demoNameFromMarkdownBasename(filename) {
1222
+ const value = String(filename);
1223
+ if (!value.endsWith(".md")) return "";
1224
+ const name = value.slice(0, -".md".length);
1225
+ return isValidDemoName(name) ? name : "";
1226
+ }
1227
+
1209
1228
  function usage() {
1210
1229
  return [
1211
1230
  `Usage:
@@ -1331,14 +1350,20 @@ Remote Markdown:
1331
1350
  Download HTTP(S) Markdown and, with Kitty mode, its HTTP(S) images; allow its code to run
1332
1351
 
1333
1352
  Experimental single-exe:
1353
+ --build-md-exe <file.md> [BUN_ARGS...]
1354
+ Build with <file.md> embedded as global.MDCUI_MAIN & exit
1355
+ --build-md-for <platform> <file.md> [BUN_ARGS...]
1356
+ Build for <platform> with <file.md> embedded & exit
1357
+
1334
1358
  --build-exe [BUN_ARGS...]
1335
1359
  Build a Bun single-file executable & exit
1336
1360
  --build-for <target> [BUN_ARGS...]
1337
1361
  Build a Bun single-file executable for target & exit
1338
1362
 
1339
1363
  [BUN_ARGS...]
1340
- --define MDCUI_XXX=true
1341
- More info in --readme
1364
+ --define MDCUI_OVERWRITE_DEMO=1
1365
+ --define global.MDCUI_MAIN=myapp.md
1366
+ More info in --readme
1342
1367
  https://bun.com/docs/bundler/executables
1343
1368
  `
1344
1369
  ].join("\n");
@@ -1400,6 +1425,7 @@ class BufferModel {
1400
1425
  this._mdcuiFenceEvents = isMdcuiEncoding(this.encoding) ? fenceEventMap(sourceText ?? tuiSourceText ?? text) : new Map();
1401
1426
  this._mdcuiImages = isMdcuiEncoding(this.encoding) ? (mdcuiImages ?? []) : [];
1402
1427
  this._mdcuiRenderWidth = Math.trunc(Number(mdcuiRenderWidth) || 0);
1428
+ this._useBundledMdcuiModules = false;
1403
1429
  this.cursor = { x: 0, y: 0 };
1404
1430
  this.scroll = { x: 0, y: 0, row: 0 };
1405
1431
  this._modified = false;
@@ -4540,7 +4566,10 @@ class App {
4540
4566
  ) return false;
4541
4567
 
4542
4568
  const frontPath = `${buf.path}.front.js`;
4543
- if (!existsSync(frontPath)) return false;
4569
+ const useBundledMdcuiModules = Boolean(
4570
+ global.MDCUI_MAIN && buf._useBundledMdcuiModules,
4571
+ );
4572
+ if (!useBundledMdcuiModules && !existsSync(frontPath)) return false;
4544
4573
  try {
4545
4574
  const selection = globalThis.$?.(`${declaration.tag}#${id}`);
4546
4575
  const target = {
@@ -4555,8 +4584,12 @@ class App {
4555
4584
  });
4556
4585
  const event = tuiKeyEventForFront(inputEvent, target, eventName);
4557
4586
  const [{ evalFront }, frontMod] = await Promise.all([
4558
- import("./cui/rpc.mjs"),
4559
- import(localModuleUrl(frontPath)),
4587
+ useBundledMdcuiModules
4588
+ ? import(global.MDCUI_MAIN + "-rpc.js")
4589
+ : import("./cui/rpc.mjs"),
4590
+ useBundledMdcuiModules
4591
+ ? import(global.MDCUI_MAIN + ".front.js")
4592
+ : import(localModuleUrl(frontPath)),
4560
4593
  ]);
4561
4594
  const result = await evalFront(frontMod, code, { event });
4562
4595
  if (result && typeof result === "object" && result.ok === false) {
@@ -4591,9 +4624,16 @@ class App {
4591
4624
  { // defaultCallback
4592
4625
  if (payload.link && /^javascript:/i.test(payload.link) && buf?.path && !isHttpUrl(buf.path)) {
4593
4626
  try {
4627
+ const useBundledMdcuiModules = Boolean(
4628
+ global.MDCUI_MAIN && buf._useBundledMdcuiModules,
4629
+ );
4594
4630
  const [{ evalFront }, frontMod] = await Promise.all([
4595
- import("./cui/rpc.mjs"),
4596
- import(localModuleUrl(`${buf.path}.front.js`)),
4631
+ useBundledMdcuiModules
4632
+ ? import(global.MDCUI_MAIN + "-rpc.js")
4633
+ : import("./cui/rpc.mjs"),
4634
+ useBundledMdcuiModules
4635
+ ? import(global.MDCUI_MAIN + ".front.js")
4636
+ : import(localModuleUrl(`${buf.path}.front.js`)),
4597
4637
  ]);
4598
4638
  const result = await evalFront(frontMod, payload.link);
4599
4639
  if (result && typeof result === "object" && result.ok === false) {
@@ -4991,7 +5031,7 @@ class App {
4991
5031
  const name = buf?.name ?? "No name";
4992
5032
  const filename = /^[^\s"'\\]+$/.test(name) ? name : JSON.stringify(name);
4993
5033
  this.openCommandMode(`save ${filename}`);
4994
- break;
5034
+ break; //"
4995
5035
  }
4996
5036
  case "row":
4997
5037
  if (isTerm) {
@@ -5481,10 +5521,18 @@ class App {
5481
5521
  const frontPath = buffer.path && !isHttpUrl(buffer.path)
5482
5522
  ? `${buffer.path}.front.js`
5483
5523
  : "";
5484
- if (!frontPath || !existsSync(frontPath)) return;
5524
+ const useBundledMdcuiModules = Boolean(
5525
+ global.MDCUI_MAIN && buffer._useBundledMdcuiModules,
5526
+ );
5527
+ if (
5528
+ !useBundledMdcuiModules
5529
+ && (!frontPath || !existsSync(frontPath))
5530
+ ) return;
5485
5531
 
5486
5532
  try {
5487
- const frontMod = await import(localModuleUrl(frontPath));
5533
+ const frontMod = useBundledMdcuiModules
5534
+ ? await import(global.MDCUI_MAIN + ".front.js")
5535
+ : await import(localModuleUrl(frontPath));
5488
5536
  if (typeof frontMod.onMdcuiExit !== "function") return;
5489
5537
  await frontMod.onMdcuiExit({
5490
5538
  reason,
@@ -7021,6 +7069,11 @@ function makeBufferAdapter(buffer) {
7021
7069
  get Name() { return buffer.Name ?? buffer.name ?? ""; },
7022
7070
  get Modified() { return buffer.modified ?? false; },
7023
7071
  get Settings() { return buffer.Settings; },
7072
+ get MdcuiModuleSource() {
7073
+ return global.MDCUI_MAIN && buffer._useBundledMdcuiModules
7074
+ ? "embedded"
7075
+ : "external";
7076
+ },
7024
7077
  Line: (...args) => buffer.Line(Number(lastArg(args))),
7025
7078
  Insert: (...args) => insertAtLoc(buffer, decodeLoc(args.at(-2)), String(args.at(-1))),
7026
7079
  Replace: (...args) => replaceAtLocs(buffer, decodeLoc(args.at(-3)), decodeLoc(args.at(-2)), String(args.at(-1))),
@@ -8137,11 +8190,11 @@ function printDemoList() {
8137
8190
  }
8138
8191
 
8139
8192
  async function prepareDemo(flags, rawFiles) {
8140
- if (!flags.demo) return true;
8193
+ if (!flags.demo) return { ok: true };
8141
8194
  if (flags.demo.error) {
8142
8195
  console.error(`Invalid demo option ${flags.demo.option}: ${flags.demo.error}`);
8143
8196
  process.exitCode = 2;
8144
- return false;
8197
+ return { ok: false };
8145
8198
  }
8146
8199
  let demoSource;
8147
8200
  try {
@@ -8149,29 +8202,170 @@ async function prepareDemo(flags, rawFiles) {
8149
8202
  } catch {
8150
8203
  console.error(`Unknown demo ${flags.demo.option}: ${flags.demo.asset} was not found`);
8151
8204
  process.exitCode = 2;
8152
- return false;
8205
+ return { ok: false };
8153
8206
  }
8207
+
8154
8208
  const demoPath = resolve(flags.demo.filename);
8155
- if (flags.overwriteDemo || !(await Bun.file(demoPath).exists())) {
8209
+ const demoFile = Bun.file(demoPath);
8210
+ const demoExists = await demoFile.exists();
8211
+ const writeBundledContent = flags.overwriteDemo || !demoExists;
8212
+ const bundledByteLength = Buffer.byteLength(demoSource);
8213
+ const demoByteLength = writeBundledContent
8214
+ ? bundledByteLength
8215
+ : demoFile.size;
8216
+ const configuredMainBase = String(
8217
+ global.MDCUI_MAIN_BASE
8218
+ || (global.MDCUI_MAIN ? basename(String(global.MDCUI_MAIN)) : ""),
8219
+ );
8220
+ const isConfiguredMainDemo = Boolean(
8221
+ global.MDCUI_MAIN
8222
+ && configuredMainBase
8223
+ && basename(demoPath) === configuredMainBase,
8224
+ );
8225
+
8226
+ if (writeBundledContent) {
8156
8227
  await Bun.write(demoPath, demoSource);
8228
+ } else if (isConfiguredMainDemo && demoByteLength !== bundledByteLength) {
8229
+ console.error(
8230
+ `[mdcui] ${demoPath} differs from the bundled demo `
8231
+ + `(${demoByteLength} bytes vs ${bundledByteLength} bytes). `
8232
+ + "The edited demo may not work correctly if it imports companion modules "
8233
+ + "instead of keeping all application code in the Markdown file.",
8234
+ );
8157
8235
  }
8236
+
8158
8237
  rawFiles.splice(0, rawFiles.length, demoPath);
8159
- return true;
8238
+ return {
8239
+ ok: true,
8240
+ path: demoPath,
8241
+ useBundledModules: isConfiguredMainDemo
8242
+ && demoByteLength === bundledByteLength,
8243
+ };
8244
+ }
8245
+
8246
+ function stringDefineFromArgv(argv, name) {
8247
+ const prefix = `${name}=`;
8248
+ for (let i = 0; i < argv.length; i++) {
8249
+ let definition = "";
8250
+ if (argv[i] === "--define") definition = argv[i + 1] ?? "";
8251
+ else if (argv[i].startsWith("--define="))
8252
+ definition = argv[i].slice("--define=".length);
8253
+ if (!definition.startsWith(prefix)) continue;
8254
+ try {
8255
+ const value = JSON.parse(definition.slice(prefix.length));
8256
+ return typeof value === "string" ? value : "";
8257
+ } catch {
8258
+ return "";
8259
+ }
8260
+ }
8261
+ return "";
8160
8262
  }
8161
8263
 
8162
8264
  async function main() {
8163
8265
  addCheckpoint("Argument Parsing");
8164
-
8266
+
8267
+ expandBuildMdAliases(process.argv);
8268
+ stringifyNonPrimitiveDefineValues(process.argv, "global.MDCUI_MAIN");
8269
+ const argvMdcuiMain = stringDefineFromArgv(
8270
+ process.argv,
8271
+ "global.MDCUI_MAIN",
8272
+ );
8273
+ let sourceMdcuiMain = "";
8274
+ if (IS_COMPILED) {
8275
+ sourceMdcuiMain = "";
8276
+ } else {
8277
+ sourceMdcuiMain = String(argvMdcuiMain || global.MDCUI_MAIN || "");
8278
+ }
8279
+
8280
+ if (sourceMdcuiMain) {
8281
+ const mdcuiMain = resolve(sourceMdcuiMain);
8282
+ const mdcuiMainBase = basename(mdcuiMain);
8283
+ const mdcuiMainDemoName = demoNameFromMarkdownBasename(mdcuiMainBase);
8284
+ if (!mdcuiMainDemoName) {
8285
+ throw new Error(
8286
+ "MDCUI_MAIN basename must end with lowercase .md, start with a "
8287
+ + "Unicode letter or number, and contain no whitespace; only Unicode "
8288
+ + "letters, numbers, dots, underscores, and hyphens are allowed",
8289
+ );
8290
+ }
8291
+ const runmd = await import("../runmd.mjs");
8292
+ const mdcuiMainFile = Bun.file(mdcuiMain);
8293
+ if (!(await mdcuiMainFile.exists())) {
8294
+ throw new Error(`MDCUI_MAIN file not found: ${mdcuiMain}`);
8295
+ }
8296
+ let markdown = await mdcuiMainFile.text();
8297
+ const demoPath = join(REPO_ROOT, "demos", mdcuiMainBase);
8298
+ if (resolve(mdcuiMain) !== resolve(demoPath)) {
8299
+ await Bun.write(demoPath, markdown);
8300
+ }
8301
+ markdown = await runmd.extractJs(markdown, mdcuiMain, {
8302
+ bundling: true,
8303
+ });
8304
+ await runmd.createWui(markdown, mdcuiMain, {
8305
+ bundling: true,
8306
+ });
8307
+
8308
+ if (
8309
+ process.argv.includes("--build-for")
8310
+ || process.argv.includes("--build-exe")
8311
+ ) {
8312
+ if (argvMdcuiMain) {
8313
+ const normalizedDefine =
8314
+ `global.MDCUI_MAIN=${JSON.stringify(mdcuiMain)}`;
8315
+ for (let i = 0; i < process.argv.length; i++) {
8316
+ if (
8317
+ process.argv[i] === "--define"
8318
+ && process.argv[i + 1]?.startsWith("global.MDCUI_MAIN=")
8319
+ ) {
8320
+ process.argv[i + 1] = normalizedDefine;
8321
+ } else if (
8322
+ process.argv[i].startsWith("--define=global.MDCUI_MAIN=")
8323
+ ) {
8324
+ process.argv[i] = `--define=${normalizedDefine}`;
8325
+ }
8326
+ }
8327
+ } else {
8328
+ process.argv.push(
8329
+ "--define",
8330
+ `global.MDCUI_MAIN=${JSON.stringify(mdcuiMain)}`,
8331
+ );
8332
+ }
8333
+ process.argv.push(
8334
+ "--define",
8335
+ `global.MDCUI_MAIN_BASE=${JSON.stringify(mdcuiMainBase)}`,
8336
+ );
8337
+ }
8338
+ }
8339
+
8165
8340
  await buildEarlyExit(null,DEFAULT_BUILD_OUTFILE)
8166
8341
  defaultEdit = await Bun.file(join(REPO_ROOT, "src", "MDCUI_DEFAULT_EDIT")).exists();
8167
8342
 
8168
8343
  const runtimeArgs = process.argv.slice(2);
8169
- const noRuntimeArgs = runtimeArgs.length === 0;
8344
+ noRuntimeArgs = runtimeArgs.length === 0;
8170
8345
  if (MDCUI_DEFAULT_EDIT_ENABLED) runtimeArgs.unshift("--edit");
8171
8346
  if (noRuntimeArgs && MDCUI_DEFAULT_DEMO_ENABLED) runtimeArgs.push("--demo");
8172
8347
  if (noRuntimeArgs && MDCUI_DEFAULT_DEMO_WUI_ENABLED) runtimeArgs.push("--wui");
8348
+ if (
8349
+ noRuntimeArgs
8350
+ && (global.MDCUI_MAIN || global.MDCUI_MAIN_BASE)
8351
+ ) {
8352
+ const mdcuiMainBase = String(
8353
+ global.MDCUI_MAIN_BASE || basename(String(global.MDCUI_MAIN)),
8354
+ );
8355
+ const demoName = demoNameFromMarkdownBasename(mdcuiMainBase);
8356
+ if (!demoName) {
8357
+ console.error(
8358
+ "Invalid MDCUI_MAIN_BASE: expected a lowercase .md basename with "
8359
+ + "Unicode letters or numbers and no whitespace",
8360
+ );
8361
+ process.exitCode = 2;
8362
+ return;
8363
+ }
8364
+ runtimeArgs.push(`--demo-${demoName}`);
8365
+ }
8173
8366
  if (MDCUI_OVERWRITE_DEMO_ENABLED) runtimeArgs.push("--overwrite-demo");
8174
8367
  const { flags, files: rawFiles } = parseArgs(runtimeArgs);
8368
+ let demoPreparation = null;
8175
8369
  kittyImageMode = flags.kittyMode;
8176
8370
  allowRemoteKittyImages = flags.allowUrl;
8177
8371
 
@@ -8195,6 +8389,14 @@ async function main() {
8195
8389
  console.log(" MDCUI_DEFAULT_DEMO:", MDCUI_DEFAULT_DEMO_ENABLED ? "enabled" : "disabled");
8196
8390
  console.log(" MDCUI_DEFAULT_DEMO_WUI:", MDCUI_DEFAULT_DEMO_WUI_ENABLED ? "enabled" : "disabled");
8197
8391
  console.log(" MDCUI_OVERWRITE_DEMO:", MDCUI_OVERWRITE_DEMO_ENABLED ? "enabled" : "disabled");
8392
+ console.log(
8393
+ " global.MDCUI_MAIN:",
8394
+ String(global.MDCUI_MAIN || "") || "not set",
8395
+ );
8396
+ console.log(
8397
+ " global.MDCUI_MAIN_BASE:",
8398
+ String(global.MDCUI_MAIN_BASE || "") || "not set",
8399
+ );
8198
8400
  const clipboard = new ClipboardManager();
8199
8401
  let osc52Available = false;
8200
8402
  if (process.stdin.isTTY && process.stdout.isTTY) {
@@ -8210,12 +8412,14 @@ async function main() {
8210
8412
  return;
8211
8413
  }
8212
8414
  if (flags.wui) {
8213
- if (!(await prepareDemo(flags, rawFiles))) return;
8415
+ demoPreparation = await prepareDemo(flags, rawFiles);
8416
+ if (!demoPreparation.ok) return;
8214
8417
  const runmd = await import("../runmd.mjs");
8215
8418
  await runmd.main(30, {
8216
8419
  mdpath: rawFiles[0] ?? null,
8217
8420
  overwriteDemo: flags.overwriteDemo && rawFiles.length === 0,
8218
8421
  printUi: flags.printUi,
8422
+ useBundledMdcuiServer: demoPreparation.useBundledModules,
8219
8423
  });
8220
8424
  return;
8221
8425
  }
@@ -8263,7 +8467,8 @@ async function main() {
8263
8467
  return;
8264
8468
  }
8265
8469
  if (flags.demo) {
8266
- if (!(await prepareDemo(flags, rawFiles))) return;
8470
+ demoPreparation = await prepareDemo(flags, rawFiles);
8471
+ if (!demoPreparation.ok) return;
8267
8472
  }
8268
8473
  if (flags.options) {
8269
8474
  for (const [key, value] of Object.entries(defaultAllSettings()).sort(([a], [b]) => a.localeCompare(b))) {
@@ -8411,6 +8616,16 @@ async function main() {
8411
8616
  const buffers = await loadBuffers(files.map((file) =>
8412
8617
  isHttpUrl(file) ? file : resolve(file)
8413
8618
  ), command);
8619
+ if (demoPreparation?.useBundledModules) {
8620
+ const bundledDemoBuffer = buffers.find((buffer) =>
8621
+ buffer.path
8622
+ && !isHttpUrl(buffer.path)
8623
+ && resolve(buffer.path) === demoPreparation.path
8624
+ );
8625
+ if (bundledDemoBuffer) {
8626
+ bundledDemoBuffer._useBundledMdcuiModules = true;
8627
+ }
8628
+ }
8414
8629
 
8415
8630
  let historyPromise = Promise.resolve();
8416
8631
  if (config.getGlobalOption("savehistory") !== false) {
@@ -1622,6 +1622,11 @@ function _makeBufAPI(buffer) {
1622
1622
  get Type() { return buffer.Type; },
1623
1623
  get Settings() { return buffer.Settings; },
1624
1624
  get Modified() { return buffer.modified; },
1625
+ get MdcuiModuleSource() {
1626
+ return global.MDCUI_MAIN && buffer._useBundledMdcuiModules
1627
+ ? "embedded"
1628
+ : "external";
1629
+ },
1625
1630
 
1626
1631
  Line: (n) => buffer.Line(n),
1627
1632
  LinesNum: () => buffer.LinesNum(),
@@ -1,8 +1,139 @@
1
1
  import { expect, test } from "bun:test";
2
- import { isCompiledBinary as isSingleExeCompiled } from "../single-exe/compiled.js";
2
+ import { expandBuildMdAliases } from "../src/build-args.js";
3
+ import {
4
+ isCompiledBinary as isSingleExeCompiled,
5
+ stringifyNonPrimitiveDefineValues,
6
+ } from "../single-exe/compiled.js";
3
7
 
4
8
  test("single-exe recognizes Bun compiled virtual paths", () => {
5
9
  expect(isSingleExeCompiled(["bun", "/$bunfs/root/app.js"])).toBe(true);
6
10
  expect(isSingleExeCompiled(["bun", "B:/~BUN/root/app.js"])).toBe(true);
7
11
  expect(isSingleExeCompiled(["bun", "/project/src/index.js"])).toBe(false);
8
12
  });
13
+
14
+ test("--build-md-exe expands before the existing build flow", () => {
15
+ const argv = [
16
+ "bun",
17
+ "src/index.js",
18
+ "--build-md-exe",
19
+ "../中文工具.md",
20
+ "--sourcemap",
21
+ ];
22
+ expect(expandBuildMdAliases(argv)).toBe(true);
23
+ expect(argv).toEqual([
24
+ "bun",
25
+ "src/index.js",
26
+ "--build-exe",
27
+ "--define",
28
+ "global.MDCUI_MAIN=../中文工具.md",
29
+ "--sourcemap",
30
+ ]);
31
+ expect(expandBuildMdAliases(argv)).toBe(false);
32
+ });
33
+
34
+ test("--build-md-exe requires a Markdown path", () => {
35
+ expect(() => expandBuildMdAliases(["bun", "index.js", "--build-md-exe"]))
36
+ .toThrow("Missing Markdown path for --build-md-exe");
37
+ expect(() => expandBuildMdAliases([
38
+ "bun",
39
+ "index.js",
40
+ "--build-md-exe",
41
+ "--sourcemap",
42
+ ])).toThrow("Missing Markdown path for --build-md-exe");
43
+ });
44
+
45
+ test("--build-md-for expands before the existing cross-build flow", () => {
46
+ const argv = [
47
+ "bun",
48
+ "src/index.js",
49
+ "--build-md-for",
50
+ "bun-linux-x64",
51
+ "../中文工具.md",
52
+ "--sourcemap",
53
+ ];
54
+ expect(expandBuildMdAliases(argv)).toBe(true);
55
+ expect(argv).toEqual([
56
+ "bun",
57
+ "src/index.js",
58
+ "--build-for",
59
+ "bun-linux-x64",
60
+ "--define",
61
+ "global.MDCUI_MAIN=../中文工具.md",
62
+ "--sourcemap",
63
+ ]);
64
+ });
65
+
66
+ test("--build-md-for requires a platform and Markdown path", () => {
67
+ expect(() => expandBuildMdAliases(["bun", "index.js", "--build-md-for"]))
68
+ .toThrow("Missing platform for --build-md-for");
69
+ expect(() => expandBuildMdAliases([
70
+ "bun",
71
+ "index.js",
72
+ "--build-md-for",
73
+ "bun-linux-x64",
74
+ ])).toThrow("Missing Markdown path for --build-md-for");
75
+ });
76
+
77
+ test("non-primitive MDCUI_MAIN define values become strings", () => {
78
+ const split = [
79
+ "bun",
80
+ "src/index.js",
81
+ "--build-exe",
82
+ "--define",
83
+ "global.MDCUI_MAIN=../中文=工具.md",
84
+ ];
85
+ expect(
86
+ stringifyNonPrimitiveDefineValues(split, "global.MDCUI_MAIN"),
87
+ ).toBe(split);
88
+ expect(split.at(-1)).toBe(
89
+ 'global.MDCUI_MAIN="../中文=工具.md"',
90
+ );
91
+
92
+ const inline = [
93
+ "--define=global.MDCUI_MAIN=../m.md",
94
+ "--define",
95
+ "OTHER=../other.md",
96
+ "--define",
97
+ "global.MDCUI_MAIN_BASE=m.md",
98
+ ];
99
+ stringifyNonPrimitiveDefineValues(inline, "global.MDCUI_MAIN");
100
+ expect(inline).toEqual([
101
+ '--define=global.MDCUI_MAIN="../m.md"',
102
+ "--define",
103
+ "OTHER=../other.md",
104
+ "--define",
105
+ "global.MDCUI_MAIN_BASE=m.md",
106
+ ]);
107
+
108
+ for (const source of ["", "[]", "{}", "makePath()"]) {
109
+ const argv = ["--define", `global.MDCUI_MAIN=${source}`];
110
+ stringifyNonPrimitiveDefineValues(argv, "global.MDCUI_MAIN");
111
+ expect(argv[1]).toBe(
112
+ `global.MDCUI_MAIN=${JSON.stringify(source)}`,
113
+ );
114
+ }
115
+ });
116
+
117
+ test("primitive MDCUI_MAIN define values keep their type", () => {
118
+ const cases = new Map([
119
+ ['"../m.md"', '"../m.md"'],
120
+ ["'../m.md'", '"../m.md"'],
121
+ ["true", "true"],
122
+ ["false", "false"],
123
+ ["null", "null"],
124
+ ["undefined", "undefined"],
125
+ ["0", "0"],
126
+ ["-1.5", "-1.5"],
127
+ ["NaN", "NaN"],
128
+ ["Infinity", "Infinity"],
129
+ ["0xff", "0xff"],
130
+ ["0b10", "0b10"],
131
+ ["1_000", "1_000"],
132
+ ]);
133
+
134
+ for (const [source, normalized] of cases) {
135
+ const argv = ["--define", `global.MDCUI_MAIN=${source}`];
136
+ stringifyNonPrimitiveDefineValues(argv, "global.MDCUI_MAIN");
137
+ expect(argv[1]).toBe(`global.MDCUI_MAIN=${normalized}`);
138
+ }
139
+ });