Skip to content

Commit

Permalink
fix: stop and restart repl to allow enquirer prompt (#188)
Browse files Browse the repository at this point in the history
  • Loading branch information
hongaar authored Jan 30, 2021
1 parent e9f8c3c commit 059461a
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 11 deletions.
2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ module.exports = {
testMatch: ['**/*.spec.ts'],
globals: {
'ts-jest': {
tsConfig: 'tsconfig.json'
tsconfig: 'tsconfig.json'
}
},
setupFiles: ['./jest.setup.js']
Expand Down
6 changes: 3 additions & 3 deletions src/command.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Argv, CommandModule, Arguments as BaseArguments } from 'yargs'
import { Arguments as BaseArguments, Argv, CommandModule } from 'yargs'
import { Argument, ArgumentOptions } from './argument'
import { Option, OptionOptions } from './option'
import { InferArgType } from './baseArg'
import { Option, OptionOptions } from './option'
import { prompter } from './prompter'

export type Arguments<T = {}> = T &
Expand Down Expand Up @@ -279,7 +279,7 @@ export class Command<T = {}> {
return this.handler(args, commandRunner)
}

// Display help this command contains sub-commands
// Display help if this command contains sub-commands
if (this.getCommands().length) {
return commandRunner(`${this.getFqn()} --help`)
}
Expand Down
3 changes: 1 addition & 2 deletions src/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ import { EventEmitter } from 'events'
import TypedEventEmitter from 'typed-emitter'
import { Argv } from 'yargs'
import createYargs from 'yargs/yargs'
import { Command, command } from './command'
import { Arguments, Command, command } from './command'
import { Repl, repl } from './repl'
import { Arguments } from './command'
import { isPromise } from './utils'

interface Events {
Expand Down
2 changes: 1 addition & 1 deletion src/prompter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ type PromptType =
type Question = any

/**
* Creates a new command, which can be added to a program.
* Creates a new prompter instance
*/
export function prompter<T = {}>(baseArgs: Array<Argument | Option>, args: T) {
return new Prompter(baseArgs, args)
Expand Down
25 changes: 22 additions & 3 deletions src/repl.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import nodeRepl, { REPLServer } from 'repl'
import { Prompt } from 'enquirer'
import { CompleterResult } from 'readline'
import { Context } from 'vm'
import nodeRepl, { REPLServer } from 'repl'
import { parseArgsStringToArgv } from 'string-argv'
import { Program } from './program'
import { Context } from 'vm'
import { autocompleter, Autocompleter } from './autocompleter'
import { Program } from './program'

const DEFAULT_PROMPT = '> '

Expand All @@ -27,6 +28,11 @@ export class Repl {
private prompt: string = DEFAULT_PROMPT
) {
this.autocompleter = autocompleter(program)

// Stop the server to avoid eval'ing stdin from prompts
this.program.on('run', () => {
this.stop()
})
}

/**
Expand All @@ -42,6 +48,14 @@ export class Repl {
completer: this.completer.bind(this),
ignoreUndefined: true,
})

// Fixes bug with hidden cursor after enquirer prompt
// @ts-ignore
new Prompt().cursorShow()
}

public stop() {
this.server?.close()
}

/**
Expand Down Expand Up @@ -101,6 +115,11 @@ export class Repl {
this.errorHandler(error)
}

// Since we stop the server when a command is executed (by listening to the
// 'run' event in the constructor), we need to start a new instance when the
// command is finished.
this.start()

// The result passed to this function is printed by the Node REPL server,
// but we don't want to use that, so we pass undefined instead.
cb(null, undefined)
Expand Down
35 changes: 34 additions & 1 deletion tests/program.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,33 @@
// @ts-ignore
import mockArgv from 'mock-argv'
import { program, Program, command } from '../src'
import { mocked } from 'ts-jest/utils'
import { command, program, Program, Repl } from '../src'

jest.mock('../src/repl', () => {
return {
repl: jest.fn().mockImplementation(() => {
return new MockedRepl()
}),
Repl: jest.fn().mockImplementation(() => {
return MockedRepl
}),
}
})

// Repl mock
const replStartFn = jest.fn()
const replPauseFn = jest.fn()
const replResumeFn = jest.fn()
class MockedRepl {
start = replStartFn
pause = replPauseFn
resume = replResumeFn
}

beforeEach(() => {
const MockedRepl = mocked(Repl, true)
MockedRepl.mockClear()
})

test('program should return new Program object', () => {
expect(program()).toBeInstanceOf(Program)
Expand All @@ -25,3 +52,9 @@ test('program executes argv', async () => {
await expect(app.run()).resolves.toBe('foo')
})
})

test('program starts repl', async () => {
const app = program()
expect(app.repl()).toBeInstanceOf(MockedRepl)
expect(replStartFn).toHaveBeenCalled()
})

0 comments on commit 059461a

Please sign in to comment.