fix(ext/node): support for inspector.url() (#31705)
Some checks failed
ci / pre-build (push) Has been cancelled
ci / test release macos-aarch64 (push) Has been cancelled
ci / bench release linux-x86_64 (push) Has been cancelled
ci / test debug linux-x86_64 (push) Has been cancelled
ci / test release linux-x86_64 (push) Has been cancelled
ci / test debug macos-x86_64 (push) Has been cancelled
ci / test release macos-x86_64 (push) Has been cancelled
ci / test debug windows-x86_64 (push) Has been cancelled
ci / test release windows-x86_64 (push) Has been cancelled
ci / test debug linux-aarch64 (push) Has been cancelled
ci / test release linux-aarch64 (push) Has been cancelled
ci / test debug macos-aarch64 (push) Has been cancelled
ci / lint debug linux-x86_64 (push) Has been cancelled
ci / lint debug macos-x86_64 (push) Has been cancelled
ci / lint debug windows-x86_64 (push) Has been cancelled
ci / build libs (push) Has been cancelled
ci / publish canary (push) Has been cancelled

Closes https://github.com/denoland/deno/issues/31700
This commit is contained in:
Bartek Iwańczuk 2025-12-23 00:42:06 +01:00 committed by GitHub
parent 1ce0e55d78
commit 11e9e073d9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 70 additions and 7 deletions

View file

@ -34,6 +34,7 @@ use deno_permissions::PermissionCheckError;
pub use node_resolver::DENO_SUPPORTED_BUILTIN_NODE_MODULES as SUPPORTED_BUILTIN_NODE_MODULES;
pub use node_resolver::PathClean;
use ops::handle_wrap::AsyncId;
pub use ops::inspector::InspectorServerUrl;
pub use ops::ipc::ChildPipeFd;
use ops::vm;
pub use ops::vm::ContextInitMode;

View file

@ -13,6 +13,8 @@ use deno_core::v8;
use deno_error::JsErrorBox;
use deno_permissions::PermissionsContainer;
pub struct InspectorServerUrl(pub String);
#[op2(fast)]
pub fn op_inspector_enabled() -> bool {
// TODO: hook up to InspectorServer
@ -49,9 +51,18 @@ pub fn op_inspector_close() {
#[op2]
#[string]
pub fn op_inspector_url() -> Option<String> {
// TODO: hook up to InspectorServer
None
pub fn op_inspector_url(
state: &mut OpState,
) -> Result<Option<String>, InspectorConnectError> {
state
.borrow_mut::<PermissionsContainer>()
.check_sys("inspector", "inspector.url")?;
Ok(
state
.try_borrow::<InspectorServerUrl>()
.map(|url| url.0.to_string()),
)
}
#[op2(fast)]

View file

@ -203,7 +203,11 @@ function close() {
}
function url() {
return op_inspector_url();
const u = op_inspector_url();
if (u === null) {
return undefined;
}
return u;
}
function waitForDebugger() {

View file

@ -30,6 +30,7 @@ use deno_core::serde_json::Value;
use deno_core::serde_json::json;
use deno_core::unsync::spawn;
use deno_core::url::Url;
use deno_node::InspectorServerUrl;
use fastwebsockets::Frame;
use fastwebsockets::OpCode;
use fastwebsockets::WebSocket;
@ -104,7 +105,7 @@ impl InspectorServer {
module_url: String,
inspector: Rc<JsRuntimeInspector>,
wait_for_session: bool,
) {
) -> InspectorServerUrl {
let session_sender = inspector.get_session_sender();
let deregister_rx = inspector.add_deregister_handler();
let info = InspectorInfo::new(
@ -114,7 +115,11 @@ impl InspectorServer {
module_url,
wait_for_session,
);
let url = InspectorServerUrl(
info.get_websocket_debugger_url(&self.host.to_string()),
);
self.register_inspector_tx.unbounded_send(info).unwrap();
url
}
}

View file

@ -619,12 +619,13 @@ impl MainWorker {
}
if let Some(server) = options.maybe_inspector_server.clone() {
server.register_inspector(
let inspector_url = server.register_inspector(
main_module.to_string(),
js_runtime.inspector(),
options.should_break_on_first_statement
|| options.should_wait_for_inspector_session,
);
js_runtime.op_state().borrow_mut().put(inspector_url);
}
let (

View file

@ -1877,3 +1877,29 @@ async fn inspector_node_worker_enable() {
tester.child.kill().unwrap();
tester.child.wait().unwrap();
}
#[test(flaky)]
fn inspector_node_runtime_api_url() {
let script = util::testdata_path().join("inspector/node/url.js");
let child = util::deno_cmd()
.arg("run")
.arg("--allow-sys")
.arg(inspect_flag_with_unique_port("--inspect"))
.arg(script)
.piped_output()
.spawn()
.unwrap();
let output = child.wait_with_output().unwrap();
let stderr = String::from_utf8(output.stderr).unwrap();
let first_line = stderr
.lines()
.collect::<Vec<_>>()
.first()
.unwrap()
.to_string();
let expected_url = first_line.strip_prefix("Debugger listening on ").unwrap();
let stdout = String::from_utf8(output.stdout).unwrap();
let actual_url = stdout.trim();
assert_eq!(actual_url, expected_url);
}

2
tests/testdata/inspector/node/url.js vendored Normal file
View file

@ -0,0 +1,2 @@
import inspector from "node:inspector";
console.log(inspector.url());

View file

@ -3,7 +3,7 @@ import inspector, { Session } from "node:inspector";
import inspectorPromises, {
Session as SessionPromise,
} from "node:inspector/promises";
import { assertEquals } from "@std/assert";
import { assertEquals, assertThrows } from "@std/assert";
Deno.test("[node/inspector] - importing inspector works", () => {
assertEquals(typeof inspector.open, "function");
@ -94,3 +94,16 @@ Deno.test({
]);
},
});
Deno.test("[node/inspector] - url() requires sys permission", {
permissions: { sys: false },
}, () => {
assertThrows(() => inspector.url(), Deno.errors.NotCapable);
});
Deno.test("[node/inspector] - url() returns undefined when no --inspect flag", {
permissions: { sys: true },
}, () => {
const url = inspector.url();
assertEquals(url, undefined);
});