Skip to content

Commit 78d4301

Browse files
authored
fix(prompt): better output with control chars (#18108)
1 parent 0da1938 commit 78d4301

File tree

6 files changed

+65
-5
lines changed

6 files changed

+65
-5
lines changed

Cargo.lock

Lines changed: 3 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ base64 = "=0.13.1"
8484
bencher = "0.1"
8585
bytes = "=1.2.1"
8686
cache_control = "=0.2.0"
87+
console_static_text = "=0.7.1"
8788
data-url = "=0.2.0"
8889
dlopen = "0.1.8"
8990
encoding_rs = "=0.8.31"

cli/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ chrono = { version = "=0.4.22", default-features = false, features = ["clock"] }
6262
clap = "=3.1.12"
6363
clap_complete = "=3.1.2"
6464
clap_complete_fig = "=3.1.5"
65-
console_static_text = "=0.3.4"
65+
console_static_text.workspace = true
6666
data-url.workspace = true
6767
dissimilar = "=1.0.4"
6868
dprint-plugin-json = "=0.17.0"

cli/tests/integration/run_tests.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4027,6 +4027,51 @@ fn stdio_streams_are_locked_in_permission_prompt() {
40274027
});
40284028
}
40294029

4030+
#[test]
4031+
fn permission_prompt_strips_ansi_codes_and_control_chars() {
4032+
let _guard = util::http_server();
4033+
util::with_pty(&["repl"], |mut console| {
4034+
console.write_line(
4035+
r#"Deno.permissions.request({ name: "env", variable: "\rDo you like ice cream? y/n" });"#
4036+
);
4037+
console.write_line("close();");
4038+
let output = console.read_all_output();
4039+
4040+
assert!(output.contains(
4041+
"┌ ⚠️ Deno requests env access to \"Do you like ice cream? y/n\"."
4042+
));
4043+
});
4044+
4045+
util::with_pty(&["repl"], |mut console| {
4046+
console.write_line(
4047+
r#"
4048+
const boldANSI = "\u001b[1m" // bold
4049+
const unboldANSI = "\u001b[22m" // unbold
4050+
4051+
const prompt = `┌ ⚠️ ${boldANSI}Deno requests run access to "echo"${unboldANSI}
4052+
├ Requested by \`Deno.Command().output()`
4053+
4054+
const moveANSIUp = "\u001b[1A" // moves to the start of the line
4055+
const clearANSI = "\u001b[2K" // clears the line
4056+
const moveANSIStart = "\u001b[1000D" // moves to the start of the line
4057+
4058+
Deno[Object.getOwnPropertySymbols(Deno)[0]].core.ops.op_spawn_child({
4059+
cmd: "cat",
4060+
args: ["/etc/passwd"],
4061+
clearEnv: false,
4062+
env: [],
4063+
stdin: "null",
4064+
stdout: "inherit",
4065+
stderr: "piped"
4066+
}, moveANSIUp + clearANSI + moveANSIStart + prompt)"#,
4067+
);
4068+
console.write_line("close();");
4069+
let output = console.read_all_output();
4070+
4071+
assert!(output.contains(r#"┌ ⚠️ Deno requests run access to "cat""#));
4072+
});
4073+
}
4074+
40304075
itest!(node_builtin_modules_ts {
40314076
args: "run --quiet --allow-read run/node_builtin_modules/mod.ts hello there",
40324077
output: "run/node_builtin_modules/mod.ts.out",

runtime/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ deno_websocket.workspace = true
8686
deno_webstorage.workspace = true
8787

8888
atty.workspace = true
89+
console_static_text.workspace = true
8990
dlopen.workspace = true
9091
encoding_rs.workspace = true
9192
filetime = "0.2.16"

runtime/permissions/prompter.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@ use deno_core::error::AnyError;
55
use deno_core::parking_lot::Mutex;
66
use once_cell::sync::Lazy;
77

8+
/// Helper function to strip ansi codes and ASCII control characters.
9+
fn strip_ansi_codes_and_ascii_control(s: &str) -> std::borrow::Cow<str> {
10+
console_static_text::strip_ansi_codes(s)
11+
.chars()
12+
.filter(|c| !c.is_ascii_control())
13+
.collect()
14+
}
15+
816
pub const PERMISSION_EMOJI: &str = "⚠️";
917

1018
#[derive(Debug, Eq, PartialEq)]
@@ -203,6 +211,10 @@ impl PermissionPrompter for TtyPrompter {
203211
let _stdout_guard = std::io::stdout().lock();
204212
let _stderr_guard = std::io::stderr().lock();
205213

214+
let message = strip_ansi_codes_and_ascii_control(message);
215+
let name = strip_ansi_codes_and_ascii_control(name);
216+
let api_name = api_name.map(strip_ansi_codes_and_ascii_control);
217+
206218
// print to stderr so that if stdout is piped this is still displayed.
207219
let opts: String = if is_unary {
208220
format!("[y/n/A] (y = yes, allow; n = no, deny; A = allow all {name} permissions)")
@@ -211,9 +223,9 @@ impl PermissionPrompter for TtyPrompter {
211223
};
212224
eprint!("┌ {PERMISSION_EMOJI} ");
213225
eprint!("{}", colors::bold("Deno requests "));
214-
eprint!("{}", colors::bold(message));
226+
eprint!("{}", colors::bold(message.clone()));
215227
eprintln!("{}", colors::bold("."));
216-
if let Some(api_name) = api_name {
228+
if let Some(api_name) = api_name.clone() {
217229
eprintln!("├ Requested by `{api_name}` API");
218230
}
219231
let msg = format!("Run again with --allow-{name} to bypass this prompt.");

0 commit comments

Comments
 (0)