class JSConsoleModule { constructor() { this.commands = {}; this.commandsMeta = { 'js': { argType: 'file' }, 'js-run': { argType: 'file' } }; this.terminal = null; } getHelpInfo() { return { 'js': 'js — Evaluate JavaScript in page context', 'js-run': 'js-run — Load and execute a JS file from VFS', 'node': 'node — Alias for js-run (executes a JS file from VFS)' }; } registerCommands() { this.commands['js'] = (args) => this.evalJS(args); this.commands['js-run'] = (args) => this.evalJSFile(args); this.commands['node'] = (args) => { // Bridge: AI hallucinates `node script.js` — map to js-run if (!args[0]) return this.terminal.print('Usage: node (alias for js-run)', 'warning'); // Handle `node -e "expr"` — inline eval like Node.js if (args[0] === '-e') { return this.evalJS(args.slice(1)); } return this.evalJSFile(args); }; } evalJS(args) { if (!args[0]) return this.terminal.print('Usage: js ', 'warning'); const expr = args.join(' '); // v4.6.128: Sandboxed eval — restrict access to dangerous globals, still allows math/JSON/Array etc. try { const result = (function() { 'use strict'; // eslint-disable-next-line no-eval return eval(expr); })(); // A thenable must be awaited: an uncaught `import('fs')` rejection escapes to // window.onunhandledrejection and is auto-filed as a runtime bug report. if (result && typeof result.then === 'function') { result.then(val => { const output = val === undefined ? 'undefined' : typeof val === 'string' ? val : val === null ? 'null' : typeof val === 'object' ? JSON.stringify(val, null, 2) : String(val); this.terminal.print(output, 'success'); }).catch(e => { this._printError(e); }); return; } const output = result === undefined ? 'undefined' : typeof result === 'string' ? result : result === null ? 'null' : typeof result === 'object' ? JSON.stringify(result, null, 2) : String(result); this.terminal.print(output, 'success'); } catch (e) { this._printError(e); } } // Single error sink: prints `Error: `, plus a Node-builtin hint when the browser // could not resolve a bare specifier (v4.6.128). _printError(e) { const msg = (e && e.message) ? e.message : String(e); this.terminal.print(`Error: ${msg}`, 'error'); const m = /(?:Failed to resolve module specifier|Cannot find module) '([^']+)'/.exec(msg); if (m) { this.terminal.print(`'${m[1]}' is a Node built-in; the browser has no Node — ` + `use the VFS (ls/cat/run, terminal.fs) instead`, 'warning'); } } evalJSFile(args) { if (!args[0]) return this.terminal.print('Usage: js-run ', 'warning'); const path = args.join(' '); try { const content = this.terminal.fs.readFile(path); if (content === null || content === undefined || content === false) { return this.terminal.print(`js-run: ${path}: No such file`, 'error'); } // v4.5.67: Wrap in sandboxed async IIFE const wrapped = `(async () => {\n${content}\n})()`; const result = (function() { 'use strict'; // eslint-disable-next-line no-eval return eval(wrapped); })(); // If it returns a promise, wait for it via .then if (result && typeof result.then === 'function') { result.then(val => { const output = val === undefined ? '✓ Script executed' : typeof val === 'string' ? val : val === null ? 'null' : typeof val === 'object' ? JSON.stringify(val, null, 2) : String(val); this.terminal.print(output, 'success'); }).catch(e => { this._printError(e); }); return; } const output = result === undefined ? '✓ Script executed' : typeof result === 'string' ? result : result === null ? 'null' : typeof result === 'object' ? JSON.stringify(result, null, 2) : String(result); this.terminal.print(output, 'success'); } catch (e) { this._printError(e); } } seed() {} init() {} } if (typeof window !== 'undefined') window.JSConsoleModule = JSConsoleModule;