Skip to content

Commit

Permalink
1.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
jhmaster2000 committed Aug 11, 2022
1 parent 40712ff commit c2338df
Show file tree
Hide file tree
Showing 13 changed files with 188 additions and 176 deletions.
14 changes: 3 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@ Pass the `-h` or `--help` CLI option for a list of all options.

Type `.help` within the REPL for a list of commands.

Press ``+`Enter` to travel up the execution history.

Press ``+`Enter` to travel back down.
Press `` and `` to travel up or down the execution history.

## Features

Expand All @@ -42,14 +40,8 @@ PRs are welcome to help fix any of the items below or anything else.
* Top level await is not supported.
* Reason: Usage of `eval()`
* Multi-line inputs are not supported.
* Reason: Same as below.
* Pressing `` and `` to traverse the input with the cursor is not supported.
* Reason: Same as below.
* The execution history is buggy to navigate and doesn't support backspacing past the history entry.
* Reason: Bun's current lack of support for event-based streams limits us to `prompt()`, which provides little to no control over the input as its being written. This also makes it impossible to intercept keypresses/combinations.
* There is a space between the execution history entry and your appended code.
* Reason: `prompt()` automatically inserts a space at the end, with no way to turn it off. Yes, quite annoying.
* To preserve lexically-scoped variables (`let` & `const`) across REPL runs, they need to be converted to `var`, which disrupts their behavior, especially `const`'s
* Reason: The library used for prompts (`rustybun`) doesn't support this.
* To preserve lexically-scoped variables (`let` & `const`) across REPL runs, they need to be converted to `var`, which disrupts their behavior, especially `const`'s (This also requires using non-strict mode)
* Reason: Usage of `eval()` which has its own lexical scope.

