buninu 0.3.0 → 0.4.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.
Files changed (142) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/README.md +334 -37
  3. package/apps/bunmsh/CHANGELOG.md +262 -0
  4. package/apps/bunmsh/LICENSE +21 -0
  5. package/apps/bunmsh/LICENSE-MKSH +295 -0
  6. package/apps/bunmsh/PORTING.md +192 -0
  7. package/apps/bunmsh/README.md +801 -0
  8. package/apps/bunmsh/bunmsh +7 -0
  9. package/apps/bunmsh/bunshell.md +61 -0
  10. package/apps/bunmsh/help/README.md +1945 -0
  11. package/apps/bunmsh/help/alias.md +22 -0
  12. package/apps/bunmsh/help/basename.md +38 -0
  13. package/apps/bunmsh/help/builtin.md +25 -0
  14. package/apps/bunmsh/help/bun.md +27 -0
  15. package/apps/bunmsh/help/bunmsh.md +47 -0
  16. package/apps/bunmsh/help/cat.md +31 -0
  17. package/apps/bunmsh/help/catfancy.md +35 -0
  18. package/apps/bunmsh/help/cd.md +22 -0
  19. package/apps/bunmsh/help/chmod.md +27 -0
  20. package/apps/bunmsh/help/clear.md +21 -0
  21. package/apps/bunmsh/help/colon.md +22 -0
  22. package/apps/bunmsh/help/command.md +27 -0
  23. package/apps/bunmsh/help/cp.md +28 -0
  24. package/apps/bunmsh/help/cut.md +25 -0
  25. package/apps/bunmsh/help/date.md +25 -0
  26. package/apps/bunmsh/help/dirname.md +25 -0
  27. package/apps/bunmsh/help/echo.md +25 -0
  28. package/apps/bunmsh/help/env.md +27 -0
  29. package/apps/bunmsh/help/eval.md +22 -0
  30. package/apps/bunmsh/help/exec.md +22 -0
  31. package/apps/bunmsh/help/exit.md +21 -0
  32. package/apps/bunmsh/help/export.md +22 -0
  33. package/apps/bunmsh/help/false.md +22 -0
  34. package/apps/bunmsh/help/find.md +33 -0
  35. package/apps/bunmsh/help/getopts.md +28 -0
  36. package/apps/bunmsh/help/grep.md +34 -0
  37. package/apps/bunmsh/help/head.md +27 -0
  38. package/apps/bunmsh/help/kill.md +27 -0
  39. package/apps/bunmsh/help/ln.md +28 -0
  40. package/apps/bunmsh/help/ls.md +37 -0
  41. package/apps/bunmsh/help/lsbun.md +29 -0
  42. package/apps/bunmsh/help/lsfancy.md +37 -0
  43. package/apps/bunmsh/help/md5sum.md +21 -0
  44. package/apps/bunmsh/help/mkdir.md +27 -0
  45. package/apps/bunmsh/help/mktemp.md +26 -0
  46. package/apps/bunmsh/help/mv.md +26 -0
  47. package/apps/bunmsh/help/print.md +30 -0
  48. package/apps/bunmsh/help/printf.md +25 -0
  49. package/apps/bunmsh/help/pwd.md +21 -0
  50. package/apps/bunmsh/help/read.md +27 -0
  51. package/apps/bunmsh/help/readonly.md +27 -0
  52. package/apps/bunmsh/help/realpath.md +21 -0
  53. package/apps/bunmsh/help/rm.md +33 -0
  54. package/apps/bunmsh/help/rmdir.md +27 -0
  55. package/apps/bunmsh/help/sed.md +29 -0
  56. package/apps/bunmsh/help/seq.md +29 -0
  57. package/apps/bunmsh/help/serve.md +50 -0
  58. package/apps/bunmsh/help/set.md +26 -0
  59. package/apps/bunmsh/help/sha256sum.md +21 -0
  60. package/apps/bunmsh/help/shift.md +23 -0
  61. package/apps/bunmsh/help/sleep.md +25 -0
  62. package/apps/bunmsh/help/sort.md +29 -0
  63. package/apps/bunmsh/help/source.md +23 -0
  64. package/apps/bunmsh/help/tab.md +30 -0
  65. package/apps/bunmsh/help/tac.md +29 -0
  66. package/apps/bunmsh/help/tail.md +27 -0
  67. package/apps/bunmsh/help/tee.md +25 -0
  68. package/apps/bunmsh/help/test.md +30 -0
  69. package/apps/bunmsh/help/time.md +29 -0
  70. package/apps/bunmsh/help/touch.md +26 -0
  71. package/apps/bunmsh/help/tr.md +26 -0
  72. package/apps/bunmsh/help/true.md +22 -0
  73. package/apps/bunmsh/help/type.md +21 -0
  74. package/apps/bunmsh/help/umask.md +22 -0
  75. package/apps/bunmsh/help/unalias.md +28 -0
  76. package/apps/bunmsh/help/uname.md +31 -0
  77. package/apps/bunmsh/help/unset.md +28 -0
  78. package/apps/bunmsh/help/wc.md +27 -0
  79. package/apps/bunmsh/help/whence.md +26 -0
  80. package/apps/bunmsh/help/which.md +41 -0
  81. package/apps/bunmsh/help/yes.md +23 -0
  82. package/apps/bunmsh/package.json +26 -0
  83. package/apps/bunmsh/serve.js +468 -0
  84. package/apps/bunmsh/single-exe/README.md +402 -0
  85. package/apps/bunmsh/single-exe/assetsHelper.js +371 -0
  86. package/apps/bunmsh/single-exe/assetsLoader.mjs +85 -0
  87. package/apps/bunmsh/single-exe/assetsPacker.js +218 -0
  88. package/apps/bunmsh/single-exe/compiled.js +527 -0
  89. package/apps/bunmsh/single-exe/entry.mjs +17 -0
  90. package/apps/bunmsh/src/cat-fancy.js +117 -0
  91. package/apps/bunmsh/src/cat-operands.js +31 -0
  92. package/apps/bunmsh/src/completion.js +247 -0
  93. package/apps/bunmsh/src/environment.js +61 -0
  94. package/apps/bunmsh/src/fancy-ls.js +186 -0
  95. package/apps/bunmsh/src/find.js +119 -0
  96. package/apps/bunmsh/src/history.js +198 -0
  97. package/apps/bunmsh/src/main.js +776 -0
  98. package/apps/bunmsh/src/mouse.js +103 -0
  99. package/apps/bunmsh/src/shell.js +3557 -0
  100. package/apps/bunmsh/test/catfancy.test.js +102 -0
  101. package/apps/bunmsh/test/reference-source.sh +1 -0
  102. package/apps/bunmsh/test/reference.test.js +677 -0
  103. package/apps/bunmsh/test/serve/README.md +3 -0
  104. package/apps/bunmsh/test/serve/bad.json +1 -0
  105. package/apps/bunmsh/test/serve/data.json +1 -0
  106. package/apps/bunmsh/test/serve/data.json5 +1 -0
  107. package/apps/bunmsh/test/serve/data.jsonc +2 -0
  108. package/apps/bunmsh/test/serve/data.jsonl +2 -0
  109. package/apps/bunmsh/test/serve/data.ndjson +1 -0
  110. package/apps/bunmsh/test/serve/data.toml +3 -0
  111. package/apps/bunmsh/test/serve/data.ts +15 -0
  112. package/apps/bunmsh/test/serve/data.xml +1 -0
  113. package/apps/bunmsh/test/serve/data.yaml +3 -0
  114. package/apps/bunmsh/test/serve/data.yml +1 -0
  115. package/apps/bunmsh/test/serve.test.js +241 -0
  116. package/apps/bunmsh/test/shell.test.js +1778 -0
  117. package/apps/bunmsh/test/t.sh +12 -0
  118. package/apps/cmdlist +6 -1
  119. package/apps/jsgotty/gotty.js +65 -76
  120. package/apps/jsgotty/package.json +6 -1
  121. package/apps/jsgotty/single-exe/README.md +358 -54
  122. package/apps/jsgotty/single-exe/assetsHelper.js +293 -12
  123. package/apps/jsgotty/single-exe/assetsLoader.mjs +1 -1
  124. package/apps/jsgotty/single-exe/assetsPacker.js +218 -0
  125. package/apps/jsgotty/single-exe/compiled.js +379 -19
  126. package/apps/jsgotty/single-exe/entry.mjs +16 -2
  127. package/apps/jsgotty/zmodem-node.js +14 -12
  128. package/apps/jsmdcui/CHANGELOG.md +56 -0
  129. package/apps/jsmdcui/package.json +12 -2
  130. package/apps/jsmdcui/single-exe/README.md +298 -375
  131. package/apps/jsmdcui/single-exe/assetsHelper.js +198 -18
  132. package/apps/jsmdcui/single-exe/assetsLoader.mjs +1 -1
  133. package/apps/jsmdcui/single-exe/assetsPacker.js +218 -0
  134. package/apps/jsmdcui/single-exe/compiled.js +291 -5
  135. package/apps/jsmdcui/single-exe/entry.mjs +7 -0
  136. package/apps/jsmdcui/src/index.js +11 -8
  137. package/apps/jsmdcui/tests/jmigt.js +16 -0
  138. package/bin/bunmsh.bat +3 -0
  139. package/bin/bunmsh.sh +5 -0
  140. package/bin/init.js +48 -5
  141. package/bin/install.js +541 -0
  142. package/package.json +4 -2
