├── .editorconfig
├── .github
└── workflows
│ ├── checks.yml
│ ├── labels.yml
│ ├── release.yml
│ └── stale.yml
├── .gitignore
├── .npmrc
├── .prettierignore
├── LICENSE.md
├── README.md
├── eslint.config.js
├── examples
├── get_quote.ts
├── index.ts
└── math.ts
├── index.ts
├── package.json
├── src
├── repl.ts
└── types.ts
└── tsconfig.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.json]
12 | insert_final_newline = ignore
13 |
14 | [**.min.js]
15 | indent_style = ignore
16 | insert_final_newline = ignore
17 |
18 | [MakeFile]
19 | indent_style = space
20 |
21 | [*.md]
22 | trim_trailing_whitespace = false
23 |
--------------------------------------------------------------------------------
/.github/workflows/checks.yml:
--------------------------------------------------------------------------------
1 | name: test
2 | on:
3 | - push
4 | - pull_request
5 | - workflow_call
6 |
7 | jobs:
8 | test:
9 | uses: adonisjs/.github/.github/workflows/test.yml@main
10 |
11 | lint:
12 | uses: adonisjs/.github/.github/workflows/lint.yml@main
13 |
14 | typecheck:
15 | uses: adonisjs/.github/.github/workflows/typecheck.yml@main
16 |
--------------------------------------------------------------------------------
/.github/workflows/labels.yml:
--------------------------------------------------------------------------------
1 | name: Sync labels
2 | on:
3 | workflow_dispatch:
4 | permissions:
5 | issues: write
6 | jobs:
7 | labels:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v4
11 | - uses: EndBug/label-sync@v2
12 | with:
13 | config-file: 'https://raw.githubusercontent.com/thetutlage/static/main/labels.yml'
14 | delete-other-labels: true
15 | token: ${{ secrets.GITHUB_TOKEN }}
16 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: release
2 | on: workflow_dispatch
3 | permissions:
4 | contents: write
5 | id-token: write
6 | jobs:
7 | checks:
8 | uses: ./.github/workflows/checks.yml
9 | release:
10 | needs: checks
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v4
14 | with:
15 | fetch-depth: 0
16 | - uses: actions/setup-node@v4
17 | with:
18 | node-version: 20
19 | - name: git config
20 | run: |
21 | git config user.name "${GITHUB_ACTOR}"
22 | git config user.email "${GITHUB_ACTOR}@users.noreply.github.com"
23 | - name: Init npm config
24 | run: npm config set //registry.npmjs.org/:_authToken $NPM_TOKEN
25 | env:
26 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
27 | - run: npm install
28 | - run: npm run release -- --ci
29 | env:
30 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
31 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
32 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
33 |
--------------------------------------------------------------------------------
/.github/workflows/stale.yml:
--------------------------------------------------------------------------------
1 | name: 'Close stale issues and PRs'
2 | on:
3 | schedule:
4 | - cron: '30 0 * * *'
5 |
6 | jobs:
7 | stale:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/stale@v9
11 | with:
12 | stale-issue-message: 'This issue has been marked as stale because it has been inactive for more than 21 days. Please reopen if you still need help on this issue'
13 | stale-pr-message: 'This pull request has been marked as stale because it has been inactive for more than 21 days. Please reopen if you still intend to submit this pull request'
14 | close-issue-message: 'This issue has been automatically closed because it has been inactive for more than 4 weeks. Please reopen if you still need help on this issue'
15 | close-pr-message: 'This pull request has been automatically closed because it has been inactive for more than 4 weeks. Please reopen if you still intend to submit this pull request'
16 | days-before-stale: 21
17 | days-before-close: 5
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | coverage
3 | .DS_STORE
4 | .nyc_output
5 | .idea
6 | .vscode/
7 | *.sublime-project
8 | *.sublime-workspace
9 | *.log
10 | build
11 | dist
12 | package-lock.json
13 | yarn.lock
14 | shrinkwrap.yaml
15 | .env
16 | !test/fixtures/.env
17 | test/__app
18 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | package-lock=false
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | build
2 | README.md
3 | docs
4 | *.md
5 | *.html
6 | config.json
7 | .eslintrc.json
8 | package.json
9 | *.txt
10 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | # The MIT License
2 |
3 | Copyright 2022 Harminder Virk, contributors
4 |
5 | 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:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | 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.
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # @adonisjs/repl
2 |
3 |
4 |
5 | [![gh-workflow-image]][gh-workflow-url] [![npm-image]][npm-url] ![][typescript-image] [![license-image]][license-url]
6 |
7 | ## Introduction
8 | REPL for AdonisJS applications. Syntax highlighting, bracket matching, ESM and TypeScript support out of the box.
9 |
10 | ## Official Documentation
11 | The documentation is available on the [AdonisJS website](https://docs.adonisjs.com/guides/repl)
12 |
13 | ## Contributing
14 | One of the primary goals of AdonisJS is to have a vibrant community of users and contributors who believes in the principles of the framework.
15 |
16 | We encourage you to read the [contribution guide](https://github.com/adonisjs/.github/blob/main/docs/CONTRIBUTING.md) before contributing to the framework.
17 |
18 | ## Code of Conduct
19 | In order to ensure that the AdonisJS community is welcoming to all, please review and abide by the [Code of Conduct](https://github.com/adonisjs/.github/blob/main/docs/CODE_OF_CONDUCT.md).
20 |
21 | ## License
22 | AdonisJS Repl is open-sourced software licensed under the [MIT license](LICENSE.md).
23 |
24 | [gh-workflow-image]: https://img.shields.io/github/actions/workflow/status/adonisjs/repl/checks.yml?style=for-the-badge
25 | [gh-workflow-url]: https://github.com/adonisjs/repl/actions/workflows/checks.yml "Github action"
26 |
27 | [npm-image]: https://img.shields.io/npm/v/@adonisjs/repl/latest.svg?style=for-the-badge&logo=npm
28 | [npm-url]: https://www.npmjs.com/package/@adonisjs/repl/v/latest "npm"
29 |
30 | [typescript-image]: https://img.shields.io/badge/Typescript-294E80.svg?style=for-the-badge&logo=typescript
31 |
32 | [license-url]: LICENSE.md
33 | [license-image]: https://img.shields.io/github/license/adonisjs/repl?style=for-the-badge
34 |
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | import { configPkg } from '@adonisjs/eslint-config'
2 | export default configPkg({
3 | ignores: ['coverage'],
4 | })
5 |
--------------------------------------------------------------------------------
/examples/get_quote.ts:
--------------------------------------------------------------------------------
1 | export async function getQuote(): Promise {
2 | return 'I promise to share a quote'
3 | }
4 |
--------------------------------------------------------------------------------
/examples/index.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @adonisjs/repl
3 | *
4 | * (c) Harminder Virk
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | import { join } from 'node:path'
11 | import { homedir } from 'node:os'
12 | import { create } from 'ts-node-maintained'
13 |
14 | import { Repl } from '../src/repl.js'
15 |
16 | /**
17 | * A dummy database object
18 | */
19 | const db = {
20 | query() {
21 | return this
22 | },
23 | from(_table: string) {
24 | return this
25 | },
26 | fetch: async () => {
27 | return [
28 | { id: 1, name: 'virk' },
29 | { id: 2, name: 'romain' },
30 | ]
31 | },
32 | }
33 |
34 | class User {}
35 | class Profile {}
36 | class Team {}
37 | class Account {}
38 |
39 | const models = {
40 | User,
41 | Team,
42 | Profile,
43 | Account,
44 | }
45 |
46 | const tsNode = create({ project: '../tsconfig.json' })
47 | const compiler = {
48 | supportsTypescript: true,
49 | compile(code: string, fileName: string) {
50 | const output = tsNode.compile(code, fileName)
51 | return output
52 | .replace('export { };', '')
53 | .replace(/\/\/# sourceMappingURL=(.*)$/, '/** sourceMappingURL=$1 */')
54 | },
55 | }
56 |
57 | new Repl({
58 | compiler,
59 | historyFilePath: join(homedir(), '.adonis_repl_history'),
60 | })
61 | .addMethod(
62 | 'getDb',
63 | function loadDatabase(repl) {
64 | repl.server!.context.db = db
65 | repl.notify(
66 | `Loaded database. You can access it using the ${repl.colors.underline('"db"')} property`
67 | )
68 | },
69 | {
70 | description: 'Loads database to the "db" property.',
71 | }
72 | )
73 | .addMethod(
74 | 'getModels',
75 | (repl) => {
76 | repl.server!.context.models = models
77 | repl.notify('Loaded models. You can access them using the "models" property')
78 | repl.server!.displayPrompt()
79 | },
80 | {
81 | description: 'Loads database to the "models" property.',
82 | }
83 | )
84 | .start()
85 |
--------------------------------------------------------------------------------
/examples/math.ts:
--------------------------------------------------------------------------------
1 | export default function () {
2 | throw new Error('foo')
3 | }
4 |
--------------------------------------------------------------------------------
/index.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @adonisjs/repl
3 | *
4 | * (c) AdonisJS
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | export { Repl } from './src/repl.js'
11 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@adonisjs/repl",
3 | "description": "REPL for AdonisJS",
4 | "version": "4.1.0",
5 | "engines": {
6 | "node": ">=18.16.0"
7 | },
8 | "type": "module",
9 | "files": [
10 | "build",
11 | "!build/bin",
12 | "!build/examples"
13 | ],
14 | "exports": {
15 | ".": "./build/index.js",
16 | "./types": "./build/src/types.js"
17 | },
18 | "scripts": {
19 | "pretest": "npm run lint",
20 | "test": "echo 'No tests'",
21 | "lint": "eslint .",
22 | "format": "prettier --write .",
23 | "typecheck": "tsc --noEmit",
24 | "clean": "del-cli build",
25 | "precompile": "npm run lint && npm run clean",
26 | "compile": "tsup-node && tsc --emitDeclarationOnly --declaration",
27 | "build": "npm run compile",
28 | "version": "npm run build",
29 | "prepublishOnly": "npm run build",
30 | "release": "release-it"
31 | },
32 | "devDependencies": {
33 | "@adonisjs/eslint-config": "^2.0.0-beta.7",
34 | "@adonisjs/prettier-config": "^1.4.0",
35 | "@adonisjs/tsconfig": "^1.4.0",
36 | "@release-it/conventional-changelog": "^10.0.0",
37 | "@swc/core": "^1.10.7",
38 | "@types/node": "^22.10.5",
39 | "del-cli": "^6.0.0",
40 | "eslint": "^9.18.0",
41 | "prettier": "^3.4.2",
42 | "release-it": "^18.1.1",
43 | "ts-node-maintained": "^10.9.4",
44 | "tsup": "^8.3.5",
45 | "typescript": "^5.7.3"
46 | },
47 | "dependencies": {
48 | "@poppinss/colors": "^4.1.4",
49 | "string-width": "^7.2.0"
50 | },
51 | "homepage": "https://github.com/adonisjs/repl#readme",
52 | "repository": {
53 | "type": "git",
54 | "url": "git+https://github.com/adonisjs/repl.git"
55 | },
56 | "bugs": {
57 | "url": "https://github.com/adonisjs/repl/issues"
58 | },
59 | "keywords": [
60 | "adonisjs",
61 | "repl",
62 | "node-repl"
63 | ],
64 | "author": "Harminder Virk ",
65 | "license": "MIT",
66 | "publishConfig": {
67 | "access": "public",
68 | "provenance": true
69 | },
70 | "tsup": {
71 | "entry": [
72 | "./index.ts",
73 | "./src/types.ts"
74 | ],
75 | "outDir": "./build",
76 | "clean": true,
77 | "format": "esm",
78 | "dts": false,
79 | "sourcemap": false,
80 | "target": "esnext"
81 | },
82 | "release-it": {
83 | "git": {
84 | "requireCleanWorkingDir": true,
85 | "requireUpstream": true,
86 | "commitMessage": "chore(release): ${version}",
87 | "tagAnnotation": "v${version}",
88 | "push": true,
89 | "tagName": "v${version}"
90 | },
91 | "github": {
92 | "release": true
93 | },
94 | "npm": {
95 | "publish": true,
96 | "skipChecks": true
97 | },
98 | "plugins": {
99 | "@release-it/conventional-changelog": {
100 | "preset": {
101 | "name": "angular"
102 | }
103 | }
104 | }
105 | },
106 | "prettier": "@adonisjs/prettier-config"
107 | }
108 |
--------------------------------------------------------------------------------
/src/repl.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @adonisjs/repl
3 | *
4 | * (c) AdonisJS
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | import stringWidth from 'string-width'
11 | import useColors from '@poppinss/colors'
12 | import type { Colors } from '@poppinss/colors/types'
13 | import { inspect, promisify as utilPromisify } from 'node:util'
14 | import { type REPLServer, Recoverable, type ReplOptions, start as startRepl } from 'node:repl'
15 |
16 | import type { MethodCallback, MethodOptions, Compiler } from './types.js'
17 |
18 | /**
19 | * List of node global properties to remove from the
20 | * ls inspect
21 | */
22 | const GLOBAL_NODE_PROPERTIES = [
23 | 'performance',
24 | 'global',
25 | 'clearInterval',
26 | 'clearTimeout',
27 | 'setInterval',
28 | 'setTimeout',
29 | 'queueMicrotask',
30 | 'clearImmediate',
31 | 'setImmediate',
32 | 'structuredClone',
33 | 'atob',
34 | 'btoa',
35 | 'fetch',
36 | 'crypto',
37 | 'navigator',
38 | ]
39 |
40 | const TS_UTILS_HELPERS = [
41 | '__extends',
42 | '__assign',
43 | '__rest',
44 | '__decorate',
45 | '__param',
46 | '__esDecorate',
47 | '__runInitializers',
48 | '__propKey',
49 | '__setFunctionName',
50 | '__metadata',
51 | '__awaiter',
52 | '__generator',
53 | '__exportStar',
54 | '__createBinding',
55 | '__values',
56 | '__read',
57 | '__spread',
58 | '__spreadArrays',
59 | '__spreadArray',
60 | '__await',
61 | '__asyncGenerator',
62 | '__asyncDelegator',
63 | '__asyncValues',
64 | '__makeTemplateObject',
65 | '__importStar',
66 | '__importDefault',
67 | '__classPrivateFieldGet',
68 | '__classPrivateFieldSet',
69 | '__classPrivateFieldIn',
70 | ]
71 |
72 | export class Repl {
73 | #replOptions: ReplOptions
74 |
75 | /**
76 | * Length of the longest custom method name. We need to show a
77 | * symmetric view of custom methods and their description
78 | */
79 | #longestCustomMethodName = 0
80 |
81 | /**
82 | * Reference to the original `eval` method of the repl server.
83 | * Since we are monkey patching it, we need a reference to it
84 | * to call it after our custom logic
85 | */
86 | #originalEval?: Function
87 |
88 | /**
89 | * Compiler that will transform the user input just
90 | * before evaluation
91 | */
92 | #compiler?: Compiler
93 |
94 | /**
95 | * Path to the history file
96 | */
97 | #historyFilePath?: string
98 |
99 | /**
100 | * Set of registered ready callbacks
101 | */
102 | #onReadyCallbacks: ((repl: Repl) => void)[] = []
103 |
104 | /**
105 | * A set of registered custom methods
106 | */
107 | #customMethods: {
108 | [name: string]: { handler: MethodCallback; options: MethodOptions & { width: number } }
109 | } = {}
110 |
111 | /**
112 | * Colors reference
113 | */
114 | colors: Colors = useColors.ansi()
115 |
116 | /**
117 | * Reference to the repl server. Available after the `start` method
118 | * is invoked
119 | */
120 | server?: REPLServer
121 |
122 | constructor(options?: { compiler?: Compiler; historyFilePath?: string } & ReplOptions) {
123 | const { compiler, historyFilePath, ...rest } = options || {}
124 | this.#compiler = compiler
125 | this.#historyFilePath = historyFilePath
126 | this.#replOptions = rest
127 | }
128 |
129 | /**
130 | * Registering custom methods with the server context by wrapping
131 | * them inside a function and passes the REPL server instance
132 | * to the method
133 | */
134 | #registerCustomMethodWithContext(name: string) {
135 | const customMethod = this.#customMethods[name]
136 | if (!customMethod) {
137 | return
138 | }
139 |
140 | /**
141 | * Wrap handler
142 | */
143 | const handler = (...args: any[]) => customMethod.handler(this, ...args)
144 |
145 | /**
146 | * Re-define the function name to be more description
147 | */
148 | Object.defineProperty(handler, 'name', { value: customMethod.handler.name })
149 |
150 | /**
151 | * Register with the context
152 | */
153 | this.server!.context[name] = handler
154 | }
155 |
156 | /**
157 | * Setup context with default globals
158 | */
159 | #setupContext() {
160 | /**
161 | * Register "clear" method
162 | */
163 | this.addMethod(
164 | 'clear',
165 | function clear(repl: Repl, key: string) {
166 | if (!key) {
167 | console.log(repl.colors.red('Define a property name to remove from the context'))
168 | } else {
169 | delete repl.server!.context[key]
170 | }
171 | repl.server!.displayPrompt()
172 | },
173 | {
174 | description: 'Clear a property from the REPL context',
175 | usage: `clear ${this.colors.gray('(propertyName)')}`,
176 | }
177 | )
178 |
179 | /**
180 | * Register "p" method
181 | */
182 | this.addMethod(
183 | 'p',
184 | function promisify(_: Repl, fn: Function) {
185 | return utilPromisify(fn)
186 | },
187 | {
188 | description: 'Promisify a function. Similar to Node.js "util.promisify"',
189 | usage: `p ${this.colors.gray('(function)')}`,
190 | }
191 | )
192 |
193 | /**
194 | * Register all custom methods with the context
195 | */
196 | Object.keys(this.#customMethods).forEach((name) => {
197 | this.#registerCustomMethodWithContext(name)
198 | })
199 | }
200 |
201 | /**
202 | * Find if the error is recoverable or not
203 | */
204 | #isRecoverableError(error: any) {
205 | return /^(Unexpected end of input|Unexpected token|' expected)/.test(error.message)
206 | }
207 |
208 | /**
209 | * Custom eval method to execute the user code
210 | *
211 | * Basically we are monkey patching the original eval method, because
212 | * we want to:
213 | * - Compile the user code before executing it
214 | * - And also benefit from the original eval method that supports
215 | * cool features like top level await
216 | */
217 | #eval(
218 | code: string,
219 | context: any,
220 | filename: string,
221 | callback: (err: Error | null, result?: any) => void
222 | ) {
223 | try {
224 | const compiled = this.#compiler ? this.#compiler!.compile(code, filename) : code
225 | return this.#originalEval!(compiled, context, filename, callback)
226 | } catch (error) {
227 | if (this.#isRecoverableError(error)) {
228 | callback(new Recoverable(error), null)
229 | return
230 | }
231 |
232 | callback(error, null)
233 | }
234 | }
235 |
236 | /**
237 | * Setup history file
238 | */
239 | #setupHistory() {
240 | if (!this.#historyFilePath) {
241 | return
242 | }
243 |
244 | this.server!.setupHistory(this.#historyFilePath, (error) => {
245 | if (!error) {
246 | return
247 | }
248 |
249 | console.log(this.colors.red('Unable to write to the history file. Exiting'))
250 | console.error(error)
251 | process.exit(1)
252 | })
253 | }
254 |
255 | /**
256 | * Prints the help for the context properties
257 | */
258 | #printContextHelp() {
259 | /**
260 | * Print context properties
261 | */
262 | console.log('')
263 | console.log(this.colors.green('CONTEXT PROPERTIES/METHODS:'))
264 |
265 | const context = Object.keys(this.server!.context).reduce(
266 | (result, key) => {
267 | if (
268 | !this.#customMethods[key] &&
269 | !GLOBAL_NODE_PROPERTIES.includes(key) &&
270 | !TS_UTILS_HELPERS.includes(key)
271 | ) {
272 | result[key] = this.server!.context[key]
273 | }
274 |
275 | return result
276 | },
277 | {} as Record
278 | )
279 |
280 | console.log(inspect(context, false, 1, true))
281 | }
282 |
283 | /**
284 | * Prints the help for the custom methods
285 | */
286 | #printCustomMethodsHelp() {
287 | /**
288 | * Print loader methods
289 | */
290 | console.log('')
291 | console.log(this.colors.green('GLOBAL METHODS:'))
292 |
293 | Object.keys(this.#customMethods).forEach((method) => {
294 | const { options } = this.#customMethods[method]
295 |
296 | const usage = this.colors.yellow(options.usage || method)
297 | const spaces = ' '.repeat(this.#longestCustomMethodName - options.width + 2)
298 | const description = this.colors.dim(options.description || '')
299 |
300 | console.log(`${usage}${spaces}${description}`)
301 | })
302 | }
303 |
304 | /**
305 | * Prints the context to the console
306 | */
307 | #ls() {
308 | this.#printCustomMethodsHelp()
309 | this.#printContextHelp()
310 | this.server!.displayPrompt()
311 | }
312 |
313 | /**
314 | * Notify by writing to the console
315 | */
316 | notify(message: string) {
317 | console.log(this.colors.yellow().italic(message))
318 | if (this.server) {
319 | this.server.displayPrompt()
320 | }
321 | }
322 |
323 | /**
324 | * Register a callback to be invoked once the server is ready
325 | */
326 | ready(callback: (repl: Repl) => void): this {
327 | this.#onReadyCallbacks.push(callback)
328 | return this
329 | }
330 |
331 | /**
332 | * Register a custom loader function to be added to the context
333 | */
334 | addMethod(name: string, handler: MethodCallback, options?: MethodOptions): this {
335 | const width = stringWidth(options?.usage || name)
336 | if (width > this.#longestCustomMethodName) {
337 | this.#longestCustomMethodName = width
338 | }
339 |
340 | this.#customMethods[name] = { handler, options: Object.assign({ width }, options) }
341 |
342 | /**
343 | * Register method right away when server has been started
344 | */
345 | if (this.server) {
346 | this.#registerCustomMethodWithContext(name)
347 | }
348 |
349 | return this
350 | }
351 |
352 | /**
353 | * Returns the collection of registered methods
354 | */
355 | getMethods() {
356 | return this.#customMethods
357 | }
358 |
359 | /**
360 | * Register a compiler. Make sure register the compiler before
361 | * calling the start method
362 | */
363 | useCompiler(compiler: Compiler): this {
364 | this.#compiler = compiler
365 | return this
366 | }
367 |
368 | /**
369 | * Start the REPL server
370 | */
371 | start(context?: Record) {
372 | console.log('')
373 | this.notify('Type ".ls" to a view list of available context methods/properties')
374 |
375 | this.server = startRepl({
376 | prompt: `> ${this.#compiler?.supportsTypescript ? '(ts) ' : '(js) '}`,
377 | input: process.stdin,
378 | output: process.stdout,
379 | terminal: process.stdout.isTTY && !Number.parseInt(process.env.NODE_NO_READLINE!, 10),
380 | useGlobal: true,
381 | ...this.#replOptions,
382 | })
383 |
384 | /**
385 | * Share context with the server
386 | */
387 | if (context) {
388 | Object.keys(context).forEach((key) => {
389 | this.server!.context[key] = context[key]
390 | })
391 | }
392 |
393 | /**
394 | * Define the `ls` command
395 | */
396 | this.server!.defineCommand('ls', {
397 | help: 'View list of available context methods/properties',
398 | action: this.#ls.bind(this),
399 | })
400 |
401 | /**
402 | * Setup context and history
403 | */
404 | this.#setupContext()
405 | this.#setupHistory()
406 |
407 | /**
408 | * Monkey patch the eval method
409 | */
410 | this.#originalEval = this.server.eval
411 | // @ts-ignore
412 | this.server.eval = this.#eval.bind(this)
413 |
414 | /**
415 | * Display prompt
416 | */
417 | this.server.displayPrompt()
418 |
419 | /**
420 | * Execute onReady callbacks
421 | */
422 | this.#onReadyCallbacks.forEach((callback) => callback(this))
423 |
424 | return this
425 | }
426 | }
427 |
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @adonisjs/repl
3 | *
4 | * (c) AdonisJS
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | import { Repl } from './repl.js'
11 |
12 | /**
13 | * Custom method callback function
14 | */
15 | export type MethodCallback = (repl: Repl, ...args: any[]) => any
16 |
17 | /**
18 | * Options that can be set when defining a custom method
19 | */
20 | export type MethodOptions = {
21 | description?: string
22 | usage?: string
23 | }
24 |
25 | /**
26 | * Shape of the Compiler that must be passed to the
27 | * repl constructor
28 | */
29 | export type Compiler = {
30 | compile: (code: string, fileName: string) => string
31 | supportsTypescript: boolean
32 | }
33 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@adonisjs/tsconfig/tsconfig.package.json",
3 | "compilerOptions": {
4 | "rootDir": "./",
5 | "outDir": "./build"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------