Skip to content
This repository was archived by the owner on Feb 1, 2022. It is now read-only.

Commit 8782559

Browse files
committed
fix: added actions
1 parent f10be7d commit 8782559

File tree

20 files changed

+1201
-26
lines changed

20 files changed

+1201
-26
lines changed

examples/action.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// tslint:disable
2+
import cli from '../src'
3+
import SimpleAction from '../src/action/simple'
4+
5+
const wait = (ms = 400) => new Promise(resolve => setTimeout(resolve, ms))
6+
7+
async function run() {
8+
cli.action.start('x foo')
9+
await wait()
10+
cli.log('1 log')
11+
await wait()
12+
cli.action.status = 'a wild status appeared!'
13+
await wait()
14+
cli.log('2 log')
15+
await wait()
16+
cli.log('3 log')
17+
await wait()
18+
cli.action.start('4 bar')
19+
await wait()
20+
cli.action.stop('now it is done')
21+
await wait()
22+
cli.action.start('x hideme')
23+
await wait()
24+
cli.action.start('5 foo')
25+
await wait()
26+
process.stderr.write('partial')
27+
await wait()
28+
cli.action.stop()
29+
await wait()
30+
cli.action.start('6 warn')
31+
await wait()
32+
cli.warn('uh oh')
33+
await wait()
34+
cli.action.stop()
35+
await wait()
36+
}
37+
38+
async function main() {
39+
console.log('SPINNER')
40+
console.log('=======')
41+
await run()
42+
43+
console.log('\nSIMPLE')
44+
console.log('======')
45+
process.env.TERM = 'dumb'
46+
cli.config.action = new SimpleAction()
47+
await run()
48+
49+
console.log('\nERROR')
50+
console.log('=====')
51+
cli.action.start('7 error out')
52+
await wait()
53+
cli.error('oh no')
54+
}
55+
main()

examples/confirm.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import {cli} from '../src'
2+
3+
const wait = (ms = 400) => new Promise(resolve => setTimeout(resolve, ms))
4+
5+
async function run() {
6+
cli.action.start('doing a thing')
7+
await wait()
8+
let input = await cli.confirm('yes or no')
9+
await wait()
10+
cli.log(`you entered: ${input}`)
11+
}
12+
run().catch(err => cli.error(err))

examples/prompt.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { cli } from '../src'
2+
3+
const wait = (ms = 400) => new Promise(resolve => setTimeout(resolve, ms))
4+
5+
async function run() {
6+
cli.action.start('doing a thing')
7+
await wait()
8+
let input = await cli.prompt('your name (normal)')
9+
cli.action.start('working')
10+
await wait()
11+
cli.log(`you entered: ${input}`)
12+
input = await cli.prompt('your name (mask)', { type: 'mask' })
13+
await wait()
14+
cli.log(`you entered: ${input}`)
15+
input = await cli.prompt('your name (hide)', { type: 'hide' })
16+
await wait()
17+
cli.action.stop()
18+
cli.log(`you entered: ${input}`)
19+
}
20+
run().catch(err => cli.error(err))

examples/spinner.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import cli from '../src'
2+
3+
function wait() {
4+
return new Promise(resolve => setTimeout(resolve, 1000))
5+
}
6+
7+
async function run() {
8+
cli.action.start('running')
9+
await wait()
10+
cli.action.stop()
11+
}
12+
run().catch(err => cli.error(err))

