Fixed CLI sandbox quiting after prompt entry.
This commit is contained in:
parent
b29510e363
commit
ff9bc2a282
@ -7,6 +7,7 @@
|
|||||||
"ethers-ts": "./lib/bin/ethers-ts.js"
|
"ethers-ts": "./lib/bin/ethers-ts.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@babel/parser": "7.8.4",
|
||||||
"@ethersproject/asm": ">=5.0.0-beta.148",
|
"@ethersproject/asm": ">=5.0.0-beta.148",
|
||||||
"@ethersproject/basex": ">=5.0.0-beta.127",
|
"@ethersproject/basex": ">=5.0.0-beta.127",
|
||||||
"ethers": ">=5.0.0-beta.156",
|
"ethers": ">=5.0.0-beta.156",
|
||||||
|
@ -6,15 +6,23 @@ import fs from "fs";
|
|||||||
import _module from "module";
|
import _module from "module";
|
||||||
import { dirname, resolve } from "path";
|
import { dirname, resolve } from "path";
|
||||||
import REPL from "repl";
|
import REPL from "repl";
|
||||||
import util from "util";
|
|
||||||
import vm from "vm";
|
import vm from "vm";
|
||||||
|
|
||||||
import { ethers } from "ethers";
|
import { ethers } from "ethers";
|
||||||
|
|
||||||
|
import { parseExpression as babelParseExpression } from "@babel/parser";
|
||||||
|
|
||||||
import { ArgParser, CLI, dump, Help, Plugin } from "../cli";
|
import { ArgParser, CLI, dump, Help, Plugin } from "../cli";
|
||||||
import { getPassword, getProgressBar } from "../prompt";
|
import { getPassword, getProgressBar } from "../prompt";
|
||||||
import { compile, ContractCode, customRequire } from "../solc";
|
import { compile, ContractCode, customRequire } from "../solc";
|
||||||
|
|
||||||
|
function repeat(c: string, length: number): string {
|
||||||
|
if (c.length === 0) { throw new Error("too short"); }
|
||||||
|
let result = c;
|
||||||
|
while (result.length < length) { result += result; }
|
||||||
|
return result.substring(0, length);
|
||||||
|
}
|
||||||
|
|
||||||
function setupContext(path: string, context: any, plugin: Plugin) {
|
function setupContext(path: string, context: any, plugin: Plugin) {
|
||||||
|
|
||||||
context.provider = plugin.provider;
|
context.provider = plugin.provider;
|
||||||
@ -24,7 +32,6 @@ function setupContext(path: string, context: any, plugin: Plugin) {
|
|||||||
if (!context.__dirname) { context.__dirname = dirname(path); }
|
if (!context.__dirname) { context.__dirname = dirname(path); }
|
||||||
if (!context.console) { context.console = console; }
|
if (!context.console) { context.console = console; }
|
||||||
if (!context.require) {
|
if (!context.require) {
|
||||||
//context.require = _module.createRequireFromPath(path);
|
|
||||||
context.require = customRequire(path);
|
context.require = customRequire(path);
|
||||||
}
|
}
|
||||||
if (!context.process) { context.process = process; }
|
if (!context.process) { context.process = process; }
|
||||||
@ -78,6 +85,42 @@ function setupContext(path: string, context: any, plugin: Plugin) {
|
|||||||
|
|
||||||
const cli = new CLI("sandbox");
|
const cli = new CLI("sandbox");
|
||||||
|
|
||||||
|
function prepareCode(code: string): string {
|
||||||
|
let ast = babelParseExpression(code, {
|
||||||
|
createParenthesizedExpressions: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// Crawl the AST, to compute needed source code manipulations
|
||||||
|
const insert: Array<{ char: string, offset: number }> = [];
|
||||||
|
const descend = function(node: any) {
|
||||||
|
if (node == null || typeof(node) !== "object") { return; }
|
||||||
|
if (Array.isArray(node)) {
|
||||||
|
return node.forEach(descend);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We will add parenthesis around ObjectExpressions, which
|
||||||
|
// otherwise look like blocks
|
||||||
|
if (node.type === "ObjectExpression") {
|
||||||
|
insert.push({ char: "(", offset: node.start });
|
||||||
|
insert.push({ char: ")", offset: node.end });
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys(node).forEach((key) => descend(key));
|
||||||
|
}
|
||||||
|
descend(ast);
|
||||||
|
|
||||||
|
// We make modifications from back to front, so we don't need
|
||||||
|
// to adjust offsets
|
||||||
|
insert.sort((a, b) => (b.offset - a.offset));
|
||||||
|
|
||||||
|
// Modify the code for REPL
|
||||||
|
insert.forEach((mod) => {
|
||||||
|
code = code.substring(0, mod.offset) + mod.char + code.substring(mod.offset);
|
||||||
|
});
|
||||||
|
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
class SandboxPlugin extends Plugin {
|
class SandboxPlugin extends Plugin {
|
||||||
static getHelp(): Help {
|
static getHelp(): Help {
|
||||||
return {
|
return {
|
||||||
@ -103,35 +146,62 @@ class SandboxPlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
run(): Promise<void> {
|
run(): Promise<void> {
|
||||||
console.log("network: " + this.network.name + " (chainId: " + this.network.chainId + ")");
|
console.log(`version: ${ ethers.version }`);
|
||||||
|
console.log(`network: ${ this.network.name } (chainId: ${ this.network.chainId })`);
|
||||||
|
|
||||||
let nextPromiseId = 0;
|
const filename = resolve(process.cwd(), "./sandbox.js");
|
||||||
function promiseWriter(output: any): string {
|
const prompt = (this.provider ? this.network.name: "no-network") + "> ";
|
||||||
if (output instanceof Promise) {
|
|
||||||
repl.context._p = output;
|
const evaluate = function(code: string, context: any, file: any, _callback: (error: Error, result?: any) => void) {
|
||||||
let promiseId = nextPromiseId++;
|
// Pausing the stdin (which prompt does when it leaves), causes
|
||||||
output.then((result) => {
|
// readline to end us. So, we always re-enable stdin on a result
|
||||||
console.log(`\n<Promise id=${promiseId} resolved>`);
|
const callback = (error: Error, result?: any) => {
|
||||||
console.log(util.inspect(result));
|
_callback(error, result);
|
||||||
repl.context._r = result;
|
process.stdin.resume();
|
||||||
repl.displayPrompt(true)
|
};
|
||||||
}, (error) => {
|
|
||||||
console.log(`\n<Promise id=${promiseId} rejected>`);
|
try {
|
||||||
console.log(util.inspect(error));
|
code = prepareCode(code);
|
||||||
repl.displayPrompt(true)
|
} catch (error) {
|
||||||
});
|
if (error instanceof SyntaxError) {
|
||||||
return `<Promise id=${promiseId} pending>`;
|
const leftover = code.substring((<any>error).pos);
|
||||||
|
const loc: { line: number, column: number } = (<any>error).loc;
|
||||||
|
if (leftover.trim()) {
|
||||||
|
// After the first line, the prompt is "... "
|
||||||
|
console.log(repeat("-", ((loc.line === 1) ? prompt.length: 4) + loc.column - 1) + "^");
|
||||||
|
console.log(`Syntax Error! ${ error.message }`);
|
||||||
|
} else {
|
||||||
|
error = new REPL.Recoverable(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return callback(error);
|
||||||
}
|
}
|
||||||
return util.inspect(output);
|
|
||||||
}
|
|
||||||
|
|
||||||
let repl = REPL.start({
|
try {
|
||||||
input: process.stdin,
|
const result = vm.runInContext(code, context, {
|
||||||
output: process.stdout,
|
filename: filename
|
||||||
prompt: (this.provider ? this.network.name: "no-network") + "> ",
|
});
|
||||||
writer: promiseWriter
|
|
||||||
|
if (result instanceof Promise) {
|
||||||
|
result.then((result) => {
|
||||||
|
callback(null, result);
|
||||||
|
}, (error) => {
|
||||||
|
callback(error);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
callback(null, result);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
callback(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const repl = REPL.start({
|
||||||
|
prompt: prompt,
|
||||||
|
eval: evaluate
|
||||||
});
|
});
|
||||||
setupContext(resolve(process.cwd(), "./sandbox.js"), repl.context, this);
|
|
||||||
|
setupContext(filename, repl.context, this);
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
repl.on("exit", function() {
|
repl.on("exit", function() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user