SERVER-119408: Create VSCode DAP scaffolding for an "attach" workflow (#48265)
GitOrigin-RevId: cf77560a2b37085af3267bf826abd46cd51d0ad6
This commit is contained in:
committed by
MongoDB Bot
parent
b792a3465b
commit
8d123f5339
4
.gitignore
vendored
4
.gitignore
vendored
@@ -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
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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
|
||||
|
||||
1
src/mongo/shell/debugger/vscode/.gitignore
vendored
Normal file
1
src/mongo/shell/debugger/vscode/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
node_modules/
|
||||
15
src/mongo/shell/debugger/vscode/BUILD.bazel
Normal file
15
src/mongo/shell/debugger/vscode/BUILD.bazel
Normal 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"],
|
||||
)
|
||||
38
src/mongo/shell/debugger/vscode/README.md
Normal file
38
src/mongo/shell/debugger/vscode/README.md
Normal 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
|
||||
14
src/mongo/shell/debugger/vscode/adapter.js
Normal file
14
src/mongo/shell/debugger/vscode/adapter.js
Normal 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);
|
||||
23
src/mongo/shell/debugger/vscode/eslint.config.mjs
Normal file
23
src/mongo/shell/debugger/vscode/eslint.config.mjs
Normal 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",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
45
src/mongo/shell/debugger/vscode/extension.js
Normal file
45
src/mongo/shell/debugger/vscode/extension.js
Normal 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};
|
||||
39
src/mongo/shell/debugger/vscode/install.sh
Executable file
39
src/mongo/shell/debugger/vscode/install.sh
Executable 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
|
||||
83
src/mongo/shell/debugger/vscode/package.json
Normal file
83
src/mongo/shell/debugger/vscode/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
94
src/mongo/shell/debugger/vscode/session.js
Normal file
94
src/mongo/shell/debugger/vscode/session.js
Normal 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};
|
||||
Reference in New Issue
Block a user