[github-url]:https://github.com/jhmaster2000/bun-repl
Expand Down
Binary file modified bun.lockb
Binary file not shown.
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"type": "module",
"name": "bun-repl",
"version": "1.0.3",
"version": "1.1.0",
"description": "Experimental unofficial REPL for Bun",
"main": "src/module/repl.ts",
"scripts": {
Expand All @@ -25,7 +25,8 @@
"keywords": ["bun", "repl", "cli", "ts", "js"],
"dependencies": {
"@swc/core": "^1.2.224",
"pretty-ms": "^8.0.0"
"pretty-ms": "^8.0.0",
"rustybun": "^0.1.2"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.32.0",
Expand Down
7 changes: 6 additions & 1 deletion src/colors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,9 @@ if (!Bun.enableANSIColors) {
//for (const color in colors) console.log(colors[color as keyof typeof colors], color, colors.reset);
//for (const color in bgColors) console.log(bgColors[color as keyof typeof bgColors], color, colors.reset);

export default { ...colors, bg: bgColors };
function bool(bool: boolean, important: boolean = false) {
if (bool) return `${colors.greenBright}true${colors.reset}`;
else return `${important ? colors.redBright : colors.gray}false${colors.reset}`;
}

export default { ...colors, bg: bgColors, bool };
6 changes: 6 additions & 0 deletions src/extendglobals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ declare global {
}
}

interface PartialNPMResponse {
'dist-tags': {
latest: string
}
}

interface ImportMeta {
require: (moduleIdentifier: string) => unknown;
}
Expand Down
30 changes: 16 additions & 14 deletions src/module/repl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
// Polyfill of the Node.js "repl" module (WIP)

import $ from '../colors';
import util from 'util';
import bun from 'bun';
import { SafeInspect } from '../utils';

class NotImplementedError extends Error {
constructor(method: string) {
Expand Down Expand Up @@ -56,7 +57,7 @@ module repl {
const REPLWriterOptionsDefaults = {
showHidden: false,
depth: 2,
colors: Bun.enableANSIColors,
colors: bun.enableANSIColors,
customInspect: true,
showProxy: true,
maxArrayLength: 100,
Expand All @@ -72,19 +73,20 @@ module repl {
options: typeof REPLWriterOptionsDefaults;
}

export const writer: REPLWriterFunction = function writer(val: any): string {
return util.inspect(val, (<REPLWriterFunction>writer).options);
};
writer.options = new Proxy(REPLWriterOptionsDefaults, {
set(target, p, value) {
console.warn(`${$.yellow+$.dim}(warning) repl.writer.options are currently not all fully respected by the REPL.${$.reset}`);
// @ts-expect-error Temporary warning Proxy
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
target[p] = value;
return true;
}
export const writer = function writer(val: any): string {
return (SafeInspect(val, (<REPLWriterFunction>writer).options) ?? bun.inspect(val));
} as REPLWriterFunction;
Object.defineProperty(writer, 'options', {
value: new Proxy(REPLWriterOptionsDefaults, {
set(target, p, value) {
console.warn(`${$.yellow+$.dim}(warning) repl.writer.options are currently not all fully respected by the REPL.${$.reset}`);
// @ts-expect-error Temporary warning Proxy
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
target[p] = value;
return true;
}
}), enumerable: true
});

}

// For verifying all builtin modules
Expand Down
56 changes: 26 additions & 30 deletions src/realm.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,34 +12,18 @@

import './extendglobals';
import $ from './colors';
import bun from 'bun';
import path from 'path';
import swcrc from './swcrc';
import { debuglog } from './debug';
import { SafeGet, SafeCall, SafeInspect } from './utils';
import { SafeGet, SafeThiscall, SafeInspect } from './utils';
import REPL, { bunPrefixedModules, nodePrefixedModules } from './module/repl';

/** Patch require so it works in a REPL context (use CWD instead of import.meta.dir for resolution) */
globalThis.require = (/** @type {string} */ moduleID) => {
if (bunPrefixedModules.includes(moduleID)) throw { name: 'ResolveError', toString() { return `ResolveError: Builtin Bun module requires "bun:" prefix`; } };
if (nodePrefixedModules.includes(moduleID)) moduleID = `node:${moduleID}`;
if (moduleID[0] === '.' || moduleID[0] === '/') {
moduleID = path.resolve(process.cwd(), moduleID);
}
debuglog((`${$.dim}Importing: ${$.blueBright+moduleID+$.reset}`));
return import.meta.require(moduleID);
};

Object.defineProperties(globalThis, {
/** Stores the last REPL result. */
_: {
value: undefined,
writable: true
},
_: { value: undefined, writable: true },
/** Stores the last REPL error thrown. */
_error: {
value: undefined,
writable: true
}
_error: { value: undefined, writable: true }
});

// By managing our own secret "global" object hidden away in an obscure constructor
Expand All @@ -60,24 +44,24 @@ Object.defineProperties(REPLGlobal, {
value: function replFormat(/** @type {any} */ val, /** @type {boolean=} */ isError = false) {
try {
if (SafeGet(val, 'name') === 'ResolveError')
return [`${$.red}ResolveError${$.reset}${$.dim}: ${$.reset}${$.whiteBright}${SafeCall(
return [`${$.red}ResolveError${$.reset}${$.dim}: ${$.reset}${$.whiteBright}${SafeThiscall(
REPLGlobal.StringReplace, SafeGet(val, 'message'), / ".+" from ".+"$/,
` ${$.blueBright}"${SafeGet(val, 'specifier') ?? '<unresolved>'}"${$.whiteBright} from ${$.cyan+process.cwd()}/${swcrc.filename}`
` ${$.blueBright}"${SafeGet(val, 'specifier') ?? '<unresolved>'}"${$.whiteBright} from ${$.cyan+REPLGlobal.process.cwd()}/${swcrc.filename}`
) ?? SafeGet(val, 'message') ?? val+''}${$.reset}`, null];
if (val instanceof REPLGlobal.Error) return [$.red + (
`${$.red}${SafeGet(val, 'name') ?? 'Error (anonymous)'}${$.reset+$.dim}: ${$.reset+$.whiteBright}` +
`${SafeGet(val, 'message') ?? ''}\n${$.reset+$.dim} at ${$.reset+$.cyan}` +
`${path.join(REPLGlobal.process.cwd(), swcrc.filename ?? '(anonymous)')}${$.reset+$.dim}:${$.reset+$.yellow}` +
`${SafeGet(val, 'line') || 0}${$.reset+$.dim}:${$.yellow}${(SafeGet(val, 'column') || 1) - 1}`
) + $.reset, null];
if (isError) return [`${$.red}Uncaught ${$.whiteBright}${REPLGlobal.Bun.inspect(val)}${$.reset}`, null];
if (typeof val === 'string') return [`${$.green}'${ SafeCall(REPLGlobal.StringReplace, val, /'/g, "\\'")}'${$.reset}`, null];
if (typeof val === 'function' && SafeCall(REPLGlobal.StringSlice, val+'', 0, 5) === 'class') {
const inspected = SafeInspect(val, REPLGlobal.REPL.writer.options) ?? REPLGlobal.Bun.inspect(val);
return [SafeCall(REPLGlobal.StringReplace, inspected, '[Function: ', '[class ') ?? inspected, null];
if (isError) return [`${$.red}Uncaught ${$.whiteBright}${bun.inspect(val)}${$.reset}`, null];
if (typeof val === 'string') return [`${$.green}'${ SafeThiscall(REPLGlobal.StringReplace, val, /'/g, "\\'")}'${$.reset}`, null];
if (typeof val === 'function' && SafeThiscall(REPLGlobal.StringSlice, val+'', 0, 5) === 'class') {
const inspected = SafeInspect(val, REPLGlobal.REPL.writer.options) ?? bun.inspect(val);
return [SafeThiscall(REPLGlobal.StringReplace, inspected, '[Function: ', '[class ') ?? inspected, null];
}
if (
typeof val === 'object' && SafeCall(REPLGlobal.ObjectToString, val) === '[object Object]' ||
typeof val === 'object' && SafeThiscall(REPLGlobal.ObjectToString, val) === '[object Object]' ||
SafeGet(val, REPLGlobal.Symbol.toStringTag) === 'Module'
) return [SafeInspect(val, REPLGlobal.REPL.writer.options) ?? val, null];
return [val, null]; // Delegate formatting to console.log since Bun.inspect won't output colors
Expand All @@ -87,7 +71,6 @@ Object.defineProperties(REPLGlobal, {
}
},
REPL: { value: REPL },
Bun: { value: Bun },
temp: { value: {} },
eval: { value: globalThis.eval },
global: { value: globalThis },
Expand All @@ -97,13 +80,13 @@ Object.defineProperties(REPLGlobal, {
process: { value: { ...process } },
StringSlice: { value: String.prototype.slice },
StringReplace: { value: String.prototype.replace },
ArrayIncludes: { value: Array.prototype.includes },
ObjectToString: { value: Object.prototype.toString },
});

/**
* @typedef REPLGlobal
* @property {typeof REPL} REPL
* @property {typeof Bun} Bun
* @property {typeof globalThis} global
* @property {typeof eval} eval
* @property {Record<any, any>} temp
Expand All @@ -114,5 +97,18 @@ Object.defineProperties(REPLGlobal, {
* @property {SymbolConstructor} Symbol
* @property {typeof String.prototype.slice} StringSlice
* @property {(find: string | RegExp, value: string) => string} StringReplace
* @property {typeof Array.prototype.includes} ArrayIncludes
* @property {typeof Object.prototype.toString} ObjectToString
*/

/** Patch require so it works in a REPL context (use `process.cwd()` instead of `import.meta.dir` for resolution) */
// @ts-expect-error Intellisense keeps conflicting with @types/node here even though its not included
globalThis.require = (/** @type {string} */ moduleID) => {
if (SafeThiscall(REPLGlobal.ArrayIncludes, bunPrefixedModules, moduleID)) throw { name: 'ResolveError', message: `Built-in Bun module should have "bun:" prefix` };
if (SafeThiscall(REPLGlobal.ArrayIncludes, nodePrefixedModules, moduleID)) moduleID = `node:${moduleID}`;
if (moduleID[0] === '.' || moduleID[0] === '/') {
moduleID = path.resolve(REPLGlobal.process.cwd(), moduleID);
}
debuglog((`${$.dim}Importing: ${$.blueBright+moduleID+$.reset}`));
return import.meta.require(moduleID);
};
Loading

0 comments on commit c2338df

Please sign in to comment.