This commit is contained in:
Tutur33
2023-11-24 22:35:41 +01:00
parent 3c0b507a93
commit 7644b2a0f7
45165 changed files with 4803356 additions and 3 deletions
+9
View File
@@ -0,0 +1,9 @@
# The MIT License
Copyright 2022 Harminder Virk, contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+216
View File
@@ -0,0 +1,216 @@
<div align="center">
<h1> AdonisJS REPL </h1>
<p>A slick <strong>framework agnostic REPL for Node.js</strong> with first class support for <br /> <code>top level await</code>, <code>typescript compilation</code>, <code>accurate stack traces</code> and a lot more.</p>
</div>
<br />
<div align="center">
[![gh-workflow-image]][gh-workflow-url] [![npm-image]][npm-url] ![][typescript-image] [![license-image]][license-url] [![synk-image]][synk-url]
</div>
<div align="center">
<h3>
<a href="#installation">
Usage
</a>
<span> | </span>
<a href="CONTRIBUTING.md">
Contributing
</a>
<span> | </span>
<a href="https://preview.adonisjs.com">
Checkout AdonisJS
</a>
</h3>
</div>
<div align="center">
<sub>Built with ❤︎ by <a href="https://github.com/thetutlage">Harminder Virk</a>
</div>
<br />
![](./assets/imports_and_await.png)
<hr />
AdonisJS REPL is a standalone and framework agnostic package to create custom Node.js REPL with first class support for:
<p>
👉 <strong>Execute typescript code with in-memory compilation.</strong> <br />
👉 <strong> Support for top level await keyword. </strong><br />
👉 <strong> Ability to define custom method with a help description. </strong><br />
</p>
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table of contents
- [Table of contents](#table-of-contents)
- [Installation](#installation)
- [Usage](#usage)
- [Typescript support](#typescript-support)
- [History file](#history-file)
- [Accurate Stack Trace](#accurate-stack-trace)
- [The `.ls` command](#the-ls-command)
- [Adding custom properties](#adding-custom-properties)
- [Global methods](#global-methods)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Installation
Install the package from the npm registry as follows:
```sh
npm i @adonisjs/repl
# Yarn
yarn add @adonisjs/repl
```
## Usage
Import the `Repl` class from the standalone module.
```ts
import { Repl } from '@adonisjs/repl/build/standalone'
const repl = new Repl()
repl.start()
```
### Typescript support
You will have to make use of [@adonisjs/require-ts](https://npm.im/@adonisjs/require-ts) in order for the REPL to compile and run the typescript code. For example:
```ts
import { loadCompiler } from '@adonisjs/require-ts'
import { Repl } from '@adonisjs/repl/build/standalone'
const compilerOptions = {
target: 'es2019',
module: 'commonjs',
allowSyntheticDefaultImports: true,
esModuleInterop: true,
}
const repl = new Repl(loadCompiler(compilerOptions))
```
If you are using `@adonisjs/require-ts` as a require hook, then there is no need to instantiate another instance of the compiler as you can reference the compiler instance from the global object.
```ts
const compiler = global[Symbol.for('REQUIRE_TS_COMPILER')]
const repl = new Repl(compiler)
```
And now run the file containing the above code as follows:
```ts
node -r @adonisjs/require-ts/build/register repl.ts
```
![](./assets/typescript.png)
### History file
AdonisJS REPL allows you store the commands history inside a file so that the subsequent sessions can reference the commands executed in an earlier session.
You need to just pass the path to the history file and rest is taken care for you.
```ts
import { join } from 'path'
import { homedir } from 'os'
import { Repl } from '@adonisjs/repl/build/standalone'
const repl = new Repl(compiler, join(homedir(), '.adonis_repl_history'))
repl.start()
```
![](./assets/history.gif)
## Accurate Stack Trace
The stack trace for the Typescript files points back to the correct file, line and the column number.
![](./assets/stack-trace.png)
## The `.ls` command
The `.ls` command prints the REPL session context. The output is divided to two sections.
![](./assets/ls-command.png)
- **Global Methods** are the methods in the repl context object, but has some description associated with them.
- **Context properties**: are the properties/methods in the context object. Only the first level of properties are printed on the console (to avoid noisy output).
## Adding custom properties
If you are aware about the [Node.js repl context](https://nodejs.org/dist/latest-v14.x/docs/api/repl.html#repl_global_and_local_scope), then you would know that you can add properties to the context as follows:
```ts
// NODE.JS EXAMPLE
const { start } = require('repl')
const server = start({})
server.context.foo = 'bar'
```
Similarly, you can add properties to the AdonisJS repl `context` by referencing the underlying `server` property.
```ts
import { Repl } from '@adonisjs/repl/build/standalone'
const repl = new Repl().start()
repl.server.context.foo = 'bar'
```
### Global methods
In addition to adding properties to the `context` directly. You can also define custom methods with a description and its usage text. For example:
```ts
import { Repl } from '@adonisjs/repl/build/standalone'
const repl = new Repl()
repl.addMethod(
'getUsers',
() => {
return [
{ id: 1, name: 'virk' },
{ id: 2, name: 'romain' },
]
},
{
description: 'Returns a list of users',
}
)
repl.start()
```
There is no technical advantage for using `addMethod` over adding properties to the `context` directly. It's just that `addMethod` properties are given special treatment during the [.ls command](#ls-command).
Checkout the following example
![](./assets/context-behavior.png)
[gh-workflow-image]: https://img.shields.io/github/workflow/status/adonisjs/repl/test?style=for-the-badge
[gh-workflow-url]: https://github.com/adonisjs/repl/actions/workflows/test.yml "Github action"
[typescript-image]: https://img.shields.io/badge/Typescript-294E80.svg?style=for-the-badge&logo=typescript
[typescript-url]: "typescript"
[npm-image]: https://img.shields.io/npm/v/@adonisjs/repl.svg?style=for-the-badge&logo=npm
[npm-url]: https://npmjs.org/package/@adonisjs/repl 'npm'
[license-image]: https://img.shields.io/npm/l/@adonisjs/repl?color=blueviolet&style=for-the-badge
[license-url]: LICENSE.md 'license'
[synk-image]: https://img.shields.io/snyk/vulnerabilities/github/adonisjs/repl?label=Synk%20Vulnerabilities&style=for-the-badge
[synk-url]: https://snyk.io/test/github/adonisjs/repl?targetFile=package.json "synk"
@@ -0,0 +1,6 @@
declare module '@ioc:Adonis/Core/Application' {
import { ReplContract } from '@ioc:Adonis/Addons/Repl';
interface ContainerBindings {
'Adonis/Addons/Repl': ReplContract;
}
}
+8
View File
@@ -0,0 +1,8 @@
/*
* @adonisjs/repl
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
+2
View File
@@ -0,0 +1,2 @@
/// <reference path="container.d.ts" />
/// <reference path="repl.d.ts" />
+10
View File
@@ -0,0 +1,10 @@
/*
* @adonisjs/repl
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/// <reference path="./container.ts" />
/// <reference path="./repl.ts" />
+46
View File
@@ -0,0 +1,46 @@
/// <reference types="node" />
declare module '@ioc:Adonis/Addons/Repl' {
import { REPLServer } from 'repl';
import { getBest } from '@poppinss/colors';
/**
* Custom method callback
*/
export type Handler = (repl: ReplContract, ...args: any[]) => any;
/**
* Options that can be set when defining a loader
* method
*/
export type ContextOptions = {
description?: string;
usage?: string;
};
/**
* Shape of the REPL class
*/
export interface ReplContract {
colors: ReturnType<typeof getBest>;
/**
* Reference to the REPL server
*/
server: REPLServer;
/**
* Start the repl
*/
start(): this;
/**
* Notify by writing message to the console
* and resuming the prompt
*/
notify(message: string): void;
/**
* Add a method. Loader methods works as a shortcut for
*/
addMethod(name: string, handler: Handler, options?: ContextOptions): this;
/**
* Register a callback to be invoked once the server is ready
*/
ready(callback: (repl: ReplContract) => void): this;
}
const Repl: ReplContract;
export default Repl;
}
+8
View File
@@ -0,0 +1,8 @@
/*
* @adonisjs/repl
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
+11
View File
@@ -0,0 +1,11 @@
import { BaseCommand } from '@adonisjs/core/build/standalone';
export default class ReplCommand extends BaseCommand {
static commandName: string;
static description: string;
static settings: {
loadApp: boolean;
environment: "repl";
stayAlive: boolean;
};
run(): Promise<void>;
}
+33
View File
@@ -0,0 +1,33 @@
"use strict";
/*
* @adonisjs/repl
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
const standalone_1 = require("@adonisjs/core/build/standalone");
class ReplCommand extends standalone_1.BaseCommand {
async run() {
this.application.container.withBindings(['Adonis/Core/Route'], (Route) => {
Route.commit();
});
this.application.container.use('Adonis/Addons/Repl').start();
/**
* Gracefully shutdown the application
*/
this.application.container.use('Adonis/Addons/Repl').server.on('exit', async () => {
await this.application.shutdown();
});
}
}
exports.default = ReplCommand;
ReplCommand.commandName = 'repl';
ReplCommand.description = 'Start a new REPL session';
ReplCommand.settings = {
loadApp: true,
environment: 'repl',
stayAlive: true,
};
+2
View File
@@ -0,0 +1,2 @@
declare const _default: string[];
export default _default;
+11
View File
@@ -0,0 +1,11 @@
"use strict";
/*
* @adonisjs/repl
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = ['@adonisjs/repl/build/commands/AdonisRepl'];
+7
View File
@@ -0,0 +1,7 @@
import { ApplicationContract } from '@ioc:Adonis/Core/Application';
export default class ReplProvider {
protected app: ApplicationContract;
constructor(app: ApplicationContract);
static needsApplication: boolean;
register(): void;
}
+26
View File
@@ -0,0 +1,26 @@
"use strict";
/*
* @adonisjs/repl
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
const path_1 = require("path");
const os_1 = require("os");
class ReplProvider {
constructor(app) {
this.app = app;
}
register() {
this.app.container.singleton('Adonis/Addons/Repl', () => {
const compiler = global[Symbol.for('REQUIRE_TS_COMPILER')];
const { Repl } = require('../src/Repl');
return new Repl(compiler, (0, path_1.join)((0, os_1.homedir)(), '.adonis_repl_history'));
});
}
}
exports.default = ReplProvider;
ReplProvider.needsApplication = true;
+29
View File
@@ -0,0 +1,29 @@
import { Compiler as TsCompiler } from '@adonisjs/require-ts/build/src/Compiler';
/**
* Exposes the API to compile the user land code to be executed
* inside Node.JS REPL.
*/
export declare class Compiler {
private tsCompiler?;
compilesTs: boolean;
constructor(tsCompiler?: TsCompiler | undefined);
/**
* Process the await keywords in the code
*/
private processAwait;
/**
* Compiles code using typescript
*/
private compileTypescript;
/**
* Compiles the code to be executed in the Node.js REPL. Under
* the hood
*
* - Typescript code is compiled
* - Await keywords are wrapped in async IIFE
*/
compile(statement: string, filename: string): Promise<{
compiled: string;
awaitPromise: boolean;
}>;
}
+68
View File
@@ -0,0 +1,68 @@
"use strict";
/*
* @adonisjs/repl
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Compiler = void 0;
const node_repl_await_1 = require("node-repl-await");
const ImportsParser_1 = require("../ImportsParser");
/**
* Exposes the API to compile the user land code to be executed
* inside Node.JS REPL.
*/
class Compiler {
constructor(tsCompiler) {
this.tsCompiler = tsCompiler;
this.compilesTs = !!this.tsCompiler;
}
/**
* Process the await keywords in the code
*/
processAwait(statement) {
const potentialWrappedCode = (0, node_repl_await_1.processTopLevelAwait)(statement);
if (!potentialWrappedCode) {
return { compiled: statement, awaitPromise: false };
}
return { compiled: potentialWrappedCode, awaitPromise: true };
}
/**
* Compiles code using typescript
*/
async compileTypescript(statement, filename) {
const lines = statement.split(/\n|\r\n/);
let compiledOutput = statement;
/**
* In case of a single line, we process the import
* keywords and define local variables to hold
* the import reference
*/
if (lines.length <= 2) {
compiledOutput = await new ImportsParser_1.ImportsParser().parse(statement);
}
/**
* Compile using typescript compiler and patch the
* `sourceMappingUrl` comment to be a block level comment.
*/
compiledOutput = this.tsCompiler.compile(filename, compiledOutput, true);
return `${compiledOutput.replace('//# sourceMappingURL=', '/**# sourceMappingURL=')} */`;
}
/**
* Compiles the code to be executed in the Node.js REPL. Under
* the hood
*
* - Typescript code is compiled
* - Await keywords are wrapped in async IIFE
*/
async compile(statement, filename) {
if (this.tsCompiler) {
statement = await this.compileTypescript(statement, filename);
}
return this.processAwait(statement);
}
}
exports.Compiler = Compiler;
+51
View File
@@ -0,0 +1,51 @@
/**
* This is a crazy attempt to hack around the Typescript behavior around un-used
* imports. Writing half baked parsers is something I want to avoid everytime.
* However, there isn't any other way (atleast I don't know about it).
*
* In REPL, you usually write one line of code at a time. For example:
* - You write 2 + 2 and then press enter. The REPL prints 4
* - You write `await Database.query().first()` and it prints the query result.
*
* However, when you write `import User from 'App/Models/User'` and expect to
* access User variable then you are out of luck.
*
* - The import statement will go to the typescript compiler
* - It will compile the code and sees that no one is using this import.
* And hence it will just remove it from the compiled output.
*
* Bang! No import/require exists and hence no `User` variable exists.
*
* To hack around it, we need to monitor and parse import statements and hold
* a reference to them so that the compiler doesn't remove. For example:
*
* Converting
* ```ts
* import User from 'App/Models/User'
* ```
*
* To
* ```ts
* import repl_User from 'App/Models/User'; var User = repl_User
* ```
*
* Now you can access the `User` variable.
*
* The tough part is attempting to parse all styles in which an import
* statement can be written. Lucikly, there are 4 different ways to
* do that as per the spec http://www.ecma-international.org/ecma-262/6.0/#table-40
*
* import v from "mod"; (Import default)
* import * as ns from "mod"; (Import alias)
* import {x} from "mod"; (Import named)
* import {x as v} from "mod"; (Import named + aliases)
*
* However, we have to be tolerant to the white spaces.
*/
export declare class ImportsParser {
private parseImport;
/**
* Parse a given line of code
*/
parse(line: string): Promise<string>;
}
+137
View File
@@ -0,0 +1,137 @@
"use strict";
/*
* @adonisjs/repl
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ImportsParser = void 0;
const parse_imports_1 = __importDefault(require("parse-imports"));
/**
* This is a crazy attempt to hack around the Typescript behavior around un-used
* imports. Writing half baked parsers is something I want to avoid everytime.
* However, there isn't any other way (atleast I don't know about it).
*
* In REPL, you usually write one line of code at a time. For example:
* - You write 2 + 2 and then press enter. The REPL prints 4
* - You write `await Database.query().first()` and it prints the query result.
*
* However, when you write `import User from 'App/Models/User'` and expect to
* access User variable then you are out of luck.
*
* - The import statement will go to the typescript compiler
* - It will compile the code and sees that no one is using this import.
* And hence it will just remove it from the compiled output.
*
* Bang! No import/require exists and hence no `User` variable exists.
*
* To hack around it, we need to monitor and parse import statements and hold
* a reference to them so that the compiler doesn't remove. For example:
*
* Converting
* ```ts
* import User from 'App/Models/User'
* ```
*
* To
* ```ts
* import repl_User from 'App/Models/User'; var User = repl_User
* ```
*
* Now you can access the `User` variable.
*
* The tough part is attempting to parse all styles in which an import
* statement can be written. Lucikly, there are 4 different ways to
* do that as per the spec http://www.ecma-international.org/ecma-262/6.0/#table-40
*
* import v from "mod"; (Import default)
* import * as ns from "mod"; (Import alias)
* import {x} from "mod"; (Import named)
* import {x as v} from "mod"; (Import named + aliases)
*
* However, we have to be tolerant to the white spaces.
*/
class ImportsParser {
async parseImport(statement) {
statement = statement.trim();
/**
* Return the value as it is when doesn't include the import
* keyword
*/
if (!statement.startsWith('import')) {
return statement;
}
/**
* Parse all imports in the current line
*/
const imports = await (0, parse_imports_1.default)(statement);
const importsAsArray = [...imports];
return importsAsArray
.map(({ moduleSpecifier, importClause }) => {
const tokens = [];
const localVariables = [];
/**
* Has `* as` namespace import
*/
if (importClause && importClause.namespace) {
const identifier = `repl_${importClause.namespace}`;
tokens.push(`* as ${identifier}`);
localVariables.push(`var ${importClause.namespace} = ${identifier}`);
}
/**
* Has default namespace import
*/
if (importClause && importClause.default) {
const identifier = `repl_${importClause.default}`;
tokens.push(identifier);
localVariables.push(`var ${importClause.default} = ${identifier}`);
}
/**
* Has named imports
*/
if (importClause && importClause.named.length) {
importClause.named.forEach((name, index) => {
const identifier = `repl_${name.binding}`;
let expression = '';
/**
* Add starting curly brace to named aliases
*/
if (index === 0) {
expression += '{';
}
/**
* Setup expression
*/
expression += `${name.specifier} as ${identifier}`;
/**
* Add ending curly brace to named aliases
*/
if (index + 1 === importClause.named.length) {
expression += '}';
}
tokens.push(expression);
localVariables.push(`var ${name.binding} = ${identifier}`);
});
}
const localVariablesDeclaration = `${localVariables.join('; ')}`;
return `import ${tokens.join(',')} from ${moduleSpecifier.code}; ${localVariablesDeclaration}`;
})
.join('');
}
/**
* Parse a given line of code
*/
async parse(line) {
const parsedStatement = await Promise.all(line.split(';').map((statement) => {
return this.parseImport(statement);
}));
return parsedStatement.join('; ');
}
}
exports.ImportsParser = ImportsParser;
+91
View File
@@ -0,0 +1,91 @@
/// <reference types="node" />
import { REPLServer } from 'repl';
import { ReplContract, Handler, ContextOptions } from '@ioc:Adonis/Addons/Repl';
import { Compiler as TsCompiler } from '@adonisjs/require-ts/build/src/Compiler';
/**
* Exposes the API to work the REPL server
*/
export declare class Repl implements ReplContract {
private tsCompiler?;
private historyFilePath?;
/**
* Compiler to compile REPL input
*/
private compiler;
/**
* Length of the longest custom method name. We need to show a
* symmetric view of custom methods and their description
*/
private longestCustomMethodName;
/**
* Set of registered ready callbacks
*/
private onReadyCallbacks;
/**
* A set of registered custom methods
*/
private customMethods;
/**
* Reference to the underlying REPL server. Available
* after `start` is invoked.
*/
server: REPLServer;
/**
* Reference to the colors to print colorful messages
*/
colors: import("@poppinss/colors/build/src/Base").Colors;
constructor(tsCompiler?: TsCompiler | undefined, historyFilePath?: string | undefined);
/**
* Find if the error is recoverable or not
*/
private isRecoverableError;
/**
* Custom eval method to execute the user code
*/
private eval;
private registerCustomMethodWithContext;
/**
* Register custom methods with the server context
*/
private registerCustomMethodsWithContext;
/**
* Setup context with default globals
*/
private setupContext;
/**
* Setup history file
*/
private setupHistory;
/**
* Prints the welcome message
*/
private printWelcomeMessage;
/**
* Prints the help for the custom methods
*/
private printCustomMethodsHelp;
/**
* Prints the help for the context properties
*/
private printContextHelp;
/**
* Prints the context to the console
*/
private ls;
/**
* Notify by writing to the console
*/
notify(message: string): void;
/**
* Start the REPL session
*/
start(): this;
/**
* Register a callback to be invoked once the server is ready
*/
ready(callback: (repl: ReplContract) => void): this;
/**
* Register a custom loader function to be added to the context
*/
addMethod(name: string, handler: Handler, options?: ContextOptions): this;
}
+358
View File
@@ -0,0 +1,358 @@
"use strict";
/*
* @adonisjs/repl
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Repl = void 0;
const vm_1 = require("vm");
const string_width_1 = __importDefault(require("string-width"));
const colors_1 = require("@poppinss/colors");
const repl_1 = require("repl");
const util_1 = require("util");
const Compiler_1 = require("../Compiler");
/**
* List of node global properties to remove from the
* ls inspect
*/
const GLOBAL_NODE_PROPERTIES = [
'performance',
'global',
'clearInterval',
'clearTimeout',
'setInterval',
'setTimeout',
'queueMicrotask',
'clearImmediate',
'setImmediate',
'exports',
'__importDefault',
];
/**
* Properties injected by the ts-utils
* library
*/
const TS_UTIL_HELPERS = [
'__extends',
'__assign',
'__rest',
'__decorate',
'__param',
'__metadata',
'__awaiter',
'__generator',
'__exportStar',
'__values',
'__read',
'__spread',
'__spreadArrays',
'__spreadArray',
'__await',
'__asyncGenerator',
'__asyncDelegator',
'__asyncValues',
'__makeTemplateObject',
'__importStar',
'__importDefault',
'__classPrivateFieldGet',
'__classPrivateFieldSet',
'__createBinding',
];
const icons = process.platform === 'win32' && !process.env.WT_SESSION
? {
tick: '√',
pointer: '>',
}
: {
tick: '✔',
pointer: '',
};
/**
* Exposes the API to work the REPL server
*/
class Repl {
constructor(tsCompiler, historyFilePath) {
this.tsCompiler = tsCompiler;
this.historyFilePath = historyFilePath;
/**
* Compiler to compile REPL input
*/
this.compiler = new Compiler_1.Compiler(this.tsCompiler);
/**
* Length of the longest custom method name. We need to show a
* symmetric view of custom methods and their description
*/
this.longestCustomMethodName = 0;
/**
* Set of registered ready callbacks
*/
this.onReadyCallbacks = [];
/**
* A set of registered custom methods
*/
this.customMethods = {};
/**
* Reference to the colors to print colorful messages
*/
this.colors = (0, colors_1.getBest)(false);
}
/**
* Find if the error is recoverable or not
*/
isRecoverableError(error) {
if (error.name === 'SyntaxError') {
return /^(Unexpected end of input|Unexpected token)/.test(error.message);
}
return false;
}
/**
* Custom eval method to execute the user code
*/
async eval(cmd, _, filename, callback) {
try {
let response;
const { compiled, awaitPromise } = await this.compiler.compile(cmd, filename);
if (awaitPromise) {
response = await new vm_1.Script(compiled, { filename }).runInThisContext();
}
else {
response = new vm_1.Script(compiled, { filename }).runInThisContext();
}
callback(null, response);
}
catch (error) {
if (this.isRecoverableError(error)) {
callback(new repl_1.Recoverable(error), null);
return;
}
callback(error, null);
}
}
registerCustomMethodWithContext(name) {
const customMethod = this.customMethods[name];
if (!customMethod) {
return;
}
/**
* Wrap handler
*/
const handler = (...args) => customMethod.handler(this, ...args);
/**
* Re-define the function name to be more description
*/
Object.defineProperty(handler, 'name', { value: customMethod.handler.name });
/**
* Register with the context
*/
this.server.context[name] = handler;
}
/**
* Register custom methods with the server context
*/
registerCustomMethodsWithContext() {
Object.keys(this.customMethods).forEach((name) => {
this.registerCustomMethodWithContext(name);
});
}
/**
* Setup context with default globals
*/
setupContext() {
/**
* Register "clear" method
*/
this.addMethod('clear', function clear(repl, key) {
if (!key) {
console.log(repl.colors.red('Define a property name to remove from the context'));
}
else {
delete repl.server.context[key];
}
repl.server.displayPrompt();
}, {
description: 'Clear a property from the REPL context',
usage: `clear ${this.colors.gray('(propertyName)')}`,
});
/**
* Register "p" method
*/
this.addMethod('p', function promisify(_, fn) {
return (0, util_1.promisify)(fn);
}, {
description: 'Promisify a function. Similar to Node.js "util.promisify"',
usage: `p ${this.colors.gray('(function)')}`,
});
/**
* Register all custom methods with the context
*/
this.registerCustomMethodsWithContext();
}
/**
* Setup history file
*/
setupHistory() {
if (!this.historyFilePath) {
return;
}
this.server.setupHistory(this.historyFilePath, (error) => {
if (!error) {
return;
}
console.log(this.colors.red('Unable to write to the history file. Exiting'));
console.error(error);
process.exit(1);
});
}
/**
* Prints the welcome message
*/
printWelcomeMessage() {
console.log('');
/**
* Log about typescript support
*/
if (this.compiler.compilesTs) {
console.log(`${this.colors.dim(icons.tick)} ${this.colors.dim('typescript compilation supported')}`);
}
/**
* Log about top level imports
*/
console.log(`${this.colors.dim(icons.tick)} ${this.colors.dim('allows top level imports')}`);
console.log('');
/**
* Log about help command
*/
this.notify('Type ".ls" to a view list of available context methods/properties');
}
/**
* Prints the help for the custom methods
*/
printCustomMethodsHelp() {
/**
* Print loader methods
*/
console.log('');
console.log(this.colors.green('GLOBAL METHODS:'));
Object.keys(this.customMethods).forEach((method) => {
const { options } = this.customMethods[method];
const spaces = new Array(this.longestCustomMethodName - options.width + 2).join(' ');
console.log(`${this.colors.yellow(options.usage || method)}${spaces}${this.colors.dim(options.description || '')}`);
});
}
/**
* Prints the help for the context properties
*/
printContextHelp() {
/**
* Print context properties
*/
console.log('');
console.log(this.colors.green('CONTEXT PROPERTIES/METHODS:'));
const context = Object.keys(this.server?.context).reduce((result, key) => {
if (!this.customMethods[key] &&
!GLOBAL_NODE_PROPERTIES.includes(key) &&
!TS_UTIL_HELPERS.includes(key)) {
result[key] = this.server?.context[key];
}
return result;
}, {});
console.log((0, util_1.inspect)(context, false, 1, true));
}
/**
* Prints the context to the console
*/
ls() {
this.printCustomMethodsHelp();
this.printContextHelp();
this.server.displayPrompt();
}
/**
* Notify by writing to the console
*/
notify(message) {
console.log(this.colors.yellow().italic(message));
if (this.server) {
this.server.displayPrompt();
}
}
/**
* Start the REPL session
*/
start() {
this.printWelcomeMessage();
this.server = (0, repl_1.start)({
prompt: '> ',
input: process.stdin,
output: process.stdout,
terminal: process.stdout.isTTY && !parseInt(process.env.NODE_NO_READLINE, 10),
useGlobal: true,
});
/**
* Define ls command
*/
this.server.defineCommand('ls', {
help: 'View a list of available context methods/properties',
action: this.ls.bind(this),
});
/**
* Setup context
*/
this.setupContext();
/**
* Setup history
*/
this.setupHistory();
/**
* Assigning eval function like this has better completion support.
*/
// @ts-ignore
this.server['eval'] = this.eval.bind(this);
/**
* Define exports variable when using Typescript
*/
if (this.compiler.compilesTs) {
new vm_1.Script('exports = module.exports', { filename: __dirname }).runInThisContext();
}
/**
* Display prompt
*/
this.server.displayPrompt();
/**
* Execute onready callbacks
*/
this.onReadyCallbacks.forEach((callback) => callback(this));
return this;
}
/**
* Register a callback to be invoked once the server is ready
*/
ready(callback) {
this.onReadyCallbacks.push(callback);
return this;
}
/**
* Register a custom loader function to be added to the context
*/
addMethod(name, handler, options) {
const width = (0, string_width_1.default)(options?.usage || name);
if (width > this.longestCustomMethodName) {
this.longestCustomMethodName = width;
}
this.customMethods[name] = { handler, options: Object.assign({ width }, options) };
/**
* Register method right away when server has been started
*/
if (this.server) {
this.registerCustomMethodWithContext(name);
}
return this;
}
}
exports.Repl = Repl;
+3
View File
@@ -0,0 +1,3 @@
export { Repl } from './src/Repl';
export { Compiler } from './src/Compiler';
export { ImportsParser } from './src/ImportsParser';
+17
View File
@@ -0,0 +1,17 @@
"use strict";
/*
* @adonisjs/repl
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ImportsParser = exports.Compiler = exports.Repl = void 0;
var Repl_1 = require("./src/Repl");
Object.defineProperty(exports, "Repl", { enumerable: true, get: function () { return Repl_1.Repl; } });
var Compiler_1 = require("./src/Compiler");
Object.defineProperty(exports, "Compiler", { enumerable: true, get: function () { return Compiler_1.Compiler; } });
var ImportsParser_1 = require("./src/ImportsParser");
Object.defineProperty(exports, "ImportsParser", { enumerable: true, get: function () { return ImportsParser_1.ImportsParser; } });
+161
View File
@@ -0,0 +1,161 @@
{
"name": "@adonisjs/repl",
"version": "3.1.11",
"description": "REPL for AdonisJS",
"main": "build/providers/ReplProvider.js",
"files": [
"build/adonis-typings",
"build/commands",
"build/providers",
"build/src",
"build/standalone.d.ts",
"build/standalone.js"
],
"typings": "./build/adonis-typings/index.d.ts",
"scripts": {
"mrm": "mrm --preset=@adonisjs/mrm-preset",
"pretest": "npm run lint",
"test": "node -r @adonisjs/require-ts/build/register ./bin/test.ts",
"clean": "del-cli build",
"compile": "npm run lint && npm run clean && tsc",
"build": "npm run compile",
"prepublishOnly": "npm run build",
"lint": "eslint . --ext=.ts",
"format": "prettier --write .",
"commit": "git-cz",
"release": "np --message=\"chore(release): %s\"",
"version": "npm run build",
"sync-labels": "github-label-sync --labels ./node_modules/@adonisjs/mrm-preset/gh-labels.json adonisjs/repl"
},
"keywords": [
"adonisjs",
"repl",
"node-repl"
],
"author": "virk,adonisjs",
"license": "MIT",
"peerDependencies": {
"@adonisjs/core": "^5.1.0"
},
"devDependencies": {
"@adonisjs/core": "^5.8.2",
"@adonisjs/mrm-preset": "^5.0.3",
"@adonisjs/require-ts": "^2.0.12",
"@japa/assert": "^1.3.4",
"@japa/run-failed-tests": "^1.0.7",
"@japa/runner": "^2.0.8",
"@japa/spec-reporter": "^1.1.12",
"@poppinss/dev-utils": "^2.0.3",
"@types/node": "^17.0.35",
"commitizen": "^4.2.4",
"cz-conventional-changelog": "^3.3.0",
"del-cli": "^4.0.1",
"doctoc": "^2.2.0",
"eslint": "^8.16.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-adonis": "^2.1.0",
"eslint-plugin-prettier": "^4.0.0",
"github-label-sync": "^2.2.0",
"husky": "^8.0.1",
"mrm": "^4.0.0",
"np": "^7.6.1",
"prettier": "^2.6.2",
"typescript": "^4.6.4"
},
"husky": {
"hooks": {
"commit-msg": "node ./node_modules/@adonisjs/mrm-preset/validateCommit/conventional/validate.js"
}
},
"nyc": {
"exclude": [
"test"
],
"extension": [
".ts"
]
},
"config": {
"commitizen": {
"path": "cz-conventional-changelog"
}
},
"np": {
"contents": ".",
"anyBranch": false
},
"publishConfig": {
"access": "public",
"tag": "latest"
},
"dependencies": {
"@poppinss/colors": "^3.0.2",
"node-repl-await": "^0.1.2",
"parse-imports": "0.0.5",
"string-width": "^4.2.2"
},
"directories": {
"example": "example",
"test": "test"
},
"repository": {
"type": "git",
"url": "git+https://github.com/adonisjs/repl.git"
},
"bugs": {
"url": "https://github.com/adonisjs/repl/issues"
},
"homepage": "https://github.com/adonisjs/repl#readme",
"adonisjs": {
"aceProviders": [
"@adonisjs/repl"
],
"commands": [
"@adonisjs/repl/build/commands"
],
"types": "@adonisjs/repl"
},
"mrmConfig": {
"core": true,
"license": "MIT",
"services": [
"github-actions"
],
"minNodeVersion": "14.15.4",
"probotApps": [
"stale",
"lock"
],
"runGhActionsOnWindows": true
},
"eslintConfig": {
"extends": [
"plugin:adonis/typescriptPackage",
"prettier"
],
"plugins": [
"prettier"
],
"rules": {
"prettier/prettier": [
"error",
{
"endOfLine": "auto"
}
]
}
},
"eslintIgnore": [
"build"
],
"prettier": {
"trailingComma": "es5",
"semi": false,
"singleQuote": true,
"useTabs": false,
"quoteProps": "consistent",
"bracketSpacing": true,
"arrowParens": "always",
"printWidth": 100
}
}