You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(node,bun): interactive REPLs, and the runtime gaps they exposed
`node` with no script said "missing script" and `bun repl` refused with a
message about a limitation that no longer held. Both now open a REPL on a
shared kit: line editing, history, multi-line, Ctrl+C/Ctrl+D, and TypeScript
in the Bun one.
Building it surfaced six things worth fixing on their own:
- stack frames named the transpiled text, not the user's file and line
- `readline` was a stub, so interactive scaffolders hung
- the argv parser silently ignored flags it claimed to accept
- a pipe on fd 0 reported itself as a terminal
- `Bun.exit` was missing, and bare `process.exit()` ignored `exitCode`
- `--watch`/`--hot` did not exist
Deliberately not done: `.load`/`.save`, tab completion, REPL history on disk.
Co-authored-by: Cursor <cursoragent@cursor.com>
help: 'NOT AVAILABLE here: copy the last result to the clipboard',
254
+
run: (arg, api) => {
255
+
// Refused by name, the way bun publish and bun patch are, rather
256
+
// than silently doing nothing: a .copy that prints "copied" and copies
257
+
// nothing is worse than one that says it cannot.
258
+
api.write('.copy is not implemented in the Vivari shim: it writes to the SYSTEM clipboard, and a Web Worker cannot reach navigator.clipboard at all — that API needs a document and a user gesture, and a worker has neither.' + NL);
259
+
api.write('Select the text in the terminal and copy it with your browser instead.' + NL);
260
+
},
261
+
},
262
+
},
263
+
});
264
+
}
265
+
161
266
// ---- run a package.json script (with pre/post), via the shell --------------
162
267
function runScriptCmd(command, rest) {
163
268
return new Promise((resolve) => {
@@ -538,6 +643,7 @@ function helpText() {
538
643
' bun <file.ts> run a TS/JS/TSX file (Bun global + zero-config TS)',
539
644
' bun run <script|file> run a package.json script or a file',
540
645
' bun install|add|remove manage dependencies (delegates to npm; writes bun.lock)',
646
+
' bun repl interactive prompt (TypeScript, Bun global, history)',
541
647
' bun x <pkg> run a package binary (bunx)',
542
648
' bun build <entry> bundle an entry point and its imports',
543
649
' bun test [filters] run bun:test suites (-t, --bail, --timeout, -u, --reporter)',
@@ -853,7 +959,53 @@ function doCreate(args) {
853
959
return doExec([pkgName].concat(rest));
854
960
}
855
961
962
+
// --watch / --hot, which bun takes anywhere before the target: 'bun --watch x.ts',
963
+
// 'bun run --watch dev'. Peeled off wherever they appear, so the argv the supervisor
964
+
// re-spawns is the same command MINUS the watch request — re-passing it would make
965
+
// every restart supervise a supervisor.
966
+
//
967
+
// --hot is the one honest compromise here. In real bun it is a SOFT reload: the module
968
+
// graph is swapped inside the running process, so globals survive and an open server
969
+
// keeps its socket. Doing that needs the loader to invalidate and re-evaluate a
970
+
// subgraph while everything else stays live, which is its own change. What runs here
971
+
// is --watch, and the one-line notice below says exactly that, once, rather than
972
+
// letting a program silently lose the state its author expected to keep.
973
+
function peelWatchFlags(list) {
974
+
const kept = [];
975
+
let watch = false, hot = false;
976
+
for (const a of list) {
977
+
if (a === '--watch') { watch = true; continue; }
978
+
if (a === '--hot') { hot = true; continue; }
979
+
// Bun clears the screen on rerun and this flag turns that off. We never clear —
980
+
// the terminal is the user's whole session record — so it is already the default
981
+
// and is dropped rather than reported as unknown.
if (!target) { err('bun --watch needs a file or a script to run'); process.exit(1); }
993
+
if (w.hot) {
994
+
err('note: --hot runs as --watch in the Vivari shim (the process is RESTARTED, not soft-reloaded), so globals and open sockets do not survive a reload.');
995
+
}
996
+
createWatcher({
997
+
argv: ['bun'].concat(w.kept),
998
+
label: target,
999
+
paths: [path.resolve(cwd, target)],
1000
+
// Bun prints no banner of its own around a rerun, so neither do we. The banners
1001
+
// node prints are node's, and copying them here would describe bun wrongly.
1002
+
onRunEnd: null,
1003
+
onRestart: null,
1004
+
clear: false,
1005
+
});
1006
+
return;
1007
+
}
1008
+
857
1009
const first = argv[0];
858
1010
if (!first || first === '--help' || first === '-h' || first === 'help') { out(helpText()); process.exit(0); }
859
1011
if (first === '--version' || first === '-v') { out(bunIdent().version); process.exit(0); }
@@ -862,7 +1014,11 @@ async function main() {
862
1014
installBun(true);
863
1015
const code = argv[1] || '';
864
1016
const fn = new Function('code', 'return eval(code)');
865
-
fn(code);
1017
+
// Transpiled, because real bun -e takes TypeScript: bun has no JS-only mode,
1018
+
// and every one of its docs' one-liners is written in TS. This handed the
1019
+
// source straight to eval, so 'bun -e' was the one place in the shim where
1020
+
// 'const n: number = 1' was a SyntaxError.
1021
+
fn(bunTranspile(code, '-e'));
866
1022
process.exit(0);
867
1023
}
868
1024
@@ -907,11 +1063,7 @@ async function main() {
907
1063
err('Edit the file in node_modules directly, or vendor the package into your source tree.');
908
1064
process.exit(1);
909
1065
return;
910
-
case 'repl':
911
-
err('bun repl is not implemented in the Vivari shim: the REPL wants a tty for line editing and history, and the sandbox has pipes rather than a terminal device.');
912
-
err('Use "bun run <file>" or "bun -e <code>" instead.');
913
-
process.exit(1);
914
-
return;
1066
+
case 'repl': return doRepl(rest);
915
1067
default:
916
1068
// Bare bun <file> / bun <script> -> run it. Anything else is a subcommand we
917
1069
// do not have; say so instead of failing later as a missing file.
0 commit comments