buninu 0.2.5 → 0.3.1

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 (122) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +100 -2
  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/native-bridge/native-bridge.js +45 -0
  119. package/apps/xdg-open/xdg-open.js +74 -2
  120. package/bin/init.js +11 -3
  121. package/demo-server.js +64 -0
  122. package/package.json +4 -2
@@ -0,0 +1,12 @@
1
+ #!/bin/sh
2
+
3
+ echo shell-before
4
+ Bun.version
5
+ Bun.e, Math.cos(1)
6
+
7
+ Bun.sha.var = { message: "shared", count: 1 }
8
+ Bun.sha.var.count += 1
9
+ Bun.sha.var
10
+ echo "from command substitution: $(Bun.sha.var.message)"
11
+
12
+ echo shell-after
@@ -101,6 +101,51 @@ export const tts = ttsStatus
101
101
  // the receiving app on modern Android.
102
102
  export const xdgOpen = (target) => call("xdgOpen", [target])
103
103
 
104
+ // The host has exactly two WebViews, both alive from startup and neither ever
105
+ // created nor closed at runtime: WEBVIEW_CONSOLE (0) is the jsgotty terminal
106
+ // this shell is being rendered in, WEBVIEW_APP (1) is the app WebView, which
107
+ // simply starts out blank and behind. WEBVIEW_CURRENT (-1) means whichever
108
+ // one is in front right now, and is the default wherever an id is optional.
109
+ // All four functions also go by openwv/evalwv/showwv/currwv, both here and
110
+ // over the wire (the Java dispatcher answers to either spelling and lists
111
+ // both in _discover), on the same grounds as getcb/setcb above.
112
+ export const WEBVIEW_CURRENT = -1
113
+ export const WEBVIEW_CONSOLE = 0
114
+ export const WEBVIEW_APP = 1
115
+
116
+ // Loads url into a WebView *without* bringing it to the front, so
117
+ // openWebView(WEBVIEW_APP, url) while the user is looking at the terminal is
118
+ // a plain background load -- showWebView is the only thing that changes what
119
+ // is on screen. Omitting url loads nothing and just reports the id back,
120
+ // which is how -1 gets resolved to a real id. Returns the id loaded into.
121
+ export const openWebView = (id = WEBVIEW_CURRENT, url = "") =>
122
+ call("openWebView", [id, url])
123
+ export const openwv = openWebView
124
+
125
+ // Resolves to the value the expression produced -- a real number/string/
126
+ // object, not a string containing one -- because the host sends
127
+ // evaluateJavascript's own JSON text straight back as the response body.
128
+ // undefined, a function, and a thrown exception all arrive as null: WebView
129
+ // itself does not distinguish them, so return something JSON can carry when
130
+ // you need to tell them apart. A page that never answers is given up on by
131
+ // the host (inside this module's own call timeout) and comes back as an
132
+ // "error: ..." string rather than hanging.
133
+ export const evalWebView = (id, js) => call("evalWebView", [id, js])
134
+ export const evalwv = evalWebView
135
+
136
+ // Brings a WebView to the front. The bottom key bar (Ctrl/Alt/Shift and
137
+ // friends), the volume-key menu and the back key all act on whatever is in
138
+ // front, so this is also what re-points them. An id of -1 -- the default --
139
+ // switches to the next WebView rather than being a no-op, which with two of
140
+ // them is simply a toggle. Returns the id now in front.
141
+ export const showWebView = (id = WEBVIEW_CURRENT) => call("showWebView", [id])
142
+ export const showwv = showWebView
143
+
144
+ // The id of the WebView in front, for code that wants to put something back
145
+ // the way it found it.
146
+ export const currWebView = () => call("currWebView")
147
+ export const currwv = currWebView
148
+
104
149
  // `bun native-bridge.js <func> [args...]`, dispatched straight to call(func,
105
150
  // args) -- not restricted to toast/clipboardRead/clipboardWrite, exactly like
106
151
  // calling rpcraw(func, args) would be. This is a raw RPC, equivalent to a
@@ -7,9 +7,74 @@
7
7
  // the default handler" mechanism -- macOS/Windows are real OS
8
8
  // commands/APIs, but Linux's is conventionally *also* a command named
9
9
  // xdg-open, so this script has to be careful not to find itself on PATH.
10
- import { xdgOpen as bridgeOpen, available } from "../native-bridge/native-bridge.js"
10
+ import {
11
+ xdgOpen as bridgeOpen, available, openWebView, showWebView,
12
+ } from "../native-bridge/native-bridge.js"
11
13
  const fs = require("fs")
12
14
 
