How can I start a REPL that can access local variables in node.js?
Asked Answered
K

5

8

Just like from IPython import embed; embed() but for node.

I want to open a REPL shell programmatically and be able to at least read the local variables. Being able to change them as well is a plus.

Kennithkennon answered 16/5, 2020 at 11:0 Comment(4)
by local variables you mean environment variables?Inadvertence
@MarcosCasagrande No, I mean local javascript variables.Kennithkennon
Hi, I think you if you need JS repl- you need to just write node command only follow thisCodpiece
I submitted a working example for deno, if you want it for Node.js let me know.Inadvertence
S
2

You can build a REPL similar to the built-in Deno REPL and and evaluate expressions using the dangerous eval function. Through it you'll be able to access local variables and other things (e.g. window).

repl.ts

import { readLines, writeAll } from "https://deno.land/[email protected]/io/mod.ts";

export default async function repl(evaluate: (x: string) => unknown) {
  await writeOutput("exit using ctrl+d or close()\n");
  await writeOutput("> ");
  for await (const input of readInputs()) {
    try {
      const value = evaluate(input);
      const output = `${Deno.inspect(value, { colors: !Deno.noColor })}\n`;
      await writeOutput(output);
      await writeOutput("> ");
    } catch (error) {
      await writeError(error);
    }
  }
}

async function* readInputs(): AsyncIterableIterator<string> {
  yield* readLines(Deno.stdin);
}

async function writeOutput(output: string) {
  await writeAll(Deno.stdout, new TextEncoder().encode(output));
}

async function writeError(error: unknown) {
  await writeAll(Deno.stderr, new TextEncoder().encode(`Uncaught ${error}\n`));
}

repl_demo.ts

import repl from "./repl.ts";

let a = 1;
let b = 2;
let c = 3;

await repl((x) => eval(x));

example usage

% deno run repl_demo.ts
exit using ctrl+d or close()
> a
1
> a = 40
40
> a + b
42
Syllabism answered 29/8, 2021 at 23:39 Comment(0)
L
4

As far I know, the closest you can get is by using the repl built-in module (which is used by node inspect itself):

// ... your code you want to debug 

const repl = require("repl");
const replServer = repl.start({
    prompt: "Your Own Repl > ",
    useGlobal: true
});

// Expose variables
const localVar = 42    
replServer.context.localVar = localVar;

By running node index.js (assuming you saved the above content in index.js) we get access to this custom repl:

$ node index.js 
Your Own Repl > localVar
42
Your Own Repl > 
(To exit, press Ctrl+C again or Ctrl+D or type .exit)
Your Own Repl > 

However, this does not work like a debugger tool, but really, it's only a REPL.

London answered 31/8, 2021 at 10:23 Comment(2)
How do we automatically expose all local vars in the scope? Also, I think this method doesn't allow changing the local variables and then resuming the execution.Kennithkennon
@Kennithkennon You will have to expose them, or create a utility function to expose them in an easier way. Indeed, this will not pause the execution of the script – for that you'd need to start a node inspect process with debuggers or breakpoints... I am just wondering why such a python-like utility would be needed in the first place – maybe we could find better alternatives. The command line Node.js debugger is quite good, and the IDEs have visual debugging interfaces too...Overbold
I
2

For deno (Title says Node.js, tag deno) you can use Deno.run to execute deno and write to stdin and read from stdout.

The following will do:

const p = Deno.run({
    cmd: ["deno"],
    stdin: "piped",
    stdout: "piped",
    stderr: "piped"
  });

async function read(waitForMessage) {
    const reader = Deno.iter(p.stdout)
    let res = '';
    for await(const chunk of reader) {      
        res += new TextDecoder().decode(chunk);
        console.log('Chunk', res, '---')
        // improve this, you should wait until the last chunk 
        // is read in case of a command resulting in a big output
        if(!waitForMessage)
            return res;
        else if(res.includes(waitForMessage))
            return res;
    }
}

async function writeCommand(command) {
    const msg = new TextEncoder().encode(command + '\n'); 

    console.log('Command: ', command)
    const readPromise = read();
    // write command
    await p.stdin.write(msg);
    // Wait for output
    const value = await readPromise

    return value;
}

// Wait for initial output: 
// Deno 1.0.0
// exit using ctrl+d or close()
await read('ctrl+d or close()');


await writeCommand('let x = 5;')
let value = await writeCommand('x') // read x
console.log('Value: ', value)

await writeCommand('x = 6;')
value = await writeCommand('x') // read x
console.log('Value: ', value)

If you run that snippet, the output will be:

Command: let x = 5;
Command: x
Value: 5

Command:  x = 6;
Command:  x
Value:  6

There are some improvements to be made, such as handling stderr but you get the idea.

Inadvertence answered 16/5, 2020 at 12:1 Comment(4)
Thanks! I like this for other purposes than my question, but I don't think it answers my question; I want to use the REPL as a debugging aid. I need it to open in the middle of the normal javascript execution, not as a standalone process. I need the REPL to be able to access the local variables of the scope that runs it.Kennithkennon
You want a debugger then.Inadvertence
I want an embeddable REPL that can also be used as a debugger. Is there such a tool available?Kennithkennon
There isn't such tool available, you can check Deno debuggerInadvertence
S
2

This feature doesn't currently exist but is being proposed in https://github.com/denoland/deno/issues/7938.

The vision being something along the lines of

Deno.eval("Deno.repl()")
Sharpset answered 26/8, 2021 at 1:23 Comment(0)
S
2

You can build a REPL similar to the built-in Deno REPL and and evaluate expressions using the dangerous eval function. Through it you'll be able to access local variables and other things (e.g. window).

repl.ts

import { readLines, writeAll } from "https://deno.land/[email protected]/io/mod.ts";

export default async function repl(evaluate: (x: string) => unknown) {
  await writeOutput("exit using ctrl+d or close()\n");
  await writeOutput("> ");
  for await (const input of readInputs()) {
    try {
      const value = evaluate(input);
      const output = `${Deno.inspect(value, { colors: !Deno.noColor })}\n`;
      await writeOutput(output);
      await writeOutput("> ");
    } catch (error) {
      await writeError(error);
    }
  }
}

async function* readInputs(): AsyncIterableIterator<string> {
  yield* readLines(Deno.stdin);
}

async function writeOutput(output: string) {
  await writeAll(Deno.stdout, new TextEncoder().encode(output));
}

async function writeError(error: unknown) {
  await writeAll(Deno.stderr, new TextEncoder().encode(`Uncaught ${error}\n`));
}

repl_demo.ts

import repl from "./repl.ts";

let a = 1;
let b = 2;
let c = 3;

await repl((x) => eval(x));

example usage

% deno run repl_demo.ts
exit using ctrl+d or close()
> a
1
> a = 40
40
> a + b
42
Syllabism answered 29/8, 2021 at 23:39 Comment(0)
J
0
const readline = require('readline');

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});
//to be sure this context is here
const ev = eval.bind(this);
function ask() {
     rl.question('>', (code) => {
           console.log(ev(code));
           ask();
     });
} 
ask();

this code ask a input with readLine module and every time a reponse is provided the code is executed and a new input is askef

Johnnie answered 1/9, 2021 at 13:1 Comment(1)
Please edit your answer to provide more information as to how the code you've provided works, what you have changed, etc. Code-only answers might solve the problem of the original asker but they don't help future readers understand the solution.Steroid

© 2022 - 2024 — McMap. All rights reserved.