SERVER-119408: Create VSCode DAP scaffolding for an "attach" workflow (#48265)

GitOrigin-RevId: cf77560a2b37085af3267bf826abd46cd51d0ad6
This commit is contained in:
Steve McClure 2026-02-19 10:03:38 -05:00 committed by MongoDB Bot
parent b792a3465b
commit 8d123f5339
13 changed files with 374 additions and 9 deletions

4
.gitignore vendored
View File

@ -55,10 +55,6 @@ venv
*.core
iwyu.dat
/src/mongo/*/*Debug*/
/src/mongo/*/*/*Debug*/
/src/mongo/*/*Release*/
/src/mongo/*/*/*Release*/
/src/ipch
/src/mongo/*/ipch
/src/mongo/*/*/ipch

View File

@ -34,6 +34,7 @@ mongo_js_library(
":node_modules/@eslint/js",
":node_modules/eslint-plugin-mongodb",
":node_modules/globals",
"//src/mongo/shell/debugger/vscode:eslint",
],
)

View File

@ -1,11 +1,12 @@
import {FlatCompat} from "@eslint/eslintrc";
import eslint from "@eslint/js";
import js from "@eslint/js";
import {default as mongodb_plugin} from "eslint-plugin-mongodb";
import globals from "globals";
import path from "node:path";
import {fileURLToPath} from "node:url";
import vscodeDebuggerConfig from "./src/mongo/shell/debugger/vscode/eslint.config.mjs";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({
@ -403,4 +404,8 @@ export default [
"prefer-spread": 2,
},
},
{
files: ["src/mongo/shell/debugger/vscode/**/*.{js,mjs}"],
...vscodeDebuggerConfig,
},
];

View File