15
+ // MINAPK_WEBVIEW names *which* of the host app's WebViews to open into (0 is
16
+ // the console, 1 the app WebView, -1 whichever is in front); unset means the
17
+ // unchanged behavior of handing the target to the system's default handler.
18
+ // It is read here rather than being detected by the host, because it is an
19
+ // environment variable of this Bun process tree -- the host app's own process
20
+ // cannot see a value exported in this shell a moment ago, and the whole point
21
+ // of the variable is that it can be.
22
+ //
23
+ // Deliberately never Number()/parseInt(): Number("") is 0, and 0 is a
24
+ // perfectly valid WebView id, so an exported-but-empty MINAPK_WEBVIEW= (the
25
+ // usual way to switch something off) would come out meaning "open in the
26
+ // console" -- i.e. navigate the terminal away. parseInt would likewise turn
27
+ // "1x" into 1. Only a bare optionally-signed integer counts; anything else is
28
+ // reported and treated as unset.
29
+ function webViewIdFromEnv() {
30
+ const raw = (process.env.MINAPK_WEBVIEW ?? "").trim()
31
+ if (raw === "") return null
32
+ if (!/^-?\d+$/.test(raw)) {
33
+ process.stderr.write(
34
+ `xdg-open: ignoring MINAPK_WEBVIEW=${raw} (not an integer WebView id)\n`)
35
+ return null
36
+ }
37
+ return Number(raw)
38
+ }
39
+
40
+ // Only URLs. A filesystem path stays on the bridgeOpen path even when
41
+ // MINAPK_WEBVIEW is set: WebView's setAllowFileAccess defaults to false from
42
+ // API 30 on, so a file:// URL under Buninu's home would load as a blank page,
43
+ // whereas the host's xdgOpen already serves that file properly through its
44
+ // read-only content:// provider.
45
+ const isUrl = (target) => /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(target)
46
+
47
+ // The host answers with the id it actually used -- a JSON number -- and with
48
+ // a string when something went wrong: "error: no such WebView: 9" from the
49
+ // dispatcher, or rpc.mjs's own "Unknown func ..." when the APK is older than
50
+ // these functions and does not list them in _discover. rpc.mjs also *resolves*
51
+ // to an Error object instead of throwing when the fetch itself fails. So
52
+ // success is checked as "is genuinely a number", never by parsing the result:
53
+ // see webViewIdFromEnv above for why a coercing parse is dangerous here.
54
+ //
55
+ // showWebView(-1) means "switch to the next one", not "the one just loaded",
56
+ // so the switch uses the id openWebView resolved and handed back, not the id
57
+ // that was asked for.
58
+ async function openInWebView(id, url) {
59
+ const opened = await openWebView(id, url)
60
+ if (typeof opened !== "number" || !Number.isInteger(opened)) {
61
+ process.stderr.write(
62
+ `xdg-open: MINAPK_WEBVIEW=${id}: ${opened?.message ?? opened}\n`)
63
+ return false
64
+ }
65
+
66
+ const shown = await showWebView(opened)
67
+ if (typeof shown !== "number" || !Number.isInteger(shown)) {
68
+ // Loaded but not brought to the front: say so rather than reporting
69
+ // success for something the user cannot see, but do not fall back to the
70
+ // system handler either -- the page really is loaded in that WebView.
71
+ process.stderr.write(
72
+ `xdg-open: loaded in WebView ${opened} but could not switch to it: ` +
73
+ `${shown?.message ?? shown}\n`)
74
+ }
75
+ return true
76
+ }
77
+
13
78
  // Buninu's own bin/ is prepended to PATH ahead of any real system binary of
14
79
  // the same name (same concern as xclip.js/tts.js), and this script is
15
80
  // itself registered as Buninu's "xdg-open" command -- a naive PATH search
