-
Notifications
You must be signed in to change notification settings - Fork 5k
node:repl: replace the stub with Node v26.3.0's REPL — v26 readline stack, acorn recoverable-parse + top-level await, completion, history, --interactive (82 vendored upstream tests) #31827
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 48 commits
Commits
Show all changes
90 commits
Select commit
Hold shift + click to select a range
ec274c8
node:repl: port Node v26.3.0 REPL + readline stack, vendor node repl …
cirospaciari f78894e
node:repl: error-name decoration, REPL frame trimming, builtin libs, …
cirospaciari cd02f77
node:repl: keep decorated ERR_* names (matches more vendored tests)
cirospaciari eae1477
[autofix.ci] apply automated fixes
autofix-ci[bot] 917df0e
lint: ignore verbatim Node ports in oxlint, fix shim unused-vars
cirospaciari f42fc1c
address review: frames guard in prepareStackTrace fallback, Rust doc …
cirospaciari 2ca63e1
vendor acorn minified to keep the embedded-source literal within comp…
cirospaciari 2d6029b
[autofix.ci] apply automated fixes
autofix-ci[bot] 8889bdf
address review: import referrer, capture decline path, scheme-only names
cirospaciari 5ea8b59
build: raise -fconstexpr-steps for the larger embedded builtin sources
cirospaciari 4022f6e
address review round 2: readline internals export, --interactive sema…
cirospaciari 12d9ff1
[autofix.ci] apply automated fixes
autofix-ci[bot] ecdb2ed
docs: node-repl.ts header no longer claims -i (taken by --install=fal…
cirospaciari 67f2809
repl: tolerate a pre-existing uncaughtException capture callback
cirospaciari 8db1820
test: re-sync util-inspect null-prototype error assertions toward ups…
cirospaciari 03225e5
make the Bun-only symbol hooks non-enumerable
cirospaciari 460b0a3
[autofix.ci] apply automated fixes
autofix-ci[bot] d662e21
repl: keep the eval property name out of member-access position in bu…
cirospaciari 2702f64
module: check exceptions in the resolveLookupPaths parent-paths loop
cirospaciari 5d550c2
test: exclude test-repl-inspect-defaults from exception-check validation
cirospaciari 063469f
fix promisify(question).name after bundler dedupe-rename; gate 5 more…
cirospaciari cdaf2a9
address review: inert overrideStackTrace, hardened primordials shim, …
cirospaciari ddfdeba
[autofix.ci] apply automated fixes
autofix-ci[bot] 6ab687f
test: exclude test-repl-dynamic-import from LSAN on the asan lane
cirospaciari 19d8c87
vm: finalize the heap on exit even when tick-scoped VM refs remain
cirospaciari 3c869c3
prettier: ignore the verbatim Node.js ports
cirospaciari 3454375
Merge branch 'main' into ciro/repl-node-tests
robobun 5fbfc32
Merge branch 'main' into ciro/repl-node-tests [build images]
cirospaciari 0235b36
Merge branch 'main' into ciro/repl-node-tests [build images]
cirospaciari 2fab595
repl: filter Bun-specific builtins from completion; vendor only the p…
cirospaciari 7da5859
Merge branch 'main' into ciro/repl-node-tests [build images]
cirospaciari 955d627
[autofix.ci] apply automated fixes
autofix-ci[bot] 86cc7bb
repl: don't throw when decorateErrorStack can't write a frozen .stack…
cirospaciari bd536eb
repl: guard _handleError's e.stack/e.message rewrites for frozen erro…
cirospaciari e69f1b4
cli: --interactive -e runs the eval then enters the REPL [build images]
cirospaciari a8289b0
cli: scope-isolate the --interactive -e user script from the REPL boo…
cirospaciari cb7a4e7
cli: document the static-import trade-off of the --interactive -e blo…
cirospaciari 15c4ccd
Merge branch 'main' into ciro/repl-node-tests [build images]
cirospaciari 6bf0206
repl: trim review-round comments to the new 3-line limit [build images]
cirospaciari 066e6dc
test(napi): split on /\r?\n/ so the napi_is_arraybuffer assertion pas…
cirospaciari a090188
Merge branch 'main' into ciro/repl-node-tests [build images]
cirospaciari 0455a5c
Merge branch 'main' into ciro/repl-node-tests
cirospaciari 0a13938
repl: don't let process.cwd() ENOENT break repl.start() from a delete…
cirospaciari f25d419
repl: address review — bootstrap rewrite, lazy acorn, ErrorCode regis…
alii 71c7172
repl: make --interactive -e errors fatal; decorate vm.Script parse er…
alii 5fe7881
repl: append new ErrorCode entries (fixes Rust index drift); drop dea…
alii 09015d9
Merge branch 'main' into ciro/repl-node-tests
cirospaciari 0c9e692
[autofix.ci] apply automated fixes
autofix-ci[bot] 61f3396
repl: address second-pass review — process._eval, --interactive routi…
alii 9412fa9
repl: decode process._eval as UTF-8; drop dispatch positionals in exe…
alii b5b5003
[autofix.ci] apply automated fixes
autofix-ci[bot] a92cab1
Merge remote-tracking branch 'origin/main' into ciro/repl-node-tests
cirospaciari 7e11a12
repl: vendor only the upstream tests that pass; drop the expectations…
cirospaciari 60e2a2a
repl: cover the process._eval UTF-8 decode and the argv 'run' leak
cirospaciari 5118400
run-eval: cover the process._eval UTF-8 decode for plain -e/--print
cirospaciari bfc54fe
Merge origin/main into ciro/repl-node-tests
cirospaciari 00df8dc
Merge branch 'main' into ciro/repl-node-tests
cirospaciari c68ad8f
vm: keep the SyntaxError when a user prepareStackTrace throws
cirospaciari d7d7177
Merge branch 'main' into ciro/repl-node-tests
cirospaciari 1ac506d
Merge branch 'main' into ciro/repl-node-tests
dylan-conway 7bf7a3e
node:vm: decorate compileFunction parse errors like new Script
cirospaciari f9490f3
node:readline: name Interface's Symbol.dispose method as a string
cirospaciari ebe8705
cli: make `-i` mean --interactive under node emulation
cirospaciari 15aab6a
[autofix.ci] apply automated fixes
autofix-ci[bot] b7aaf14
node:repl: document why `-e` runs before the REPL starts
cirospaciari f7b10e3
node:repl: evaluate `-e` after the REPL starts, with node's bindings
cirospaciari 817a588
node:repl: vendor three upstream tests that already pass
cirospaciari a2c033b
node:repl: vendor the .load multiline tests and their fixtures
cirospaciari 0858117
node:repl: vendor the nested-repls completion test and its fixture
cirospaciari 6488283
node:repl: vendor sigint/autocomplete tests; stop spreading in ALS.run
cirospaciari e691d59
[autofix.ci] apply automated fixes
autofix-ci[bot] 64ccb74
Merge branch 'main' into ciro/repl-node-tests
robobun 0e0d9e4
node:repl: signed lineOffset in arrow headers; ALS run short-circuit …
robobun b7588cd
[autofix.ci] apply automated fixes
autofix-ci[bot] 527b2b2
node:repl: neutralize upstream author notes so diff-hygiene passes; c…
robobun 8cc2f12
acorn: reword the \uXXXX placeholder in the vendored bundle's error m…
robobun fcafdeb
ci: retrigger
robobun ed8df5c
docs+tests: mark the three vendored-test deviations with bun:; fix th…
robobun b05ef0a
Merge branch 'main' into ciro/repl-node-tests
robobun 39b90ae
readline: correct the ReplHistory laziness comment
robobun e0c3731
Merge branch 'main' into ciro/repl-node-tests
robobun 6398622
node:repl,readline: defer loading so bare require stays near-free
robobun c089c02
cli(node-emulation): bare `node` on a TTY enters the REPL
robobun 97d8107
[autofix.ci] apply automated fixes
autofix-ci[bot] 1ed9551
test(as-node): pin piped stdin for the bare-node case
robobun dd1a095
test(vm): hoist the 5 Script-construction tests out of the 6x helper
robobun 31b294c
cli(node-emulation): use Output::is_stdin_tty() for the bare-node REP…
robobun ede4f5c
node:repl: hollow out the public surface so only calling start() load…
robobun a4af413
repl(node-shims): route decorateErrorStack's frame rewrite through th…
robobun 0a2d409
Merge branch 'main' into ciro/repl-node-tests
dylan-conway File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| // Entry script for `bun --interactive` (`-i` is taken by `--install=fallback`): starts the Node.js-compatible | ||
| // REPL (the ported node:repl) the way Node's internal/main/repl.js does. This | ||
| // file runs as a regular entrypoint (not a builtin), so it reaches | ||
| // createInternalRepl via a Symbol.for hook on node:repl and never re-implements | ||
| // the NODE_REPL_* env parsing that internal/repl.js already owns. | ||
|
|
||
| // exec_node_repl injects the user's `-e` script as a JSON string literal here | ||
| // (data, not code — a syntax error or unterminated token in `-e` cannot bleed | ||
| // into this bootstrap). Read and clear it before any user code runs. | ||
| declare const __BUN_EVAL_SCRIPT__: string | undefined; | ||
| const evalScript: string | undefined = typeof __BUN_EVAL_SCRIPT__ === "string" ? __BUN_EVAL_SCRIPT__ : undefined; | ||
|
|
||
| const ext = process.env.NODE_REPL_EXTERNAL_MODULE; | ||
| if (ext) { | ||
| // Node loads this in place of the built-in REPL (lib/internal/main/repl.js). | ||
| require(require("node:path").resolve(ext)); | ||
|
cirospaciari marked this conversation as resolved.
|
||
| } else { | ||
| const REPL = require("node:repl"); | ||
| const createInternalRepl = (REPL as Record<symbol, Function>)[Symbol.for("bun.repl.createInternalRepl")]; | ||
|
|
||
| console.log( | ||
| `Welcome to Bun v${(globalThis as any).Bun.version} (Node.js-compatible REPL, node:repl ${process.version}).\n` + | ||
| 'Type ".help" for more information.', | ||
| ); | ||
|
|
||
| // `node -i -e`: an -e error is fatal (uncaught, exit 1), not caught by the | ||
| // REPL. Runs before REPL.start so the shim's process-wide capture callback | ||
| // isn't installed yet; `var`/`function` still land on globalThis. | ||
| if (evalScript !== undefined) { | ||
| require("node:vm").runInThisContext(evalScript, { filename: "[eval]", displayErrors: true }); | ||
| } | ||
|
|
||
|
claude[bot] marked this conversation as resolved.
Outdated
|
||
| createInternalRepl(process.env, (err: Error | null, replServer: any) => { | ||
| if (err) throw err; | ||
|
|
||
| replServer.on("exit", () => { | ||
| if (replServer.historyManager?.isFlushing) { | ||
| replServer.once("flushHistory", () => process.exit()); | ||
| return; | ||
| } | ||
| process.exit(); | ||
| }); | ||
| }); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,122 @@ | ||
| // Ported from Node.js v26.3.0 lib/internal/readline/callbacks.js for Bun's node:repl. | ||
| // Attribution: derived from Node.js, MIT licensed (Node.js contributors). | ||
| // prettier-ignore | ||
| const primordials = require("internal/repl/node-primordials"); | ||
| var __node_module__ = { exports: {} }; | ||
|
|
||
| const { NumberIsNaN } = primordials; | ||
|
|
||
| const { | ||
| codes: { ERR_INVALID_ARG_VALUE, ERR_INVALID_CURSOR_POS }, | ||
| } = require("internal/repl/node-errors"); | ||
|
|
||
| const { validateFunction } = require("internal/validators"); | ||
| const { CSI } = require("internal/readline/utils"); | ||
|
|
||
| const { kClearLine, kClearScreenDown, kClearToLineBeginning, kClearToLineEnd } = CSI; | ||
|
|
||
| /** | ||
| * moves the cursor to the x and y coordinate on the given stream | ||
| */ | ||
|
|
||
| function cursorTo(stream, x, y, callback) { | ||
| if (callback !== undefined) { | ||
| validateFunction(callback, "callback"); | ||
| } | ||
|
|
||
| if (typeof y === "function") { | ||
| callback = y; | ||
| y = undefined; | ||
| } | ||
|
|
||
| if (NumberIsNaN(x)) throw new ERR_INVALID_ARG_VALUE("x", x); | ||
| if (NumberIsNaN(y)) throw new ERR_INVALID_ARG_VALUE("y", y); | ||
|
|
||
| if (stream == null || (typeof x !== "number" && typeof y !== "number")) { | ||
| if (typeof callback === "function") process.nextTick(callback, null); | ||
| return true; | ||
| } | ||
|
|
||
| if (typeof x !== "number") throw new ERR_INVALID_CURSOR_POS(); | ||
|
|
||
| const data = typeof y !== "number" ? CSI`${x + 1}G` : CSI`${y + 1};${x + 1}H`; | ||
| return stream.write(data, callback); | ||
| } | ||
|
|
||
| /** | ||
| * moves the cursor relative to its current location | ||
| */ | ||
|
|
||
| function moveCursor(stream, dx, dy, callback) { | ||
| if (callback !== undefined) { | ||
| validateFunction(callback, "callback"); | ||
| } | ||
|
|
||
| if (stream == null || !(dx || dy)) { | ||
| if (typeof callback === "function") process.nextTick(callback, null); | ||
| return true; | ||
| } | ||
|
|
||
| let data = ""; | ||
|
|
||
| if (dx < 0) { | ||
| data += CSI`${-dx}D`; | ||
| } else if (dx > 0) { | ||
| data += CSI`${dx}C`; | ||
| } | ||
|
|
||
| if (dy < 0) { | ||
| data += CSI`${-dy}A`; | ||
| } else if (dy > 0) { | ||
| data += CSI`${dy}B`; | ||
| } | ||
|
|
||
| return stream.write(data, callback); | ||
| } | ||
|
|
||
| /** | ||
| * clears the current line the cursor is on: | ||
| * -1 for left of the cursor | ||
| * +1 for right of the cursor | ||
| * 0 for the entire line | ||
| */ | ||
|
|
||
| function clearLine(stream, dir, callback) { | ||
| if (callback !== undefined) { | ||
| validateFunction(callback, "callback"); | ||
| } | ||
|
|
||
| if (stream === null || stream === undefined) { | ||
| if (typeof callback === "function") process.nextTick(callback, null); | ||
| return true; | ||
| } | ||
|
|
||
| const type = dir < 0 ? kClearToLineBeginning : dir > 0 ? kClearToLineEnd : kClearLine; | ||
| return stream.write(type, callback); | ||
| } | ||
|
|
||
| /** | ||
| * clears the screen from the current position of the cursor down | ||
| */ | ||
|
|
||
| function clearScreenDown(stream, callback) { | ||
| if (callback !== undefined) { | ||
| validateFunction(callback, "callback"); | ||
| } | ||
|
|
||
| if (stream === null || stream === undefined) { | ||
| if (typeof callback === "function") process.nextTick(callback, null); | ||
| return true; | ||
| } | ||
|
|
||
| return stream.write(kClearScreenDown, callback); | ||
| } | ||
|
|
||
| __node_module__.exports = { | ||
| clearLine, | ||
| clearScreenDown, | ||
| cursorTo, | ||
| moveCursor, | ||
| }; | ||
|
|
||
| export default __node_module__.exports; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| // Ported from Node.js v26.3.0 lib/internal/readline/emitKeypressEvents.js for Bun's node:repl. | ||
| // Attribution: derived from Node.js, MIT licensed (Node.js contributors). | ||
| // prettier-ignore | ||
| const primordials = require("internal/repl/node-primordials"); | ||
| var __node_module__ = { exports: {} }; | ||
|
|
||
| const { SafeStringIterator, Symbol } = primordials; | ||
|
|
||
| const { charLengthAt, CSI, emitKeys } = require("internal/readline/utils"); | ||
| const { kSawKeyPress } = require("internal/readline/interface"); | ||
|
|
||
| const { clearTimeout, setTimeout } = require("node:timers"); | ||
| const { kEscape } = CSI; | ||
|
|
||
| const { StringDecoder } = require("node:string_decoder"); | ||
|
|
||
| const KEYPRESS_DECODER = Symbol("keypress-decoder"); | ||
| const ESCAPE_DECODER = Symbol("escape-decoder"); | ||
|
|
||
| // GNU readline library - keyseq-timeout is 500ms (default) | ||
| const ESCAPE_CODE_TIMEOUT = 500; | ||
|
|
||
| /** | ||
| * accepts a readable Stream instance and makes it emit "keypress" events | ||
| */ | ||
|
|
||
| function emitKeypressEvents(stream, iface = {}) { | ||
| if (stream[KEYPRESS_DECODER]) return; | ||
|
|
||
| stream[KEYPRESS_DECODER] = new StringDecoder("utf8"); | ||
|
|
||
| stream[ESCAPE_DECODER] = emitKeys(stream); | ||
| stream[ESCAPE_DECODER].next(); | ||
|
|
||
| const triggerEscape = () => stream[ESCAPE_DECODER].next(""); | ||
| const { escapeCodeTimeout = ESCAPE_CODE_TIMEOUT } = iface; | ||
| let timeoutId; | ||
|
|
||
| function onData(input) { | ||
| if (stream.listenerCount("keypress") > 0) { | ||
| const string = stream[KEYPRESS_DECODER].write(input); | ||
| if (string) { | ||
| clearTimeout(timeoutId); | ||
|
|
||
| // This supports characters of length 2. | ||
| iface[kSawKeyPress] = charLengthAt(string, 0) === string.length; | ||
| iface.isCompletionEnabled = false; | ||
|
|
||
| let length = 0; | ||
| for (const character of new SafeStringIterator(string)) { | ||
| length += character.length; | ||
| if (length === string.length) { | ||
| iface.isCompletionEnabled = true; | ||
| } | ||
|
|
||
| try { | ||
| stream[ESCAPE_DECODER].next(character); | ||
| // Escape letter at the tail position | ||
| if (length === string.length && character === kEscape) { | ||
| timeoutId = setTimeout(triggerEscape, escapeCodeTimeout); | ||
| } | ||
| } catch (err) { | ||
| // If the generator throws (it could happen in the `keypress` | ||
| // event), we need to restart it. | ||
| stream[ESCAPE_DECODER] = emitKeys(stream); | ||
| stream[ESCAPE_DECODER].next(); | ||
| throw err; | ||
| } | ||
| } | ||
| } | ||
| } else { | ||
| // Nobody's watching anyway | ||
| stream.removeListener("data", onData); | ||
| stream.on("newListener", onNewListener); | ||
| } | ||
| } | ||
|
|
||
| function onNewListener(event) { | ||
| if (event === "keypress") { | ||
| stream.on("data", onData); | ||
| stream.removeListener("newListener", onNewListener); | ||
| } | ||
| } | ||
|
|
||
| if (stream.listenerCount("keypress") > 0) { | ||
| stream.on("data", onData); | ||
| } else { | ||
| stream.on("newListener", onNewListener); | ||
| } | ||
| } | ||
|
|
||
| __node_module__.exports = emitKeypressEvents; | ||
|
|
||
| export default __node_module__.exports; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.