@ -1,13 +1,13 @@
# JS Debugging in the MongoDB Shell
Use the `--shellJSDebugMode` flag for resmoke (or the `--jsDebugMode` flag directly on the mongo shell) to trigger an interactive debug prompt when `debugger` statements are hit in JS test code.
Sample JS Test:
```js
let x = 42;
let y = ["a", 15, [1, 2, 3]];
let z = {id: ObjectId(), value:42};
let z = {id: ObjectId(), value: 42};
debugger;
assert.eq(x, 7);
@ -16,27 +16,33 @@ print("Test Passed!");
```
Running this test from the shell will fail since `x != 7`, and the `debugger` is a no-op (does not have any callback handler):
```bash
./bazel-bin/install/bin/mongo --nodb jstests/my_test.js
```
Output:
```
Test Passed!
```
Run with the `--jsDebugMode` flag:
```bash
./bazel-bin/install/bin/mongo --nodb --jsDebugMode jstests/my_test.js
```
Should pause first at line 5, and prompt the user for input:
```
JSDEBUG> JavaScript execution paused in 'debugger' statement.
JSDEBUG> Type 'dbcont' to continue
JSDEBUG@jstests/my_test.js:5>
JSDEBUG@jstests/my_test.js:5>
```
Inspect variables:
```
JSDEBUG@jstests/my_test.js:5> x
42
@ -47,12 +53,14 @@ JSDEBUG@jstests/my_test.js:5> z
```
Modify `x` so it passes its upcoming assertion:
```
JSDEBUG@jstests/my_test.js:5> x = 7
7
```
The `q` variable does not exist yet, so we can set it to pass its upcoming assertion:
```
JSDEBUG@jstests/my_test.js:5> q
ReferenceError: q is not defined
@ -61,22 +69,25 @@ foo
```
Send a `dbcont` command to continue execution, and now the test passes!
```
JSDEBUG@jstests/my_test.js:5> dbcont
JSDEBUG> Continuing execution...
Test Passed!
All pids dead / alive (0):
All pids dead / alive (0):
Searching for files in: /home/ubuntu/mongo
```
## Resmoke
Use the `--shellJSDebugMode` flag in resmoke to stop on debugger statements:
```bash
buildscripts/resmoke.py run --suites=no_passthrough --shellJSDebugMode jstests/my_test.js
```
Update variables `x` and `q` to repair the failing assertions:
```
JSDEBUG> JavaScript execution paused in 'debugger' statement.
JSDEBUG> Type 'dbcont' to continue

View File

@ -0,0 +1 @@
node_modules/

View File

@ -0,0 +1,15 @@
load("//bazel:mongo_js_rules.bzl", "mongo_js_library")
package(default_visibility = ["//visibility:public"])
mongo_js_library(
name = "all_javascript_files",
srcs = glob([
"*.js",
]),
)
mongo_js_library(
name = "eslint",
srcs = ["eslint.config.mjs"],
)

View File

@ -0,0 +1,38 @@
# VSCode Extension for Debugging JS in the Mongo Shell
Install the extension:
```bash
./src/mongo/shell/debugger/vscode/install.sh
```
Add a "mongo-shell" type configuration in your `~/.vscode/launch.json` file:
```json
{
"version": "0.1.0",
"configurations": [
{
"type": "mongo-shell",
"request": "attach",
"name": "Attach to MongoDB Shell",
"debugPort": 9229
}
]
}
```
Restart VSCode, or use CMD+SHIFT+P and select "Developer: Reload Window".
## Usage
1. Open a .js test file in VSCode
2. Add a breakpoint next to the line number (a red dot)
3. Press F5 to start the (VSCode) debug server, ready to "attach" to a debug shell
> You should see the following in the "Debug Console" of VSCode:
```
Debug server listening on port 9229
Waiting for mongo shell to connect on port 9229...
Use resmoke's --shellJSDebugMode flag when running a JS test file to stop on breakpoints.
```
4. [TODO] Now you can run resmoke with the `--shellJSDebugMode` flag to stop on the breakpoints

View File

@ -0,0 +1,14 @@
#!/usr/bin/env node
/**
* Debug Adapter entry point for MongoDB Shell JS Debugger
* This executable is invoked by VSCode when starting a debug session
*/
console.log("Starting MongoDB Shell JS Debug Adapter...");
const {MongoShellDebugSession} = require("./session");
const {DebugSession} = require("vscode-debugadapter");
// Start the debug session
DebugSession.run(MongoShellDebugSession);

View File

@ -0,0 +1,23 @@
// This folder contains JS intended to run in a Node-based environment, not the Mongo shell.
import globals from "globals";
export default {
languageOptions: {
ecmaVersion: 2022,
sourceType: "module",
globals: {
...globals.node,
...globals.es2021,
},
},
rules: {
"no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
caughtErrors: "all",
},
],
},
};

View File

@ -0,0 +1,45 @@
/**
* VSCode Extension for MongoDB Shell Debugger
*
* Registers the 'mongo-shell' debug type and provides configuration.
*/
const vscode = require("vscode");
const path = require("path");
// Extension entry point
function activate(context) {
// Register debug config provider
context.subscriptions.push(
vscode.debug.registerDebugConfigurationProvider("mongo-shell", {
provideDebugConfigurations,
}),
);
// Register debug adapter factory
context.subscriptions.push(
vscode.debug.registerDebugAdapterDescriptorFactory("mongo-shell", {
createDebugAdapterDescriptor: (session) => createDebugAdapter(context, session),
}),
);
}
function deactivate() {}
function provideDebugConfigurations(_folder) {
return [
{
type: "mongo-shell",
request: "attach",
name: "Attach to MongoDB Shell",
debugPort: 9229,
},
];
}
function createDebugAdapter(context, _session) {
const adapterPath = path.join(context.extensionPath, "adapter.js");
return new vscode.DebugAdapterExecutable("node", [adapterPath]);
}
module.exports = {activate, deactivate};

View File

@ -0,0 +1,39 @@
#!/bin/bash
cd $MONGO_REPO
cd src/mongo/shell/debugger/vscode
npm install
# Get version from package.json
VERSION=$(node -p "require('./package.json').version")
PUBLISHER=$(node -p "require('./package.json').publisher")
EXT_NAME=$(node -p "require('./package.json').name")
# Clean up old versions of the extension
rm -rf ~/.vscode/extensions/$PUBLISHER.$EXT_NAME-*
rm -rf ~/.vscode-server/extensions/$PUBLISHER.$EXT_NAME-*
# Detect if using VS Code Remote SSH or local
if [ -d ~/.vscode-server/extensions ]; then
EXT_DIR=~/.vscode-server/extensions/$PUBLISHER.$EXT_NAME-$VERSION
else
EXT_DIR=~/.vscode/extensions/$PUBLISHER.$EXT_NAME-$VERSION
fi
# Create extension directory and copy all necessary files
mkdir -p $EXT_DIR
cp ./package.json $EXT_DIR/
cp ./README.md $EXT_DIR/
cp ./adapter.js $EXT_DIR/
cp ./extension.js $EXT_DIR/
cp ./session.js $EXT_DIR/
cp -r ./node_modules $EXT_DIR/
echo "Extension installed to $EXT_DIR"
echo "Restart VSCode or run 'Developer: Reload Window' to activate the extension"
cd $MONGO_REPO

View File

@ -0,0 +1,83 @@
{
"name": "mongo-shell-debugger",
"displayName": "MongoDB Shell Debugger",
"description": "Debug JavaScript code in MongoDB Shell (MozJS)",
"version": "0.1.0",
"publisher": "mongodb",
"repository": {
"type": "git",
"url": "https://github.com/10gen/mongo.git"
},
"engines": {
"vscode": "^1.75.0"
},
"categories": [
"Debuggers"
],
"keywords": [
"mongodb",
"javascript",
"debugger",
"mongo-shell"
],
"main": "./extension.js",
"activationEvents": [
"onDebug",
"onDebugResolve:mongo-shell",
"onDebugDynamicConfigurations:mongo-shell"
],
"contributes": {
"breakpoints": [
{
"language": "javascript"
}
],
"debuggers": [
{
"type": "mongo-shell",
"label": "Mongo Shell",
"program": "./adapter.js",
"runtime": "node",
"configurationAttributes": {
"attach": {
"required": [
"debugPort"
],
"properties": {
"debugPort": {
"type": "number",
"description": "Port of running debug server",
"default": 9229
},
"trace": {
"type": "boolean",
"description": "Enable logging of the Debug Adapter Protocol",
"default": false
}
}
}
},
"initialConfigurations": [
],
"configurationSnippets": [
{
"label": "MongoDB Shell: Attach",
"description": "Attach to running MongoDB Shell debug server",
"body": {
"type": "mongo-shell",
"request": "attach",
"name": "Attach to MongoDB Shell",
"debugPort": 9229
}
}
],
"variables": {
"AskForProgramName": "extension.mongo-shell-debugger.getProgramName"
}
}
]
},
"devDependencies": {
"@types/vscode": "^1.75.0"
}
}

View File

@ -0,0 +1,94 @@
/**
* MongoDB Shell JS Debug Session
*
* Implements the Debug Adapter Protocol for debugging JavaScript in MongoDB Shell.
* https://microsoft.github.io/debug-adapter-protocol/overview
*/
const {
DebugSession,
OutputEvent,
// https://github.com/microsoft/vscode-debugadapter-node/tree/main/adapter
} = require("vscode-debugadapter");
const net = require("net");
class MongoShellDebugSession extends DebugSession {
constructor() {
super();
this.setDebuggerLinesStartAt1(true);
this.setDebuggerColumnsStartAt1(true);
// State
this.debugConnection = null;
this.debugServer = null;
this.connected = false;
}
// Attach - start a debug server, listening for a mongo shell to attach to
async attachRequest(response, args) {
try {
await this.startDebugServer(args.debugPort || 9229);
this.log(`Waiting for mongo shell to connect on port ${args.debugPort || 9229}...\n`);
this.log("Use resmoke's --shellJSDebugMode flag when running a JS test file to stop on breakpoints.\n");
this.sendResponse(response);
} catch (err) {
this.sendErrorResponse(response, 1000, `Failed to attach: ${err.message}`);
}
}
// Start TCP server to accept connections from mongo shell
startDebugServer(port) {
return new Promise((resolve, reject) => {
this.debugServer = net.createServer((socket) => {
this.debugConnection = socket;
this.connected = true;
this.setupDebugConnection();
});
this.debugServer.listen(port, "localhost", () => {
this.log(`Debug server listening on port ${port}\n`);
resolve();
});
this.debugServer.on("error", reject);
});
}
// Set up message handling for debug protocol
setupDebugConnection() {
this.debugConnection.on("data", (_data) => {
// TODO
});
this.debugConnection.on("end", () => {
this.connected = false;
});
}
// Helper to send output to debug console
log(text, category = "stdout") {
this.sendEvent(new OutputEvent(text, category));
}
disconnectRequest(response, _args) {
this.cleanup();
this.sendResponse(response);
}
// Clean up resources
cleanup() {
if (this.debugConnection) {
this.debugConnection.end();
this.debugConnection = null;
}
if (this.debugServer) {
this.debugServer.close();
this.debugServer = null;
}
this.connected = false;
}
}
module.exports = {MongoShellDebugSession};