@@ -46,7 +111,14 @@ let ok = false
46
111
  try {
47
112
  if (process.platform === "android") {
48
113
  if (available()) {
49
- ok = await bridgeOpen(target)
114
+ const webViewId = webViewIdFromEnv()
115
+ // A failed WebView open falls back to the system handler rather than
116
+ // failing outright: the request was "open this", and MINAPK_WEBVIEW
117
+ // only says where it would be preferred. openInWebView has already
118
+ // explained on stderr why it could not.
119
+ ok = webViewId !== null && isUrl(target)
120
+ ? (await openInWebView(webViewId, target)) || (await bridgeOpen(target))
121
+ : await bridgeOpen(target)
50
122
  } else if (Bun.which("termux-open")) {
51
123
  // Plain Termux (no minapk) still reports platform "android" but has
52
124
  // no PKG_BRIDGE_SOCK -- termux-open (Termux:API) does the same
package/bin/init.js CHANGED
@@ -546,7 +546,11 @@ function shellCommandArguments(shell, command, exitAfterCmd) {
546
546
  return [shell, "-c", wrapped, shell, command];
547
547
  }
548
548
 
549
- const startScript = pkg.scripts?.start;
549
+ let startScript = pkg.scripts?.start;
550
+
551
+ if(process.argv.includes('--local'))
552
+ startScript = pkg.scripts?.local ;
553
+
550
554
  if (typeof startScript !== "string" || !startScript.trim()) {
551
555
  fail("package.json does not define scripts.start");
552
556
  }
@@ -606,9 +610,13 @@ const hasExplicitPort = forwardedArguments.some((argument) =>
606
610
  const portArguments = hasExplicitPort ? [] : ["-p", String(getFreePort())];
607
611
  const childEnvironment = await createChildEnvironment(environment);
608
612
 
613
+ const infoSuffix =
614
+ process.argv.includes('--local') ?
615
+ 'bunmsh' :
616
+ `port=${portArguments[1] || "user-defined"}, shell=${environment.shell}` ;
617
+
609
618
  console.error(
610
- `${pkg.name}@${pkg.version}: ${environment.name}/${process.arch}, ` +
611
- `port=${portArguments[1] || "user-defined"}, shell=${environment.shell}`,
619
+ `${pkg.name}@${pkg.version}: ${environment.name}/${process.arch}, ` + infoSuffix
612
620
  );
613
621
 
614
622
  const child = Bun.spawn(
package/demo-server.js ADDED
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env bun
2
+
3
+ // Smallest end-to-end demo of the WebView bridge: serve one fixed page on a
4
+ // port nobody picked in advance, then have xdg-open put it in the host app's
5
+ // app WebView and switch to it. Run it from a Buninu shell inside minapk's
6
+ // APK:
7
+ //
8
+ // bun demo-server.js
9
+ //
10
+ // Outside such an APK it still runs and still serves; xdg-open just falls
11
+ // back to whatever that platform's default handler is (see apps/xdg-open).
12
+
13
+ const BODY = "Hello 你好 こんにちは"
14
+
15
+ // port: 0 asks the OS for a free port instead of guessing one and racing
16
+ // whatever already has it -- Buninu's own launcher picks jsgotty's port the
17
+ // same way. The real number is only knowable after the listener exists, so
18
+ // the URL is built from server.port, never from a constant.
19
+ // hostname stays on loopback: this is a demo page for the WebView on this
20
+ // device, not something other machines on the network should reach.
21
+ const server = Bun.serve({
22
+ hostname: "127.0.0.1",
23
+ port: 0,
24
+ fetch() {
25
+ // charset is not optional here: without it the WebView guesses an
26
+ // encoding for the response, and the CJK half of BODY is what shows up
27
+ // as mojibake when it guesses wrong.
28
+ return new Response(BODY, {
29
+ headers: { "Content-Type": "text/plain; charset=utf-8" },
30
+ })
31
+ },
32
+ })
33
+
34
+ // 127.0.0.1 rather than localhost: the listener is bound to the v4 loopback
35
+ // address specifically, while "localhost" can resolve to ::1 first and give
36
+ // the WebView a connection refused before it ever tries v4.
37
+ const url = `http://127.0.0.1:${server.port}`
38
+ console.log(`demo-server: serving ${JSON.stringify(BODY)} at ${url}`)
39
+
40
+ // Buninu puts its own bin/ on PATH, so "xdg-open" normally resolves to
41
+ // apps/xdg-open through the shloader. Fall back to running the script
42
+ // directly for the case where this file is run outside a Buninu shell (a bare
43
+ // `bun demo-server.js` in a checkout), where that PATH entry is not there.
44
+ const xdgOpen = Bun.which("xdg-open")
45
+ const command = xdgOpen
46
+ ? [xdgOpen, url]
47
+ : [process.execPath, `${import.meta.dir}/apps/xdg-open/xdg-open.js`, url]
48
+
49
+ // MINAPK_WEBVIEW is read by xdg-open itself, not by the host app -- it names
50
+ // which WebView to load into (1 is the app WebView) and brings it to the
51
+ // front. Passed to this one child rather than exported, so it changes nothing
52
+ // for the rest of the session.
53
+ const child = Bun.spawn(command, {
54
+ env: { ...process.env, MINAPK_WEBVIEW: "1" },
55
+ stdout: "inherit",
56
+ stderr: "inherit",
57
+ })
58
+
59
+ if (await child.exited !== 0)
60
+ console.error(`demo-server: ${command[0]} exited with ${child.exitCode}; page is still served at ${url}`)
61
+
62
+ // The server has to outlive the open: the WebView fetches the page after
63
+ // xdg-open has already returned. Ctrl-C to stop.
64
+ console.log("demo-server: Ctrl-C to stop")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "buninu",
3
- "version": "0.2.5",
3
+ "version": "0.3.1",
4
4
  "description": "Buninu is a portable, self-bootstrapping Unix-like userspace built on Bun that runs across operating systems",
5
5
  "type": "module",
6
6
  "main": "./bin/init.js",
@@ -23,10 +23,12 @@
23
23
  "macos": null,
24
24
  "windows": null
25
25
  },
26
- "exitAfterCmd": false
26
+ "exitAfterCmd": false,
27
+ "backToConsole": true
27
28
  },
28
29
  "scripts": {
29
30
  "start": "bun ./apps/jsgotty/gotty.js --reconnect -r -w --webgl -a 127.0.0.1",
31
+ "local": "bun ./apps/bunmsh/bunmsh -c 'exec bunmsh'",
30
32
  "check": "node --check ./bin/init.js"
31
33
  }
32
34
  }