@@ -1,15 +1,19 @@
1
1
  import { basename, dirname, resolve } from "node:path";
2
- import { readFileSync } from "node:fs";
2
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import child_process from "node:child_process";
5
+ import { ASSETS_ROOT } from "./assetsPacker.js";
5
6
 
6
7
 
7
8
  export const IS_COMPILED = isCompiledBinary();
8
9
  export const REPO_ROOT = IS_COMPILED
9
10
  ? getExeDirname()
10
- : resolve(import.meta.dirname, "..");
11
- const SINGLE_EXE_DIR = resolve(REPO_ROOT, "single-exe");
12
- const SINGLE_EXE_ENTRY = resolve(SINGLE_EXE_DIR, "entry.mjs");
11
+ : resolve(import.meta.dirname, ASSETS_ROOT);
12
+ // This file's own directory. Not derived from REPO_ROOT: the only user
13
+ // is buildExecutable(), which already refuses to run when IS_COMPILED,
14
+ // so REPO_ROOT there is always the source tree, and going back down
15
+ // through it would hard-code this folder name.
16
+ const SINGLE_EXE_DIR = import.meta.dirname;
13
17
 
14
18
 
15
19
  export function isCompiledBinary(argv = process.argv) {
@@ -182,7 +186,7 @@ export async function buildExecutable(target = "",build_outfile="single.exe", bu
182
186
  }
183
187
  }
