buninu-linux 0.2.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.
- package/LICENSE +21 -0
- package/LICENSES/Bun-LICENSE.md +84 -0
- package/LICENSES/GCC-Runtime-Library-Exception-3.1.txt +73 -0
- package/LICENSES/GPL-2.0.txt +338 -0
- package/LICENSES/GPL-3.0.txt +674 -0
- package/LICENSES/LGPL-2.1.txt +501 -0
- package/LICENSES/Linux-syscall-note.txt +25 -0
- package/LICENSES/musl-COPYRIGHT.txt +193 -0
- package/NOTICE.md +184 -0
- package/README.md +537 -0
- package/build-image.sh +44 -0
- package/build-uki.sh +75 -0
- package/fetch-alpine.sh +80 -0
- package/fetch-bun.sh +41 -0
- package/fetch-plus-build.sh +11 -0
- package/hello/build-hello.sh +14 -0
- package/hello/hello.c +50 -0
- package/index.js +235 -0
- package/initramfs/bin/sh +3 -0
- package/initramfs/buninu/.bashrc +11 -0
- package/initramfs/buninu/ARCHITECTURE.md +452 -0
- package/initramfs/buninu/CHANGELOG.md +580 -0
- package/initramfs/buninu/LICENSE +21 -0
- package/initramfs/buninu/README.md +1161 -0
- package/initramfs/buninu/apps/bun/make-bunBin.sh +99 -0
- package/initramfs/buninu/apps/bunmsh/CHANGELOG.md +493 -0
- package/initramfs/buninu/apps/bunmsh/LICENSE +21 -0
- package/initramfs/buninu/apps/bunmsh/LICENSE-MICRO +58 -0
- package/initramfs/buninu/apps/bunmsh/LICENSE-MKSH +295 -0
- package/initramfs/buninu/apps/bunmsh/PORTING.md +192 -0
- package/initramfs/buninu/apps/bunmsh/README.md +1042 -0
- package/initramfs/buninu/apps/bunmsh/bunmsh +7 -0
- package/initramfs/buninu/apps/bunmsh/bunshell.md +61 -0
- package/initramfs/buninu/apps/bunmsh/help/README.md +2237 -0
- package/initramfs/buninu/apps/bunmsh/help/alias.md +22 -0
- package/initramfs/buninu/apps/bunmsh/help/basename.md +38 -0
- package/initramfs/buninu/apps/bunmsh/help/builtin.md +25 -0
- package/initramfs/buninu/apps/bunmsh/help/bun.md +27 -0
- package/initramfs/buninu/apps/bunmsh/help/bunmsh.md +47 -0
- package/initramfs/buninu/apps/bunmsh/help/cat.md +31 -0
- package/initramfs/buninu/apps/bunmsh/help/catfancy.md +35 -0
- package/initramfs/buninu/apps/bunmsh/help/cd.md +22 -0
- package/initramfs/buninu/apps/bunmsh/help/chmod.md +27 -0
- package/initramfs/buninu/apps/bunmsh/help/clear.md +21 -0
- package/initramfs/buninu/apps/bunmsh/help/colon.md +22 -0
- package/initramfs/buninu/apps/bunmsh/help/command.md +27 -0
- package/initramfs/buninu/apps/bunmsh/help/cp.md +28 -0
- package/initramfs/buninu/apps/bunmsh/help/curl.md +181 -0
- package/initramfs/buninu/apps/bunmsh/help/cut.md +25 -0
- package/initramfs/buninu/apps/bunmsh/help/date.md +25 -0
- package/initramfs/buninu/apps/bunmsh/help/dirname.md +25 -0
- package/initramfs/buninu/apps/bunmsh/help/echo.md +25 -0
- package/initramfs/buninu/apps/bunmsh/help/env.md +27 -0
- package/initramfs/buninu/apps/bunmsh/help/eval.md +22 -0
- package/initramfs/buninu/apps/bunmsh/help/exec.md +22 -0
- package/initramfs/buninu/apps/bunmsh/help/exit.md +21 -0
- package/initramfs/buninu/apps/bunmsh/help/export.md +22 -0
- package/initramfs/buninu/apps/bunmsh/help/false.md +22 -0
- package/initramfs/buninu/apps/bunmsh/help/find.md +33 -0
- package/initramfs/buninu/apps/bunmsh/help/getopts.md +28 -0
- package/initramfs/buninu/apps/bunmsh/help/grep.md +34 -0
- package/initramfs/buninu/apps/bunmsh/help/head.md +27 -0
- package/initramfs/buninu/apps/bunmsh/help/kill.md +43 -0
- package/initramfs/buninu/apps/bunmsh/help/ln.md +28 -0
- package/initramfs/buninu/apps/bunmsh/help/ls.md +37 -0
- package/initramfs/buninu/apps/bunmsh/help/lsbun.md +29 -0
- package/initramfs/buninu/apps/bunmsh/help/lsfancy.md +37 -0
- package/initramfs/buninu/apps/bunmsh/help/md5sum.md +21 -0
- package/initramfs/buninu/apps/bunmsh/help/mkdir.md +27 -0
- package/initramfs/buninu/apps/bunmsh/help/mktemp.md +26 -0
- package/initramfs/buninu/apps/bunmsh/help/mv.md +26 -0
- package/initramfs/buninu/apps/bunmsh/help/print.md +30 -0
- package/initramfs/buninu/apps/bunmsh/help/printf.md +25 -0
- package/initramfs/buninu/apps/bunmsh/help/pspa.md +38 -0
- package/initramfs/buninu/apps/bunmsh/help/pspac.md +57 -0
- package/initramfs/buninu/apps/bunmsh/help/pwd.md +21 -0
- package/initramfs/buninu/apps/bunmsh/help/read.md +27 -0
- package/initramfs/buninu/apps/bunmsh/help/readonly.md +27 -0
- package/initramfs/buninu/apps/bunmsh/help/realpath.md +21 -0
- package/initramfs/buninu/apps/bunmsh/help/rm.md +33 -0
- package/initramfs/buninu/apps/bunmsh/help/rmdir.md +27 -0
- package/initramfs/buninu/apps/bunmsh/help/sed.md +29 -0
- package/initramfs/buninu/apps/bunmsh/help/seq.md +29 -0
- package/initramfs/buninu/apps/bunmsh/help/serve.md +50 -0
- package/initramfs/buninu/apps/bunmsh/help/set.md +26 -0
- package/initramfs/buninu/apps/bunmsh/help/sha256sum.md +21 -0
- package/initramfs/buninu/apps/bunmsh/help/shift.md +23 -0
- package/initramfs/buninu/apps/bunmsh/help/sleep.md +25 -0
- package/initramfs/buninu/apps/bunmsh/help/sort.md +29 -0
- package/initramfs/buninu/apps/bunmsh/help/source.md +23 -0
- package/initramfs/buninu/apps/bunmsh/help/tab.md +30 -0
- package/initramfs/buninu/apps/bunmsh/help/tac.md +29 -0
- package/initramfs/buninu/apps/bunmsh/help/tail.md +27 -0
- package/initramfs/buninu/apps/bunmsh/help/tee.md +25 -0
- package/initramfs/buninu/apps/bunmsh/help/test.md +30 -0
- package/initramfs/buninu/apps/bunmsh/help/time.md +29 -0
- package/initramfs/buninu/apps/bunmsh/help/touch.md +26 -0
- package/initramfs/buninu/apps/bunmsh/help/tr.md +26 -0
- package/initramfs/buninu/apps/bunmsh/help/true.md +22 -0
- package/initramfs/buninu/apps/bunmsh/help/type.md +21 -0
- package/initramfs/buninu/apps/bunmsh/help/umask.md +22 -0
- package/initramfs/buninu/apps/bunmsh/help/unalias.md +28 -0
- package/initramfs/buninu/apps/bunmsh/help/uname.md +31 -0
- package/initramfs/buninu/apps/bunmsh/help/unset.md +28 -0
- package/initramfs/buninu/apps/bunmsh/help/wc.md +27 -0
- package/initramfs/buninu/apps/bunmsh/help/whence.md +26 -0
- package/initramfs/buninu/apps/bunmsh/help/which.md +41 -0
- package/initramfs/buninu/apps/bunmsh/help/yes.md +23 -0
- package/initramfs/buninu/apps/bunmsh/index.js +7 -0
- package/initramfs/buninu/apps/bunmsh/package.json +30 -0
- package/initramfs/buninu/apps/bunmsh/serve.js +468 -0
- package/initramfs/buninu/apps/bunmsh/single-exe/README.md +402 -0
- package/initramfs/buninu/apps/bunmsh/single-exe/assetsHelper.js +371 -0
- package/initramfs/buninu/apps/bunmsh/single-exe/assetsLoader.mjs +85 -0
- package/initramfs/buninu/apps/bunmsh/single-exe/assetsPacker.js +218 -0
- package/initramfs/buninu/apps/bunmsh/single-exe/compiled.js +527 -0
- package/initramfs/buninu/apps/bunmsh/single-exe/entry.mjs +17 -0
- package/initramfs/buninu/apps/bunmsh/src/cat-fancy.js +117 -0
- package/initramfs/buninu/apps/bunmsh/src/cat-operands.js +31 -0
- package/initramfs/buninu/apps/bunmsh/src/completion.js +363 -0
- package/initramfs/buninu/apps/bunmsh/src/curl.js +1191 -0
- package/initramfs/buninu/apps/bunmsh/src/environment.js +61 -0
- package/initramfs/buninu/apps/bunmsh/src/fancy-ls.js +186 -0
- package/initramfs/buninu/apps/bunmsh/src/find.js +119 -0
- package/initramfs/buninu/apps/bunmsh/src/history.js +198 -0
- package/initramfs/buninu/apps/bunmsh/src/main.js +850 -0
- package/initramfs/buninu/apps/bunmsh/src/mouse.js +103 -0
- package/initramfs/buninu/apps/bunmsh/src/shell.js +3932 -0
- package/initramfs/buninu/apps/bunmsh/test/catfancy.test.js +102 -0
- package/initramfs/buninu/apps/bunmsh/test/curl.test.js +335 -0
- package/initramfs/buninu/apps/bunmsh/test/reference-source.sh +1 -0
- package/initramfs/buninu/apps/bunmsh/test/reference.test.js +817 -0
- package/initramfs/buninu/apps/bunmsh/test/serve/README.md +3 -0
- package/initramfs/buninu/apps/bunmsh/test/serve/bad.json +1 -0
- package/initramfs/buninu/apps/bunmsh/test/serve/data.json +1 -0
- package/initramfs/buninu/apps/bunmsh/test/serve/data.json5 +1 -0
- package/initramfs/buninu/apps/bunmsh/test/serve/data.jsonc +2 -0
- package/initramfs/buninu/apps/bunmsh/test/serve/data.jsonl +2 -0
- package/initramfs/buninu/apps/bunmsh/test/serve/data.ndjson +1 -0
- package/initramfs/buninu/apps/bunmsh/test/serve/data.toml +3 -0
- package/initramfs/buninu/apps/bunmsh/test/serve/data.ts +15 -0
- package/initramfs/buninu/apps/bunmsh/test/serve/data.xml +1 -0
- package/initramfs/buninu/apps/bunmsh/test/serve/data.yaml +3 -0
- package/initramfs/buninu/apps/bunmsh/test/serve/data.yml +1 -0
- package/initramfs/buninu/apps/bunmsh/test/serve.test.js +241 -0
- package/initramfs/buninu/apps/bunmsh/test/shell.test.js +2092 -0
- package/initramfs/buninu/apps/bunmsh/test/shell1.test.js +1087 -0
- package/initramfs/buninu/apps/bunmsh/test/shell2.test.js +457 -0
- package/initramfs/buninu/apps/bunmsh/test/shell3.test.js +741 -0
- package/initramfs/buninu/apps/bunmsh/test/t.sh +12 -0
- package/initramfs/buninu/apps/bunx/bunx.js +172 -0
- package/initramfs/buninu/apps/cmdlist +48 -0
- package/initramfs/buninu/apps/jsgotty/LICENSE +23 -0
- package/initramfs/buninu/apps/jsgotty/LICENSES/Apache-2.0-zmodem.js.txt +201 -0
- package/initramfs/buninu/apps/jsgotty/NOTICE +21 -0
- package/initramfs/buninu/apps/jsgotty/README.en.md +294 -0
- package/initramfs/buninu/apps/jsgotty/README.md +296 -0
- package/initramfs/buninu/apps/jsgotty/client.mjs +700 -0
- package/initramfs/buninu/apps/jsgotty/env.sh +2 -0
- package/initramfs/buninu/apps/jsgotty/gotty.js +3334 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/.package-lock.json +35 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/LICENSE.md +9 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/README.md +319 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/common.gypi +20 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/except.gypi +25 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/index.js +12 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/napi-inl.deprecated.h +186 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/napi-inl.h +6607 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/napi.h +3201 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/node_addon_api.gyp +32 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/node_api.gyp +9 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/noexcept.gypi +26 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/nothing.c +0 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/package-support.json +21 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/package.json +480 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/tools/README.md +73 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/tools/check-napi.js +99 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/tools/clang-format.js +71 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/tools/conversion.js +301 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/tools/eslint-format.js +79 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/ws/LICENSE +20 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/ws/README.md +548 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/ws/browser.js +8 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/ws/index.js +22 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/ws/lib/buffer-util.js +131 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/ws/lib/constants.js +19 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/ws/lib/event-target.js +292 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/ws/lib/extension.js +203 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/ws/lib/limiter.js +55 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/ws/lib/permessage-deflate.js +528 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/ws/lib/receiver.js +760 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/ws/lib/sender.js +607 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/ws/lib/stream.js +161 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/ws/lib/subprotocol.js +62 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/ws/lib/validation.js +152 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/ws/lib/websocket-server.js +562 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/ws/lib/websocket.js +1407 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/ws/package.json +70 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/ws/wrapper.mjs +21 -0
- package/initramfs/buninu/apps/jsgotty/package-lock.json +46 -0
- package/initramfs/buninu/apps/jsgotty/package.json +19 -0
- package/initramfs/buninu/apps/jsgotty/patches/windowsPtyAgent.js +326 -0
- package/initramfs/buninu/apps/jsgotty/patches/windowsTerminal.js +204 -0
- package/initramfs/buninu/apps/jsgotty/rz.js +199 -0
- package/initramfs/buninu/apps/jsgotty/single-exe/README.md +384 -0
- package/initramfs/buninu/apps/jsgotty/single-exe/assetsHelper.js +371 -0
- package/initramfs/buninu/apps/jsgotty/single-exe/assetsLoader.mjs +85 -0
- package/initramfs/buninu/apps/jsgotty/single-exe/assetsPacker.js +218 -0
- package/initramfs/buninu/apps/jsgotty/single-exe/compiled.js +498 -0
- package/initramfs/buninu/apps/jsgotty/single-exe/entry.mjs +23 -0
- package/initramfs/buninu/apps/jsgotty/single-exe/packAssets.sh +7 -0
- package/initramfs/buninu/apps/jsgotty/static/css/index.css +13 -0
- package/initramfs/buninu/apps/jsgotty/static/css/kitty.css +61 -0
- package/initramfs/buninu/apps/jsgotty/static/css/xterm.css +218 -0
- package/initramfs/buninu/apps/jsgotty/static/css/xterm_customize.css +35 -0
- package/initramfs/buninu/apps/jsgotty/static/favicon.ico +0 -0
- package/initramfs/buninu/apps/jsgotty/static/help.md +125 -0
- package/initramfs/buninu/apps/jsgotty/static/icon.svg +66 -0
- package/initramfs/buninu/apps/jsgotty/static/icon_192.png +0 -0
- package/initramfs/buninu/apps/jsgotty/static/index.html +164 -0
- package/initramfs/buninu/apps/jsgotty/static/js/gotty.js +31537 -0
- package/initramfs/buninu/apps/jsgotty/static/js/gotty.js.LICENSE.txt +7 -0
- package/initramfs/buninu/apps/jsgotty/static/js/gotty.js.map +1 -0
- package/initramfs/buninu/apps/jsgotty/static/js/gotty.licenses.txt +650 -0
- package/initramfs/buninu/apps/jsgotty/static/js/kitty.js +1101 -0
- package/initramfs/buninu/apps/jsgotty/static/js/windows_bridge.js +194 -0
- package/initramfs/buninu/apps/jsgotty/static/js/xterm-addon-unicode11.js +2 -0
- package/initramfs/buninu/apps/jsgotty/static/js/zmodem.js +1 -0
- package/initramfs/buninu/apps/jsgotty/static/manifest.json +15 -0
- package/initramfs/buninu/apps/jsgotty/sz.js +273 -0
- package/initramfs/buninu/apps/jsgotty/tests/kitty-order.test.js +58 -0
- package/initramfs/buninu/apps/jsgotty/tests/websocket-origin.test.js +54 -0
- package/initramfs/buninu/apps/jsgotty/viu.mjs +422 -0
- package/initramfs/buninu/apps/jsgotty/wgotty +62 -0
- package/initramfs/buninu/apps/jsgotty/windows-bridge-node.js +60 -0
- package/initramfs/buninu/apps/jsgotty/zmodem-node.js +262 -0
- package/initramfs/buninu/apps/jsmdcui/CHANGELOG.md +1118 -0
- package/initramfs/buninu/apps/jsmdcui/LICENSE +22 -0
- package/initramfs/buninu/apps/jsmdcui/README.md +1283 -0
- package/initramfs/buninu/apps/jsmdcui/cdp-maze.js +141 -0
- package/initramfs/buninu/apps/jsmdcui/clean.sh +9 -0
- package/initramfs/buninu/apps/jsmdcui/demos/basic.jpg +0 -0
- package/initramfs/buninu/apps/jsmdcui/demos/chinese.md +32 -0
- package/initramfs/buninu/apps/jsmdcui/demos/event.md +170 -0
- package/initramfs/buninu/apps/jsmdcui/demos/good.jpg +0 -0
- package/initramfs/buninu/apps/jsmdcui/demos/image-processor.md +366 -0
- package/initramfs/buninu/apps/jsmdcui/demos/image-processor.zh-TW.md +366 -0
- package/initramfs/buninu/apps/jsmdcui/demos/img-change.md +73 -0
- package/initramfs/buninu/apps/jsmdcui/demos/imgtool-zh.md +298 -0
- package/initramfs/buninu/apps/jsmdcui/demos/imgtool.md +298 -0
- package/initramfs/buninu/apps/jsmdcui/demos/maze.md +149 -0
- package/initramfs/buninu/apps/jsmdcui/demos/reactive.md +40 -0
- package/initramfs/buninu/apps/jsmdcui/demos/reader.md +513 -0
- package/initramfs/buninu/apps/jsmdcui/demos/select.md +63 -0
- package/initramfs/buninu/apps/jsmdcui/demos/todo-zh.md +190 -0
- package/initramfs/buninu/apps/jsmdcui/demos/todo.md +191 -0
- package/initramfs/buninu/apps/jsmdcui/edit +9 -0
- package/initramfs/buninu/apps/jsmdcui/jsmdcui.sh +16 -0
- package/initramfs/buninu/apps/jsmdcui/llm-maze.txt +94 -0
- package/initramfs/buninu/apps/jsmdcui/package.json +29 -0
- package/initramfs/buninu/apps/jsmdcui/project_structure.txt +35 -0
- package/initramfs/buninu/apps/jsmdcui/runmd.mjs +1140 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/atom-dark.micro +33 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/bubblegum.micro +31 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/cmc-16.micro +47 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/cmc-tc.micro +43 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/darcula.micro +34 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/default.micro +1 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/dracula-tc.micro +49 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/dukedark-tc.micro +38 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/dukelight-tc.micro +38 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/dukeubuntu-tc.micro +38 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/geany.micro +29 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/gotham.micro +29 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/gruvbox-tc.micro +29 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/gruvbox.micro +26 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/material-tc.micro +36 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/monokai-dark.micro +28 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/monokai.micro +34 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/one-dark.micro +39 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/railscast.micro +37 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/simple.micro +33 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/solarized-tc.micro +31 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/solarized.micro +30 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/sunny-day.micro +29 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/twilight.micro +40 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/zenburn.micro +30 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/help/actions.md +161 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/help/cdp.md +130 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/help/colors.md +421 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/help/commands.md +161 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/help/copypaste.md +149 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/help/defaultkeys.md +148 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/help/help.md +1283 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/help/keybindings.md +355 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/help/linter.md +90 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/help/options.md +709 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/help/plugins.md +544 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/help/tutorial.md +112 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/jsplugins/cdp/cdp-server.js +1263 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/jsplugins/cdp/cdp.js +203 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/jsplugins/chapter/chapter.js +111 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/jsplugins/diff/diff.js +46 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/jsplugins/example/example.js +117 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/jsplugins/linter/linter.js +281 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/plugins/autoclose/autoclose.lua +75 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/plugins/ftoptions/ftoptions.lua +17 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/plugins/literate/README.md +5 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/plugins/literate/literate.lua +55 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/plugins/status/help/status.md +21 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/plugins/status/status.lua +62 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/LICENSE +22 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/PowerShell.yaml +128 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/README.md +63 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/ada.yaml +43 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/apacheconf.yaml +59 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/arduino.yaml +101 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/asciidoc.yaml +51 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/asm.yaml +123 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/ats.yaml +99 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/awk.yaml +44 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/b.yaml +87 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/bat.yaml +57 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/c.yaml +60 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/caddyfile.yaml +23 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/cake.yaml +7 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/clojure.yaml +38 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/cmake.yaml +42 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/coffeescript.yaml +56 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/colortest.yaml +19 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/conky.yaml +17 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/cpp.yaml +91 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/crontab.yaml +36 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/crystal.yaml +72 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/csharp.yaml +52 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/css.yaml +44 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/csx.yaml +8 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/cuda.yaml +68 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/cython.yaml +52 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/d.yaml +121 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/dart.yaml +46 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/default.yaml +10 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/dockerfile.yaml +36 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/dot.yaml +29 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/elixir.yaml +30 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/elm.yaml +38 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/erb.yaml +42 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/erlang.yaml +45 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/fish.yaml +48 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/forth.yaml +34 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/fortran.yaml +64 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/freebsd-kernel.yaml +14 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/fsharp.yaml +48 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/gdscript.yaml +61 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/gemini.yaml +19 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/gentoo-ebuild.yaml +48 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/gentoo-etc-portage.yaml +23 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/git-commit.yaml +35 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/git-config.yaml +14 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/git-rebase-todo.yaml +19 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/gleam.yaml +69 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/glsl.yaml +26 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/gnuplot.yaml +15 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/go.yaml +62 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/godoc.yaml +17 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/golo.yaml +73 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/gomod.yaml +31 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/graphql.yaml +47 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/groff.yaml +30 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/groovy.yaml +111 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/haml.yaml +16 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/hare.yaml +52 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/haskell.yaml +52 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/hc.yaml +52 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/html.yaml +70 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/html4.yaml +25 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/html5.yaml +25 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/ini.yaml +23 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/inputrc.yaml +14 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/java.yaml +37 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/javascript.yaml +124 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/jinja2.yaml +19 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/json.yaml +39 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/jsonnet.yaml +92 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/julia.yaml +57 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/justfile.yaml +40 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/keymap.yaml +27 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/kickstart.yaml +16 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/kotlin.yaml +66 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/kvlang.yaml +67 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/ledger.yaml +14 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/lfe.yaml +17 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/lilypond.yaml +26 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/lisp.yaml +17 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/log.yaml +92 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/lua.yaml +111 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/mail.yaml +25 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/makefile.yaml +38 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/man.yaml +12 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/markdown.yaml +65 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/mc.yaml +23 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/meson.yaml +51 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/micro.yaml +34 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/mpdconf.yaml +13 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/msbuild.yaml +6 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/nanorc.yaml +16 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/nftables.yaml +30 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/nginx.yaml +22 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/nim.yaml +27 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/nix.yaml +32 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/nu.yaml +114 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/objc.yaml +60 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/ocaml.yaml +43 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/octave.yaml +83 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/odin.yaml +64 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/pascal.yaml +45 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/patch.yaml +14 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/peg.yaml +16 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/perl.yaml +58 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/php.yaml +60 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/pkg-config.yaml +12 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/po.yaml +12 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/pony.yaml +37 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/pov.yaml +21 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/privoxy-action.yaml +14 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/privoxy-config.yaml +10 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/privoxy-filter.yaml +12 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/proto.yaml +40 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/prql.yaml +84 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/puppet.yaml +22 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/python2.yaml +60 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/python3.yaml +80 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/r.yaml +32 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/raku.yaml +42 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/reST.yaml +18 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/renpy.yaml +15 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/rpmspec.yaml +43 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/ruby.yaml +73 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/rust.yaml +78 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/sage.yaml +60 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/scad.yaml +53 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/scala.yaml +33 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/sed.yaml +13 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/sh.yaml +69 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/sls.yaml +15 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/smalltalk.yaml +55 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/solidity.yaml +41 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/sql.yaml +35 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/stata.yaml +67 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/svelte.yaml +27 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/swift.yaml +103 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/systemd.yaml +16 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/tcl.yaml +18 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/terraform.yaml +87 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/tex.yaml +32 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/toml.yaml +56 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/twig.yaml +55 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/typescript.yaml +49 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/v.yaml +80 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/vala.yaml +26 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/verilog.yaml +60 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/vhdl.yaml +37 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/vi.yaml +31 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/vue.yaml +64 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/xml.yaml +37 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/xresources.yaml +14 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/yaml.yaml +34 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/yum.yaml +12 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/zig.yaml +52 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/zscript.yaml +72 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/zsh.yaml +52 -0
- package/initramfs/buninu/apps/jsmdcui/single-exe/README.md +396 -0
- package/initramfs/buninu/apps/jsmdcui/single-exe/assetsHelper.js +371 -0
- package/initramfs/buninu/apps/jsmdcui/single-exe/assetsLoader.mjs +85 -0
- package/initramfs/buninu/apps/jsmdcui/single-exe/assetsPacker.js +218 -0
- package/initramfs/buninu/apps/jsmdcui/single-exe/compiled.js +498 -0
- package/initramfs/buninu/apps/jsmdcui/single-exe/entry.mjs +17 -0
- package/initramfs/buninu/apps/jsmdcui/single-exe/packAssets.sh +7 -0
- package/initramfs/buninu/apps/jsmdcui/src/MDCUI_DEFAULT_EDIT +0 -0
- package/initramfs/buninu/apps/jsmdcui/src/buffer/backup.js +165 -0
- package/initramfs/buninu/apps/jsmdcui/src/buffer/buffer.js +126 -0
- package/initramfs/buninu/apps/jsmdcui/src/buffer/fixed3-codec.js +140 -0
- package/initramfs/buninu/apps/jsmdcui/src/buffer/loc.js +38 -0
- package/initramfs/buninu/apps/jsmdcui/src/buffer/message.js +29 -0
- package/initramfs/buninu/apps/jsmdcui/src/build-args.js +40 -0
- package/initramfs/buninu/apps/jsmdcui/src/config/clean.js +172 -0
- package/initramfs/buninu/apps/jsmdcui/src/config/colorscheme.js +99 -0
- package/initramfs/buninu/apps/jsmdcui/src/config/config.js +125 -0
- package/initramfs/buninu/apps/jsmdcui/src/config/defaults.js +109 -0
- package/initramfs/buninu/apps/jsmdcui/src/cui/fence-events.mjs +144 -0
- package/initramfs/buninu/apps/jsmdcui/src/cui/heading-ids.mjs +1568 -0
- package/initramfs/buninu/apps/jsmdcui/src/cui/id-collision.mjs +111 -0
- package/initramfs/buninu/apps/jsmdcui/src/cui/identity.mjs +26 -0
- package/initramfs/buninu/apps/jsmdcui/src/cui/kitty-debug.mjs +25 -0
- package/initramfs/buninu/apps/jsmdcui/src/cui/kitty-images.mjs +280 -0
- package/initramfs/buninu/apps/jsmdcui/src/cui/kitty-mode.mjs +6 -0
- package/initramfs/buninu/apps/jsmdcui/src/cui/rerender-queue.mjs +66 -0
- package/initramfs/buninu/apps/jsmdcui/src/cui/rpc.mjs +1554 -0
- package/initramfs/buninu/apps/jsmdcui/src/cui/server.mjs +128 -0
- package/initramfs/buninu/apps/jsmdcui/src/cui/table-render.mjs +286 -0
- package/initramfs/buninu/apps/jsmdcui/src/cui/table-row-edit.mjs +226 -0
- package/initramfs/buninu/apps/jsmdcui/src/cui/task-checkbox.mjs +60 -0
- package/initramfs/buninu/apps/jsmdcui/src/cui/template-components.mjs +253 -0
- package/initramfs/buninu/apps/jsmdcui/src/cui/text-block-resize.mjs +177 -0
- package/initramfs/buninu/apps/jsmdcui/src/cui/tui-links.mjs +560 -0
- package/initramfs/buninu/apps/jsmdcui/src/display/ansi-style.js +60 -0
- package/initramfs/buninu/apps/jsmdcui/src/highlight/highlighter.js +243 -0
- package/initramfs/buninu/apps/jsmdcui/src/highlight/parser.js +225 -0
- package/initramfs/buninu/apps/jsmdcui/src/index.js +9586 -0
- package/initramfs/buninu/apps/jsmdcui/src/lua/engine.js +68 -0
- package/initramfs/buninu/apps/jsmdcui/src/platform/archive.js +50 -0
- package/initramfs/buninu/apps/jsmdcui/src/platform/clipboard.js +278 -0
- package/initramfs/buninu/apps/jsmdcui/src/platform/commands.js +221 -0
- package/initramfs/buninu/apps/jsmdcui/src/platform/terminal.js +5 -0
- package/initramfs/buninu/apps/jsmdcui/src/plugins/js-bridge.js +3377 -0
- package/initramfs/buninu/apps/jsmdcui/src/plugins/manager.js +674 -0
- package/initramfs/buninu/apps/jsmdcui/src/runtime/encodings.js +10 -0
- package/initramfs/buninu/apps/jsmdcui/src/runtime/registry.js +134 -0
- package/initramfs/buninu/apps/jsmdcui/src/screen/cell-buffer.js +81 -0
- package/initramfs/buninu/apps/jsmdcui/src/screen/events.js +263 -0
- package/initramfs/buninu/apps/jsmdcui/src/screen/screen.js +249 -0
- package/initramfs/buninu/apps/jsmdcui/src/screen/vt100.js +571 -0
- package/initramfs/buninu/apps/jsmdcui/src/shell/shell.js +70 -0
- package/initramfs/buninu/apps/jsmdcui/testapp.md +78 -0
- package/initramfs/buninu/apps/jsmdcui/tests/assets-helper.test.js +150 -0
- package/initramfs/buninu/apps/jsmdcui/tests/backup.test.js +158 -0
- package/initramfs/buninu/apps/jsmdcui/tests/cat-markdown.test.js +139 -0
- package/initramfs/buninu/apps/jsmdcui/tests/cdp-security.test.js +32 -0
- package/initramfs/buninu/apps/jsmdcui/tests/cdp-websocket-test.html +46 -0
- package/initramfs/buninu/apps/jsmdcui/tests/cmphex3b64.js +95 -0
- package/initramfs/buninu/apps/jsmdcui/tests/compiled-runtime.test.js +139 -0
- package/initramfs/buninu/apps/jsmdcui/tests/config-settings.test.js +52 -0
- package/initramfs/buninu/apps/jsmdcui/tests/demo.test.js +448 -0
- package/initramfs/buninu/apps/jsmdcui/tests/docs-sync.test.js +14 -0
- package/initramfs/buninu/apps/jsmdcui/tests/fence-events.test.js +1293 -0
- package/initramfs/buninu/apps/jsmdcui/tests/heading-ids.test.js +559 -0
- package/initramfs/buninu/apps/jsmdcui/tests/heading-list-selector.test.js +2393 -0
- package/initramfs/buninu/apps/jsmdcui/tests/id-collision.test.js +514 -0
- package/initramfs/buninu/apps/jsmdcui/tests/jmigt.js +16 -0
- package/initramfs/buninu/apps/jsmdcui/tests/js-plugin-prompts.test.js +110 -0
- package/initramfs/buninu/apps/jsmdcui/tests/key-event.md +10 -0
- package/initramfs/buninu/apps/jsmdcui/tests/kitty-demo.md +184 -0
- package/initramfs/buninu/apps/jsmdcui/tests/kitty-images.test.js +288 -0
- package/initramfs/buninu/apps/jsmdcui/tests/kitty-mode.test.js +14 -0
- package/initramfs/buninu/apps/jsmdcui/tests/platform-commands.test.js +18 -0
- package/initramfs/buninu/apps/jsmdcui/tests/platform-terminal.test.js +8 -0
- package/initramfs/buninu/apps/jsmdcui/tests/pty-demo.js +492 -0
- package/initramfs/buninu/apps/jsmdcui/tests/rerender-queue.test.js +130 -0
- package/initramfs/buninu/apps/jsmdcui/tests/table-render.test.js +150 -0
- package/initramfs/buninu/apps/jsmdcui/tests/table-row-edit.test.js +138 -0
- package/initramfs/buninu/apps/jsmdcui/tests/task-checkbox.test.js +33 -0
- package/initramfs/buninu/apps/jsmdcui/tests/template-components.test.js +422 -0
- package/initramfs/buninu/apps/jsmdcui/tests/text-block-resize.test.js +222 -0
- package/initramfs/buninu/apps/jsmdcui/tests/textarea.md +8 -0
- package/initramfs/buninu/apps/jsmdcui/tests/tui-links.test.js +298 -0
- package/initramfs/buninu/apps/jsmdcui/tests/wui-responsive-images.test.js +60 -0
- package/initramfs/buninu/apps/jsmdcui/tests/wui.test.js +108 -0
- package/initramfs/buninu/apps/jsmdcui/tests/wv-client.js +96 -0
- package/initramfs/buninu/apps/jsmdcui/tui +7 -0
- package/initramfs/buninu/apps/jsmdcui/wui +9 -0
- package/initramfs/buninu/apps/musl-la/LICENSES/GPL-3.0.txt +674 -0
- package/initramfs/buninu/apps/musl-la/LICENSES/gcc_runtime_exception.txt +73 -0
- package/initramfs/buninu/apps/musl-la/LICENSE_musl.txt +193 -0
- package/initramfs/buninu/apps/musl-la/NOTICE +41 -0
- package/initramfs/buninu/apps/musl-la/ld-musl-aarch64.so.1 +0 -0
- package/initramfs/buninu/apps/musl-la/libgcc_s.so.1 +0 -0
- package/initramfs/buninu/apps/musl-la/libstdc++.so.6 +0 -0
- package/initramfs/buninu/apps/musl-la/musl-la.sh +36 -0
- package/initramfs/buninu/apps/native-bridge/native-bridge.js +199 -0
- package/initramfs/buninu/apps/native-bridge/package.json +8 -0
- package/initramfs/buninu/apps/tts/tts.js +214 -0
- package/initramfs/buninu/apps/xclip/xclip.js +121 -0
- package/initramfs/buninu/apps/xdg-open/xdg-open.js +151 -0
- package/initramfs/buninu/bin/bun.bat +37 -0
- package/initramfs/buninu/bin/bun.sh +57 -0
- package/initramfs/buninu/bin/buninu-help.bat +4 -0
- package/initramfs/buninu/bin/buninu-help.sh +7 -0
- package/initramfs/buninu/bin/bunmsh.bat +3 -0
- package/initramfs/buninu/bin/bunmsh.sh +5 -0
- package/initramfs/buninu/bin/bunx.bat +3 -0
- package/initramfs/buninu/bin/glow.bat +3 -0
- package/initramfs/buninu/bin/glow.sh +5 -0
- package/initramfs/buninu/bin/init.js +779 -0
- package/initramfs/buninu/bin/install.js +570 -0
- package/initramfs/buninu/bin/jmi.bat +3 -0
- package/initramfs/buninu/bin/jmi.sh +5 -0
- package/initramfs/buninu/bin/jsgotty.bat +3 -0
- package/initramfs/buninu/bin/jsgotty.sh +5 -0
- package/initramfs/buninu/bin/jsmdcui.bat +11 -0
- package/initramfs/buninu/bin/libsh-loader.c +135 -0
- package/initramfs/buninu/bin/libsh-loader.so +0 -0
- package/initramfs/buninu/bin/multicall.sh +103 -0
- package/initramfs/buninu/bin/native-bridge.bat +3 -0
- package/initramfs/buninu/bin/rz.bat +3 -0
- package/initramfs/buninu/bin/rz.sh +5 -0
- package/initramfs/buninu/bin/shc.js +5 -0
- package/initramfs/buninu/bin/showimg.bat +3 -0
- package/initramfs/buninu/bin/showimg.sh +5 -0
- package/initramfs/buninu/bin/switch_to_android.sh +9 -0
- package/initramfs/buninu/bin/switch_to_linux.sh +7 -0
- package/initramfs/buninu/bin/sz.bat +3 -0
- package/initramfs/buninu/bin/sz.sh +5 -0
- package/initramfs/buninu/bin/tts.bat +3 -0
- package/initramfs/buninu/bin/xclip.bat +3 -0
- package/initramfs/buninu/bin/xdg-open.bat +3 -0
- package/initramfs/buninu/demo-server.js +64 -0
- package/initramfs/buninu/icon.png +0 -0
- package/initramfs/buninu/package.json +59 -0
- package/initramfs/etc/resolv.conf +1 -0
- package/initramfs/init.js +339 -0
- package/initramfs/lib/ld-musl-x86_64.so.1 +0 -0
- package/initramfs/lib/libc.musl-x86_64.so.1 +0 -0
- package/initramfs/lib/libgcc_s.so.1 +0 -0
- package/initramfs/lib/libstdc++.so.6 +0 -0
- package/initramfs/usr/bin/env +7 -0
- package/package.json +42 -0
- package/run-qemu.sh +44 -0
- package/scripts/fetch.sh +26 -0
- package/scripts/pack-initramfs.sh +41 -0
- package/test/build-init-bootstrap.sh +23 -0
- package/test/init-bootstrap.c +55 -0
|
@@ -0,0 +1,2092 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { chmodSync, mkdirSync, mkdtempSync, readlinkSync, rmSync, statSync, symlinkSync, utimesSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import {
|
|
6
|
+
bunShellFallbackArgv,
|
|
7
|
+
builtinNames,
|
|
8
|
+
createState,
|
|
9
|
+
decode,
|
|
10
|
+
execute,
|
|
11
|
+
colorProcessCommand,
|
|
12
|
+
colorProcessTable,
|
|
13
|
+
highlightShellCommand,
|
|
14
|
+
executeArgv,
|
|
15
|
+
formatProcessTable,
|
|
16
|
+
isJavaScriptMode,
|
|
17
|
+
needsMoreInput,
|
|
18
|
+
parse,
|
|
19
|
+
parsePosixProcessList,
|
|
20
|
+
parseWindowsProcessList,
|
|
21
|
+
runUnameFallback,
|
|
22
|
+
SH_COLORS,
|
|
23
|
+
taskkillFailure,
|
|
24
|
+
tokenize,
|
|
25
|
+
windowsKillCommand,
|
|
26
|
+
} from "../src/shell.js";
|
|
27
|
+
import {
|
|
28
|
+
CommandIndex,
|
|
29
|
+
FileIndex,
|
|
30
|
+
VariableIndex,
|
|
31
|
+
completionContext,
|
|
32
|
+
fitGhost,
|
|
33
|
+
firstPrefixMatch,
|
|
34
|
+
historyGhost,
|
|
35
|
+
javascriptContext,
|
|
36
|
+
nextGhostChunk,
|
|
37
|
+
prefixMatches,
|
|
38
|
+
variableCompletion,
|
|
39
|
+
variableContext,
|
|
40
|
+
} from "../src/completion.js";
|
|
41
|
+
import {
|
|
42
|
+
bunmshHistoryPath,
|
|
43
|
+
importedHistory,
|
|
44
|
+
parseBashHistory,
|
|
45
|
+
parseFishHistory,
|
|
46
|
+
readlineHistory,
|
|
47
|
+
saveBunmshHistory,
|
|
48
|
+
safeHistoryEntry,
|
|
49
|
+
} from "../src/history.js";
|
|
50
|
+
import { isLinkerPath } from "../single-exe/compiled.js";
|
|
51
|
+
import { readAssetText } from "../single-exe/assetsHelper.js";
|
|
52
|
+
import { fancyLs } from "../src/fancy-ls.js";
|
|
53
|
+
import { MOUSE_OFF, MOUSE_ON, mouseInput } from "../src/mouse.js";
|
|
54
|
+
import { canonicalEnvironment, environmentValue, homeRelativePath } from "../src/environment.js";
|
|
55
|
+
import { findIsRegularBuiltin } from "../src/find.js";
|
|
56
|
+
|
|
57
|
+
async function run(source, options = {}) {
|
|
58
|
+
const state = createState({
|
|
59
|
+
env: { HOME: "/tmp", ...options.env },
|
|
60
|
+
cwd: options.cwd ?? process.cwd(),
|
|
61
|
+
args: options.args ?? ["bunmsh"],
|
|
62
|
+
history: options.history ?? [],
|
|
63
|
+
mouseTracking: options.mouseTracking,
|
|
64
|
+
pathSearch: options.pathSearch,
|
|
65
|
+
});
|
|
66
|
+
const output = await execute(source, state, { capture: true });
|
|
67
|
+
return {
|
|
68
|
+
...output,
|
|
69
|
+
stdout: decode(output.stdout),
|
|
70
|
+
stderr: decode(output.stderr),
|
|
71
|
+
state,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
describe("parser", () => {
|
|
76
|
+
test("recognizes supported ELF and Android dynamic linker names", () => {
|
|
77
|
+
expect(isLinkerPath("/lib/ld-linux-aarch64.so.1")).toBe(true);
|
|
78
|
+
expect(isLinkerPath("/lib/ld-musl-x86_64.so.1")).toBe(true);
|
|
79
|
+
expect(isLinkerPath("/system/bin/linker64")).toBe(true);
|
|
80
|
+
expect(isLinkerPath("/system/bin/linker")).toBe(true);
|
|
81
|
+
expect(isLinkerPath("/usr/bin/ld")).toBe(false);
|
|
82
|
+
expect(isLinkerPath("/usr/bin/bun")).toBe(false);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("tokenizes quotes and operators", () => {
|
|
86
|
+
const tokens = tokenize(`echo "a b" | cat && print ok`);
|
|
87
|
+
expect(tokens.filter((token) => token.type === "op").map((token) => token.value))
|
|
88
|
+
.toEqual(["|", "&&"]);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("parses a pipeline", () => {
|
|
92
|
+
expect(parse("echo hi | tr a-z A-Z")[0].pipeline).toHaveLength(2);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("a bare trailing backslash asks for one more interactive line, then joins", () => {
|
|
96
|
+
// This is what the interactive prompt loop's `pending` string looks like
|
|
97
|
+
// the instant Enter is pressed after typing "echo hi \" -- the next
|
|
98
|
+
// physical line hasn't arrived yet, so there's no following "\n" for the
|
|
99
|
+
// ordinary backslash-newline splice to recognize.
|
|
100
|
+
expect(needsMoreInput("echo hi \\")).toBe(true);
|
|
101
|
+
expect(() => tokenize("echo hi \\", { strict: true }))
|
|
102
|
+
.toThrow("unterminated line continuation");
|
|
103
|
+
// Once the next line lands, `pending` gains the "\n" and the pair joins
|
|
104
|
+
// into one already-complete command -- no more waiting.
|
|
105
|
+
expect(needsMoreInput("echo hi \\\nbye")).toBe(false);
|
|
106
|
+
expect(tokenize("echo hi \\\nbye").map((token) => token.fragments?.[0]?.text))
|
|
107
|
+
.toEqual(["echo", "hi", "bye"]);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("non-strict tokenize keeps accepting a trailing backslash as a literal word", () => {
|
|
111
|
+
// Only needsMoreInput's strict parse should wait for more input; a
|
|
112
|
+
// script or -c string whose last line genuinely ends in "\" at EOF keeps
|
|
113
|
+
// parsing the same way it always has.
|
|
114
|
+
const tokens = tokenize("echo hi \\");
|
|
115
|
+
expect(tokens.at(-1).fragments).toEqual([{ text: "\\", quote: "none" }]);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe("completion", () => {
|
|
120
|
+
test("treats Windows Path as PATH after copying process.env", () => {
|
|
121
|
+
const env = canonicalEnvironment({
|
|
122
|
+
TEMP: "C:\\Temp",
|
|
123
|
+
Path: "C:\\Windows;C:\\Tools",
|
|
124
|
+
UserProfile: "C:\\Users\\me",
|
|
125
|
+
}, "win32");
|
|
126
|
+
expect(env.PATH).toBe("C:\\Windows;C:\\Tools");
|
|
127
|
+
expect(env.Path).toBeUndefined();
|
|
128
|
+
expect(env.HOME).toBe("C:/Users/me");
|
|
129
|
+
expect(environmentValue({ path: "C:\\Bin" }, "PATH", "win32")).toBe("C:\\Bin");
|
|
130
|
+
expect(canonicalEnvironment({
|
|
131
|
+
HOME: "D:\\ShellHome",
|
|
132
|
+
USERPROFILE: "C:\\Users\\ignored",
|
|
133
|
+
}, "win32").HOME).toBe("D:/ShellHome");
|
|
134
|
+
expect(canonicalEnvironment({
|
|
135
|
+
HOMEDRIVE: "C:",
|
|
136
|
+
HOMEPATH: "\\Users\\fallback",
|
|
137
|
+
}, "win32").HOME).toBe("C:/Users/fallback");
|
|
138
|
+
expect(findIsRegularBuiltin("win32")).toBe(true);
|
|
139
|
+
expect(findIsRegularBuiltin("linux")).toBe(false);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("recognizes Android app-data aliases and Windows case in HOME-relative paths", () => {
|
|
143
|
+
expect(homeRelativePath(
|
|
144
|
+
"/data/user/0/com.termux/files/home",
|
|
145
|
+
"/data/data/com.termux/files/home",
|
|
146
|
+
)).toBe("");
|
|
147
|
+
expect(homeRelativePath(
|
|
148
|
+
"/data/data/com.termux/files/home/project",
|
|
149
|
+
"/data/user/0/com.termux/files/home",
|
|
150
|
+
)).toBe("/project");
|
|
151
|
+
expect(homeRelativePath(
|
|
152
|
+
"c:/users/name/project",
|
|
153
|
+
"C:\\Users\\Name",
|
|
154
|
+
"win32",
|
|
155
|
+
)).toBe("/project");
|
|
156
|
+
expect(homeRelativePath("/elsewhere", "/home/name")).toBeNull();
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("removes split SGR mouse and cursor reports from readline input", async () => {
|
|
160
|
+
const mice = [];
|
|
161
|
+
const cursors = [];
|
|
162
|
+
const shortcuts = [];
|
|
163
|
+
let forwarded = "";
|
|
164
|
+
const input = mouseInput(
|
|
165
|
+
(event) => mice.push(event),
|
|
166
|
+
(position) => cursors.push(position),
|
|
167
|
+
(shortcut) => shortcuts.push(shortcut),
|
|
168
|
+
);
|
|
169
|
+
input.on("data", (chunk) => { forwarded += chunk.toString(); });
|
|
170
|
+
input.write("echo ");
|
|
171
|
+
input.write("\x1b[<0;12");
|
|
172
|
+
input.write(";3M\x1b[4;9Rok\x14\x1bt\x1bl\x1bu\x1bp\x1bc");
|
|
173
|
+
input.end();
|
|
174
|
+
await new Promise((resolve) => input.once("end", resolve));
|
|
175
|
+
expect(forwarded).toBe("echo ok");
|
|
176
|
+
expect(mice).toEqual([{ button: 0, x: 12, y: 3, press: true }]);
|
|
177
|
+
expect(cursors).toEqual([{ row: 4, column: 9 }]);
|
|
178
|
+
expect(shortcuts).toEqual([
|
|
179
|
+
"tab", "tab-left", "lsfancy", "lsfancy-parent", "lsfancy-parent",
|
|
180
|
+
"tab-close",
|
|
181
|
+
]);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test("imports Bash and Fish history by default and can be disabled", async () => {
|
|
185
|
+
expect(parseBashHistory("#1720000000\necho bash\n\nshared\n"))
|
|
186
|
+
.toEqual(["echo bash", "shared"]);
|
|
187
|
+
expect(parseFishHistory("- cmd: echo fish\n when: 1720000001\n- cmd: shared\n"))
|
|
188
|
+
.toEqual(["echo fish", "shared"]);
|
|
189
|
+
expect(parseFishHistory("- cmd: echo invalid YAML: value\n when: 1720000002\n- cmd: cp Hello2.apk /sdcard/Documents/\n"))
|
|
190
|
+
.toEqual(["echo invalid YAML: value", "cp Hello2.apk /sdcard/Documents/"]);
|
|
191
|
+
|
|
192
|
+
const home = mkdtempSync(join(tmpdir(), "bunmsh-history-"));
|
|
193
|
+
try {
|
|
194
|
+
mkdirSync(join(home, ".local", "share", "fish"), { recursive: true });
|
|
195
|
+
await Bun.write(join(home, ".bash_history"), "echo bash\nshared\n");
|
|
196
|
+
await Bun.write(join(home, ".local", "share", "fish", "fish_history"),
|
|
197
|
+
"- cmd: echo fish\n when: 1720000001\n- cmd: shared\n");
|
|
198
|
+
expect(await importedHistory({ HOME: home }))
|
|
199
|
+
.toEqual(["echo bash", "echo fish", "shared"]);
|
|
200
|
+
expect(await importedHistory({ HOME: home, BUNMSH_IMPORT_HISTORY: "off" })).toEqual([]);
|
|
201
|
+
} finally { rmSync(home, { recursive: true, force: true }); }
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("rejects terminal controls from every history source and ghost lookup", () => {
|
|
205
|
+
const dangerous = "cp file\x1b[2D";
|
|
206
|
+
expect(safeHistoryEntry(dangerous)).toBe(false);
|
|
207
|
+
expect(safeHistoryEntry("cp file\nrm file")).toBe(false);
|
|
208
|
+
expect(parseBashHistory(`cp safe\n${dangerous}\n`)).toEqual(["cp safe"]);
|
|
209
|
+
expect(parseFishHistory(`- cmd: cp safe\n- cmd: ${dangerous}\n`))
|
|
210
|
+
.toEqual(["cp safe"]);
|
|
211
|
+
expect(historyGhost([dangerous, "cp safe-file"], "cp ")).toBe("safe-file");
|
|
212
|
+
expect(historyGhost([dangerous], "cp ")).toBeNull();
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test("feeds saved history to readline in newest-first order", () => {
|
|
216
|
+
expect(readlineHistory(["echo first", "ls", "pwd"]))
|
|
217
|
+
.toEqual(["pwd", "ls", "echo first"]);
|
|
218
|
+
expect(readlineHistory(["safe", "bad\x1b[2D"]))
|
|
219
|
+
.toEqual(["safe"]);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test("uses platform-standard bunmsh history paths", () => {
|
|
223
|
+
expect(bunmshHistoryPath({ HOME: "/home/user" }, "linux"))
|
|
224
|
+
.toBe("/home/user/.local/share/bunmsh/history");
|
|
225
|
+
expect(bunmshHistoryPath({ HOME: "/home/user", XDG_DATA_HOME: "/data" }, "linux"))
|
|
226
|
+
.toBe("/data/bunmsh/history");
|
|
227
|
+
expect(bunmshHistoryPath({ LOCALAPPDATA: "C:\\Users\\me\\AppData\\Local" }, "win32"))
|
|
228
|
+
.toBe("C:\\Users\\me\\AppData\\Local/bunmsh/history");
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test("finds prefix ranges in a sorted command index", () => {
|
|
232
|
+
const names = ["bun", "bunx", "cat", "git"];
|
|
233
|
+
expect(prefixMatches(names, "bu")).toEqual(["bun", "bunx"]);
|
|
234
|
+
expect(firstPrefixMatch(names, "gi")).toBe("git");
|
|
235
|
+
expect(prefixMatches(names, "")).toEqual([]);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test("recognizes command positions after assignments and operators", () => {
|
|
239
|
+
expect(completionContext("bu")).toMatchObject({ command: true, prefix: "bu" });
|
|
240
|
+
expect(completionContext("X=1 bu")).toMatchObject({ command: true, prefix: "bu" });
|
|
241
|
+
expect(completionContext("echo hi | gr")).toMatchObject({ command: true, prefix: "gr" });
|
|
242
|
+
expect(completionContext("echo bu")).toMatchObject({ command: false, prefix: "bu" });
|
|
243
|
+
expect(completionContext("echo ")).toMatchObject({ command: false, prefix: "" });
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test("uses recent history for ghosts and accepts one word at a time", () => {
|
|
247
|
+
const history = ["git status", "git log --oneline", "git status --short"];
|
|
248
|
+
expect(historyGhost(history, "git status")).toBe(" --short");
|
|
249
|
+
expect(nextGhostChunk(" --short branch")).toBe(" --short ");
|
|
250
|
+
expect(nextGhostChunk("branch")).toBe("branch");
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test("clips long ghosts before they wrap and disturb the cursor", () => {
|
|
254
|
+
const ghost = "src/proot packed/proot; PROOT_PORT_ADD=3000 bun packed/srpr.mjs";
|
|
255
|
+
expect(fitGhost(ghost, 20)).toEqual({ output: "src/proot packed/pro", width: 20 });
|
|
256
|
+
expect(fitGhost(ghost, 0)).toEqual({ output: "", width: 0 });
|
|
257
|
+
expect(fitGhost("檔案-name", 5)).toEqual({ output: "檔案-", width: 5 });
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test("indexes PATH names without checking executable permission", async () => {
|
|
261
|
+
const directory = mkdtempSync(join(tmpdir(), "bunmsh-completion-"));
|
|
262
|
+
try {
|
|
263
|
+
await Bun.write(`${directory}/alpha`, "not executable");
|
|
264
|
+
await Bun.write(`${directory}/alpine`, "also not executable");
|
|
265
|
+
const index = new CommandIndex(["alias"]);
|
|
266
|
+
await index.refresh({ cwd: process.cwd(), env: { PATH: directory }, aliases: {} });
|
|
267
|
+
expect(index.matches("al")).toEqual(["alias", "alpha", "alpine"]);
|
|
268
|
+
} finally {
|
|
269
|
+
rmSync(directory, { recursive: true, force: true });
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
test("uses the platform PATH delimiter and hides Windows executable extensions", async () => {
|
|
274
|
+
const first = mkdtempSync(join(tmpdir(), "bunmsh-win-path-a-"));
|
|
275
|
+
const second = mkdtempSync(join(tmpdir(), "bunmsh-win-path-b-"));
|
|
276
|
+
try {
|
|
277
|
+
await Bun.write(`${first}/Alpha.EXE`, "exe");
|
|
278
|
+
await Bun.write(`${first}/ignored.txt`, "text");
|
|
279
|
+
await Bun.write(`${second}/beta.cmd`, "cmd");
|
|
280
|
+
await Bun.write(`${second}/gamma.bat`, "bat");
|
|
281
|
+
const index = new CommandIndex([], { platform: "win32", pathDelimiter: ";" });
|
|
282
|
+
await index.refresh({
|
|
283
|
+
cwd: process.cwd(),
|
|
284
|
+
env: { PATH: `${first};${second}` },
|
|
285
|
+
aliases: {},
|
|
286
|
+
});
|
|
287
|
+
expect(index.matches("A")).toEqual(["Alpha"]);
|
|
288
|
+
expect(index.matches("b")).toEqual(["beta"]);
|
|
289
|
+
expect(index.matches("g")).toEqual(["gamma"]);
|
|
290
|
+
expect(index.matches("i")).toEqual([]);
|
|
291
|
+
} finally {
|
|
292
|
+
rmSync(first, { recursive: true, force: true });
|
|
293
|
+
rmSync(second, { recursive: true, force: true });
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
test("suggests files and marks directories with a trailing slash", async () => {
|
|
298
|
+
const directory = mkdtempSync(join(tmpdir(), "bunmsh-files-"));
|
|
299
|
+
try {
|
|
300
|
+
await Bun.write(`${directory}/alpha.txt`, "alpha");
|
|
301
|
+
mkdirSync(`${directory}/alpine`);
|
|
302
|
+
const index = new FileIndex();
|
|
303
|
+
const state = { cwd: directory, env: {} };
|
|
304
|
+
expect(index.matches("", state)).toEqual(["alpha.txt", "alpine/"]);
|
|
305
|
+
expect(index.matches("al", state)).toEqual(["alpha.txt", "alpine/"]);
|
|
306
|
+
expect(index.first("alpha", state)).toBe("alpha.txt");
|
|
307
|
+
} finally {
|
|
308
|
+
rmSync(directory, { recursive: true, force: true });
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
describe("execution", () => {
|
|
314
|
+
test("normalizes Bun Shell cp recursion and provides a PATH-independent cat", async () => {
|
|
315
|
+
expect(bunShellFallbackArgv(["cp", "-rv", "a", "b"]))
|
|
316
|
+
.toEqual(["cp", "-Rv", "a", "b"]);
|
|
317
|
+
const directory = mkdtempSync(join(tmpdir(), "bunmsh-cat-"));
|
|
318
|
+
try {
|
|
319
|
+
await Bun.write(`${directory}/a.txt`, "alpha");
|
|
320
|
+
await Bun.write(`${directory}/b.txt`, "beta");
|
|
321
|
+
const output = await run("cat a.txt b.txt", {
|
|
322
|
+
cwd: directory,
|
|
323
|
+
env: { PATH: "/no/such/path" },
|
|
324
|
+
});
|
|
325
|
+
expect(output).toMatchObject({ status: 0, stdout: "alphabeta", stderr: "" });
|
|
326
|
+
const excluded = await run("builtin cat --exclude a.txt *.txt", { cwd: directory });
|
|
327
|
+
expect(excluded).toMatchObject({ status: 0, stdout: "beta", stderr: "" });
|
|
328
|
+
const patternExcluded = await run("builtin cat --exclude 'a.*' *.txt", { cwd: directory });
|
|
329
|
+
expect(patternExcluded).toMatchObject({ status: 0, stdout: "beta", stderr: "" });
|
|
330
|
+
mkdirSync(`${directory}/source`);
|
|
331
|
+
await Bun.write(`${directory}/source/item.txt`, "copied");
|
|
332
|
+
const copied = await run("cp -r source target", {
|
|
333
|
+
cwd: directory,
|
|
334
|
+
env: { PATH: "/no/such/path" },
|
|
335
|
+
});
|
|
336
|
+
expect(copied).toMatchObject({ status: 0, stdout: "", stderr: "" });
|
|
337
|
+
expect(await Bun.file(`${directory}/target/item.txt`).text()).toBe("copied");
|
|
338
|
+
} finally {
|
|
339
|
+
rmSync(directory, { recursive: true, force: true });
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
test("normalizes CRLF source before parsing scripts", async () => {
|
|
344
|
+
const compound = [
|
|
345
|
+
'value="two words"',
|
|
346
|
+
'if [ "$value" = "two words" ]; then',
|
|
347
|
+
" printf '[%s]\\n' \"$value\"",
|
|
348
|
+
"fi",
|
|
349
|
+
"",
|
|
350
|
+
].join("\r\n");
|
|
351
|
+
expect(await run(compound)).toMatchObject({
|
|
352
|
+
status: 0,
|
|
353
|
+
stdout: "[two words]\n",
|
|
354
|
+
stderr: "",
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
const heredoc = [
|
|
358
|
+
"builtin cat <<'EOF'",
|
|
359
|
+
"$value remains literal",
|
|
360
|
+
"EOF",
|
|
361
|
+
"",
|
|
362
|
+
].join("\r\n");
|
|
363
|
+
expect(await run(heredoc)).toMatchObject({
|
|
364
|
+
status: 0,
|
|
365
|
+
stdout: "$value remains literal\n",
|
|
366
|
+
stderr: "",
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
test("provides basic fallback text and utility commands", async () => {
|
|
371
|
+
expect(await run("printf 'c\\na\\nb\\n' | builtin sort")).toMatchObject({ stdout: "a\nb\nc\n" });
|
|
372
|
+
expect(await run("printf 'one two\\nthree\\n' | builtin wc -lwc")).toMatchObject({ stdout: "2 3 14\n" });
|
|
373
|
+
expect(await run("printf 'abc\\n' | builtin tr a-z A-Z")).toMatchObject({ stdout: "ABC\n" });
|
|
374
|
+
expect(await run("printf '1\\n2\\n3\\n' | builtin head -n 2")).toMatchObject({ stdout: "1\n2\n" });
|
|
375
|
+
expect(await run("printf '1\\n2\\n3\\n' | builtin tail -n 2")).toMatchObject({ stdout: "2\n3\n" });
|
|
376
|
+
expect(await run("printf '1\\n2\\n3\\n' | builtin tail -n +2")).toMatchObject({ stdout: "2\n3\n" });
|
|
377
|
+
expect(await run("printf '1\\n2\\n3\\n' | builtin tail -n+3")).toMatchObject({ stdout: "3\n" });
|
|
378
|
+
expect(await run("printf '1\\n2\\n3\\n' | builtin tac")).toMatchObject({ stdout: "3\n2\n1\n" });
|
|
379
|
+
expect(await run("printf '1\\n2' | builtin tac")).toMatchObject({ stdout: "21\n" });
|
|
380
|
+
expect(await run("builtin date +%F")).toMatchObject({ status: 0 });
|
|
381
|
+
expect(await run("builtin sleep 1ms")).toMatchObject({ status: 0, stdout: "", stderr: "" });
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
test("renders asset-backed Markdown help for every documented builtin", async () => {
|
|
385
|
+
const excluded = new Set([".", "..", "//", "-", "~"]);
|
|
386
|
+
const titles = { "[": "test", __builtin: "builtin", chdir: "cd" };
|
|
387
|
+
const files = { ":": "colon", ...titles };
|
|
388
|
+
const optionDocs = new Set([
|
|
389
|
+
"basename", "builtin", "bun", "bunmsh", "cat", "catfancy", "chmod", "command", "cp", "cut",
|
|
390
|
+
"curl", "date", "dirname", "echo", "env", "find", "getopts", "grep", "head", "kill",
|
|
391
|
+
"ln", "ls", "lsbun", "lsfancy", "mkdir", "mktemp", "mv", "print", "printf",
|
|
392
|
+
"read", "readonly", "rm", "rmdir", "sed", "seq", "serve", "set", "sleep",
|
|
393
|
+
"sort", "tab", "tac", "tail", "tee", "test", "touch", "tr", "unalias",
|
|
394
|
+
"uname", "unset", "wc", "whence",
|
|
395
|
+
]);
|
|
396
|
+
const state = createState({ env: { HOME: "/tmp", PATH: "/no/such/path" } });
|
|
397
|
+
for (const name of builtinNames().filter((item) => !excluded.has(item))) {
|
|
398
|
+
const output = await executeArgv(["builtin", name, "--help"], state, { capture: true });
|
|
399
|
+
expect(output.status, name).toBe(0);
|
|
400
|
+
const text = decode(output.stdout);
|
|
401
|
+
expect(text, name).toContain("\x1b[");
|
|
402
|
+
expect(Bun.stripANSI(text), name).toStartWith(titles[name] ?? name);
|
|
403
|
+
const source = await readAssetText(`help/${files[name] ?? name}.md`);
|
|
404
|
+
expect(source, name).toStartWith(`## ${titles[name] ?? name}`);
|
|
405
|
+
expect(source, name).toContain("\n### Example\n");
|
|
406
|
+
expect(source, name).toContain("\n```sh\n");
|
|
407
|
+
expect(source, name).toContain("\nOutput:\n\n```text\n");
|
|
408
|
+
if (optionDocs.has(files[name] ?? name))
|
|
409
|
+
expect(source, name).toMatch(/^### Options(?: and forms)?$/m);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const short = await executeArgv(["builtin", "catfancy", "-h"], state, { capture: true });
|
|
413
|
+
expect(short.status).toBe(0);
|
|
414
|
+
expect(Bun.stripANSI(decode(short.stdout))).toStartWith("catfancy");
|
|
415
|
+
|
|
416
|
+
const lsfancyShort = await executeArgv(
|
|
417
|
+
["builtin", "lsfancy", "-h"],
|
|
418
|
+
createState({ cwd: new URL("..", import.meta.url).pathname }),
|
|
419
|
+
{ capture: true },
|
|
420
|
+
);
|
|
421
|
+
expect(lsfancyShort.status).toBe(0);
|
|
422
|
+
expect(Bun.stripANSI(decode(lsfancyShort.stdout))).toContain("README.md");
|
|
423
|
+
expect(Bun.stripANSI(decode(lsfancyShort.stdout))).not.toContain("Usage");
|
|
424
|
+
|
|
425
|
+
const lsfancyHelp = await executeArgv(
|
|
426
|
+
["builtin", "lsfancy", "--help"], state, { capture: true },
|
|
427
|
+
);
|
|
428
|
+
expect(lsfancyHelp.status).toBe(0);
|
|
429
|
+
expect(Bun.stripANSI(decode(lsfancyHelp.stdout))).toStartWith("lsfancy");
|
|
430
|
+
|
|
431
|
+
const basenameHelp = await readAssetText("help/basename.md");
|
|
432
|
+
expect(basenameHelp).toContain("basename archive.tar.gz .gz");
|
|
433
|
+
expect(basenameHelp).toContain("archive.tar");
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
test("catfancy is a PATH-overridable fallback builtin", async () => {
|
|
437
|
+
const directory = mkdtempSync(join(tmpdir(), "bunmsh-catfancy-path-"));
|
|
438
|
+
try {
|
|
439
|
+
await Bun.write(join(directory, "data.json5"), "{answer: 42}");
|
|
440
|
+
const fallback = await run("catfancy data.json5", {
|
|
441
|
+
cwd: directory,
|
|
442
|
+
env: { PATH: "/no/such/path" },
|
|
443
|
+
});
|
|
444
|
+
expect(fallback.status).toBe(0);
|
|
445
|
+
expect(Bun.stripANSI(fallback.stdout)).toBe('{\n "answer": 42\n}\n');
|
|
446
|
+
|
|
447
|
+
const executable = join(directory, "catfancy");
|
|
448
|
+
await Bun.write(executable, "#!/bin/sh\necho external-catfancy\n");
|
|
449
|
+
chmodSync(executable, 0o755);
|
|
450
|
+
const external = await run("catfancy data.json5; builtin catfancy data.json5", {
|
|
451
|
+
cwd: directory,
|
|
452
|
+
env: { PATH: directory },
|
|
453
|
+
});
|
|
454
|
+
expect(external.status).toBe(0);
|
|
455
|
+
expect(external.stdout).toStartWith("external-catfancy\n");
|
|
456
|
+
expect(Bun.stripANSI(external.stdout)).toContain('"answer": 42');
|
|
457
|
+
} finally {
|
|
458
|
+
rmSync(directory, { recursive: true, force: true });
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
test("lsfancy classifies files by extension and always reads the directory", async () => {
|
|
463
|
+
const cwd = mkdtempSync(join(tmpdir(), "bunmsh-lsfancy-"));
|
|
464
|
+
try {
|
|
465
|
+
mkdirSync(join(cwd, "package"));
|
|
466
|
+
await Bun.write(join(cwd, "photo.png"), "image");
|
|
467
|
+
await Bun.write(join(cwd, "song.mp3"), "music");
|
|
468
|
+
await Bun.write(join(cwd, "app.mjs"), "js");
|
|
469
|
+
await Bun.write(join(cwd, "types.mts"), "ts");
|
|
470
|
+
await Bun.write(join(cwd, "page.html"), "html");
|
|
471
|
+
await Bun.write(join(cwd, "style.css"), "css");
|
|
472
|
+
const first = await run("builtin lsfancy", { cwd });
|
|
473
|
+
expect(first.status).toBe(0);
|
|
474
|
+
expect(first.stdout).toContain("📦 package/");
|
|
475
|
+
expect(first.stdout).toContain("🖼️ photo.png");
|
|
476
|
+
expect(first.stdout).toContain("🎵 song.mp3");
|
|
477
|
+
expect(first.stdout).toContain("🟨 app.mjs");
|
|
478
|
+
expect(first.stdout).toContain("🟦 types.mts");
|
|
479
|
+
expect(first.stdout).toContain("🌐 page.html");
|
|
480
|
+
expect(first.stdout).toContain("🎨 style.css");
|
|
481
|
+
await Bun.write(join(cwd, "new.py"), "pass\n");
|
|
482
|
+
expect((await run("builtin lsfancy", { cwd })).stdout).toContain("🐍 new.py");
|
|
483
|
+
await Bun.write(join(cwd, "large.bin"), "x".repeat(1536));
|
|
484
|
+
const long = await run("builtin lsfancy -lh large.bin", { cwd });
|
|
485
|
+
expect(long.status).toBe(0);
|
|
486
|
+
expect(long.stdout).toContain("1.5K");
|
|
487
|
+
expect(long.stdout).toContain("📄 large.bin");
|
|
488
|
+
await Bun.write(join(cwd, "time-old.txt"), "old");
|
|
489
|
+
await Bun.write(join(cwd, "time-new.txt"), "new");
|
|
490
|
+
utimesSync(join(cwd, "time-old.txt"), new Date(1000), new Date(1000));
|
|
491
|
+
utimesSync(join(cwd, "time-new.txt"), new Date(2000), new Date(2000));
|
|
492
|
+
const timed = await run("builtin lsfancy -ltr", { cwd });
|
|
493
|
+
expect(timed.status).toBe(0);
|
|
494
|
+
expect(timed.stdout.indexOf("time-old.txt"))
|
|
495
|
+
.toBeLessThan(timed.stdout.indexOf("time-new.txt"));
|
|
496
|
+
} finally { rmSync(cwd, { recursive: true, force: true }); }
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
test("lsfancy supports -S (sort by size), -1 (one per line), and -F (classify)", async () => {
|
|
500
|
+
const cwd = mkdtempSync(join(tmpdir(), "bunmsh-lsfancy-flags-"));
|
|
501
|
+
try {
|
|
502
|
+
await Bun.write(join(cwd, "small.txt"), "x");
|
|
503
|
+
await Bun.write(join(cwd, "big.txt"), "x".repeat(100));
|
|
504
|
+
await Bun.write(join(cwd, "script.sh"), "#!/bin/sh\n");
|
|
505
|
+
chmodSync(join(cwd, "script.sh"), 0o755);
|
|
506
|
+
mkdirSync(join(cwd, "subdir"));
|
|
507
|
+
symlinkSync("small.txt", join(cwd, "link_ok"));
|
|
508
|
+
|
|
509
|
+
const bySize = await run("builtin lsfancy -S", { cwd });
|
|
510
|
+
expect(bySize.status).toBe(0);
|
|
511
|
+
expect(bySize.stdout.indexOf("big.txt")).toBeLessThan(bySize.stdout.indexOf("small.txt"));
|
|
512
|
+
|
|
513
|
+
const classified = await run("builtin lsfancy -F", { cwd });
|
|
514
|
+
expect(classified.status).toBe(0);
|
|
515
|
+
expect(classified.stdout).toContain("subdir/");
|
|
516
|
+
expect(classified.stdout).toContain("script.sh*");
|
|
517
|
+
expect(classified.stdout).toContain("link_ok@");
|
|
518
|
+
// A plain regular file gets no classify suffix at all.
|
|
519
|
+
expect(classified.stdout).toContain("small.txt\n");
|
|
520
|
+
|
|
521
|
+
// -1 only differs from the default in a terminal (execute()'s non-tty
|
|
522
|
+
// capture path is already one entry per line either way), so exercise
|
|
523
|
+
// fancyLs directly with terminal forced on to see the effect.
|
|
524
|
+
const state = createState({ cwd });
|
|
525
|
+
const wide = fancyLs(["lsfancy"], state, true);
|
|
526
|
+
const single = fancyLs(["lsfancy", "-1"], state, true);
|
|
527
|
+
expect(wide.stdout.split("\n").filter(Boolean).length).toBeLessThan(5);
|
|
528
|
+
expect(single.stdout.split("\n").filter(Boolean).length).toBe(5);
|
|
529
|
+
} finally { rmSync(cwd, { recursive: true, force: true }); }
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
test("lsfancy -l shows a file's mtime in local time, not UTC", async () => {
|
|
533
|
+
const cwd = mkdtempSync(join(tmpdir(), "bunmsh-lsfancy-localtime-"));
|
|
534
|
+
try {
|
|
535
|
+
await Bun.write(join(cwd, "stamped.txt"), "x");
|
|
536
|
+
const stamp = new Date(2026, 2, 4, 9, 7, 0);
|
|
537
|
+
utimesSync(join(cwd, "stamped.txt"), stamp, stamp);
|
|
538
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
539
|
+
const expected = `${stamp.getFullYear()}-${pad(stamp.getMonth() + 1)}-${pad(stamp.getDate())} `
|
|
540
|
+
+ `${pad(stamp.getHours())}:${pad(stamp.getMinutes())}`;
|
|
541
|
+
const long = await run("builtin lsfancy -l stamped.txt", { cwd });
|
|
542
|
+
expect(long.status).toBe(0);
|
|
543
|
+
expect(long.stdout).toContain(expected);
|
|
544
|
+
} finally { rmSync(cwd, { recursive: true, force: true }); }
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
test("lsfancy -l shows a symlink's target, including a broken one", async () => {
|
|
548
|
+
const cwd = mkdtempSync(join(tmpdir(), "bunmsh-lsfancy-symlink-"));
|
|
549
|
+
try {
|
|
550
|
+
await Bun.write(join(cwd, "target.txt"), "hi");
|
|
551
|
+
mkdirSync(join(cwd, "targetdir"));
|
|
552
|
+
symlinkSync("target.txt", join(cwd, "link_ok"));
|
|
553
|
+
symlinkSync("targetdir", join(cwd, "link_dir"));
|
|
554
|
+
symlinkSync("/nonexistent/path", join(cwd, "link_broken"));
|
|
555
|
+
symlinkSync("loop_b", join(cwd, "loop_a"));
|
|
556
|
+
symlinkSync("loop_a", join(cwd, "loop_b"));
|
|
557
|
+
const long = await run("builtin lsfancy -l", { cwd });
|
|
558
|
+
expect(long.status).toBe(0);
|
|
559
|
+
expect(long.stdout).toContain("link_ok -> target.txt");
|
|
560
|
+
expect(long.stdout).toContain("link_dir -> targetdir");
|
|
561
|
+
expect(long.stdout).toContain("link_broken -> /nonexistent/path");
|
|
562
|
+
expect(long.stdout).toContain("loop_a -> loop_b");
|
|
563
|
+
// Non-symlink entries get no "-> target" suffix at all.
|
|
564
|
+
expect(long.stdout).not.toContain("target.txt ->");
|
|
565
|
+
expect(long.stdout).not.toContain("targetdir/ ->");
|
|
566
|
+
// A working symlink still gets the plain link icon; one whose target
|
|
567
|
+
// doesn't resolve (missing, or a cycle) gets a distinct broken-link
|
|
568
|
+
// icon instead, in both the plain and long listing forms.
|
|
569
|
+
expect(long.stdout).toContain("🔗 link_ok");
|
|
570
|
+
expect(long.stdout).toContain("🚫 link_broken");
|
|
571
|
+
expect(long.stdout).toContain("🚫 loop_a");
|
|
572
|
+
expect(long.stdout).toContain("🚫 loop_b");
|
|
573
|
+
const plain = await run("builtin lsfancy", { cwd });
|
|
574
|
+
expect(plain.stdout).toContain("🔗 link_ok");
|
|
575
|
+
expect(plain.stdout).toContain("🚫 link_broken");
|
|
576
|
+
} finally { rmSync(cwd, { recursive: true, force: true }); }
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
test("fallback ls is lsfancy; Bun Shell's own ls moved to lsbun", async () => {
|
|
580
|
+
const cwd = mkdtempSync(join(tmpdir(), "bunmsh-ls-rename-"));
|
|
581
|
+
try {
|
|
582
|
+
await Bun.write(join(cwd, "photo.png"), "image");
|
|
583
|
+
const asLs = await run("builtin ls", { cwd });
|
|
584
|
+
expect(asLs.status).toBe(0);
|
|
585
|
+
expect(asLs.stdout).toContain("🖼️ photo.png");
|
|
586
|
+
const asLsfancy = await run("builtin lsfancy", { cwd });
|
|
587
|
+
expect(asLsfancy.stdout).toBe(asLs.stdout);
|
|
588
|
+
// Bun Shell's own implementation is still reachable, just renamed, and
|
|
589
|
+
// produces its old plain (no emoji) output.
|
|
590
|
+
const asLsbun = await run("builtin lsbun", { cwd });
|
|
591
|
+
expect(asLsbun.status).toBe(0);
|
|
592
|
+
expect(asLsbun.stdout).toContain("photo.png");
|
|
593
|
+
expect(asLsbun.stdout).not.toContain("🖼️");
|
|
594
|
+
// Errors report the name actually invoked, not a hardcoded "lsfancy".
|
|
595
|
+
const badFlag = await run("builtin ls -Z", { cwd });
|
|
596
|
+
expect(badFlag).toMatchObject({ status: 2, stdout: "", stderr: "bunmsh: ls: unsupported option: -Z\n" });
|
|
597
|
+
const missing = await run("builtin ls does-not-exist", { cwd });
|
|
598
|
+
expect(missing.status).toBe(1);
|
|
599
|
+
expect(missing.stderr).toContain("bunmsh: ls: does-not-exist:");
|
|
600
|
+
} finally { rmSync(cwd, { recursive: true, force: true }); }
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
test("fallback find filters paths and supports both -exec modes", async () => {
|
|
604
|
+
const cwd = mkdtempSync(join(tmpdir(), "bunmsh-find-"));
|
|
605
|
+
try {
|
|
606
|
+
mkdirSync(join(cwd, "sub"));
|
|
607
|
+
await Bun.write(join(cwd, "a.txt"), "a");
|
|
608
|
+
await Bun.write(join(cwd, "skip.js"), "js");
|
|
609
|
+
await Bun.write(join(cwd, "sub", "b.txt"), "b");
|
|
610
|
+
const filtered = await run("builtin find . -type f -name '*.txt'", { cwd });
|
|
611
|
+
expect(filtered).toMatchObject({
|
|
612
|
+
status: 0,
|
|
613
|
+
stdout: "./a.txt\n./sub/b.txt\n",
|
|
614
|
+
stderr: "",
|
|
615
|
+
});
|
|
616
|
+
const each = await run("builtin find . -type f -name '*.txt' -exec basename {} \\;", { cwd });
|
|
617
|
+
expect(each).toMatchObject({ status: 0, stdout: "a.txt\nb.txt\n", stderr: "" });
|
|
618
|
+
const batch = await run("builtin find . -type f -name '*.txt' -exec echo batch {} +", { cwd });
|
|
619
|
+
expect(batch).toMatchObject({
|
|
620
|
+
status: 0,
|
|
621
|
+
stdout: "batch ./a.txt ./sub/b.txt\n",
|
|
622
|
+
stderr: "",
|
|
623
|
+
});
|
|
624
|
+
} finally { rmSync(cwd, { recursive: true, force: true }); }
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
test("fallback grep supports matching, output modes, quiet, and recursion", async () => {
|
|
628
|
+
expect(await run("printf 'Alpha beta\\nnone\\nBETA\\n' | builtin grep -Ein 'beta'"))
|
|
629
|
+
.toMatchObject({ status: 0, stdout: "1:Alpha beta\n3:BETA\n" });
|
|
630
|
+
expect(await run("printf 'abc123\\n' | builtin grep -Eo '[0-9]+'"))
|
|
631
|
+
.toMatchObject({ status: 0, stdout: "123\n" });
|
|
632
|
+
expect(await run("printf 'keep\\ndrop\\n' | builtin grep -v drop"))
|
|
633
|
+
.toMatchObject({ status: 0, stdout: "keep\n" });
|
|
634
|
+
expect(await run("printf 'found\\n' | builtin grep -q found"))
|
|
635
|
+
.toMatchObject({ status: 0, stdout: "" });
|
|
636
|
+
expect(await run("printf 'red red\\n' | builtin grep --color=always red"))
|
|
637
|
+
.toMatchObject({
|
|
638
|
+
status: 0,
|
|
639
|
+
stdout: "\x1b[01;31mred\x1b[m \x1b[01;31mred\x1b[m\n",
|
|
640
|
+
});
|
|
641
|
+
expect(await run("printf 'red\\n' | builtin grep --color=never red"))
|
|
642
|
+
.toMatchObject({ status: 0, stdout: "red\n" });
|
|
643
|
+
expect(await run("printf 'red\\n' | builtin grep --color=always -o red"))
|
|
644
|
+
.toMatchObject({ status: 0, stdout: "\x1b[01;31mred\x1b[m\n" });
|
|
645
|
+
const directory = mkdtempSync(join(tmpdir(), "bunmsh-grep-"));
|
|
646
|
+
try {
|
|
647
|
+
mkdirSync(`${directory}/nested`);
|
|
648
|
+
await Bun.write(`${directory}/nested/a.txt`, "needle\n");
|
|
649
|
+
const recursive = await run("builtin grep -rn needle nested", { cwd: directory });
|
|
650
|
+
expect(recursive).toMatchObject({ status: 0, stdout: "nested/a.txt:1:needle\n" });
|
|
651
|
+
} finally { rmSync(directory, { recursive: true, force: true }); }
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
test("fallback head closes an infinite upstream and file utilities work", async () => {
|
|
655
|
+
expect(await run("yes | builtin head -n 2")).toMatchObject({ status: 0, stdout: "y\ny\n" });
|
|
656
|
+
const directory = mkdtempSync(join(tmpdir(), "bunmsh-utils-"));
|
|
657
|
+
try {
|
|
658
|
+
const tee = await run("printf data | builtin tee saved.txt", { cwd: directory });
|
|
659
|
+
expect(tee).toMatchObject({ status: 0, stdout: "data" });
|
|
660
|
+
expect(await Bun.file(`${directory}/saved.txt`).text()).toBe("data");
|
|
661
|
+
const md5 = await run("builtin md5sum saved.txt", { cwd: directory });
|
|
662
|
+
expect(md5.stdout).toBe("8d777f385d3dfec8815d20f7496026dc saved.txt\n");
|
|
663
|
+
const sha = await run("builtin sha256sum saved.txt", { cwd: directory });
|
|
664
|
+
expect(sha.stdout).toBe("3a6eb0790f39ac87c94f3856b2dd2c5d110e6811602261a9a923d3bb23adc8b7 saved.txt\n");
|
|
665
|
+
const temporary = await run("builtin mktemp -d sample.XXXXXX", { cwd: directory });
|
|
666
|
+
const created = temporary.stdout.trim();
|
|
667
|
+
expect(created.startsWith(`${directory}/sample.`)).toBe(true);
|
|
668
|
+
expect(await run(`builtin rmdir ${created}`, { cwd: directory })).toMatchObject({ status: 0 });
|
|
669
|
+
expect(await Bun.file(created).exists()).toBe(false);
|
|
670
|
+
expect(await run("builtin clear")).toMatchObject({ stdout: "\x1b[2J\x1b[H" });
|
|
671
|
+
} finally { rmSync(directory, { recursive: true, force: true }); }
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
test("reflects bunmsh and process.argv0 through fallback commands", async () => {
|
|
675
|
+
const nested = await run("bunmsh -cc printf nested", { env: { PATH: "/no/such/path" } });
|
|
676
|
+
expect(nested).toMatchObject({ status: 0, stdout: "nested", stderr: "" });
|
|
677
|
+
const runtime = await run("bun --version", { env: { PATH: "/no/such/path" } });
|
|
678
|
+
expect(runtime).toMatchObject({ status: 0, stdout: `${Bun.version}\n`, stderr: "" });
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
test("env recursively dispatches env commands with isolated assignments", async () => {
|
|
682
|
+
const chained = await run("env env a=1 env b=2");
|
|
683
|
+
expect(chained.status).toBe(0);
|
|
684
|
+
expect(chained.stdout.split("\n")).toContain("a=1");
|
|
685
|
+
expect(chained.stdout.split("\n")).toContain("b=2");
|
|
686
|
+
expect(chained.state.env.a).toBeUndefined();
|
|
687
|
+
expect(chained.state.env.b).toBeUndefined();
|
|
688
|
+
|
|
689
|
+
const clean = await run("env -i a=1 env b=2");
|
|
690
|
+
expect(clean.stdout.split("\n").filter(Boolean).sort()).toEqual(["a=1", "b=2"]);
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
test("supports if, elif, negation, functions, and set --", async () => {
|
|
694
|
+
const script = `
|
|
695
|
+
show_value() {
|
|
696
|
+
if [ "$1" = "yes" ] ; then
|
|
697
|
+
printf 'fn:%s' "$2"
|
|
698
|
+
else
|
|
699
|
+
printf bad
|
|
700
|
+
fi
|
|
701
|
+
}
|
|
702
|
+
set -- alpha beta
|
|
703
|
+
if ! [ "$1" = "wrong" ] && [ "$2" = "beta" ] ; then
|
|
704
|
+
show_value yes nested
|
|
705
|
+
elif [ "$1" = "alpha" ] ; then
|
|
706
|
+
printf elif
|
|
707
|
+
else
|
|
708
|
+
printf else
|
|
709
|
+
fi
|
|
710
|
+
`;
|
|
711
|
+
expect(await run(script)).toMatchObject({ status: 0, stdout: "fn:nested", stderr: "" });
|
|
712
|
+
expect(await run("! false; printf $?")).toMatchObject({ status: 0, stdout: "0" });
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
test("exec stops the current shell flow and fd duplication redirects output", async () => {
|
|
716
|
+
const execution = await run("printf before; exec printf after; printf never");
|
|
717
|
+
expect(execution).toMatchObject({ status: 0, stdout: "beforeafter", stderr: "" });
|
|
718
|
+
expect(execution.state.exitRequested).toBe(true);
|
|
719
|
+
expect(await run("printf error 1>&2")).toMatchObject({ status: 0, stdout: "", stderr: "error" });
|
|
720
|
+
const directory = mkdtempSync(join(tmpdir(), "bunmsh-fd-dup-"));
|
|
721
|
+
try {
|
|
722
|
+
const merged = await run("builtin cat missing-file 2>&1", { cwd: directory });
|
|
723
|
+
expect(merged).toMatchObject({ status: 1, stderr: "" });
|
|
724
|
+
expect(merged.stdout).toContain("bunmsh: cat: missing-file:");
|
|
725
|
+
expect(await Bun.file(join(directory, "&1")).exists()).toBe(false);
|
|
726
|
+
} finally { rmSync(directory, { recursive: true, force: true }); }
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
test("fallback script utilities cover required head grep cut ln chmod and uname forms", async () => {
|
|
730
|
+
expect(await run("printf abcdef | builtin head -c 3")).toMatchObject({ stdout: "abc" });
|
|
731
|
+
expect(await run("printf 'exact\\nextra\\n' | builtin grep -qFx exact")).toMatchObject({ status: 0, stdout: "" });
|
|
732
|
+
expect(await run("printf '123456789\\n' | builtin cut -c6-")).toMatchObject({ stdout: "6789\n" });
|
|
733
|
+
expect(await run("builtin uname -m")).toMatchObject({ status: 0 });
|
|
734
|
+
const windows = runUnameFallback(["uname", "-mprs"], {
|
|
735
|
+
arch: "x64",
|
|
736
|
+
type: "Windows_NT",
|
|
737
|
+
hostname: "windows-host",
|
|
738
|
+
release: "10.0.26100",
|
|
739
|
+
version: "Windows 11 Pro",
|
|
740
|
+
});
|
|
741
|
+
expect(decode(windows.stdout)).toBe("Windows_NT 10.0.26100 x86_64 x86_64\n");
|
|
742
|
+
const directory = mkdtempSync(join(tmpdir(), "bunmsh-script-tools-"));
|
|
743
|
+
try {
|
|
744
|
+
await Bun.write(`${directory}/source`, "one");
|
|
745
|
+
await Bun.write(`${directory}/other`, "two");
|
|
746
|
+
expect(await run("builtin ln -sfT source link", { cwd: directory })).toMatchObject({ status: 0 });
|
|
747
|
+
expect(readlinkSync(`${directory}/link`)).toBe("source");
|
|
748
|
+
expect(await run("builtin ln -sfT other link", { cwd: directory })).toMatchObject({ status: 0 });
|
|
749
|
+
expect(readlinkSync(`${directory}/link`)).toBe("other");
|
|
750
|
+
expect(await run("builtin chmod 777 source; builtin chmod +x other", { cwd: directory }))
|
|
751
|
+
.toMatchObject({ status: 0 });
|
|
752
|
+
expect(statSync(`${directory}/source`).mode & 0o777).toBe(0o777);
|
|
753
|
+
expect(statSync(`${directory}/other`).mode & 0o111).toBe(0o111);
|
|
754
|
+
} finally { rmSync(directory, { recursive: true, force: true }); }
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
test("evaluates raw Bun. lines before shell parsing and expansion", async () => {
|
|
758
|
+
const output = await run(` Bun.version + " $HOME * ; raw"`);
|
|
759
|
+
expect(output.status).toBe(0);
|
|
760
|
+
expect(output.stdout).toBe(`${Bun.version} $HOME * ; raw\n`);
|
|
761
|
+
expect(output.stderr).toBe("");
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
test("awaits Bun. eval results, suppresses undefined, and reports errors", async () => {
|
|
765
|
+
const empty = await run("Bun.sleep(0)");
|
|
766
|
+
expect(empty).toMatchObject({ status: 0, stdout: "", stderr: "" });
|
|
767
|
+
|
|
768
|
+
const failure = await run("Bun.thisMethodDoesNotExist()");
|
|
769
|
+
expect(failure.status).toBe(1);
|
|
770
|
+
expect(failure.stdout).toBe("");
|
|
771
|
+
expect(failure.stderr).toContain("TypeError");
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
test("runs Bun. eval with the active shell cwd and restores the process cwd", async () => {
|
|
775
|
+
const cwd = mkdtempSync(join(tmpdir(), "bunmsh-js-cwd-"));
|
|
776
|
+
const previousCwd = process.cwd();
|
|
777
|
+
try {
|
|
778
|
+
await Bun.write(join(cwd, "relative-marker.txt"), "cwd-marker");
|
|
779
|
+
const shown = await run("Bun.e, process.cwd()", { cwd });
|
|
780
|
+
expect(shown).toMatchObject({ status: 0, stdout: `${cwd}\n`, stderr: "" });
|
|
781
|
+
const relative = await run('Bun.file("relative-marker.txt").text()', { cwd });
|
|
782
|
+
expect(relative).toMatchObject({ status: 0, stdout: "cwd-marker\n", stderr: "" });
|
|
783
|
+
expect(process.cwd()).toBe(previousCwd);
|
|
784
|
+
} finally { rmSync(cwd, { recursive: true, force: true }); }
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
test("provides color aliases for common commands", () => {
|
|
788
|
+
const state = createState();
|
|
789
|
+
// toybox diff takes a bare --color; it has no =auto form.
|
|
790
|
+
expect(state.aliases).toEqual({
|
|
791
|
+
ls: ["ls", "--color=auto"],
|
|
792
|
+
diff: ["diff", "--color"],
|
|
793
|
+
grep: ["grep", "--color=auto"],
|
|
794
|
+
});
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
test("expands variables while respecting single quotes", async () => {
|
|
798
|
+
const output = await run(`X=world; print -r -- "hello $X"; print -r -- '$X'`);
|
|
799
|
+
expect(output.stdout).toBe("hello world\n$X\n");
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
test("supports status connectors", async () => {
|
|
803
|
+
const output = await run("false && echo no; false || echo yes; true && echo done");
|
|
804
|
+
expect(output.stdout).toBe("yes\ndone\n");
|
|
805
|
+
expect(output.status).toBe(0);
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
test("runs external pipelines through Bun.spawn", async () => {
|
|
809
|
+
const output = await run("printf hello | tr a-z A-Z");
|
|
810
|
+
expect(output.stdout).toBe("HELLO");
|
|
811
|
+
expect(output.stderr).toBe("");
|
|
812
|
+
expect(output.status).toBe(0);
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
test("keeps builtin state outside pipelines", async () => {
|
|
816
|
+
const output = await run("export BUNMSH_TEST=value; print -r -- $BUNMSH_TEST");
|
|
817
|
+
expect(output.stdout).toBe("value\n");
|
|
818
|
+
expect(output.state.env.BUNMSH_TEST).toBe("value");
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
test("supports cd - and previous-child navigation", async () => {
|
|
822
|
+
const cwd = process.cwd();
|
|
823
|
+
const previous = await run("cd src; cd -; pwd", { cwd });
|
|
824
|
+
expect(previous.stdout).toBe(`${cwd}\n${cwd}\n`);
|
|
825
|
+
expect(previous.state.env.OLDPWD).toBe(`${cwd}/src`);
|
|
826
|
+
|
|
827
|
+
const child = await run("cd src; cd ..; //; pwd", { cwd });
|
|
828
|
+
expect(child.stdout).toBe(`${cwd}/src\n`);
|
|
829
|
+
expect(child.state.cwd).toBe(`${cwd}/src`);
|
|
830
|
+
});
|
|
831
|
+
|
|
832
|
+
test("treats .. as cd ..", async () => {
|
|
833
|
+
const cwd = process.cwd();
|
|
834
|
+
const output = await run("cd src; ..; pwd", { cwd });
|
|
835
|
+
expect(output.stdout).toBe(`${cwd}\n`);
|
|
836
|
+
expect(output.state.cwd).toBe(cwd);
|
|
837
|
+
expect(output.state.env.OLDPWD).toBe(`${cwd}/src`);
|
|
838
|
+
});
|
|
839
|
+
|
|
840
|
+
test("treats standalone - as cd -", async () => {
|
|
841
|
+
const cwd = process.cwd();
|
|
842
|
+
const output = await run("cd src; -; pwd", { cwd });
|
|
843
|
+
expect(output.stdout).toBe(`${cwd}\n${cwd}\n`);
|
|
844
|
+
expect(output.state.cwd).toBe(cwd);
|
|
845
|
+
expect(output.state.env.OLDPWD).toBe(`${cwd}/src`);
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
test("treats standalone ~ as cd HOME", async () => {
|
|
849
|
+
const cwd = process.cwd();
|
|
850
|
+
const home = cwd.slice(0, cwd.lastIndexOf("/"));
|
|
851
|
+
const output = await run("cd src; ~; pwd", { cwd, env: { HOME: home } });
|
|
852
|
+
expect(output.stdout).toBe(`${home}\n`);
|
|
853
|
+
expect(output.state.cwd).toBe(home);
|
|
854
|
+
expect(output.state.env.OLDPWD).toBe(`${cwd}/src`);
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
test("// reports a missing child and continues", async () => {
|
|
858
|
+
const output = await run("//; print still-running");
|
|
859
|
+
expect(output.stdout).toBe("still-running\n");
|
|
860
|
+
expect(output.stderr).toBe("bunmsh: //: no previous child directory\n");
|
|
861
|
+
expect(output.status).toBe(0);
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
test("exposes positional parameters and status", async () => {
|
|
865
|
+
const output = await run("false; print -r -- $0 $1 $# $?", {
|
|
866
|
+
args: ["script", "arg"],
|
|
867
|
+
});
|
|
868
|
+
expect(output.stdout).toBe("script arg 1 1\n");
|
|
869
|
+
});
|
|
870
|
+
|
|
871
|
+
test("a single question mark prints the previous status and succeeds", async () => {
|
|
872
|
+
const failed = await run("false");
|
|
873
|
+
const execution = await execute("?", failed.state, { capture: true });
|
|
874
|
+
const output = {
|
|
875
|
+
...execution,
|
|
876
|
+
stdout: decode(execution.stdout),
|
|
877
|
+
stderr: decode(execution.stderr),
|
|
878
|
+
};
|
|
879
|
+
expect(output).toMatchObject({ status: 0, stdout: "1\n", stderr: "" });
|
|
880
|
+
expect(failed.state.lastStatus).toBe(0);
|
|
881
|
+
expect(await run("?")).toMatchObject({ status: 0, stdout: "0\n", stderr: "" });
|
|
882
|
+
});
|
|
883
|
+
|
|
884
|
+
test("tab creates cwd-only workspaces and cycles between them", async () => {
|
|
885
|
+
const cwd = process.cwd();
|
|
886
|
+
const output = await run("tab; cd src; tab; pwd; tab; pwd", { cwd });
|
|
887
|
+
expect(output.stdout).toBe(`${cwd}\n${cwd}/src\n`);
|
|
888
|
+
expect(output.state.tabs).toEqual([cwd, `${cwd}/src`]);
|
|
889
|
+
expect(output.state.activeTab).toBe(1);
|
|
890
|
+
expect(output.state.cwd).toBe(`${cwd}/src`);
|
|
891
|
+
expect(output.state.env.OLDPWD).toBe(cwd);
|
|
892
|
+
});
|
|
893
|
+
|
|
894
|
+
test("tab supports new, numbered, left, right, and close operations", async () => {
|
|
895
|
+
const cwd = process.cwd();
|
|
896
|
+
const output = await run(
|
|
897
|
+
"tab n; cd src; tab n; cd ../test; tab 1; tab r; pwd; tab l; pwd; " +
|
|
898
|
+
"tab 3; pwd; tab x; pwd",
|
|
899
|
+
{ cwd },
|
|
900
|
+
);
|
|
901
|
+
expect(output.stdout).toBe(`${cwd}/src\n${cwd}\n${cwd}/test\n${cwd}/src\n`);
|
|
902
|
+
expect(output.state.tabs).toEqual([cwd, `${cwd}/src`]);
|
|
903
|
+
expect(output.state.activeTab).toBe(1);
|
|
904
|
+
expect(output.state.cwd).toBe(`${cwd}/src`);
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
test("tab c closes the active tab like tab x", async () => {
|
|
908
|
+
const output = await run("tab n; tab c");
|
|
909
|
+
expect(output.status).toBe(0);
|
|
910
|
+
expect(output.state.tabs).toHaveLength(1);
|
|
911
|
+
expect(output.state.activeTab).toBe(0);
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
test("tab mouse toggles and explicitly sets mouse tracking", async () => {
|
|
915
|
+
let output = await run("tab mouse", { mouseTracking: false });
|
|
916
|
+
expect(output.state.mouseTracking).toBe(true);
|
|
917
|
+
output = await run("tab mouse off", { mouseTracking: true });
|
|
918
|
+
expect(output.state.mouseTracking).toBe(false);
|
|
919
|
+
output = await run("tab mouse true", { mouseTracking: false });
|
|
920
|
+
expect(output.state.mouseTracking).toBe(true);
|
|
921
|
+
output = await run("tab mouse false", { mouseTracking: true });
|
|
922
|
+
expect(output.state.mouseTracking).toBe(false);
|
|
923
|
+
expect(await run("tab mouse maybe")).toMatchObject({
|
|
924
|
+
status: 1,
|
|
925
|
+
stderr: "bunmsh: tab: mouse: expected on, off, true, or false\n",
|
|
926
|
+
});
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
test("tab path toggles and explicitly controls PATH lookup", async () => {
|
|
930
|
+
let output = await run("tab path; sh -c 'echo external'; printf builtin");
|
|
931
|
+
expect(output.state.pathSearch).toBe(false);
|
|
932
|
+
expect(output.stdout).toBe("builtin");
|
|
933
|
+
expect(output.stderr).toContain("bunmsh: sh: not found");
|
|
934
|
+
|
|
935
|
+
output = await run("tab path off; tab path on; sh -c 'printf external'");
|
|
936
|
+
expect(output.state.pathSearch).toBe(true);
|
|
937
|
+
expect(output.stdout).toBe("external");
|
|
938
|
+
|
|
939
|
+
output = await run("tab path false; printf fallback-ok");
|
|
940
|
+
expect(output.state.pathSearch).toBe(false);
|
|
941
|
+
expect(output.stdout).toBe("fallback-ok");
|
|
942
|
+
expect(await run("tab path maybe")).toMatchObject({
|
|
943
|
+
status: 1,
|
|
944
|
+
stderr: "bunmsh: tab: path: expected on, off, true, or false\n",
|
|
945
|
+
});
|
|
946
|
+
});
|
|
947
|
+
|
|
948
|
+
test("which still searches PATH while direct PATH lookup is disabled", async () => {
|
|
949
|
+
const shell = Bun.which("sh");
|
|
950
|
+
expect(shell).toBeTruthy();
|
|
951
|
+
const output = await run("tab path off; which sh; \"$(which sh)\" -c 'printf explicit-path'");
|
|
952
|
+
expect(output.status).toBe(0);
|
|
953
|
+
expect(output.stdout).toBe(`${shell}\nexplicit-path`);
|
|
954
|
+
expect(output.stderr).toBe("");
|
|
955
|
+
});
|
|
956
|
+
|
|
957
|
+
test("tab rejects invalid selection and closing the final tab", async () => {
|
|
958
|
+
const missing = await run("tab 2");
|
|
959
|
+
expect(missing.status).toBe(1);
|
|
960
|
+
expect(missing.stderr).toBe("bunmsh: tab: 2: no such tab\n");
|
|
961
|
+
|
|
962
|
+
const last = await run("tab x");
|
|
963
|
+
expect(last.status).toBe(1);
|
|
964
|
+
expect(last.stderr).toBe("bunmsh: tab: cannot close the last tab\n");
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
test("tab save persists bunmsh history only when requested", async () => {
|
|
968
|
+
const home = mkdtempSync(join(tmpdir(), "bunmsh-own-history-"));
|
|
969
|
+
try {
|
|
970
|
+
const env = { HOME: home, BUNMSH_IMPORT_HISTORY: "off" };
|
|
971
|
+
const saved = await run("tab save", {
|
|
972
|
+
env,
|
|
973
|
+
history: ["echo first", "echo duplicate", "echo duplicate", "echo last"],
|
|
974
|
+
});
|
|
975
|
+
const path = join(home, ".local", "share", "bunmsh", "history");
|
|
976
|
+
expect(saved).toMatchObject({ status: 0, stdout: `${path}\n`, stderr: "" });
|
|
977
|
+
// The plain save appends everything as-is, one JSON value per line —
|
|
978
|
+
// it does not dedupe; that is what `tab save d` is for.
|
|
979
|
+
expect(Bun.JSONL.parse(await Bun.file(path).text()))
|
|
980
|
+
.toEqual(["echo first", "echo duplicate", "echo duplicate", "echo last"]);
|
|
981
|
+
// Reading history back still dedupes in memory (keeping the most
|
|
982
|
+
// recent occurrence), regardless of what is actually on disk.
|
|
983
|
+
expect(await importedHistory(env)).toEqual(["echo first", "echo duplicate", "echo last"]);
|
|
984
|
+
} finally { rmSync(home, { recursive: true, force: true }); }
|
|
985
|
+
});
|
|
986
|
+
|
|
987
|
+
test("saveBunmshHistory from two sessions never clobbers the other's entries", async () => {
|
|
988
|
+
const home = mkdtempSync(join(tmpdir(), "bunmsh-history-concurrent-"));
|
|
989
|
+
try {
|
|
990
|
+
const env = { HOME: home, BUNMSH_IMPORT_HISTORY: "off" };
|
|
991
|
+
// Two independent sessions, as if two bunmsh processes were running at
|
|
992
|
+
// once: each only knows about its own new commands (historySaved: 0),
|
|
993
|
+
// not about anything the other has written.
|
|
994
|
+
const sessionA = { history: ["a1", "a2"], historySaved: 0 };
|
|
995
|
+
const sessionB = { history: ["b1", "b2"], historySaved: 0 };
|
|
996
|
+
await saveBunmshHistory(sessionA, env);
|
|
997
|
+
await saveBunmshHistory(sessionB, env);
|
|
998
|
+
expect(await importedHistory(env)).toEqual(["a1", "a2", "b1", "b2"]);
|
|
999
|
+
// A keeps going and saves again; B's earlier entries must still be
|
|
1000
|
+
// there — a second save from one session must not touch what the
|
|
1001
|
+
// other already wrote (this is the whole point of appending instead
|
|
1002
|
+
// of rewriting the file).
|
|
1003
|
+
sessionA.history.push("a3");
|
|
1004
|
+
await saveBunmshHistory(sessionA, env);
|
|
1005
|
+
expect(await importedHistory(env)).toEqual(["a1", "a2", "b1", "b2", "a3"]);
|
|
1006
|
+
} finally { rmSync(home, { recursive: true, force: true }); }
|
|
1007
|
+
});
|
|
1008
|
+
|
|
1009
|
+
test("tab save d dedupes the whole history file", async () => {
|
|
1010
|
+
const home = mkdtempSync(join(tmpdir(), "bunmsh-history-dedupe-"));
|
|
1011
|
+
try {
|
|
1012
|
+
const env = { HOME: home, BUNMSH_IMPORT_HISTORY: "off" };
|
|
1013
|
+
await saveBunmshHistory({ history: ["x", "y", "x", "z"], historySaved: 0 }, env);
|
|
1014
|
+
const deduped = await run("tab s d", { env, history: [] });
|
|
1015
|
+
const path = join(home, ".local", "share", "bunmsh", "history");
|
|
1016
|
+
expect(deduped).toMatchObject({ status: 0, stderr: "" });
|
|
1017
|
+
expect(deduped.stdout).toContain(`${path}: 3 unique entries`);
|
|
1018
|
+
expect(Bun.JSONL.parse(await Bun.file(path).text())).toEqual(["y", "x", "z"]);
|
|
1019
|
+
} finally { rmSync(home, { recursive: true, force: true }); }
|
|
1020
|
+
});
|
|
1021
|
+
|
|
1022
|
+
test("tab save rejects an unknown modifier", async () => {
|
|
1023
|
+
expect(await run("tab s bogus")).toMatchObject({
|
|
1024
|
+
status: 1,
|
|
1025
|
+
stderr: "bunmsh: tab: save: expected d or dedupe\n",
|
|
1026
|
+
});
|
|
1027
|
+
});
|
|
1028
|
+
|
|
1029
|
+
test("saveBunmshHistory migrates a legacy JSON-array history file to JSONL", async () => {
|
|
1030
|
+
const home = mkdtempSync(join(tmpdir(), "bunmsh-history-migrate-"));
|
|
1031
|
+
try {
|
|
1032
|
+
const env = { HOME: home, BUNMSH_IMPORT_HISTORY: "off" };
|
|
1033
|
+
const path = join(home, ".local", "share", "bunmsh", "history");
|
|
1034
|
+
mkdirSync(join(home, ".local", "share", "bunmsh"), { recursive: true });
|
|
1035
|
+
await Bun.write(path, `${JSON.stringify(["old1", "old2"], null, 2)}\n`);
|
|
1036
|
+
expect(await importedHistory(env)).toEqual(["old1", "old2"]);
|
|
1037
|
+
await saveBunmshHistory({ history: ["new1"], historySaved: 0 }, env);
|
|
1038
|
+
const text = await Bun.file(path).text();
|
|
1039
|
+
expect(text.trimStart().startsWith("[")).toBe(false);
|
|
1040
|
+
expect(Bun.JSONL.parse(text)).toEqual(["old1", "old2", "new1"]);
|
|
1041
|
+
} finally { rmSync(home, { recursive: true, force: true }); }
|
|
1042
|
+
});
|
|
1043
|
+
});
|
|
1044
|
+
|
|
1045
|
+
describe("CLI", () => {
|
|
1046
|
+
test("detects highest-priority Bun. lines inside script files", async () => {
|
|
1047
|
+
const cwd = new URL("..", import.meta.url).pathname;
|
|
1048
|
+
const proc = Bun.spawn([process.execPath, "src/main.js", "test/t.sh"], {
|
|
1049
|
+
cwd,
|
|
1050
|
+
stdout: "pipe",
|
|
1051
|
+
stderr: "pipe",
|
|
1052
|
+
});
|
|
1053
|
+
const [status, stdout, stderr] = await Promise.all([
|
|
1054
|
+
proc.exited,
|
|
1055
|
+
new Response(proc.stdout).text(),
|
|
1056
|
+
new Response(proc.stderr).text(),
|
|
1057
|
+
]);
|
|
1058
|
+
expect(status).toBe(0);
|
|
1059
|
+
expect(stdout).toBe(
|
|
1060
|
+
`shell-before\n${Bun.version}\n${Math.cos(1)}\n` +
|
|
1061
|
+
`{ message: 'shared', count: 1 }\n2\n` +
|
|
1062
|
+
`{ message: 'shared', count: 2 }\n` +
|
|
1063
|
+
`from command substitution: shared\nshell-after\n`,
|
|
1064
|
+
);
|
|
1065
|
+
expect(stderr).toBe("");
|
|
1066
|
+
});
|
|
1067
|
+
|
|
1068
|
+
test("--readme renders the bundled README and exits", async () => {
|
|
1069
|
+
const proc = Bun.spawn([process.execPath, "src/main.js", "--readme"], {
|
|
1070
|
+
cwd: new URL("..", import.meta.url).pathname,
|
|
1071
|
+
stdout: "pipe",
|
|
1072
|
+
stderr: "pipe",
|
|
1073
|
+
});
|
|
1074
|
+
const [status, stdout, stderr] = await Promise.all([
|
|
1075
|
+
proc.exited,
|
|
1076
|
+
new Response(proc.stdout).text(),
|
|
1077
|
+
new Response(proc.stderr).text(),
|
|
1078
|
+
]);
|
|
1079
|
+
expect(status).toBe(0);
|
|
1080
|
+
expect(stdout).toContain("bunmsh");
|
|
1081
|
+
expect(stdout).toContain("Built-in documentation");
|
|
1082
|
+
expect(stderr).toBe("");
|
|
1083
|
+
});
|
|
1084
|
+
|
|
1085
|
+
test("--changelog renders the bundled changelog and exits", async () => {
|
|
1086
|
+
const proc = Bun.spawn([process.execPath, "src/main.js", "--changelog"], {
|
|
1087
|
+
cwd: new URL("..", import.meta.url).pathname,
|
|
1088
|
+
stdout: "pipe",
|
|
1089
|
+
stderr: "pipe",
|
|
1090
|
+
});
|
|
1091
|
+
const [status, stdout, stderr] = await Promise.all([
|
|
1092
|
+
proc.exited,
|
|
1093
|
+
new Response(proc.stdout).text(),
|
|
1094
|
+
new Response(proc.stderr).text(),
|
|
1095
|
+
]);
|
|
1096
|
+
expect(status).toBe(0);
|
|
1097
|
+
expect(stdout).toContain("Changelog");
|
|
1098
|
+
expect(stdout).toContain("0.1.8");
|
|
1099
|
+
expect(stdout).toContain("serve");
|
|
1100
|
+
expect(stderr).toBe("");
|
|
1101
|
+
});
|
|
1102
|
+
|
|
1103
|
+
test("expands \\w in the interactive prompt", async () => {
|
|
1104
|
+
const cwd = new URL("..", import.meta.url).pathname.replace(/\/$/, "");
|
|
1105
|
+
const home = cwd.slice(0, cwd.lastIndexOf("/"));
|
|
1106
|
+
const proc = Bun.spawn([process.execPath, "src/main.js", "-i"], {
|
|
1107
|
+
cwd,
|
|
1108
|
+
env: { ...process.env, HOME: home, PS1: "[\\w] " },
|
|
1109
|
+
stdin: "pipe",
|
|
1110
|
+
stdout: "pipe",
|
|
1111
|
+
stderr: "pipe",
|
|
1112
|
+
});
|
|
1113
|
+
proc.stdin.write("exit\n");
|
|
1114
|
+
proc.stdin.end();
|
|
1115
|
+
const [status, stdout, stderr] = await Promise.all([
|
|
1116
|
+
proc.exited,
|
|
1117
|
+
new Response(proc.stdout).text(),
|
|
1118
|
+
new Response(proc.stderr).text(),
|
|
1119
|
+
]);
|
|
1120
|
+
expect(status).toBe(0);
|
|
1121
|
+
expect(stdout).toBe(`[~${cwd.slice(home.length)}] `);
|
|
1122
|
+
expect(stderr).toBe("");
|
|
1123
|
+
});
|
|
1124
|
+
|
|
1125
|
+
test("colors only the prompt dollar red after an error and ? reports it", async () => {
|
|
1126
|
+
let transcript = "";
|
|
1127
|
+
const terminal = new Bun.Terminal({
|
|
1128
|
+
cols: 80,
|
|
1129
|
+
rows: 24,
|
|
1130
|
+
data(_terminal, data) { transcript += data.toString(); },
|
|
1131
|
+
});
|
|
1132
|
+
const proc = Bun.spawn({
|
|
1133
|
+
cmd: [process.execPath, "src/main.js", "-i"],
|
|
1134
|
+
cwd: new URL("..", import.meta.url).pathname,
|
|
1135
|
+
env: { ...process.env, PS1: "$ " },
|
|
1136
|
+
terminal,
|
|
1137
|
+
});
|
|
1138
|
+
try {
|
|
1139
|
+
await Bun.sleep(120);
|
|
1140
|
+
terminal.write("false\r");
|
|
1141
|
+
await Bun.sleep(80);
|
|
1142
|
+
terminal.write("?\r");
|
|
1143
|
+
await Bun.sleep(80);
|
|
1144
|
+
terminal.write("exit\r");
|
|
1145
|
+
expect(await proc.exited).toBe(0);
|
|
1146
|
+
} finally { terminal.close(); }
|
|
1147
|
+
expect(transcript).toContain("\x1b[31m$\x1b[0m ");
|
|
1148
|
+
expect(transcript).toContain("1\r\n");
|
|
1149
|
+
});
|
|
1150
|
+
|
|
1151
|
+
test("shows a PS2 continuation prompt across a here-document, like mksh", async () => {
|
|
1152
|
+
let transcript = "";
|
|
1153
|
+
const terminal = new Bun.Terminal({
|
|
1154
|
+
cols: 80,
|
|
1155
|
+
rows: 24,
|
|
1156
|
+
data(_terminal, data) { transcript += data.toString(); },
|
|
1157
|
+
});
|
|
1158
|
+
const proc = Bun.spawn({
|
|
1159
|
+
cmd: [process.execPath, "src/main.js", "-i"],
|
|
1160
|
+
cwd: new URL("..", import.meta.url).pathname,
|
|
1161
|
+
env: { ...process.env, PS1: "$ " },
|
|
1162
|
+
terminal,
|
|
1163
|
+
});
|
|
1164
|
+
try {
|
|
1165
|
+
await Bun.sleep(120);
|
|
1166
|
+
terminal.write("cat <<EOF\r");
|
|
1167
|
+
await Bun.sleep(80);
|
|
1168
|
+
terminal.write("hello world\r");
|
|
1169
|
+
await Bun.sleep(80);
|
|
1170
|
+
terminal.write("EOF\r");
|
|
1171
|
+
await Bun.sleep(120);
|
|
1172
|
+
terminal.write("echo after:$?\r");
|
|
1173
|
+
await Bun.sleep(80);
|
|
1174
|
+
terminal.write("exit\r");
|
|
1175
|
+
expect(await proc.exited).toBe(0);
|
|
1176
|
+
} finally { terminal.close(); }
|
|
1177
|
+
// Two "> " continuation prompts (for the body line and the terminator),
|
|
1178
|
+
// the heredoc's own output, and the shell resuming normally afterward.
|
|
1179
|
+
expect(transcript.match(/\r\n\x1b\[1G\x1b\[0J> /g)).toHaveLength(2);
|
|
1180
|
+
expect(transcript).toContain("hello world\r\n");
|
|
1181
|
+
expect(transcript).toContain("after:0\r\n");
|
|
1182
|
+
});
|
|
1183
|
+
|
|
1184
|
+
test("Ctrl-C during a here-document continuation returns to the primary prompt", async () => {
|
|
1185
|
+
let transcript = "";
|
|
1186
|
+
const terminal = new Bun.Terminal({
|
|
1187
|
+
cols: 80,
|
|
1188
|
+
rows: 24,
|
|
1189
|
+
data(_terminal, data) { transcript += data.toString(); },
|
|
1190
|
+
});
|
|
1191
|
+
const proc = Bun.spawn({
|
|
1192
|
+
cmd: [process.execPath, "src/main.js", "-i"],
|
|
1193
|
+
cwd: new URL("..", import.meta.url).pathname,
|
|
1194
|
+
env: { ...process.env, PS1: "$ " },
|
|
1195
|
+
terminal,
|
|
1196
|
+
});
|
|
1197
|
+
try {
|
|
1198
|
+
await Bun.sleep(120);
|
|
1199
|
+
terminal.write("cat <<EOF\r");
|
|
1200
|
+
await Bun.sleep(80);
|
|
1201
|
+
terminal.write("partial\r");
|
|
1202
|
+
await Bun.sleep(80);
|
|
1203
|
+
terminal.write("\x03");
|
|
1204
|
+
await Bun.sleep(120);
|
|
1205
|
+
terminal.write("echo back:$?\r");
|
|
1206
|
+
await Bun.sleep(80);
|
|
1207
|
+
terminal.write("exit\r");
|
|
1208
|
+
expect(await proc.exited).toBe(0);
|
|
1209
|
+
} finally { terminal.close(); }
|
|
1210
|
+
// The interrupt aborted the pending heredoc before `cat` ever ran, so
|
|
1211
|
+
// $? reflects the Ctrl-C signal (130), not a successful `cat` (0).
|
|
1212
|
+
expect(transcript).toContain("back:130\r\n");
|
|
1213
|
+
});
|
|
1214
|
+
|
|
1215
|
+
test("Ctrl-D during a here-document continuation ends the body, not the shell", async () => {
|
|
1216
|
+
let transcript = "";
|
|
1217
|
+
const terminal = new Bun.Terminal({
|
|
1218
|
+
cols: 80,
|
|
1219
|
+
rows: 24,
|
|
1220
|
+
data(_terminal, data) { transcript += data.toString(); },
|
|
1221
|
+
});
|
|
1222
|
+
const proc = Bun.spawn({
|
|
1223
|
+
cmd: [process.execPath, "src/main.js", "-i"],
|
|
1224
|
+
cwd: new URL("..", import.meta.url).pathname,
|
|
1225
|
+
env: { ...process.env, PS1: "$ " },
|
|
1226
|
+
terminal,
|
|
1227
|
+
});
|
|
1228
|
+
try {
|
|
1229
|
+
await Bun.sleep(120);
|
|
1230
|
+
terminal.write("cat <<EOF\r");
|
|
1231
|
+
await Bun.sleep(80);
|
|
1232
|
+
terminal.write("partial body no terminator\r");
|
|
1233
|
+
await Bun.sleep(80);
|
|
1234
|
+
// Ctrl-D on the empty PS2 line: Node's readline closes itself here,
|
|
1235
|
+
// like it would on an empty primary-prompt line, but that must only
|
|
1236
|
+
// end the here-document (leniently, like a script hitting real EOF),
|
|
1237
|
+
// not exit the whole shell.
|
|
1238
|
+
terminal.write("\x04");
|
|
1239
|
+
await Bun.sleep(150);
|
|
1240
|
+
terminal.write("echo still-alive:$?\r");
|
|
1241
|
+
await Bun.sleep(80);
|
|
1242
|
+
terminal.write("exit\r");
|
|
1243
|
+
expect(await proc.exited).toBe(0);
|
|
1244
|
+
} finally { terminal.close(); }
|
|
1245
|
+
expect(transcript).toContain("partial body no terminator");
|
|
1246
|
+
expect(transcript).toContain("still-alive:0\r\n");
|
|
1247
|
+
});
|
|
1248
|
+
|
|
1249
|
+
test("Ctrl-D at the primary prompt still exits the shell", async () => {
|
|
1250
|
+
let transcript = "";
|
|
1251
|
+
const terminal = new Bun.Terminal({
|
|
1252
|
+
cols: 80,
|
|
1253
|
+
rows: 24,
|
|
1254
|
+
data(_terminal, data) { transcript += data.toString(); },
|
|
1255
|
+
});
|
|
1256
|
+
const proc = Bun.spawn({
|
|
1257
|
+
cmd: [process.execPath, "src/main.js", "-i"],
|
|
1258
|
+
cwd: new URL("..", import.meta.url).pathname,
|
|
1259
|
+
env: { ...process.env, PS1: "$ " },
|
|
1260
|
+
terminal,
|
|
1261
|
+
});
|
|
1262
|
+
try {
|
|
1263
|
+
await Bun.sleep(120);
|
|
1264
|
+
terminal.write("echo hi\r");
|
|
1265
|
+
await Bun.sleep(100);
|
|
1266
|
+
terminal.write("\x04");
|
|
1267
|
+
expect(await proc.exited).toBe(0);
|
|
1268
|
+
} finally { terminal.close(); }
|
|
1269
|
+
expect(transcript).toContain("hi\r\n");
|
|
1270
|
+
});
|
|
1271
|
+
|
|
1272
|
+
test("exiting flushes history without waiting for the periodic autosave", async () => {
|
|
1273
|
+
const home = mkdtempSync(join(tmpdir(), "bunmsh-history-exit-flush-"));
|
|
1274
|
+
try {
|
|
1275
|
+
const terminal = new Bun.Terminal({
|
|
1276
|
+
cols: 80,
|
|
1277
|
+
rows: 24,
|
|
1278
|
+
data() {},
|
|
1279
|
+
});
|
|
1280
|
+
const proc = Bun.spawn({
|
|
1281
|
+
cmd: [process.execPath, "src/main.js", "-i"],
|
|
1282
|
+
cwd: new URL("..", import.meta.url).pathname,
|
|
1283
|
+
env: { ...process.env, PS1: "$ ", HOME: home, BUNMSH_IMPORT_HISTORY: "off" },
|
|
1284
|
+
terminal,
|
|
1285
|
+
});
|
|
1286
|
+
try {
|
|
1287
|
+
await Bun.sleep(120);
|
|
1288
|
+
terminal.write("echo flush-me\r");
|
|
1289
|
+
await Bun.sleep(100);
|
|
1290
|
+
terminal.write("exit\r");
|
|
1291
|
+
expect(await proc.exited).toBe(0);
|
|
1292
|
+
} finally { terminal.close(); }
|
|
1293
|
+
// The 60s periodic autosave never had a chance to fire here; only the
|
|
1294
|
+
// on-exit flush could have written this.
|
|
1295
|
+
const saved = await importedHistory({ HOME: home, BUNMSH_IMPORT_HISTORY: "off" });
|
|
1296
|
+
expect(saved).toContain("echo flush-me");
|
|
1297
|
+
expect(saved).toContain("exit");
|
|
1298
|
+
} finally { rmSync(home, { recursive: true, force: true }); }
|
|
1299
|
+
});
|
|
1300
|
+
|
|
1301
|
+
test.each(["SIGTERM", "SIGHUP"])(
|
|
1302
|
+
"%s while waiting at the prompt flushes history and exits with a signal-derived status",
|
|
1303
|
+
async (signal) => {
|
|
1304
|
+
const home = mkdtempSync(join(tmpdir(), `bunmsh-history-${signal}-`));
|
|
1305
|
+
try {
|
|
1306
|
+
const terminal = new Bun.Terminal({ cols: 80, rows: 24, data() {} });
|
|
1307
|
+
const proc = Bun.spawn({
|
|
1308
|
+
cmd: [process.execPath, "src/main.js", "-i"],
|
|
1309
|
+
cwd: new URL("..", import.meta.url).pathname,
|
|
1310
|
+
env: { ...process.env, PS1: "$ ", HOME: home, BUNMSH_IMPORT_HISTORY: "off" },
|
|
1311
|
+
terminal,
|
|
1312
|
+
});
|
|
1313
|
+
try {
|
|
1314
|
+
await Bun.sleep(120);
|
|
1315
|
+
terminal.write(`echo ${signal.toLowerCase()}-flush\r`);
|
|
1316
|
+
await Bun.sleep(100);
|
|
1317
|
+
proc.kill(signal);
|
|
1318
|
+
const expectedStatus = signal === "SIGHUP" ? 129 : 143;
|
|
1319
|
+
expect(await proc.exited).toBe(expectedStatus);
|
|
1320
|
+
} finally { terminal.close(); }
|
|
1321
|
+
const saved = await importedHistory({ HOME: home, BUNMSH_IMPORT_HISTORY: "off" });
|
|
1322
|
+
expect(saved).toContain(`echo ${signal.toLowerCase()}-flush`);
|
|
1323
|
+
} finally { rmSync(home, { recursive: true, force: true }); }
|
|
1324
|
+
},
|
|
1325
|
+
);
|
|
1326
|
+
|
|
1327
|
+
test("preserves command output without a trailing newline before repainting", async () => {
|
|
1328
|
+
const cwd = new URL("..", import.meta.url).pathname;
|
|
1329
|
+
let transcript = "";
|
|
1330
|
+
let terminal;
|
|
1331
|
+
terminal = new Bun.Terminal({
|
|
1332
|
+
cols: 80,
|
|
1333
|
+
rows: 24,
|
|
1334
|
+
data(_terminal, data) {
|
|
1335
|
+
const text = data.toString();
|
|
1336
|
+
transcript += text;
|
|
1337
|
+
if (text.includes("\x1b[6n")) terminal.write("\x1b[4;4R");
|
|
1338
|
+
},
|
|
1339
|
+
});
|
|
1340
|
+
const entry = join(new URL("..", import.meta.url).pathname, "src/main.js");
|
|
1341
|
+
const proc = Bun.spawn({
|
|
1342
|
+
cmd: [Bun.which("bun") || process.argv0, entry, "-i"],
|
|
1343
|
+
cwd,
|
|
1344
|
+
env: { ...process.env, PS1: "> " },
|
|
1345
|
+
terminal,
|
|
1346
|
+
});
|
|
1347
|
+
try {
|
|
1348
|
+
await Bun.sleep(150);
|
|
1349
|
+
terminal.write("printf hello\r");
|
|
1350
|
+
await Bun.sleep(150);
|
|
1351
|
+
terminal.write("exit\r");
|
|
1352
|
+
expect(await proc.exited).toBe(0);
|
|
1353
|
+
} finally { terminal.close(); }
|
|
1354
|
+
// The "↩️" marker must appear right after the unterminated output and its
|
|
1355
|
+
// cursor-position query, before the next prompt is repainted. The exact
|
|
1356
|
+
// bytes readline uses to repaint that prompt (padding cursor moves and
|
|
1357
|
+
// erase-to-end-of-line codes around the "> " text) are an internal
|
|
1358
|
+
// implementation detail of the readline version in use, not something
|
|
1359
|
+
// this shell controls, so check the marker's position and that a fresh
|
|
1360
|
+
// prompt follows it, rather than pinning readline's own redraw bytes.
|
|
1361
|
+
const markerIndex = transcript.indexOf("hello\x1b[6n↩️\r\n");
|
|
1362
|
+
expect(markerIndex).toBeGreaterThan(-1);
|
|
1363
|
+
const afterMarker = transcript.slice(markerIndex);
|
|
1364
|
+
expect(afterMarker.indexOf("> ")).toBeGreaterThan(-1);
|
|
1365
|
+
expect(afterMarker.indexOf("> ")).toBeLessThan(afterMarker.indexOf("exit"));
|
|
1366
|
+
});
|
|
1367
|
+
|
|
1368
|
+
test("Ctrl-C returns to the prompt while Ctrl-D exits, including during serve", async () => {
|
|
1369
|
+
const cwd = mkdtempSync(join(tmpdir(), "bunmsh-sigint-"));
|
|
1370
|
+
let transcript = "";
|
|
1371
|
+
let terminal;
|
|
1372
|
+
terminal = new Bun.Terminal({
|
|
1373
|
+
cols: 80,
|
|
1374
|
+
rows: 24,
|
|
1375
|
+
data(_terminal, data) {
|
|
1376
|
+
const text = data.toString();
|
|
1377
|
+
transcript += text;
|
|
1378
|
+
if (text.includes("\x1b[6n")) terminal.write("\x1b[4;1R");
|
|
1379
|
+
},
|
|
1380
|
+
});
|
|
1381
|
+
const entry = join(new URL("..", import.meta.url).pathname, "src/main.js");
|
|
1382
|
+
const proc = Bun.spawn({
|
|
1383
|
+
cmd: [Bun.which("bun") || process.argv0, entry, "-i"],
|
|
1384
|
+
cwd,
|
|
1385
|
+
env: { ...process.env, PORT: "0", PS1: "> " },
|
|
1386
|
+
terminal,
|
|
1387
|
+
});
|
|
1388
|
+
const waitFor = async (needle) => {
|
|
1389
|
+
for (let attempt = 0; attempt < 100 && !transcript.includes(needle); attempt++)
|
|
1390
|
+
await Bun.sleep(10);
|
|
1391
|
+
expect(transcript).toContain(needle);
|
|
1392
|
+
};
|
|
1393
|
+
try {
|
|
1394
|
+
await waitFor("> ");
|
|
1395
|
+
terminal.write("\x03");
|
|
1396
|
+
await Bun.sleep(30);
|
|
1397
|
+
terminal.write("echo prompt-survived\r");
|
|
1398
|
+
await waitFor("prompt-survived\r\n");
|
|
1399
|
+
terminal.write("builtin serve\r");
|
|
1400
|
+
await waitFor("http://localhost:");
|
|
1401
|
+
const beforeQuit = transcript.length;
|
|
1402
|
+
terminal.write("q\r");
|
|
1403
|
+
for (let attempt = 0; attempt < 100 && !transcript.slice(beforeQuit).includes("> "); attempt++)
|
|
1404
|
+
await Bun.sleep(10);
|
|
1405
|
+
expect(transcript.slice(beforeQuit)).toContain("> ");
|
|
1406
|
+
terminal.write("builtin serve\r");
|
|
1407
|
+
for (let attempt = 0; attempt < 100 &&
|
|
1408
|
+
!transcript.slice(beforeQuit).includes("http://localhost:"); attempt++)
|
|
1409
|
+
await Bun.sleep(10);
|
|
1410
|
+
const promptsWhileServing = transcript.split("> ").length - 1;
|
|
1411
|
+
terminal.resize(79, 24);
|
|
1412
|
+
await Bun.sleep(30);
|
|
1413
|
+
expect(transcript.split("> ").length - 1).toBe(promptsWhileServing);
|
|
1414
|
+
proc.kill("SIGINT");
|
|
1415
|
+
await Bun.sleep(30);
|
|
1416
|
+
terminal.write("echo server-survived\r");
|
|
1417
|
+
await waitFor("server-survived\r\n");
|
|
1418
|
+
terminal.write("\x04");
|
|
1419
|
+
expect(await proc.exited).toBe(0);
|
|
1420
|
+
} finally { terminal.close(); }
|
|
1421
|
+
expect(transcript).toContain("prompt-survived\r\n");
|
|
1422
|
+
expect(transcript).toContain("Serving ");
|
|
1423
|
+
expect(transcript).toContain("server-survived\r\n");
|
|
1424
|
+
});
|
|
1425
|
+
|
|
1426
|
+
test("shows all tab paths and marks the active tab", async () => {
|
|
1427
|
+
const cwd = new URL("..", import.meta.url).pathname.replace(/\/$/, "");
|
|
1428
|
+
const home = cwd.slice(0, cwd.lastIndexOf("/"));
|
|
1429
|
+
const shown = `~${cwd.slice(home.length)}`;
|
|
1430
|
+
const proc = Bun.spawn([process.execPath, "src/main.js", "-i"], {
|
|
1431
|
+
cwd,
|
|
1432
|
+
env: { ...process.env, HOME: home, PS1: "[\\w] " },
|
|
1433
|
+
stdin: "pipe",
|
|
1434
|
+
stdout: "pipe",
|
|
1435
|
+
stderr: "pipe",
|
|
1436
|
+
});
|
|
1437
|
+
proc.stdin.write("tab\nexit\n");
|
|
1438
|
+
proc.stdin.end();
|
|
1439
|
+
const [status, stdout, stderr] = await Promise.all([
|
|
1440
|
+
proc.exited,
|
|
1441
|
+
new Response(proc.stdout).text(),
|
|
1442
|
+
new Response(proc.stderr).text(),
|
|
1443
|
+
]);
|
|
1444
|
+
expect(status).toBe(0);
|
|
1445
|
+
expect(stdout).toBe(
|
|
1446
|
+
`[${shown}] [📁 ${shown} \x1b[38;5;81m📂 ${shown}\x1b[0m] `,
|
|
1447
|
+
);
|
|
1448
|
+
expect(stderr).toBe("");
|
|
1449
|
+
});
|
|
1450
|
+
|
|
1451
|
+
test("adds the active tab number to the default multi-tab prompt", async () => {
|
|
1452
|
+
const cwd = new URL("..", import.meta.url).pathname.replace(/\/$/, "");
|
|
1453
|
+
const home = cwd.slice(0, cwd.lastIndexOf("/"));
|
|
1454
|
+
const shown = `~${cwd.slice(home.length)}`;
|
|
1455
|
+
const env = { ...process.env, HOME: home };
|
|
1456
|
+
delete env.PS1;
|
|
1457
|
+
const proc = Bun.spawn([process.execPath, "src/main.js", "-i"], {
|
|
1458
|
+
cwd,
|
|
1459
|
+
env,
|
|
1460
|
+
stdin: "pipe",
|
|
1461
|
+
stdout: "pipe",
|
|
1462
|
+
stderr: "pipe",
|
|
1463
|
+
});
|
|
1464
|
+
proc.stdin.write("tab\nexit\n");
|
|
1465
|
+
proc.stdin.end();
|
|
1466
|
+
const [status, stdout, stderr] = await Promise.all([
|
|
1467
|
+
proc.exited,
|
|
1468
|
+
new Response(proc.stdout).text(),
|
|
1469
|
+
new Response(proc.stderr).text(),
|
|
1470
|
+
]);
|
|
1471
|
+
expect(status).toBe(0);
|
|
1472
|
+
expect(stdout).toBe(
|
|
1473
|
+
`📁 ${shown}\n$ 📁 ${shown} \x1b[38;5;81m📂 ${shown}\x1b[0m\n[2]$ `,
|
|
1474
|
+
);
|
|
1475
|
+
expect(stderr).toBe("");
|
|
1476
|
+
});
|
|
1477
|
+
|
|
1478
|
+
test("recalls saved bunmsh history with the Up arrow after startup", async () => {
|
|
1479
|
+
const home = mkdtempSync(join(tmpdir(), "bunmsh-readline-history-"));
|
|
1480
|
+
let transcript = "";
|
|
1481
|
+
const terminal = new Bun.Terminal({
|
|
1482
|
+
cols: 100,
|
|
1483
|
+
rows: 30,
|
|
1484
|
+
data(_terminal, data) { transcript += data.toString(); },
|
|
1485
|
+
});
|
|
1486
|
+
try {
|
|
1487
|
+
const historyPath = join(home, ".local", "share", "bunmsh", "history");
|
|
1488
|
+
mkdirSync(join(home, ".local", "share", "bunmsh"), { recursive: true });
|
|
1489
|
+
await Bun.write(historyPath, `${JSON.stringify(["echo recalled-marker"])}\n`);
|
|
1490
|
+
const proc = Bun.spawn({
|
|
1491
|
+
cmd: [Bun.which("bun") || process.argv0, "src/main.js", "-i"],
|
|
1492
|
+
cwd: new URL("..", import.meta.url).pathname,
|
|
1493
|
+
env: {
|
|
1494
|
+
...process.env,
|
|
1495
|
+
HOME: home,
|
|
1496
|
+
PS1: "> ",
|
|
1497
|
+
BUNMSH_IMPORT_HISTORY: "off",
|
|
1498
|
+
},
|
|
1499
|
+
terminal,
|
|
1500
|
+
});
|
|
1501
|
+
await Bun.sleep(150);
|
|
1502
|
+
terminal.write("\x1b[A\r");
|
|
1503
|
+
await Bun.sleep(100);
|
|
1504
|
+
terminal.write("exit\r");
|
|
1505
|
+
expect(await proc.exited).toBe(0);
|
|
1506
|
+
expect(transcript).toContain("recalled-marker\r\n");
|
|
1507
|
+
} finally {
|
|
1508
|
+
terminal.close();
|
|
1509
|
+
rmSync(home, { recursive: true, force: true });
|
|
1510
|
+
}
|
|
1511
|
+
});
|
|
1512
|
+
|
|
1513
|
+
test("enables terminal mouse reporting only through BUNMSH_MOUSE", async () => {
|
|
1514
|
+
let transcript = "";
|
|
1515
|
+
const terminal = new Bun.Terminal({
|
|
1516
|
+
cols: 80,
|
|
1517
|
+
rows: 24,
|
|
1518
|
+
data(_terminal, data) { transcript += data.toString(); },
|
|
1519
|
+
});
|
|
1520
|
+
const proc = Bun.spawn({
|
|
1521
|
+
cmd: [Bun.which("bun") || process.argv0, "src/main.js", "-i"],
|
|
1522
|
+
cwd: new URL("..", import.meta.url).pathname,
|
|
1523
|
+
env: { ...process.env, PS1: "> ", BUNMSH_MOUSE: "1" },
|
|
1524
|
+
terminal,
|
|
1525
|
+
});
|
|
1526
|
+
try {
|
|
1527
|
+
await Bun.sleep(150);
|
|
1528
|
+
terminal.write("exit\r");
|
|
1529
|
+
expect(await proc.exited).toBe(0);
|
|
1530
|
+
} finally { terminal.close(); }
|
|
1531
|
+
expect(transcript).toContain(MOUSE_ON);
|
|
1532
|
+
expect(transcript).toContain(MOUSE_OFF);
|
|
1533
|
+
});
|
|
1534
|
+
|
|
1535
|
+
test("--mouse enables terminal mouse reporting", async () => {
|
|
1536
|
+
let transcript = "";
|
|
1537
|
+
const terminal = new Bun.Terminal({
|
|
1538
|
+
cols: 80,
|
|
1539
|
+
rows: 24,
|
|
1540
|
+
data(_terminal, data) { transcript += data.toString(); },
|
|
1541
|
+
});
|
|
1542
|
+
const env = { ...process.env, PS1: "> " };
|
|
1543
|
+
delete env.BUNMSH_MOUSE;
|
|
1544
|
+
const proc = Bun.spawn({
|
|
1545
|
+
cmd: [Bun.which("bun") || process.argv0, "src/main.js", "--mouse", "-i"],
|
|
1546
|
+
cwd: new URL("..", import.meta.url).pathname,
|
|
1547
|
+
env,
|
|
1548
|
+
terminal,
|
|
1549
|
+
});
|
|
1550
|
+
try {
|
|
1551
|
+
await Bun.sleep(150);
|
|
1552
|
+
terminal.write("exit\r");
|
|
1553
|
+
expect(await proc.exited).toBe(0);
|
|
1554
|
+
} finally { terminal.close(); }
|
|
1555
|
+
expect(transcript).toContain(MOUSE_ON);
|
|
1556
|
+
expect(transcript).toContain(MOUSE_OFF);
|
|
1557
|
+
});
|
|
1558
|
+
|
|
1559
|
+
test("--builtin-only skips PATH lookup but keeps builtins available", () => {
|
|
1560
|
+
const bun = Bun.which("bun") || process.argv0;
|
|
1561
|
+
const cwd = new URL("..", import.meta.url).pathname;
|
|
1562
|
+
const builtin = Bun.spawnSync({
|
|
1563
|
+
cmd: [bun, "src/main.js", "--builtin-only", "-c", "printf '%s' builtin-ok"],
|
|
1564
|
+
cwd,
|
|
1565
|
+
});
|
|
1566
|
+
expect(builtin.exitCode).toBe(0);
|
|
1567
|
+
expect(builtin.stdout.toString()).toBe("builtin-ok");
|
|
1568
|
+
|
|
1569
|
+
const external = Bun.spawnSync({
|
|
1570
|
+
cmd: [bun, "src/main.js", "--builtin-only", "-c", "sh -c 'printf external'"],
|
|
1571
|
+
cwd,
|
|
1572
|
+
});
|
|
1573
|
+
expect(external.exitCode).toBe(127);
|
|
1574
|
+
expect(external.stderr.toString()).toContain("bunmsh: sh: not found");
|
|
1575
|
+
});
|
|
1576
|
+
|
|
1577
|
+
test("tab mouse applies tracking changes immediately", async () => {
|
|
1578
|
+
let transcript = "";
|
|
1579
|
+
const terminal = new Bun.Terminal({
|
|
1580
|
+
cols: 80,
|
|
1581
|
+
rows: 24,
|
|
1582
|
+
data(_terminal, data) { transcript += data.toString(); },
|
|
1583
|
+
});
|
|
1584
|
+
const env = { ...process.env, PS1: "> " };
|
|
1585
|
+
delete env.BUNMSH_MOUSE;
|
|
1586
|
+
const proc = Bun.spawn({
|
|
1587
|
+
cmd: [Bun.which("bun") || process.argv0, "src/main.js", "-i"],
|
|
1588
|
+
cwd: new URL("..", import.meta.url).pathname,
|
|
1589
|
+
env,
|
|
1590
|
+
terminal,
|
|
1591
|
+
});
|
|
1592
|
+
try {
|
|
1593
|
+
await Bun.sleep(150);
|
|
1594
|
+
terminal.write("tab mouse on\r");
|
|
1595
|
+
await Bun.sleep(100);
|
|
1596
|
+
terminal.write("tab mouse off\r");
|
|
1597
|
+
await Bun.sleep(100);
|
|
1598
|
+
terminal.write("exit\r");
|
|
1599
|
+
expect(await proc.exited).toBe(0);
|
|
1600
|
+
} finally { terminal.close(); }
|
|
1601
|
+
expect(transcript).toContain(MOUSE_ON);
|
|
1602
|
+
expect(transcript).toContain(MOUSE_OFF);
|
|
1603
|
+
});
|
|
1604
|
+
|
|
1605
|
+
test("Ctrl-T and Alt-T switch tabs without discarding the edited line", async () => {
|
|
1606
|
+
let transcript = "";
|
|
1607
|
+
const terminal = new Bun.Terminal({
|
|
1608
|
+
cols: 100,
|
|
1609
|
+
rows: 30,
|
|
1610
|
+
data(_terminal, data) { transcript += data.toString(); },
|
|
1611
|
+
});
|
|
1612
|
+
const env = { ...process.env };
|
|
1613
|
+
delete env.PS1;
|
|
1614
|
+
const proc = Bun.spawn({
|
|
1615
|
+
cmd: [Bun.which("bun") || process.argv0, "src/main.js", "-i"],
|
|
1616
|
+
cwd: new URL("..", import.meta.url).pathname,
|
|
1617
|
+
env,
|
|
1618
|
+
terminal,
|
|
1619
|
+
});
|
|
1620
|
+
try {
|
|
1621
|
+
await Bun.sleep(150);
|
|
1622
|
+
terminal.write("echo shortcut-preserved");
|
|
1623
|
+
terminal.write("\x14");
|
|
1624
|
+
await Bun.sleep(100);
|
|
1625
|
+
terminal.write("\r");
|
|
1626
|
+
await Bun.sleep(100);
|
|
1627
|
+
terminal.write("\x1bt");
|
|
1628
|
+
await Bun.sleep(100);
|
|
1629
|
+
terminal.write("exit\r");
|
|
1630
|
+
expect(await proc.exited).toBe(0);
|
|
1631
|
+
} finally { terminal.close(); }
|
|
1632
|
+
expect(transcript).toContain("shortcut-preserved\r\n");
|
|
1633
|
+
expect(transcript).toContain("[2]$ ");
|
|
1634
|
+
expect(transcript).toContain("[1]$ ");
|
|
1635
|
+
});
|
|
1636
|
+
|
|
1637
|
+
test("clicking the prompt tab number creates a tab and Alt-C closes it", async () => {
|
|
1638
|
+
let transcript = "";
|
|
1639
|
+
let terminal;
|
|
1640
|
+
terminal = new Bun.Terminal({
|
|
1641
|
+
cols: 100,
|
|
1642
|
+
rows: 30,
|
|
1643
|
+
data(_terminal, data) {
|
|
1644
|
+
const text = data.toString();
|
|
1645
|
+
transcript += text;
|
|
1646
|
+
if (text.includes("\x1b[6n")) terminal.write("\x1b[10;6R");
|
|
1647
|
+
},
|
|
1648
|
+
});
|
|
1649
|
+
const env = { ...process.env };
|
|
1650
|
+
delete env.PS1;
|
|
1651
|
+
const proc = Bun.spawn({
|
|
1652
|
+
cmd: [Bun.which("bun") || process.argv0, "src/main.js", "--mouse", "-i"],
|
|
1653
|
+
cwd: new URL("..", import.meta.url).pathname,
|
|
1654
|
+
env,
|
|
1655
|
+
terminal,
|
|
1656
|
+
});
|
|
1657
|
+
try {
|
|
1658
|
+
await Bun.sleep(150);
|
|
1659
|
+
terminal.write("\x14");
|
|
1660
|
+
await Bun.sleep(100);
|
|
1661
|
+
terminal.write("\x1b[<0;2;10M");
|
|
1662
|
+
await Bun.sleep(120);
|
|
1663
|
+
terminal.write("\x1bc");
|
|
1664
|
+
await Bun.sleep(100);
|
|
1665
|
+
terminal.write("exit\r");
|
|
1666
|
+
expect(await proc.exited).toBe(0);
|
|
1667
|
+
} finally { terminal.close(); }
|
|
1668
|
+
expect(transcript).toContain("[3]$ ");
|
|
1669
|
+
});
|
|
1670
|
+
|
|
1671
|
+
test("clicking inside the typed line moves the cursor there instead of appending", async () => {
|
|
1672
|
+
let transcript = "";
|
|
1673
|
+
const terminal = new Bun.Terminal({
|
|
1674
|
+
cols: 80,
|
|
1675
|
+
rows: 24,
|
|
1676
|
+
data(_terminal, data) {
|
|
1677
|
+
const text = data.toString();
|
|
1678
|
+
transcript += text;
|
|
1679
|
+
// Prompt "$ " (2 cols) + "echo hello" (10 chars) = col 13, row 1
|
|
1680
|
+
// (nothing has scrolled yet in a fresh session).
|
|
1681
|
+
if (text.includes("\x1b[6n")) terminal.write("\x1b[1;13R");
|
|
1682
|
+
},
|
|
1683
|
+
});
|
|
1684
|
+
const proc = Bun.spawn({
|
|
1685
|
+
cmd: [Bun.which("bun") || process.argv0, "src/main.js", "--mouse", "-i"],
|
|
1686
|
+
cwd: new URL("..", import.meta.url).pathname,
|
|
1687
|
+
env: { ...process.env, PS1: "$ " },
|
|
1688
|
+
terminal,
|
|
1689
|
+
});
|
|
1690
|
+
try {
|
|
1691
|
+
await Bun.sleep(150);
|
|
1692
|
+
terminal.write("echo hello");
|
|
1693
|
+
await Bun.sleep(100);
|
|
1694
|
+
// Column 8 (1-based) lands right after "echo " (index 5 of the line),
|
|
1695
|
+
// just before "hello".
|
|
1696
|
+
terminal.write("\x1b[<0;8;1M");
|
|
1697
|
+
await Bun.sleep(150);
|
|
1698
|
+
terminal.write("X\r");
|
|
1699
|
+
await Bun.sleep(150);
|
|
1700
|
+
terminal.write("exit\r");
|
|
1701
|
+
expect(await proc.exited).toBe(0);
|
|
1702
|
+
} finally { terminal.close(); }
|
|
1703
|
+
expect(transcript).toContain("Xhello\r\n");
|
|
1704
|
+
expect(transcript).not.toContain("helloX\r\n");
|
|
1705
|
+
});
|
|
1706
|
+
|
|
1707
|
+
test("Alt-L, Alt-U, and Alt-P list cwd or parent without discarding the edited line", async () => {
|
|
1708
|
+
const cwd = mkdtempSync(join(tmpdir(), "bunmsh-alt-l-"));
|
|
1709
|
+
const child = join(cwd, "child");
|
|
1710
|
+
mkdirSync(child);
|
|
1711
|
+
await Bun.write(join(child, "alt-l-marker.txt"), "marker");
|
|
1712
|
+
await Bun.write(join(cwd, "alt-u-parent-marker.txt"), "marker");
|
|
1713
|
+
let transcript = "";
|
|
1714
|
+
const terminal = new Bun.Terminal({
|
|
1715
|
+
cols: 100,
|
|
1716
|
+
rows: 30,
|
|
1717
|
+
data(_terminal, data) { transcript += data.toString(); },
|
|
1718
|
+
});
|
|
1719
|
+
const proc = Bun.spawn({
|
|
1720
|
+
cmd: [Bun.which("bun") || process.argv0, "src/main.js", "-i"],
|
|
1721
|
+
cwd: new URL("..", import.meta.url).pathname,
|
|
1722
|
+
env: { ...process.env, PS1: "> " },
|
|
1723
|
+
terminal,
|
|
1724
|
+
});
|
|
1725
|
+
try {
|
|
1726
|
+
await Bun.sleep(150);
|
|
1727
|
+
terminal.write(`cd ${child}\r`);
|
|
1728
|
+
await Bun.sleep(80);
|
|
1729
|
+
terminal.write("echo alt-l-preserved");
|
|
1730
|
+
terminal.write("\x1bl");
|
|
1731
|
+
await Bun.sleep(100);
|
|
1732
|
+
terminal.write("\x1bu");
|
|
1733
|
+
await Bun.sleep(100);
|
|
1734
|
+
terminal.write("\x1bp");
|
|
1735
|
+
await Bun.sleep(100);
|
|
1736
|
+
terminal.write("\r");
|
|
1737
|
+
await Bun.sleep(80);
|
|
1738
|
+
terminal.write("exit\r");
|
|
1739
|
+
expect(await proc.exited).toBe(0);
|
|
1740
|
+
} finally {
|
|
1741
|
+
terminal.close();
|
|
1742
|
+
rmSync(cwd, { recursive: true, force: true });
|
|
1743
|
+
}
|
|
1744
|
+
expect(transcript).toContain("alt-l-marker.txt");
|
|
1745
|
+
expect(transcript).toContain("alt-u-parent-marker.txt");
|
|
1746
|
+
expect(transcript).toContain("alt-l-preserved\r\n");
|
|
1747
|
+
});
|
|
1748
|
+
|
|
1749
|
+
test("renders ghosts and accepts history words, file paths, and commands", async () => {
|
|
1750
|
+
let transcript = "";
|
|
1751
|
+
const terminal = new Bun.Terminal({
|
|
1752
|
+
cols: 100,
|
|
1753
|
+
rows: 30,
|
|
1754
|
+
data(_terminal, data) { transcript += data.toString(); },
|
|
1755
|
+
});
|
|
1756
|
+
const proc = Bun.spawn({
|
|
1757
|
+
cmd: [Bun.which("bun") || process.argv0, "src/main.js", "-i"],
|
|
1758
|
+
cwd: new URL("..", import.meta.url).pathname,
|
|
1759
|
+
env: { ...process.env, PS1: "> " },
|
|
1760
|
+
terminal,
|
|
1761
|
+
});
|
|
1762
|
+
const send = async (text) => {
|
|
1763
|
+
terminal.write(text);
|
|
1764
|
+
await Bun.sleep(80);
|
|
1765
|
+
};
|
|
1766
|
+
try {
|
|
1767
|
+
await Bun.sleep(150);
|
|
1768
|
+
await send("echo hello world\r");
|
|
1769
|
+
await send("echo h");
|
|
1770
|
+
await send("\x1b[C");
|
|
1771
|
+
await send("\r");
|
|
1772
|
+
await send("echo h");
|
|
1773
|
+
await send("\t");
|
|
1774
|
+
await send("\r");
|
|
1775
|
+
await send("basename pack");
|
|
1776
|
+
await send("\x1b[C");
|
|
1777
|
+
await send("\r");
|
|
1778
|
+
await send("pri");
|
|
1779
|
+
await send("\t");
|
|
1780
|
+
await send("\r");
|
|
1781
|
+
await send("exit\r");
|
|
1782
|
+
expect(await proc.exited).toBe(0);
|
|
1783
|
+
} finally {
|
|
1784
|
+
terminal.close();
|
|
1785
|
+
}
|
|
1786
|
+
expect(transcript).toContain("\x1b[2mello world\x1b[0m");
|
|
1787
|
+
expect(transcript).toContain("\x1b[2mage.json\x1b[0m");
|
|
1788
|
+
expect(transcript).toContain("package.json\r\n");
|
|
1789
|
+
expect(transcript).toContain("\x1b[2mnt\x1b[0m");
|
|
1790
|
+
expect(transcript).toContain("\x1b[0Knt\r");
|
|
1791
|
+
expect(transcript).not.toContain(MOUSE_ON);
|
|
1792
|
+
});
|
|
1793
|
+
});
|
|
1794
|
+
|
|
1795
|
+
describe("pspa", () => {
|
|
1796
|
+
test("lists every PID with its full command line", async () => {
|
|
1797
|
+
const execution = await executeArgv(["builtin", "pspa"], createState(), { capture: true });
|
|
1798
|
+
expect(execution.status).toBe(0);
|
|
1799
|
+
const lines = decode(execution.stdout).split("\n");
|
|
1800
|
+
// POSIX passes `ps -eo pid,args` through untouched, so its header is the
|
|
1801
|
+
// header, and this test process has to be in the listing under itself.
|
|
1802
|
+
expect(lines[0]).toMatch(/^\s*PID COMMAND$/);
|
|
1803
|
+
const own = lines.find((line) => new RegExp(`^\\s*${process.pid} `).test(line));
|
|
1804
|
+
expect(own).toBeDefined();
|
|
1805
|
+
expect(own.length).toBeGreaterThan(String(process.pid).length + 1);
|
|
1806
|
+
});
|
|
1807
|
+
|
|
1808
|
+
test("takes no operands", async () => {
|
|
1809
|
+
const execution = await executeArgv(["builtin", "pspa", "extra"], createState(), { capture: true });
|
|
1810
|
+
expect(execution.status).toBe(2);
|
|
1811
|
+
expect(decode(execution.stderr)).toBe("bunmsh: pspa: unexpected operand: extra\n");
|
|
1812
|
+
});
|
|
1813
|
+
|
|
1814
|
+
test("parses the Windows Win32_Process listing into sorted rows", () => {
|
|
1815
|
+
// What the PowerShell one-liner writes: PID, a space, then the command
|
|
1816
|
+
// line — or the image name when a system process has none.
|
|
1817
|
+
const rows = parseWindowsProcessList(
|
|
1818
|
+
"4 System\r\n" +
|
|
1819
|
+
"9876 \"C:\\Program Files\\App\\app.exe\" --flag \"a b\"\r\n" +
|
|
1820
|
+
"1200 C:\\Windows\\system32\\svchost.exe -k netsvcs\r\n" +
|
|
1821
|
+
"\r\n",
|
|
1822
|
+
);
|
|
1823
|
+
expect(rows).toEqual([
|
|
1824
|
+
{ pid: 4, args: "System" },
|
|
1825
|
+
{ pid: 1200, args: "C:\\Windows\\system32\\svchost.exe -k netsvcs" },
|
|
1826
|
+
{ pid: 9876, args: '"C:\\Program Files\\App\\app.exe" --flag "a b"' },
|
|
1827
|
+
]);
|
|
1828
|
+
});
|
|
1829
|
+
|
|
1830
|
+
test("drops the continuation lines a multi-line command line produces", () => {
|
|
1831
|
+
expect(parseWindowsProcessList("42 one\nwrapped continuation\n88 two\n")).toEqual([
|
|
1832
|
+
{ pid: 42, args: "one" },
|
|
1833
|
+
{ pid: 88, args: "two" },
|
|
1834
|
+
]);
|
|
1835
|
+
});
|
|
1836
|
+
|
|
1837
|
+
test("formats the Windows rows into the same two columns ps prints", () => {
|
|
1838
|
+
expect(formatProcessTable([{ pid: 4, args: "System" }, { pid: 1200, args: "svchost.exe" }]))
|
|
1839
|
+
.toBe(" PID COMMAND\n 4 System\n 1200 svchost.exe\n");
|
|
1840
|
+
// The column widens past procps' five columns for a longer PID.
|
|
1841
|
+
expect(formatProcessTable([{ pid: 123456, args: "app.exe" }]))
|
|
1842
|
+
.toBe(" PID COMMAND\n123456 app.exe\n");
|
|
1843
|
+
expect(formatProcessTable([])).toBe(" PID COMMAND\n");
|
|
1844
|
+
});
|
|
1845
|
+
});
|
|
1846
|
+
|
|
1847
|
+
describe("kill", () => {
|
|
1848
|
+
test("probes with signal 0 and reports an unknown pid", async () => {
|
|
1849
|
+
expect(await executeArgv(["builtin", "kill", "-0", String(process.pid)], createState(), { capture: true }))
|
|
1850
|
+
.toMatchObject({ status: 0 });
|
|
1851
|
+
const missing = await executeArgv(["builtin", "kill", "-0", "999999"], createState(), { capture: true });
|
|
1852
|
+
expect(missing.status).toBe(1);
|
|
1853
|
+
expect(decode(missing.stderr)).toContain("bunmsh: kill: 999999:");
|
|
1854
|
+
expect(await executeArgv(["builtin", "kill"], createState(), { capture: true }))
|
|
1855
|
+
.toMatchObject({ status: 2 });
|
|
1856
|
+
});
|
|
1857
|
+
|
|
1858
|
+
test.skipIf(process.platform === "win32")("signals a real process", async () => {
|
|
1859
|
+
const proc = Bun.spawn({ cmd: ["sleep", "30"], stdin: "ignore", stdout: "ignore", stderr: "ignore" });
|
|
1860
|
+
try {
|
|
1861
|
+
expect(await executeArgv(["builtin", "kill", String(proc.pid)], createState(), { capture: true }))
|
|
1862
|
+
.toMatchObject({ status: 0 });
|
|
1863
|
+
await proc.exited;
|
|
1864
|
+
expect(proc.signalCode).toBe("SIGTERM");
|
|
1865
|
+
} finally { proc.kill("SIGKILL"); }
|
|
1866
|
+
});
|
|
1867
|
+
|
|
1868
|
+
test("builds the taskkill command a Windows kill goes through", () => {
|
|
1869
|
+
// /T reaches the children libuv's TerminateProcess leaves running, /F is
|
|
1870
|
+
// what makes it work on a console process with no message loop.
|
|
1871
|
+
expect(windowsKillCommand(1234)).toEqual(["taskkill", "/PID", "1234", "/T", "/F"]);
|
|
1872
|
+
expect(windowsKillCommand("1234")).toEqual(["taskkill", "/PID", "1234", "/T", "/F"]);
|
|
1873
|
+
});
|
|
1874
|
+
|
|
1875
|
+
test("rewrites a taskkill failure as one of our own lines", () => {
|
|
1876
|
+
expect(taskkillFailure(0, "SUCCESS: The process with PID 1234 has been terminated.\r\n", ""))
|
|
1877
|
+
.toBeNull();
|
|
1878
|
+
expect(taskkillFailure(128, "", 'ERROR: The process "1234" not found.\r\n'))
|
|
1879
|
+
.toBe('The process "1234" not found.');
|
|
1880
|
+
expect(taskkillFailure(1, "ERROR: Access is denied.\r\n", "")).toBe("Access is denied.");
|
|
1881
|
+
expect(taskkillFailure(1, "", "")).toBe("taskkill exited with status 1");
|
|
1882
|
+
});
|
|
1883
|
+
});
|
|
1884
|
+
|
|
1885
|
+
describe("pspac", () => {
|
|
1886
|
+
const wrap = (name, value) => `${SH_COLORS[name]}${value}${SH_COLORS.reset}`;
|
|
1887
|
+
|
|
1888
|
+
test("reads the rows back out of the plain ps listing", () => {
|
|
1889
|
+
expect(parsePosixProcessList(" PID COMMAND\n 1 /init\n12771 bash -l\n\n")).toEqual([
|
|
1890
|
+
{ pid: 1, args: "/init" },
|
|
1891
|
+
{ pid: 12771, args: "bash -l" },
|
|
1892
|
+
]);
|
|
1893
|
+
});
|
|
1894
|
+
|
|
1895
|
+
test("dims the directory and names the program the first token really is", () => {
|
|
1896
|
+
const line = colorProcessCommand("/usr/bin/bash --login -l notes.txt");
|
|
1897
|
+
// Colour is the only difference: the text itself is untouched.
|
|
1898
|
+
expect(Bun.stripANSI(line)).toBe("/usr/bin/bash --login -l notes.txt");
|
|
1899
|
+
expect(line).toContain(wrap("path", "/usr/bin/"));
|
|
1900
|
+
expect(line).toContain(wrap("type", "bash"));
|
|
1901
|
+
expect(line).toContain(wrap("statement", "--login"));
|
|
1902
|
+
expect(line).toContain(wrap("statement", "-l"));
|
|
1903
|
+
// An ordinary operand is left alone.
|
|
1904
|
+
expect(line).toContain(" notes.txt");
|
|
1905
|
+
// Word boundaries are micro's: the `sh` ending `script.sh` is one of
|
|
1906
|
+
// sh.yaml's command names, and it is painted like one here too.
|
|
1907
|
+
expect(colorProcessCommand("bun run script.sh")).toContain(wrap("type", "sh"));
|
|
1908
|
+
|
|
1909
|
+
// A program sh.yaml's word lists have never heard of is still the
|
|
1910
|
+
// command, because here the first token is known to be one.
|
|
1911
|
+
expect(colorProcessCommand("fish")).toBe(wrap("type", "fish"));
|
|
1912
|
+
expect(colorProcessCommand("")).toBe("");
|
|
1913
|
+
});
|
|
1914
|
+
|
|
1915
|
+
test("applies sh.yaml's rules to the rest of the command line", () => {
|
|
1916
|
+
const source = 'if test 42 = "$HOME"; then echo ok; fi # note';
|
|
1917
|
+
const line = highlightShellCommand(source);
|
|
1918
|
+
expect(Bun.stripANSI(line)).toBe(source);
|
|
1919
|
+
expect(line).toContain(wrap("statement", "if"));
|
|
1920
|
+
expect(line).toContain(wrap("statement", "then"));
|
|
1921
|
+
expect(line).toContain(wrap("constant", "42"));
|
|
1922
|
+
expect(line).toContain(wrap("type", "test"));
|
|
1923
|
+
expect(line).toContain(wrap("special", "="));
|
|
1924
|
+
// The string is a region, so it swallows the $HOME inside it.
|
|
1925
|
+
expect(line).toContain(wrap("string", '"$HOME"'));
|
|
1926
|
+
expect(line).toContain(wrap("comment", "# note"));
|
|
1927
|
+
expect(highlightShellCommand("run $HOME/bin ${PATH}"))
|
|
1928
|
+
.toContain(wrap("identifier", "$HOME"));
|
|
1929
|
+
});
|
|
1930
|
+
|
|
1931
|
+
test("resolves rule precedence the way micro does", () => {
|
|
1932
|
+
// The flag rule is listed after the command names, so it wins the
|
|
1933
|
+
// overlap: --cat is a flag, not the coreutils cat.
|
|
1934
|
+
expect(highlightShellCommand("bun --cat run")).toContain(wrap("statement", "--cat"));
|
|
1935
|
+
// A # inside a quoted argument does not open a comment.
|
|
1936
|
+
const quoted = highlightShellCommand('echo "a # b" done');
|
|
1937
|
+
expect(quoted).toContain(wrap("string", '"a # b"'));
|
|
1938
|
+
expect(quoted).toContain(wrap("statement", "done"));
|
|
1939
|
+
});
|
|
1940
|
+
|
|
1941
|
+
test("lets a kernel thread and a quoted program keep their own colour", () => {
|
|
1942
|
+
expect(colorProcessCommand("[kworker/0:1]")).toBe(wrap("path", "[kworker/0:1]"));
|
|
1943
|
+
const windows = colorProcessCommand('"C:\\Program Files\\App\\app.exe" --flag');
|
|
1944
|
+
expect(Bun.stripANSI(windows)).toBe('"C:\\Program Files\\App\\app.exe" --flag');
|
|
1945
|
+
expect(windows).toContain(wrap("string", '"C:\\Program Files\\App\\app.exe"'));
|
|
1946
|
+
expect(windows).toContain(wrap("statement", "--flag"));
|
|
1947
|
+
});
|
|
1948
|
+
|
|
1949
|
+
test("strips back to exactly what pspa prints", () => {
|
|
1950
|
+
const rows = [
|
|
1951
|
+
{ pid: 1, args: "/init" },
|
|
1952
|
+
{ pid: 4, args: "" },
|
|
1953
|
+
{ pid: 123456, args: "/usr/bin/bun run dev" },
|
|
1954
|
+
];
|
|
1955
|
+
expect(Bun.stripANSI(colorProcessTable(rows))).toBe(formatProcessTable(rows));
|
|
1956
|
+
});
|
|
1957
|
+
|
|
1958
|
+
test("lists the same processes pspa does, in colour", async () => {
|
|
1959
|
+
const execution = await executeArgv(["builtin", "pspac"], createState(), { capture: true });
|
|
1960
|
+
expect(execution.status).toBe(0);
|
|
1961
|
+
const text = decode(execution.stdout);
|
|
1962
|
+
expect(text).toContain("\x1b[");
|
|
1963
|
+
const lines = Bun.stripANSI(text).split("\n");
|
|
1964
|
+
expect(lines[0]).toMatch(/^\s*PID COMMAND$/);
|
|
1965
|
+
expect(lines.some((line) => new RegExp(`^\\s*${process.pid} `).test(line))).toBe(true);
|
|
1966
|
+
});
|
|
1967
|
+
|
|
1968
|
+
test("takes no operands", async () => {
|
|
1969
|
+
const execution = await executeArgv(["builtin", "pspac", "extra"], createState(), { capture: true });
|
|
1970
|
+
expect(execution.status).toBe(2);
|
|
1971
|
+
expect(decode(execution.stderr)).toBe("bunmsh: pspac: unexpected operand: extra\n");
|
|
1972
|
+
});
|
|
1973
|
+
});
|
|
1974
|
+
|
|
1975
|
+
describe("variable completion", () => {
|
|
1976
|
+
const state = { env: { HOME: "/home/user", HOSTNAME: "box", PATH: "/bin", LOCAL_ONLY: "x" } };
|
|
1977
|
+
|
|
1978
|
+
test("recognizes a name being typed after $, ${, and ${#", () => {
|
|
1979
|
+
expect(variableContext("echo $HO")).toMatchObject({ prefix: "HO", lead: "$", brace: false });
|
|
1980
|
+
expect(variableContext("echo ${HO")).toMatchObject({ prefix: "HO", lead: "${", brace: true });
|
|
1981
|
+
expect(variableContext("echo ${#HO")).toMatchObject({ prefix: "HO", lead: "${#", brace: true });
|
|
1982
|
+
// A bare $ is a name with nothing typed yet, not a non-match.
|
|
1983
|
+
expect(variableContext("echo $")).toMatchObject({ prefix: "", lead: "$" });
|
|
1984
|
+
// Position on the line does not matter: inside double quotes, on the
|
|
1985
|
+
// right of an assignment, or where a command name would go.
|
|
1986
|
+
expect(variableContext('echo "$HO')).toMatchObject({ prefix: "HO" });
|
|
1987
|
+
expect(variableContext("X=$HO")).toMatchObject({ prefix: "HO" });
|
|
1988
|
+
expect(variableContext("$ED")).toMatchObject({ prefix: "ED" });
|
|
1989
|
+
});
|
|
1990
|
+
|
|
1991
|
+
test("leaves alone every other thing a $ can start", () => {
|
|
1992
|
+
expect(variableContext("echo $(")).toBeNull();
|
|
1993
|
+
expect(variableContext("echo $(l")).toBeNull();
|
|
1994
|
+
expect(variableContext("echo $?")).toBeNull();
|
|
1995
|
+
expect(variableContext("echo $1")).toBeNull();
|
|
1996
|
+
// $$ is the pid, already complete; the second $ is not a name opening.
|
|
1997
|
+
expect(variableContext("echo $$")).toBeNull();
|
|
1998
|
+
expect(variableContext("echo hi")).toBeNull();
|
|
1999
|
+
expect(variableContext("echo $HOME ")).toBeNull();
|
|
2000
|
+
});
|
|
2001
|
+
|
|
2002
|
+
test("counts backslashes so an escaped dollar stays literal", () => {
|
|
2003
|
+
expect(variableContext(String.raw`echo \$HO`)).toBeNull();
|
|
2004
|
+
// An escaped backslash is not escaping the dollar.
|
|
2005
|
+
expect(variableContext(String.raw`echo \\$HO`)).toMatchObject({ prefix: "HO" });
|
|
2006
|
+
expect(variableContext(String.raw`echo \\\$HO`)).toBeNull();
|
|
2007
|
+
});
|
|
2008
|
+
|
|
2009
|
+
test("completes from the shell's own table, exported or not", () => {
|
|
2010
|
+
const index = new VariableIndex();
|
|
2011
|
+
expect(index.matches(state, "HO")).toEqual(["HOME", "HOSTNAME"]);
|
|
2012
|
+
expect(index.first(state, "LOCAL")).toBe("LOCAL_ONLY");
|
|
2013
|
+
expect(index.first(state, "ZZ")).toBeNull();
|
|
2014
|
+
// An empty prefix lists everything, the way Tab on an empty word does.
|
|
2015
|
+
expect(index.matches(state, "")).toEqual(["HOME", "HOSTNAME", "LOCAL_ONLY", "PATH"]);
|
|
2016
|
+
expect(index.first(state, "")).toBeNull();
|
|
2017
|
+
});
|
|
2018
|
+
|
|
2019
|
+
test("picks up a name added after the index was first built", () => {
|
|
2020
|
+
const index = new VariableIndex();
|
|
2021
|
+
const live = { env: { ...state.env } };
|
|
2022
|
+
expect(index.first(live, "TA")).toBeNull();
|
|
2023
|
+
live.env.TAG = "v1";
|
|
2024
|
+
expect(index.first(live, "TA")).toBe("TAG");
|
|
2025
|
+
});
|
|
2026
|
+
|
|
2027
|
+
test("closes the brace it was given", () => {
|
|
2028
|
+
expect(variableCompletion(variableContext("echo $HO"), "HOME")).toBe("$HOME");
|
|
2029
|
+
expect(variableCompletion(variableContext("echo ${HO"), "HOME")).toBe("${HOME}");
|
|
2030
|
+
expect(variableCompletion(variableContext("echo ${#HO"), "HOME")).toBe("${#HOME}");
|
|
2031
|
+
});
|
|
2032
|
+
|
|
2033
|
+
test("reports the quote a word is inside, so nothing expands in single quotes", () => {
|
|
2034
|
+
expect(completionContext("echo 'foo")).toMatchObject({ quote: "'" });
|
|
2035
|
+
expect(completionContext('echo "foo')).toMatchObject({ quote: '"' });
|
|
2036
|
+
expect(completionContext("echo foo")).toMatchObject({ quote: null });
|
|
2037
|
+
});
|
|
2038
|
+
|
|
2039
|
+
test("treats $( as a command position and ) as the end of one", () => {
|
|
2040
|
+
expect(completionContext("echo $(l")).toMatchObject({ command: true, prefix: "l" });
|
|
2041
|
+
expect(completionContext("echo $(ls /tm")).toMatchObject({ command: false, prefix: "/tm" });
|
|
2042
|
+
expect(completionContext("echo $(date) fi")).toMatchObject({ command: false, prefix: "fi" });
|
|
2043
|
+
});
|
|
2044
|
+
});
|
|
2045
|
+
|
|
2046
|
+
describe("javascript mode completion", () => {
|
|
2047
|
+
test("decides mode by the rule the evaluator dispatches on", () => {
|
|
2048
|
+
expect(isJavaScriptMode("Bun.e, 1")).toBe(true);
|
|
2049
|
+
expect(isJavaScriptMode(" Bun.file('x')")).toBe(true);
|
|
2050
|
+
expect(isJavaScriptMode("echo Bun.e")).toBe(false);
|
|
2051
|
+
});
|
|
2052
|
+
|
|
2053
|
+
test("stops offering a continuation a JavaScript line could never accept", () => {
|
|
2054
|
+
// A bare trailing backslash is a syntax error in JavaScript, not a
|
|
2055
|
+
// request for another line, so the prompt no longer asks for one.
|
|
2056
|
+
expect(needsMoreInput("Bun.e; const p = \\")).toBe(false);
|
|
2057
|
+
// An unterminated string still continues: that is JavaScript's own line
|
|
2058
|
+
// continuation, and the evaluator accepts it.
|
|
2059
|
+
expect(needsMoreInput('Bun.e, "abc\\')).toBe(true);
|
|
2060
|
+
expect(needsMoreInput("Bun.e, `abc")).toBe(true);
|
|
2061
|
+
// Shell text is untouched.
|
|
2062
|
+
expect(needsMoreInput("echo hi \\")).toBe(true);
|
|
2063
|
+
expect(needsMoreInput('echo "abc')).toBe(true);
|
|
2064
|
+
expect(needsMoreInput("echo hi")).toBe(false);
|
|
2065
|
+
});
|
|
2066
|
+
|
|
2067
|
+
test("completes a shell variable after $. and nothing after a bare $", () => {
|
|
2068
|
+
expect(javascriptContext("Bun.e, $.HO")).toMatchObject({ kind: "variable", prefix: "HO", lead: "$." });
|
|
2069
|
+
expect(javascriptContext("Bun.e, $.")).toMatchObject({ kind: "variable", prefix: "" });
|
|
2070
|
+
// In JavaScript a bare `$` is the table object itself; a name only
|
|
2071
|
+
// starts after the dot.
|
|
2072
|
+
expect(javascriptContext("Bun.e, $")).toBeNull();
|
|
2073
|
+
expect(javascriptContext("Bun.e, 1 + 2")).toBeNull();
|
|
2074
|
+
});
|
|
2075
|
+
|
|
2076
|
+
test("completes a path inside an unclosed string literal", () => {
|
|
2077
|
+
expect(javascriptContext('Bun.e, Bun.file("/tm')).toMatchObject({ kind: "path", prefix: "/tm", quote: '"' });
|
|
2078
|
+
expect(javascriptContext("Bun.e, Bun.file('/tm")).toMatchObject({ kind: "path", prefix: "/tm", quote: "'" });
|
|
2079
|
+
expect(javascriptContext("Bun.e, Bun.file(`/tm")).toMatchObject({ kind: "path", prefix: "/tm", quote: "`" });
|
|
2080
|
+
// A closed string is not a path being typed.
|
|
2081
|
+
expect(javascriptContext('Bun.e, Bun.file("/tmp/x")')).toBeNull();
|
|
2082
|
+
// An escaped quote does not close it.
|
|
2083
|
+
expect(javascriptContext('Bun.e, "it\\"s /tm')).toMatchObject({ kind: "path", prefix: 'it\\"s /tm' });
|
|
2084
|
+
});
|
|
2085
|
+
|
|
2086
|
+
test("treats a template interpolation as code again", () => {
|
|
2087
|
+
// Inside ${ } the cursor is back in JavaScript, so $. completes there.
|
|
2088
|
+
expect(javascriptContext("Bun.e, `a${$.HO")).toMatchObject({ kind: "variable", prefix: "HO" });
|
|
2089
|
+
// And the template resumes after the closing brace.
|
|
2090
|
+
expect(javascriptContext("Bun.e, `a${b}/tm")).toMatchObject({ kind: "path", quote: "`" });
|
|
2091
|
+
});
|
|
2092
|
+
});
|