package.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,25 @@
77
"dependencies": {
88
"@dxcli/screen": "^0.0.0",
99
"@heroku/linewrap": "^1.0.0",
10+
"ansi-styles": "^3.2.0",
1011
"chalk": "^2.3.0",
1112
"fs-extra": "^5.0.0",
1213
"lodash": "^4.17.4",
14+
"password-prompt": "^1.0.3",
1315
"rxjs": "^5.5.6",
14-
"semver": "^5.4.1"
16+
"semver": "^5.4.1",
17+
"strip-ansi": "^4.0.0",
18+
"supports-color": "^5.1.0"
1519
},
1620
"devDependencies": {
1721
"@dxcli/dev": "^1.1.3",
1822
"@dxcli/dev-semantic-release": "^0.0.3",
19-
"@dxcli/dev-test": "^0.2.0",
23+
"@dxcli/dev-test": "^0.2.1",
2024
"@dxcli/dev-tslint": "^0.0.14",
2125
"@types/fs-extra": "^5.0.0",
2226
"@types/node": "^9.3.0",
2327
"@types/semver": "^5.4.0",
28+
"@types/supports-color": "^3.1.0",
2429
"eslint": "^4.15.0",
2530
"eslint-config-dxcli": "^1.1.4",
2631
"husky": "^0.14.3",

src/action/base.ts

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
import * as _ from 'lodash'
2+
import {inspect} from 'util'
3+
4+
export interface ITask {
5+
action: string
6+
status: string | undefined
7+
active: boolean
8+
}
9+
10+
export type ActionType = 'spinner' | 'simple' | 'debug'
11+
12+
export interface Options {
13+
stderr?: boolean
14+
}
15+
16+
const stdmockWrite = {
17+
stdout: process.stdout.write,
18+
stderr: process.stderr.write,
19+
}
20+
21+
export class ActionBase {
22+
type: ActionType
23+
std: 'stdout' | 'stderr' = 'stdout'
24+
protected stdmocks?: ['stdout' | 'stderr', string[]][]
25+
26+
public start(action: string, status?: string, opts: Options = {}) {
27+
this.std = opts.stderr ? 'stderr' : 'stdout'
28+
const task = (this.task = {action, status, active: !!(this.task && this.task.active)})
29+
this._start()
30+
task.active = true
31+
this._stdout(true)
32+
}
33+
34+
public stop(msg: string = 'done') {
35+
const task = this.task
36+
if (!task) {
37+
return
38+
}
39+
this._stop(msg)
40+
task.active = false
41+
this.task = undefined
42+
this._stdout(false)
43+
}
44+
45+
private get globals(): { action: { task?: ITask }; output: string | undefined } {
46+
const globals = ((global as any)['cli-ux'] = (global as any)['cli-ux'] || {})
47+
globals.action = globals.action || {}
48+
return globals
49+
}
50+
51+
public get task(): ITask | undefined {
52+
return this.globals.action.task
53+
}
54+
55+
public set task(task: ITask | undefined) {
56+
this.globals.action.task = task
57+
}
58+
59+
protected get output(): string | undefined {
60+
return this.globals.output
61+
}
62+
protected set output(output: string | undefined) {
63+
this.globals.output = output
64+
}
65+
66+
get running(): boolean {
67+
return !!this.task
68+
}
69+
70+
get status(): string | undefined {
71+
return this.task ? this.task.status : undefined
72+
}
73+
set status(status: string | undefined) {
74+
const task = this.task
75+
if (!task) {
76+
return
77+
}
78+
if (task.status === status) {
79+
return
80+
}
81+
this._updateStatus(status, task.status)
82+
task.status = status
83+
}
84+
85+
public async pauseAsync(fn: () => Promise<any>, icon?: string) {
86+
const task = this.task
87+
const active = task && task.active
88+
if (task && active) {
89+
this._pause(icon)
90+
this._stdout(false)
91+
task.active = false
92+
}
93+
const ret = await fn()
94+
if (task && active) {
95+
this._resume()
96+
}
97+
return ret
98+
}
99+
100+
public pause(fn: () => any, icon?: string) {
101+
const task = this.task
102+
const active = task && task.active
103+
if (task && active) {
104+
this._pause(icon)
105+
this._stdout(false)
106+
task.active = false
107+
}
108+
const ret = fn()
109+
if (task && active) {
110+
this._resume()
111+
}
112+
return ret
113+
}
114+
115+
protected _start() {
116+
throw new Error('not implemented')
117+
}
118+
protected _stop(_: string) {
119+
throw new Error('not implemented')
120+
}
121+
protected _resume() {
122+
if (this.task) this.start(this.task.action, this.task.status)
123+
}
124+
protected _pause(_?: string) {
125+
throw new Error('not implemented')
126+
}
127+
protected _updateStatus(_: string | undefined, __?: string) {}
128+
129+
/**
130+
* mock out stdout/stderr so it doesn't screw up the rendering
131+
*/
132+
protected _stdout(toggle: boolean) {
133+
try {
134+
const outputs: ['stdout', 'stderr'] = ['stdout', 'stderr']
135+
if (toggle) {
136+
if (this.stdmocks) return
137+
this.stdmocks = []
138+
for (let std of outputs) {
139+
(process[std] as any).write = (...args: any[]) => {
140+
this.stdmocks!.push([std, args] as ['stdout' | 'stderr', string[]])
141+
}
142+
}
143+
} else {
144+
if (!this.stdmocks) return
145+
// this._write('stderr', '\nresetstdmock\n\n\n')
146+
delete this.stdmocks
147+
for (let std of outputs) process[std].write = stdmockWrite[std] as any
148+
}
149+
} catch (err) {
150+
this._write('stderr', inspect(err))
151+
}
152+
}
153+
154+
/**
155+
* flush mocked stdout/stderr
156+
*/
157+
protected _flushStdout() {
158+
try {
159+
let output = ''
160+
let std: 'stdout' | 'stderr' | undefined
161+
while (this.stdmocks && this.stdmocks.length) {
162+
let cur = this.stdmocks.shift() as ['stdout' | 'stderr', string[]]
163+
std = cur[0]
164+
this._write(std, cur[1])
165+
output += cur[1].map((a: any) => a.toString('utf8')).join('')
166+
}
167+
// add newline if there isn't one already
168+
// otherwise we'll just overwrite it when we render
169+
if (output && std && output[output.length - 1] !== '\n') {
170+
this._write(std, '\n')
171+
}
172+
} catch (err) {
173+
this._write('stderr', inspect(err))
174+
}
175+
}
176+
177+
/**
178+
* write to the real stdout/stderr
179+
*/
180+
protected _write(std: 'stdout' | 'stderr', s: string | string[]) {
181+
stdmockWrite[std].apply(process[std], _.castArray(s))
182+
}
183+
}

src/action/simple.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import {ActionBase, ActionType} from './base'
2+
3+
export default class SimpleAction extends ActionBase {
4+
public type: ActionType = 'simple'
5+
6+
protected _start() {
7+
const task = this.task
8+
if (!task) return
9+
this._render(task.action, task.status)
10+
}
11+
12+
protected _pause(icon?: string) {
13+
if (icon) this._updateStatus(icon)
14+
else this._flush()
15+
}
16+
17+
protected _resume() {}
18+
19+
protected _updateStatus(status: string, prevStatus?: string, newline: boolean = false) {
20+
const task = this.task
21+
if (!task) return
22+
if (task.active && !prevStatus) this._write(this.std, ` ${status}`)
23+
else this._write(this.std, `${task.action}... ${status}`)
24+
if (newline || !prevStatus) this._flush()
25+
}
26+
27+
protected _stop(status: string) {
28+
const task = this.task
29+
if (!task) return
30+
this._updateStatus(status, task.status, true)
31+
}
32+
33+
private _render(action: string, status?: string) {
34+
const task = this.task
35+
if (!task) return
36+
if (task.active) this._flush()
37+
this._write(this.std, status ? `${action}... ${status}` : `${action}...`)
38+
}
39+
40+
private _flush() {
41+
this._write(this.std, '\n')
42+
this._flushStdout()
43+
}
44+
}

0 commit comments

Comments
 (0)