diff --git a/src/cli/repl_command.zig b/src/cli/repl_command.zig index dd71a3c83b..f1b5c68be1 100644 --- a/src/cli/repl_command.zig +++ b/src/cli/repl_command.zig @@ -1296,11 +1296,70 @@ pub const Repl = struct { } fn runShellCommand(self: *Self, code: []const u8) !void { - _ = code; - _ = self; - // Execute shell command using Bun's shell - // TODO: Integrate with shell interpreter - Output.pretty("Shell mode not yet implemented\n", .{}); + // Transform $`command` into await Bun.$`command` and execute it + // The shell template tag returns a ShellPromise which gets awaited + + const start_time = std.time.nanoTimestamp(); + + // Parse the shell command from the input + // Formats: $`command` or $ `command` + var cmd_start: usize = 0; + if (strings.startsWith(code, "$`")) { + cmd_start = 1; // Skip the $, keep the backtick + } else if (strings.startsWith(code, "$ `")) { + cmd_start = 2; // Skip "$ ", keep the backtick + } else { + Output.pretty("Invalid shell command syntax. Use $`command`\n", .{}); + return; + } + + // Build the JavaScript code to execute the shell command + // Using Bun.$ template literal with await for automatic promise handling + const shell_code = code[cmd_start..]; + + // Wrap in await Bun.$ - use the same transformation as normal code + const js_code = try std.fmt.allocPrint(self.allocator, "await Bun.${s}", .{shell_code}); + defer self.allocator.free(js_code); + + // Transform code using the transpiler with replMode (for top-level await support) + const transformed = self.transformCode(js_code) catch |err| { + Output.pretty("Shell command transformation failed: {s}\n", .{@errorName(err)}); + return; + }; + defer self.allocator.free(transformed); + + if (transformed.len == 0) { + return; + } + + // Execute the transformed code + const wrapper_result = self.executeCode(transformed) catch |err| { + Output.pretty("Shell command failed: {s}\n", .{@errorName(err)}); + return; + }; + + // Handle async result (the shell command returns a promise) + if (wrapper_result.asPromise()) |_| { + self.awaitAndPrintResult(wrapper_result, start_time); + } else { + const end_time = std.time.nanoTimestamp(); + const elapsed_ms = @as(f64, @floatFromInt(end_time - start_time)) / 1_000_000.0; + + // Extract the actual result value from the wrapper object + const result = if (wrapper_result.isObject()) blk: { + const val = wrapper_result.get(self.global, "value") catch break :blk wrapper_result; + break :blk val orelse wrapper_result; + } else wrapper_result; + + // Print result + if (!result.isUndefined()) { + self.printResult(result); + } + + if (self.show_timing) { + Output.pretty("({d:.2}ms)\n", .{elapsed_ms}); + } + } } }; diff --git a/test/cli/repl.test.ts b/test/cli/repl.test.ts index 251e83d0ea..75d563e08e 100644 --- a/test/cli/repl.test.ts +++ b/test/cli/repl.test.ts @@ -304,4 +304,59 @@ describe("bun repl", () => { expect(stdout).toContain("3"); expect(exitCode).toBe(0); }); + + test("shell command syntax works", async () => { + await using proc = Bun.spawn({ + cmd: [bunExe(), "repl"], + env: bunEnv, + stdin: "pipe", + stdout: "pipe", + stderr: "pipe", + }); + + proc.stdin.write("$`echo hello from shell`\n"); + proc.stdin.end(); + + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + + expect(stdout).toContain("hello from shell"); + expect(exitCode).toBe(0); + }); + + test("Bun.$ template literal works", async () => { + await using proc = Bun.spawn({ + cmd: [bunExe(), "repl"], + env: bunEnv, + stdin: "pipe", + stdout: "pipe", + stderr: "pipe", + }); + + proc.stdin.write("await Bun.$`echo test output`\n"); + proc.stdin.end(); + + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + + expect(stdout).toContain("test output"); + expect(exitCode).toBe(0); + }); + + test("Bun.sleep works", async () => { + await using proc = Bun.spawn({ + cmd: [bunExe(), "repl"], + env: bunEnv, + stdin: "pipe", + stdout: "pipe", + stderr: "pipe", + }); + + proc.stdin.write("await Bun.sleep(10)\n"); + proc.stdin.write("'slept'\n"); + proc.stdin.end(); + + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + + expect(stdout).toContain("slept"); + expect(exitCode).toBe(0); + }); });