184
188
 
185
- export const buildExe = buildExecutable;
189
+ export const buildExe = buildExeAssets;
186
190
 
187
191
  export async function buildEarlyExit(argv,build_outfile) {
188
192
  argv = argv || process.argv
@@ -210,3 +214,285 @@ export async function buildEarlyExit(argv,build_outfile) {
210
214
 
211
215
  process.exit(await buildExe(null, build_outfile, argv.slice(buildExeIndex + 1)));
212
216
  }
217
+
218
+
219
+ // Multi-package variant of buildExecutable().
220
+ //
221
+ // Every package that ships assets vendors its own single-exe/, and its
222
+ // assetsHelper imports its assetsPacker, so a packer shows up in the
223
+ // bundle's module graph exactly when that package is actually reachable
224
+ // from this entry point. A scan build writes that graph to a metafile,
225
+ // and each packer found there appends its own namespaced members to one
226
+ // shared assets.tar before the real compile runs.
227
+ export async function buildExeAssets(target = "", build_outfile = "single.exe", bunArgs = []) {
228
+ let outfile = resolve(process.cwd(), build_outfile);
229
+ const normalizedTarget = String(target || "").trim();
230
+ const extraBunArgs = Array.from(bunArgs ?? [], String);
231
+
232
+ if (!globalThis.Bun || IS_COMPILED) {
233
+ console.log("Build exe can only be run by Bun in the source tree");
234
+ return 1;
235
+ }
236
+
237
+ const bunBin = Bun.which("bun") || process.argv0;
238
+ const ownPacker = resolve(SINGLE_EXE_DIR, "assetsPacker.js");
239
+ const archive = resolve(SINGLE_EXE_DIR, "assets.tar");
240
+ const metafile = `${outfile}.meta.json`;
241
+ const scanDir = `${outfile}.scan`;
242
+
243
+ // ASSETS_BUNFS swaps the tar for `--compile --asset`: every packer
244
+ // copies into one fixed build/assets, and --asset keeps only that
245
+ // folder's basename, so the members land at assets/<name>@<version>/
246
+ // exactly as the tar back end writes them.
247
+ const bunfs = Boolean(process.env.ASSETS_BUNFS);
248
+ const staging = resolve(REPO_ROOT, "build", "assets");
249
+
250
+ // Nothing loads a tar in bunfs mode, so the asset loader — and the
251
+ // `assets.tar` it imports — cannot be in the graph. Everything else in
252
+ // entry.mjs still has to run, so the compile entry is a generated copy
253
+ // of it with that one import removed.
254
+ //
255
+ // Handing the main program to `bun build` directly would be simpler and
256
+ // breaks two ways. Whatever entry.mjs does after the import is lost, and
257
+ // a CommonJS program relying on a `require.main` guard the bundler
258
+ // inlines to false would never start. Worse, `--bytecode` only marks the
259
+ // chunk as an async module when a top-level await appears in an
260
+ // unwrapped part's own source; a main program that reaches top-level
261
+ // await only through `__esm`-wrapped dependencies is then evaluated on
262
+ // the sync path, its suspended generator is dropped, and the binary
263
+ // exits 0 having run nothing. entry.mjs awaits in its own source, so the
264
+ // copy carries that flag the way the tar build always has.
265
+ const bunfsEntry = resolve(SINGLE_EXE_DIR, ".bunfs-entry.mjs");
266
+ const dropBunfsEntry = () => bunfs && rmSync(bunfsEntry, { force: true });
267
+
268
+ if (bunfs && !writeBunfsEntry(resolve(SINGLE_EXE_DIR, "entry.mjs"), bunfsEntry)) {
269
+ return 1;
270
+ }
271
+
272
+ const compileEntry = bunfs ? bunfsEntry : "./entry.mjs";
273
+
274
+ const step = (label, args, cwd) => {
275
+ console.log("");
276
+ console.log(Bun?.markdown?.ansi?.("## " + label) || label);
277
+ console.log("Running: ", bunBin, args);
278
+
279
+ const result = child_process.spawnSync(bunBin, args, {
280
+ cwd,
281
+ stdio: "inherit",
282
+ env: process.env,
283
+ });
284
+
285
+ console.log("");
286
+ console.log(Bun?.markdown?.ansi?.("- Status: " + result.status + " for " + label) || result.status);
287
+
288
+ if (result.error) console.error(result.error);
289
+ return result.status ?? 1;
290
+ };
291
+
292
+ // What each packer is told to write into, and in which mode.
293
+ const packInto = bunfs ? ["-f", staging] : ["-f", archive, "-p"];
294
+
295
+ if (bunfs) {
296
+ mkdirSync(staging, { recursive: true });
297
+ console.log("");
298
+ console.log(`Staging assets into ${staging}`);
299
+ } else if (step("Pack own assets", [ownPacker, ...packInto], SINGLE_EXE_DIR) !== 0) {
300
+ // Tar mode packs first: the scan below has to resolve entry.mjs's
301
+ // `with { type: "file" }` import of the archive.
302
+ console.error("Own assets failed to pack");
303
+ return 1;
304
+ }
305
+
306
+ // Bundle only. The binary is thrown away, so skip --compile, --minify
307
+ // and --bytecode; the module graph is the same and it is much faster.
308
+ if (step("Scan module graph", ["build", compileEntry, "--target=bun", `--metafile=${metafile}`, `--outdir=${scanDir}`], SINGLE_EXE_DIR) !== 0) {
309
+ console.error("Scan build failed");
310
+ dropBunfsEntry();
311
+ return 1;
312
+ }
313
+
314
+ const packers = findAssetPackers(metafile, SINGLE_EXE_DIR, bunfs ? "" : ownPacker);
315
+ rmSync(scanDir, { recursive: true, force: true });
316
+
317
+ console.log("");
318
+ console.log(`Found ${packers.length} asset packer(s) to run`);
319
+
320
+ for (const packer of packers) {
321
+ const name = dirname(dirname(packer)).split("/").pop();
322
+
323
+ // Tar mode appends to a shared archive; folder mode needs no -r
324
+ // because each package owns its own <name>@<version> subtree.
325
+ const args = bunfs ? [packer, ...packInto] : [packer, ...packInto, "-r"];
326
+
327
+ if (step(`Pack ${name}`, args, dirname(packer)) !== 0) {
328
+ console.error(`Failed to pack assets from ${packer}`);
329
+ dropBunfsEntry();
330
+ return 1;
331
+ }
332
+ }
333
+
334
+ // Compress last. Every packer above reads the archive and writes it
335
+ // back uncompressed, so this has to happen after the final append.
336
+ // libarchive sniffs the format on read, so assetsLoader needs no
337
+ // change to consume it. Nothing to compress in bunfs mode.
338
+ if (bunfs) {
339
+ // no archive in this mode
340
+ } else if (process.env.ASSETS_NO_GZIP) {
341
+ console.log("");
342
+ console.log("ASSETS_NO_GZIP is set, leaving the archive uncompressed");
343
+ } else {
344
+ console.log("");
345
+ console.log(Bun?.markdown?.ansi?.("## Compress assets") || "Compress assets");
346
+
347
+ try {
348
+ const raw = await Bun.file(archive).bytes();
349
+ const gzipped = Bun.gzipSync(raw, { level: 9 });
350
+
351
+ await Bun.write(archive, gzipped);
352
+
353
+ const pct = raw.byteLength ? Math.round((1 - gzipped.byteLength / raw.byteLength) * 100) : 0;
354
+ console.log(`${raw.byteLength} -> ${gzipped.byteLength} bytes (-${pct}%)`);
355
+ } catch (e) {
356
+ console.error("Could not compress the archive; continuing uncompressed");
357
+ console.error(e);
358
+ }
359
+ }
360
+
361
+ if (step("Compile executable", [
362
+ "build",
363
+ "--format=esm",
364
+ "--compile",
365
+ "--minify",
366
+ "--bytecode",
367
+ compileEntry,
368
+ ...(bunfs ? ["--asset", staging] : []),
369
+ `--outfile=${outfile}`,
370
+ `--metafile-md=${outfile}.meta.md`,
371
+ ...(normalizedTarget ? [`--target=${normalizedTarget}`] : []),
372
+ ...extraBunArgs,
373
+ ], SINGLE_EXE_DIR) !== 0) {
374
+ console.error("Compile failed");
375
+ }
376
+
377
+ dropBunfsEntry();
378
+
379
+ const isWindows = normalizedTarget ? normalizedTarget.toLowerCase().includes("windows") : process.platform === "win32";
380
+ if (isWindows && !outfile.toLowerCase().endsWith(".exe")) {
381
+ outfile += ".exe";
382
+ }
383
+
384
+ if (await Bun.file(outfile).exists()) {
385
+ console.log(`Built executable: ${outfile}`);
386
+ return 0;
387
+ }
388
+
389
+ console.log(`Error while building executable: ${outfile}`);
390
+ return 1;
391
+ }
392
+
393
+
394
+ // entry.mjs, minus the import of the asset loader, written to `outFile`
395
+ // as the compile entry for bunfs builds. Returns whether it was written.
396
+ //
397
+ // A textual copy, not a generated stub: entry.mjs may do more than import
398
+ // the main program, and all of it has to survive.
399
+ export function writeBunfsEntry(entryFile, outFile) {
400
+ let src;
401
+
402
+ try {
403
+ src = readFileSync(entryFile, "utf8");
404
+ } catch (e) {
405
+ console.error(`Could not read ${entryFile}`);
406
+ console.error(e);
407
+ return false;
408
+ }
409
+
410
+ // Whole-line side-effect import of the loader, in any of the shapes a
411
+ // project might have written it. Nothing else may reference it: the
412
+ // loader is what pulls in `assets.tar`, which no bunfs build produces.
413
+ const loaderImport = /^[ \t]*import\s+(?:[^"'\n]*\bfrom\s+)?["'][^"'\n]*assetsLoader\.mjs["'][ \t]*;?[ \t]*$/gm; // "
414
+ const stripped = src.replace(loaderImport, "");
415
+
416
+ if (stripped === src) {
417
+ console.log("");
418
+ console.log(`No assetsLoader import found in ${entryFile}; compiling it as is`);
419
+ }
420
+
421
+ const header =
422
+ "// Generated by single-exe/compiled.js for ASSETS_BUNFS=1 builds.\n"
423
+ + "// A copy of entry.mjs without the asset loader; edit entry.mjs.\n";
424
+
425
+ // A hashbang is only a hashbang on the first line; anywhere else it is
426
+ // a syntax error. Keep it there and put the header underneath, unless
427
+ // the file is nothing but a hashbang and there is no underneath.
428
+ const firstBreak = stripped.indexOf("\n");
429
+ const hasHashbang = stripped.startsWith("#!");
430
+ const hashbang = hasHashbang && firstBreak !== -1 ? stripped.slice(0, firstBreak + 1) : "";
431
+
432
+ try {
433
+ writeFileSync(
434
+ outFile,
435
+ hasHashbang && !hashbang
436
+ ? stripped
437
+ : hashbang + header + stripped.slice(hashbang.length),
438
+ );
439
+ } catch (e) {
440
+ console.error(`Could not write ${outFile}`);
441
+ console.error(e);
442
+ return false;
443
+ }
444
+
445
+ return true;
446
+ }
447
+
448
+
449
+ // The main program, as declared by the last import of entry.mjs. That
450
+ // file is already the one place an adopting project edits.
451
+ export function lastEntryImport(entryFile) {
452
+ let src;
453
+
454
+ try {
455
+ src = readFileSync(entryFile, "utf8");
456
+ } catch {
457
+ return "";
458
+ }
459
+
460
+ // Drop comments first, or a commented-out import would win.
461
+ src = src.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^[ \t]*\/\/.*$/gm, "");
462
+
463
+ const re = /\bimport\b[^"'\n]*["']([^"']+)["']/g; // "
464
+ let last = "";
465
+
466
+ for (let m; (m = re.exec(src)) !== null; ) last = m[1];
467
+
468
+ return last;
469
+ }
470
+
471
+
472
+ // Absolute, de-duplicated paths of every assetsPacker.js in the metafile,
473
+ // minus `own`. Metafile input keys are relative to the build's cwd.
474
+ export function findAssetPackers(metafilePath, baseDir, own = "") {
475
+ if (!existsSync(metafilePath)) return [];
476
+
477
+ let meta;
478
+ try {
479
+ meta = JSON.parse(readFileSync(metafilePath, "utf8"));
480
+ } catch (e) {
481
+ console.error(`Could not read metafile ${metafilePath}`);
482
+ console.error(e);
483
+ return [];
484
+ }
485
+
486
+ const found = new Set();
487
+
488
+ for (const key of Object.keys(meta?.inputs ?? {})) {
489
+ if (basename(key) !== "assetsPacker.js") continue;
490
+
491
+ const abs = resolve(baseDir, key);
492
+ if (abs === own || !existsSync(abs)) continue;
493
+
494
+ found.add(abs);
495
+ }
496
+
497
+ return [...found].sort();
498
+ }
@@ -6,5 +6,12 @@
6
6
  // 3. Starts the main program only after the assets are ready
7
7
  import "./assetsLoader.mjs";
8
8
 
9
+ // Keep these as `await`, not `.then()`. Besides the sequencing, they are
10
+ // the only top-level await this program writes anywhere, and `--bytecode`
11
+ // reads exactly that to decide the program starts asynchronously. With
12
+ // none written, the executable is evaluated synchronously, its suspended
13
+ // startup is dropped, and it exits 0 having run nothing — no error, at any
14
+ // size. Awaits the bundler generates for us do not count; see the README's
15
+ // "The await in entry.mjs is load-bearing".
9
16
  await globalThis.assetsLoaderPromise;
10
17
  await import("../src/index.js");
@@ -117,7 +117,7 @@ import { Config } from "./config/config.js";
117
117
  import { defaultAllSettings, OPTION_CHOICES, LOCAL_SETTINGS } from "./config/defaults.js";
118
118
  import { cleanConfig } from "./config/clean.js";
119
119
  import { RuntimeRegistry, RTColorscheme, RTHelp } from "./runtime/registry.js";
120
- import { assetPath, buildHtmlBundleImageMap, hasInternalAssets, listInternalAssetDirs, listInternalAssetPaths, readInternalAssetText } from "../single-exe/assetsHelper.js";
120
+ import { assetDiskPath, assetPath, buildHtmlBundleImageMap, hasInternalAssets, listInternalAssetDirs, listInternalAssetPaths, readAssetText, readInternalAssetText } from "../single-exe/assetsHelper.js";
121
121
  //import { PluginManager } from "./plugins/manager.js";
122
122
  import { JsPluginManager, buildMicroGlobal, buildTuiBlockIndex, captureTuiRerenderState, clearTuiSourceDependentState, findTuiBlockInIndex, indexTuiHeadingRows, insertTuiTextareaNewline, markTuiTableCellDirtyAtPosition, mergeTuiTextareaBackward, mergeTuiTextareaForward, navigateTuiHeadingFragment, restoreTuiHiddenHeadings, restoreTuiRerenderState, toggleTuiHeadingAt, tuiCheckboxRerenderMismatchMessage, tuiTableCellAtPosition, runAction, listActions } from "./plugins/js-bridge.js";
123
123
  import { Colorscheme } from "./config/colorscheme.js";
@@ -8466,7 +8466,7 @@ async function printReadmeDocs() {
8466
8466
  }
8467
8467
 
8468
8468
  async function bundledReadmeSource() {
8469
- return readInternalAssetText("README.md") ?? await Bun.file(join(REPO_ROOT, "README.md")).text();
8469
+ return readAssetText("README.md");
8470
8470
  }
8471
8471
 
8472
8472
  async function exportReadme() {
@@ -8482,7 +8482,7 @@ async function exportCdpMaze() {
8482
8482
  }
8483
8483
 
8484
8484
  async function printChangelogDocs() {
8485
- const changelog = readInternalAssetText("CHANGELOG.md") ?? await Bun.file(join(REPO_ROOT, "CHANGELOG.md")).text();
8485
+ const changelog = await readAssetText("CHANGELOG.md");
8486
8486
  process.stdout.write(Bun.markdown.ansi(changelog, { hyperlinks: true }));
8487
8487
  }
8488
8488
 
@@ -8491,7 +8491,7 @@ async function printTestappSource() {
8491
8491
  }
8492
8492
 
8493
8493
  async function bundledMarkdownSource(filename) {
8494
- return readInternalAssetText(filename) ?? await Bun.file(join(REPO_ROOT, filename)).text();
8494
+ return readAssetText(filename);
8495
8495
  }
8496
8496
 
8497
8497
  function availableDemoAssets() {
@@ -8500,7 +8500,7 @@ function availableDemoAssets() {
8500
8500
  paths = listInternalAssetPaths("demos");
8501
8501
  } else {
8502
8502
  try {
8503
- paths = readdirSync(join(REPO_ROOT, "demos"), { withFileTypes: true })
8503
+ paths = readdirSync(assetDiskPath("demos"), { withFileTypes: true })
8504
8504
  .filter((entry) => entry.isFile())
8505
8505
  .map((entry) => assetPath("demos", entry.name));
8506
8506
  } catch {
@@ -8853,7 +8853,10 @@ async function main() {
8853
8853
  syncEditorSettings(config);
8854
8854
 
8855
8855
  addCheckpoint("Runtime Registry Init");
8856
- const runtime = new RuntimeRegistry({ repoRoot: REPO_ROOT, configDir: config.configDir });
8856
+ // Not REPO_ROOT: --assets-extract writes the tree under its own
8857
+ // assets/<name>@<version>/ namespace, and --assets-external then reads
8858
+ // from there. assetDiskPath("") is that root, or REPO_ROOT in a checkout.
8859
+ const runtime = new RuntimeRegistry({ repoRoot: assetDiskPath(""), configDir: config.configDir });
8857
8860
  await runtime.init({ user: true });
8858
8861
 
8859
8862
  addCheckpoint("Colorscheme & Syntax Load");
@@ -8887,7 +8890,7 @@ async function main() {
8887
8890
  jsItems.push({ name, builtin: true });
8888
8891
  }
8889
8892
  } else {
8890
- const builtinJsDir = join(REPO_ROOT, "runtime", "jsplugins");
8893
+ const builtinJsDir = assetDiskPath("runtime/jsplugins");
8891
8894
  if (existsSync(builtinJsDir)) {
8892
8895
  for (const entry of readdirSync(builtinJsDir, { withFileTypes: true })) {
8893
8896
  if (!entry.isDirectory()) continue;
@@ -8961,7 +8964,7 @@ async function main() {
8961
8964
  const jsPromise = (async () => {
8962
8965
  const start = Bun.nanoseconds();
8963
8966
  const jsDirs = [
8964
- { dir: join(REPO_ROOT, "runtime", "jsplugins"), builtin: true },
8967
+ { dir: assetDiskPath("runtime/jsplugins"), builtin: true },
8965
8968
  { dir: join(config.configDir, "jsplug"), builtin: false },
8966
8969
  ];
8967
8970
  await jsPlugins.loadFrom(jsDirs);
@@ -0,0 +1,16 @@
1
+ #!/bin/sh
2
+
3
+ ':' //; sd=$(dirname "$(realpath "$0")")
4
+
5
+ ':' //; exec bun "$sd"/src/index.js "$@"
6
+
7
+ console.log(Bun.argv)
8
+
9
+ if(Bun.argv[2]=='--jsgotty')
10
+ {
11
+ Bun.argv.splice(2,1);
12
+ let m=await import('../../wgotty/gotty.js');
13
+ m.bootstrap()
14
+ }
15
+ else
16
+ await import('../src/index.js');
package/bin/bunmsh.bat ADDED
@@ -0,0 +1,3 @@
1
+ @echo off
2
+ call "%~dp0bun.bat" "%~dp0..\apps\bunmsh\bunmsh" %*
3
+ exit /b %ERRORLEVEL%
package/bin/bunmsh.sh ADDED
@@ -0,0 +1,5 @@
1
+ #!/bin/sh
2
+
3
+ sd=$(dirname "$(realpath "$0")")
4
+
5
+ exec bun "$sd"/../apps/bunmsh/bunmsh "$@"
package/bin/init.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  import pkg from "../package.json" with { type: "json" };
4
4
  import { existsSync, lstatSync, mkdirSync, mkdtempSync, rmSync, symlinkSync, unlinkSync } from "node:fs";
5
- import { tmpdir as systemTmpDir } from "node:os";
5
+ import { homedir, tmpdir as systemTmpDir } from "node:os";
6
6
  import { basename, delimiter, dirname, isAbsolute, resolve } from "node:path";
7
7
  import { createInterface } from "node:readline/promises";
8
8
  import { fileURLToPath } from "node:url";
@@ -29,6 +29,10 @@ Init options:
29
29
  --changelog Render CHANGELOG.md in the terminal and exit
30
30
  --export [output.tgz] Export this Buninu installation (default: ./buninu.tgz)
31
31
  --export-config [output.json] Export this package.json (default: ./buninu.json)
32
+ -i, --install [dir] Install this package into <dir>/${pkg.name} (default: .)
33
+ -si, --strip-install [dir] Install into <dir> directly, without a
34
+ top-level directory of its own
35
+ (--install --help shows the full install options)
32
36
  --shell <path|name> Override buninu.shell for this run
33
37
  --command <command> Override buninu.command for this run
34
38
 
@@ -401,6 +405,19 @@ function getFreePort() {
401
405
  return port;
402
406
  }
403
407
 
408
+ // buninu.xdgDataHome moves the data directory XDG-aware programs write to,
409
+ // bunmsh's command history among them, without touching HOME: a shell started
410
+ // here still finds the SSH keys and Git configuration in the user's own home.
411
+ // `true` puts it inside the installation so it travels with a copied tree; a
412
+ // string names a directory, resolved from the package.json directory when it
413
+ // is relative, the way buninu.shell already resolves one.
414
+ function configuredXdgDataHome() {
415
+ const configured = pkg.buninu?.xdgDataHome;
416
+ if (configured === true) return resolve(rootDir, ".local", "share");
417
+ if (typeof configured !== "string" || !configured.trim()) return null;
418
+ return isAbsolute(configured) ? configured : resolve(rootDir, configured);
419
+ }
420
+
404
421
  async function createChildEnvironment(environment) {
405
422
  const packageDir = dirname(rootDir);
406
423
  const androidCacheDir = resolve(packageDir, "cache");
@@ -411,14 +428,17 @@ async function createChildEnvironment(environment) {
411
428
  : resolve(rootDir, "tmp")
412
429
  : systemTmpDir()
413
430
  );
414
- const homeDir = process.env.HOME || rootDir;
431
+ const homeDir = process.env.HOME || homedir() || rootDir;
415
432
  const envFile = await pathExists(resolve(rootDir, ".bashrc"))
416
433
  ? resolve(rootDir, ".bashrc")
417
434
  : await pathExists(resolve(homeDir, ".bashrc"))
418
435
  ? resolve(homeDir, ".bashrc")
419
436
  : null;
420
437
 
438
+ const xdgDataHome = configuredXdgDataHome();
439
+
421
440
  mkdirSync(tmpDir, { recursive: true });
441
+ if (xdgDataHome) mkdirSync(xdgDataHome, { recursive: true });
422
442
 
423
443
  const inheritedPath = process.env.PATH || "";
424
444
  const hasTermuxExecPreload =
@@ -443,6 +463,7 @@ async function createChildEnvironment(environment) {
443
463
  TMPDIR: tmpDir,
444
464
  SHELL: process.env.SHELL || environment.shell,
445
465
  BUNINU_HOME: rootDir,
466
+ ...(xdgDataHome ? { XDG_DATA_HOME: xdgDataHome } : {}),
446
467
  ...(envFile ? { ENV: envFile } : {}),
447
468
  TERM: process.env.TERM || "xterm-256color",
448
469
  COLORTERM: process.env.COLORTERM || "truecolor",
@@ -546,7 +567,25 @@ function shellCommandArguments(shell, command, exitAfterCmd) {
546
567
  return [shell, "-c", wrapped, shell, command];
547
568
  }
548
569
 
549
- const startScript = pkg.scripts?.start;
570
+ // Install is a mode of its own rather than a startup option, so it follows the
571
+ // same "must be the first argument" rule as the direct app launches above and
572
+ // never competes with an argument being forwarded to jsgotty.
573
+ function isInstallArgument(argument = "") {
574
+ return argument === "-i" || argument === "--install" ||
575
+ argument === "-si" || argument === "--strip-install" ||
576
+ argument.startsWith("--install=") || argument.startsWith("--strip-install=");
577
+ }
578
+
579
+ if (isInstallArgument(process.argv[2])) {
580
+ const { runInstall } = await import("./install.js");
581
+ process.exit(await runInstall(process.argv.slice(2)));
582
+ }
583
+
584
+ let startScript = pkg.scripts?.start;
585
+
586
+ if(process.argv.includes('--local'))
587
+ startScript = pkg.scripts?.local ;
588
+
550
589
  if (typeof startScript !== "string" || !startScript.trim()) {
551
590
  fail("package.json does not define scripts.start");
552
591
  }
@@ -606,9 +645,13 @@ const hasExplicitPort = forwardedArguments.some((argument) =>
606
645
  const portArguments = hasExplicitPort ? [] : ["-p", String(getFreePort())];
607
646
  const childEnvironment = await createChildEnvironment(environment);
608
647
 
648
+ const infoSuffix =
649
+ process.argv.includes('--local') ?
650
+ 'bunmsh' :
651
+ `port=${portArguments[1] || "user-defined"}, shell=${environment.shell}` ;
652
+
609
653
  console.error(
610
- `${pkg.name}@${pkg.version}: ${environment.name}/${process.arch}, ` +
611
- `port=${portArguments[1] || "user-defined"}, shell=${environment.shell}`,
654
+ `${pkg.name}@${pkg.version}: ${environment.name}/${process.arch}, ` + infoSuffix
612
655
  );
613
656
 
614
657
  const child = Bun.spawn(