on master help sign in
Use custom context when running scripts
commit49753ee7d2b3412563664d79edff98b774520f68 authormasfrost <farismasad@gmail.com> dateThu 4 Nov 2021, 16:51 (5 years ago) parent7c2024e
1 file changed+148−46
languages/nodejs/repl.js+148−46
languages/nodejs/repl.js+148−46
@@ -4,7 +4,8 @@ const path = require('path');
44const fs = require('fs');
55const vm = require('vm');
66const { isatty } = require('tty');
7−const assets_dir = process.env.PRYBAR_ASSETS_DIR || path.join(process.cwd(), 'prybar_assets');
7+const assets_dir =
8+ process.env.PRYBAR_ASSETS_DIR || path.join(process.cwd(), 'prybar_assets');
89const rl = require(path.join(assets_dir, 'nodejs', 'input-sync.js'));
910const Module = require('module');
1011
@@ -52,7 +53,9 @@ function handleError(e) {
5253
5354 if (e && typeof e === 'object' && e.stack && e.name) {
5455 if (e.name === 'SyntaxError') {
55− e.stack = e.stack.replace(/^repl:\d+\r?\n/, '').replace(/^\s+at\s.*\n?/gm, '');
56+ e.stack = e.stack
57+ .replace(/^repl:\d+\r?\n/, '')
58+ .replace(/^\s+at\s.*\n?/gm, '');
5659 }
5760
5861 logError(e.stack);
@@ -68,69 +71,159 @@ function handleError(e) {
6871 }
6972}
7073
71−function startRepl() {
72− r = repl.start({
73− prompt: process.env.PRYBAR_PS1,
74− useGlobal: true,
75− });
76−
77− // remove the internal error and ours for red etc.
78− r._domain.removeListener('error', r._domain.listeners('error')[0]);
79− r._domain.on('error', handleError);
80− process.on('uncaughtException', handleError);
81−}
74+const sandbox = {
75+ // The console in the context doesn't bind to our stdout
76+ console,
77+ // Browser stdio polyfills
78+ alert: console.log,
79+ prompt: (p) => {
80+ pauseRepl();
81+ clearLine();
8282
83−global.alert = console.log;
84−global.prompt = (p) => {
85− pauseRepl();
86− clearLine();
83+ let ret = rl.question(`${p}> `);
8784
88− let ret = rl.question(`${p}> `);
85+ resumeRepl();
8986
90− resumeRepl();
87+ // Display prompt on the next turn.
88+ if (r) setImmediate(() => r.displayPrompt());
9189
92− // Display prompt on the next turn.
93− if (r) setImmediate(() => r.displayPrompt());
90+ return ret;
91+ },
92+ confirm: (q) => {
93+ pauseRepl();
94+ clearLine();
9495
95− return ret;
96−};
97−
98−global.confirm = (q) => {
99− pauseRepl();
100− clearLine();
101−
102− const ret = rl.keyInYNStrict(q);
96+ const ret = rl.keyInYNStrict(q);
10397
104− resumeRepl();
98+ resumeRepl();
10599
106− // Display prompt on the next turn.
107− if (r) setImmediate(() => r.displayPrompt());
108− return ret;
100+ // Display prompt on the next turn.
101+ if (r) setImmediate(() => r.displayPrompt());
102+ return ret;
103+ },
109104};
110105
111−if (process.env.PRYBAR_FILE) {
112− const mainPath = path.resolve(process.env.PRYBAR_FILE);
113− const main = fs.readFileSync(mainPath, 'utf-8');
114− const module = new Module(mainPath, null);
106+const mainPath = process.env.PRYBAR_FILE
107+ ? path.resolve(process.env.PRYBAR_FILE)
108+ : null;
115109
110+if (mainPath) {
111+ // Make module resolution stuff work as if we're running
112+ // `node mainPath`
113+ const module = new Module(mainPath, null);
116114 module.id = '.';
117115 module.filename = mainPath;
118116 module.paths = Module._nodeModulePaths(path.dirname(mainPath));
119117
120118 process.mainModule = module;
121119
122− global.module = module;
123− global.require = module.require.bind(module);
124− global.__dirname = path.dirname(mainPath);
125− global.__filename = mainPath;
120+ sandbox.module = module;
121+ sandbox.require = module.require.bind(module);
122+ sandbox.__dirname = path.dirname(mainPath);
123+ sandbox.__filename = mainPath;
124+}
126125
127− if (isTTY) {
128− console.log('\u001b[0m\u001b[90mHint: hit control+c anytime to enter REPL.\u001b[0m');
126+const context = vm.createContext(sandbox);
127+// VM context doesn't have a built-in "global", so we
128+// have to tell it to point to itself
129+Object.defineProperties(context, {
130+ global: {
131+ ...Object.getOwnPropertyDescriptor(global, 'global'),
132+ value: context,
133+ },
134+ globalThis: {
135+ ...Object.getOwnPropertyDescriptor(global, 'globalThis'),
136+ value: context,
137+ },
138+ GLOBAL: {
139+ ...Object.getOwnPropertyDescriptor(global, 'GLOBAL'),
140+ get: util.deprecate(
141+ () => context,
142+ `'GLOBAL' is deprecated, use 'global'`,
143+ 'DEP0016',
144+ ),
145+ set: util.deprecate(
146+ (value) => {
147+ Object.defineProperty(context, 'GLOBAL', {
148+ configurable: true,
149+ writable: true,
150+ enumerable: true,
151+ value: value,
152+ });
153+ },
154+ `'GLOBAL' is deprecated, use 'global'`,
155+ 'DEP0016',
156+ ),
157+ },
158+ root: {
159+ ...Object.getOwnPropertyDescriptor(global, 'root'),
160+ get: util.deprecate(
161+ () => context,
162+ `'root' is deprecated, use 'global'`,
163+ 'DEP0016',
164+ ),
165+ set: util.deprecate(
166+ (value) => {
167+ Object.defineProperty(context, 'root', {
168+ configurable: true,
169+ writable: true,
170+ enumerable: true,
171+ value: value,
172+ });
173+ },
174+ `'root' is deprecated, use 'global'`,
175+ 'DEP0016',
176+ ),
177+ },
178+});
179+
180+// Fill in all the missing globals in the context.
181+// We have to activate the runtime to actually get
182+// a list of globals available on this context
183+const contextGlobals = vm.runInContext(
184+ 'Object.getOwnPropertyNames(global)',
185+ context,
186+);
187+const localGlobals = Object.getOwnPropertyNames(global);
188+for (let prop of localGlobals) {
189+ if (contextGlobals.includes(prop)) {
190+ continue;
191+ }
192+
193+ Object.defineProperty(
194+ context,
195+ prop,
196+ Object.getOwnPropertyDescriptor(global, prop),
197+ );
198+}
199+
200+function startRepl() {
201+ r = repl.start({
202+ prompt: process.env.PRYBAR_PS1,
203+ });
204+
205+ const replContext = r.context;
206+ r.context = context;
207+
208+ if (!mainPath) {
209+ // Since we don't have a mainpath, we will use
210+ // module resolution that's built into the repl
211+ r.context.require = replContext.require;
212+ r.context.module = replContext.module;
129213 }
130214
215+ // remove the internal error and ours for red etc.
216+ r._domain.removeListener('error', r._domain.listeners('error')[0]);
217+ r._domain.on('error', handleError);
218+ process.on('uncaughtException', handleError);
219+}
220+
221+if (mainPath) {
222+ const main = fs.readFileSync(mainPath, 'utf-8');
223+
131224 let script;
132225 try {
133− script = vm.createScript(main, {
226+ script = new vm.Script(main, {
134227 filename: mainPath,
135228 displayErrors: false,
136229 });
@@ -141,7 +234,7 @@ if (process.env.PRYBAR_FILE) {
141234 if (script) {
142235 let res;
143236 try {
144− res = script.runInThisContext({
237+ res = script.runInContext(context, {
145238 displayErrors: false,
146239 });
147240 } catch (e) {
@@ -155,6 +248,12 @@ if (process.env.PRYBAR_FILE) {
155248 }
156249 }
157250
251+ if (isTTY && process.env.PRYBAR_INTERACTIVE) {
252+ console.log(
253+ '\u001b[0m\u001b[90mHint: hit control+c anytime to enter REPL.\u001b[0m',
254+ );
255+ }
256+
158257 if (process.env.PRYBAR_INTERACTIVE) {
159258 process.once('SIGINT', () => startRepl());
160259 process.once('beforeExit', () => startRepl());
@@ -163,7 +262,10 @@ if (process.env.PRYBAR_FILE) {
163262 const code = process.env.PRYBAR_CODE || process.env.PRYBAR_EXP;
164263
165264 if (code) {
166− const result = vm.runInThisContext(code);
265+ const result = vm.runInContext(code, context, {
266+ breakOnSigint: isTTY || process.env.PRYBAR_INTERACTIVE,
267+ });
268+
167269 if (process.env.PRYBAR_EXP) {
168270 console.log(result);
169271 }