Comparing version 2.0.4 to 2.0.5
@@ -62,3 +62,4 @@ { | ||
"String": false, | ||
"Function": false | ||
"Function": false, | ||
"Object": false | ||
}, | ||
@@ -65,0 +66,0 @@ "extendDefaults": true |
{ | ||
"type": "module", | ||
"name": "bun-repl", | ||
"version": "2.0.4", | ||
"version": "2.0.5", | ||
"description": "Experimental REPL for Bun", | ||
@@ -6,0 +6,0 @@ "main": "src/module/repl.ts", |
108
src/repl.ts
import { type JSC } from 'bun-devtools'; | ||
import { join } from 'node:path'; | ||
import { join, dirname, basename, resolve as pathresolve } from 'node:path'; | ||
import os from 'node:os'; | ||
@@ -75,5 +75,7 @@ import readline from 'node:readline'; | ||
trace: GLOBAL.console.trace, | ||
assert: GLOBAL.console.assert, | ||
}; | ||
const IS_DEBUG = process.argv.includes('--debug'); | ||
const debuglog = IS_DEBUG ? (...args: string[]) => (console.debug($.dim+'DEBUG:', ...args, $.reset)) : () => void 0; | ||
//const SLOPPY_MODE = process.argv.includes('--sloppy'); | ||
@@ -174,6 +176,8 @@ type Primordial<T, M extends keyof T> = <S extends T>( | ||
// Bun.resolveSync takes a *directory* path for the parent. | ||
const { resolveSync } = import.meta; | ||
const { resolveSync } = Bun; | ||
const ArrayIncludes = Function.prototype.call.bind(Array.prototype.includes) as Primordial<Array<any>, 'includes'>; | ||
const nodePrefixedModules = repl.builtinModules | ||
.filter(x => x[0] !== '_' && !x.includes(':') && !['undici', 'ws', 'detect-libc', 'bun'].includes(x)); | ||
// eslint-disable-next-line unicorn/prefer-module | ||
const originalRequire = require; | ||
Object.defineProperty(GLOBAL, 'require', { value: function require(moduleID: string) { | ||
@@ -183,11 +187,10 @@ if (ArrayIncludes(nodePrefixedModules, moduleID)) moduleID = `node:${moduleID}`; | ||
if (moduleID === 'bun') return bun; // workaround | ||
const here = join(cwd(), swcrc.filename!); | ||
const here = cwd(); | ||
try { | ||
moduleID = resolveSync(moduleID, here); | ||
} catch { | ||
//Bun.gc(true); // attempt to mitigate the resolveSync segfault, doesn't fully prevent it but seems to make it a little less frequent | ||
// TODO: attempt to load the module from the global directory if it's not found in the local directory | ||
throw { | ||
name: 'ResolveError', | ||
message: `Cannot find module "${moduleID}" from "${here}"`, | ||
message: `Cannot find module "${moduleID}" from "${join(here, swcrc.filename!)}"`, | ||
specifier: moduleID | ||
@@ -208,2 +211,23 @@ }; | ||
} }); | ||
Object.defineProperties(GLOBAL.require, { | ||
resolve: { value: function resolve(moduleID: string, { paths = ['.'] } = {}) { | ||
if (paths.length === 0) paths.push('.'); | ||
const base = cwd(); | ||
for (const path of paths) { | ||
const from = pathresolve(base, path); | ||
try { | ||
return resolveSync(moduleID, from); | ||
} catch { continue; } | ||
} | ||
throw { | ||
name: 'ResolveError', | ||
message: `Cannot find module "${moduleID}"`, | ||
specifier: moduleID | ||
}; | ||
} }, | ||
main: { value: Reflect.get(originalRequire, 'main') as unknown }, | ||
extensions: { value: Reflect.get(originalRequire, 'extensions') as unknown }, | ||
cache: { value: Reflect.get(originalRequire, 'cache') as unknown }, | ||
}); | ||
GLOBAL.require.resolve.paths = function paths() { return []; }; // Bun's polyfill of this function is also like this for now | ||
const REQUIRE = GLOBAL.require; | ||
@@ -232,2 +256,11 @@ for (const module of nodePrefixedModules) { | ||
} | ||
Object.defineProperty(GLOBAL, '@@bunReplRuntimeHelpers', { | ||
value: Object.freeze({ | ||
join, dirname, basename, resolve: pathresolve, getcwd: process.cwd, | ||
pathToFileURL: Bun.pathToFileURL, fileURLToPath: Bun.fileURLToPath, | ||
StringStartsWith: Function.prototype.call.bind(String.prototype.startsWith), | ||
ObjectFreeze: Object.freeze, | ||
}), | ||
}); | ||
Object.defineProperty(GLOBAL, 'eval', { value: eval, configurable: false, writable: false }); // used by inlined import.meta trick | ||
Object.freeze(Promise); // must preserve .name property | ||
@@ -298,25 +331,42 @@ // eslint-disable-next-line @typescript-eslint/no-floating-promises | ||
debuglog(`transpiled code: ${code.trimEnd()}`); | ||
const { result, wasThrown } = await this.rawEval(code); | ||
const { result, wasThrown: thrown } = await this.rawEval(code); | ||
let remoteObj: EvalRemoteObject = result; | ||
remoteObj.wasThrown = thrown; | ||
switch (result.type) { | ||
case 'object': { | ||
if (result.subtype === 'null') break; | ||
if (!result.objectId) throw new EvalError(`Received non-null object without objectId: ${JSONStringify(result)}`); | ||
if (result.className === 'Promise') { | ||
if (!result.preview) throw new EvalError(`Received Promise object without preview: ${JSONStringify(result)}}`); | ||
if (topLevelAwaited) { | ||
const awaited = await this.request('Runtime.awaitPromise', { promiseObjectId: result.objectId, generatePreview: false }); | ||
remoteObj = awaited.result; | ||
if (result.type === 'object') ObjectTypeHandler: { | ||
if (result.subtype === 'null') break ObjectTypeHandler; | ||
if (!result.objectId) throw new EvalError(`Received non-null object without objectId: ${JSONStringify(result)}`); | ||
if (result.className === 'Promise') { | ||
if (!result.preview) throw new EvalError(`Received Promise object without preview: ${JSONStringify(result)}}`); | ||
if (topLevelAwaited) { | ||
const awaited = await this.request('Runtime.awaitPromise', { promiseObjectId: result.objectId, generatePreview: false }); | ||
remoteObj = awaited.result; | ||
remoteObj.wasThrown = awaited.wasThrown; | ||
if (remoteObj.type === 'undefined') { | ||
console.warn( | ||
$.yellow+$.dim+'[!] REPL top-level await is still very experimental and prone to failing on complex code snippets.\n' + | ||
' You are seeing this because top-level await usage was detected with undefined as the result, if that was expected, you can ignore this.'+$.reset | ||
); | ||
} | ||
if (result.preview.properties?.find(p => p.name === 'status')?.value === 'rejected') { | ||
remoteObj.wasRejectedPromise = true; | ||
} | ||
break; | ||
} | ||
break; | ||
if (result.preview.properties?.find(p => p.name === 'status')?.value === 'rejected') { | ||
remoteObj.wasRejectedPromise = true; | ||
} | ||
break ObjectTypeHandler; | ||
} | ||
default: break; | ||
} | ||
//! bug workaround, bigints are being passed as undefined to Runtime.callFunctionOn (?) | ||
if (remoteObj.type === 'bigint') { | ||
if (!remoteObj.description) throw new EvalError(`Received BigInt value without description: ${JSONStringify(remoteObj)}`); | ||
const value = BigInt(remoteObj.description.slice(0, -1)); | ||
Reflect.set(GLOBAL, remoteObj.wasThrown ? '_error' : '_', value); | ||
return (remoteObj.wasThrown ? $.red + 'Uncaught ' + $.reset : '') + SafeInspect(value, | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access | ||
Reflect.get(GLOBAL, 'repl')?.writer?.options as utl.InspectOptions | ||
?? { colors: Bun.enableANSIColors, showProxy: true } | ||
); | ||
} | ||
const { wasThrown } = remoteObj; | ||
const REPL_INTERNALS = '@@bunReplInternals'; | ||
@@ -403,2 +453,10 @@ Object.defineProperty(GLOBAL, REPL_INTERNALS, { | ||
async function tryGetPackageVersion() { | ||
try { | ||
return 'v' + (await import('../package.json')).default.version; | ||
} catch { | ||
return $.dim+$.redBright+'failed to retrieve version'+$.reset; | ||
} | ||
} | ||
export default { | ||
@@ -444,3 +502,3 @@ async start(singleshotCode?: string, printSingleshot = false) { | ||
else if (printSingleshot) console.log(evaled); | ||
} | ||
} else if (!extraInfo.errored && printSingleshot) console.log(evaled); | ||
return exit(0); | ||
@@ -475,2 +533,5 @@ } | ||
console.log(`Welcome to Bun v${Bun.version}\nType ".help" for more information.`); | ||
console.warn( | ||
`${$.yellow+$.dim}[!] Please note that the REPL implementation is still experimental!\n` + | ||
` Don't consider it to be representative of the stability or behavior of Bun overall.${$.reset}`); | ||
//* Only primordials should be used beyond this point! | ||
@@ -503,2 +564,3 @@ rl.on('close', () => { | ||
`Running on Bun v${Bun.version} ${$.gray}(${Bun.revision})${$.reset}\n` + | ||
` REPL version: ${await tryGetPackageVersion()}\n` + | ||
` Color mode: ${Bun.enableANSIColors ? `${$.greenBright}Enabled` : 'Disabled'}${$.reset}\n` + | ||
@@ -543,3 +605,3 @@ ` Debug mode: ${IS_DEBUG ? `${$.greenBright}Enabled` : $.dim+'Disabled'}${$.reset}` | ||
else console.log(evaled); | ||
} | ||
} else if (!extraInfo.errored) console.log(evaled); | ||
} | ||
@@ -546,0 +608,0 @@ rl.prompt(); |
import swc from '@swc/core'; | ||
const REPL_FILENAME = '$bun$repl.ts' as const; | ||
const swcrc: swc.Options = { | ||
@@ -9,3 +11,3 @@ inlineSourcesContent: false, | ||
swcrc: false, | ||
filename: '$bun$repl.ts', | ||
filename: REPL_FILENAME, | ||
module: { | ||
@@ -30,3 +32,33 @@ type: 'commonjs', | ||
optimizer: { | ||
simplify: false | ||
simplify: false, | ||
globals: { | ||
vars: { | ||
'import.meta': /*js*/`((cwd) => { | ||
const { | ||
join, dirname, basename, resolve, getcwd, | ||
pathToFileURL, fileURLToPath, | ||
StringStartsWith, ObjectFreeze | ||
} = (0, eval)('this')['@@bunReplRuntimeHelpers']; | ||
cwd = join(getcwd(), '${REPL_FILENAME}'); | ||
return ObjectFreeze({ | ||
url: pathToFileURL(cwd).href, | ||
main: true, | ||
path: cwd, | ||
dir: dirname(cwd), | ||
file: basename(cwd), | ||
require: require, | ||
async resolve(id, parent) { | ||
return this.resolveSync(id, parent); | ||
}, | ||
resolveSync(id, parent) { | ||
return require.resolve(id, { | ||
paths: typeof parent === "string" ? [ | ||
resolve(StringStartsWith(parent, 'file://') ? fileURLToPath(parent) : parent, '.') | ||
] : void 0 | ||
}); | ||
} | ||
}); | ||
})()`, | ||
} | ||
}, | ||
} | ||
@@ -33,0 +65,0 @@ }, |
@@ -52,3 +52,3 @@ import swc from '@swc/core'; | ||
let importsData = [] as ({ requireVar: string, requireStr: string, info: replTranspiledImportInfo, uuid: string })[]; | ||
code = (SLOPPY_MODE ? '' : '"use strict";void 0;') + code | ||
code = (SLOPPY_MODE ? '' : '"use strict";void 0;\n') + code | ||
.replaceAll(/(?:var|let|const) (_.+?) = require\("(.+?)"\);[ \t\n;]*\/\*\$replTranspiledImport:({.+?})\*\//g, | ||
@@ -55,0 +55,0 @@ ($0, requireVar: string, requireStr: string, infoStr: string) => { |
Sorry, the diff of this file is not supported yet
271382
4037